mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-07-10 15:59:26 -07:00
Merge branch 'main' into launcher_loading_screen
This commit is contained in:
@@ -46,7 +46,6 @@ dist
|
||||
/prof/
|
||||
README.html
|
||||
.vs/
|
||||
EnemizerCLI/
|
||||
/Players/
|
||||
/SNI/
|
||||
/sni-*/
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"../test/programs/test_multi_server.py",
|
||||
"../test/utils/__init__.py",
|
||||
"../test/webhost/test_descriptions.py",
|
||||
"../test/webhost/test_suuid.py",
|
||||
"../worlds/AutoSNIClient.py",
|
||||
"type_check.py"
|
||||
],
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z"
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
draft: true # don't publish right away, especially since windows build is added by hand
|
||||
prerelease: false
|
||||
@@ -97,13 +97,15 @@ jobs:
|
||||
build/exe.*/ArchipelagoServer.exe
|
||||
setups/*
|
||||
- name: Add to Release
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
draft: true # see above
|
||||
prerelease: false
|
||||
name: Archipelago ${{ env.RELEASE_VERSION }}
|
||||
files: |
|
||||
setups/*
|
||||
fail_on_unmatched_files: true
|
||||
overwrite_files: false # Windows release is usually built by hand
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -165,12 +167,14 @@ jobs:
|
||||
build/exe.*/ArchipelagoServer
|
||||
dist/*
|
||||
- name: Add to Release
|
||||
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
draft: true # see above
|
||||
prerelease: false
|
||||
name: Archipelago ${{ env.RELEASE_VERSION }}
|
||||
files: |
|
||||
dist/*
|
||||
fail_on_unmatched_files: true
|
||||
overwrite_files: false # should never happen; avoids accidentally changing a release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+5
-1
@@ -1069,7 +1069,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
if "players" in args:
|
||||
ctx.consume_players_package(args["players"])
|
||||
if "hint_points" in args:
|
||||
ctx.hint_points = args['hint_points']
|
||||
ctx.hint_points = args["hint_points"]
|
||||
if "checked_locations" in args:
|
||||
checked = set(args["checked_locations"])
|
||||
ctx.checked_locations |= checked
|
||||
@@ -1077,6 +1077,10 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
||||
if "permissions" in args:
|
||||
ctx.update_permissions(args["permissions"])
|
||||
|
||||
# Update hint info for local display
|
||||
if "hint_cost" in args:
|
||||
ctx.hint_cost = int(args["hint_cost"])
|
||||
|
||||
elif cmd == 'Print':
|
||||
ctx.on_print(args)
|
||||
|
||||
|
||||
-27
@@ -1,23 +1,5 @@
|
||||
# hadolint global ignore=SC1090,SC1091
|
||||
|
||||
# Source
|
||||
FROM scratch AS release
|
||||
WORKDIR /release
|
||||
ADD https://github.com/Ijwu/Enemizer/releases/latest/download/ubuntu.16.04-x64.zip Enemizer.zip
|
||||
|
||||
# Enemizer
|
||||
FROM alpine:3.21 AS enemizer
|
||||
ARG TARGETARCH
|
||||
WORKDIR /release
|
||||
COPY --from=release /release/Enemizer.zip .
|
||||
|
||||
# No release for arm architecture. Skip.
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
apk add unzip=6.0-r15 --no-cache && \
|
||||
unzip -u Enemizer.zip -d EnemizerCLI && \
|
||||
chmod -R 777 EnemizerCLI; \
|
||||
else touch EnemizerCLI; fi
|
||||
|
||||
# Cython builder stage
|
||||
FROM python:3.12 AS cython-builder
|
||||
|
||||
@@ -81,15 +63,6 @@ RUN apt-get purge -y \
|
||||
g++ && \
|
||||
apt-get autoremove -y
|
||||
|
||||
# Copy necessary components
|
||||
COPY --from=enemizer /release/EnemizerCLI /tmp/EnemizerCLI
|
||||
|
||||
# No release for arm architecture. Skip.
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
cp -r /tmp/EnemizerCLI EnemizerCLI; \
|
||||
fi; \
|
||||
rm -rf /tmp/EnemizerCLI
|
||||
|
||||
# Define health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:${PORT:-80} || exit 1
|
||||
|
||||
+13
-2
@@ -40,6 +40,8 @@ def mystery_argparse(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
|
||||
parser.add_argument('--outputpath', default=settings.general_options.output_path,
|
||||
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
|
||||
parser.add_argument('--allow_quantity', action="store_true", default=defaults.allow_quantity,
|
||||
help='Allows the use of the quantity option in yamls. Default is the set value in the host.yaml.')
|
||||
parser.add_argument('--race', action='store_true', default=defaults.race)
|
||||
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
|
||||
parser.add_argument('--log_level', default=defaults.loglevel, help='Sets log level')
|
||||
@@ -123,6 +125,7 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
|
||||
player_id: int = 1
|
||||
player_files: dict[int, str] = {}
|
||||
player_errors: list[str] = []
|
||||
allow_quantity = args.allow_quantity
|
||||
for file in os.scandir(args.player_files_path):
|
||||
fname = file.name
|
||||
if file.is_file() and not fname.startswith(".") and not fname.lower().endswith(".ini") and \
|
||||
@@ -134,7 +137,14 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
|
||||
if yaml is None:
|
||||
logging.warning(f"Ignoring empty yaml document #{doc_idx + 1} in {fname}")
|
||||
else:
|
||||
weights_for_file.append(yaml)
|
||||
quantity = yaml.get("quantity", 1)
|
||||
if quantity <= 0:
|
||||
raise ValueError("A quantity of 0 or less is invalid. Please change it to at least 1.")
|
||||
if not allow_quantity and quantity > 1:
|
||||
raise ValueError("Quantity greater than 1 is deactivated by host settings.")
|
||||
|
||||
for _ in range(quantity):
|
||||
weights_for_file.append(yaml)
|
||||
weights_cache[fname] = tuple(weights_for_file)
|
||||
|
||||
except Exception as e:
|
||||
@@ -575,7 +585,8 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
||||
raise Exception(f"Invalid game: {ret.game}")
|
||||
if ret.game not in AutoWorldRegister.world_types:
|
||||
from worlds import failed_world_loads
|
||||
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0]
|
||||
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + list(failed_world_loads.keys()),
|
||||
limit=1)[0]
|
||||
if picks[0] in failed_world_loads:
|
||||
raise Exception(f"No functional world found to handle game {ret.game}. "
|
||||
f"Did you mean '{picks[0]}' ({picks[1]}% sure)? "
|
||||
|
||||
+108
-1
@@ -34,6 +34,79 @@ from Utils import env_cleared_lib_path, init_logging, is_linux, is_macos, is_win
|
||||
if __name__ == "__main__":
|
||||
init_logging('Launcher')
|
||||
|
||||
from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type
|
||||
from worlds import failed_world_loads
|
||||
|
||||
|
||||
def open_host_yaml():
|
||||
s = settings.get_settings()
|
||||
file = s.filename
|
||||
s.save()
|
||||
assert file, "host.yaml missing"
|
||||
if is_linux:
|
||||
exe = which('sensible-editor') or which('gedit') or \
|
||||
which('xdg-open') or which('gnome-open') or which('kde-open')
|
||||
elif is_macos:
|
||||
exe = which("open")
|
||||
else:
|
||||
webbrowser.open(file)
|
||||
return
|
||||
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, file], env=env)
|
||||
|
||||
def open_patch():
|
||||
suffixes = []
|
||||
for c in components:
|
||||
if c.type == Type.CLIENT and \
|
||||
isinstance(c.file_identifier, SuffixIdentifier) and \
|
||||
(c.script_name is None or isfile(get_exe(c)[-1])):
|
||||
suffixes += c.file_identifier.suffixes
|
||||
try:
|
||||
filename = open_filename("Select patch", (("Patches", suffixes),))
|
||||
except Exception as e:
|
||||
messagebox("Error", str(e), error=True)
|
||||
else:
|
||||
file, component = identify(filename)
|
||||
if file and component:
|
||||
exe = get_exe(component)
|
||||
if exe is None or not isfile(exe[-1]):
|
||||
exe = get_exe("Launcher")
|
||||
|
||||
launch([*exe, file], component.cli)
|
||||
|
||||
|
||||
def generate_yamls(*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)
|
||||
if not args.skip_open_folder:
|
||||
open_folder(target)
|
||||
|
||||
|
||||
def browse_files():
|
||||
open_folder(user_path())
|
||||
|
||||
|
||||
def open_folder(folder_path):
|
||||
if is_linux:
|
||||
exe = which('xdg-open') or which('gnome-open') or which('kde-open')
|
||||
elif is_macos:
|
||||
exe = which("open")
|
||||
else:
|
||||
webbrowser.open(folder_path)
|
||||
return
|
||||
|
||||
if exe:
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, folder_path], env=env)
|
||||
else:
|
||||
logging.warning(f"No file browser available to open {folder_path}")
|
||||
|
||||
|
||||
def update_settings():
|
||||
@@ -150,7 +223,8 @@ def run_gui(launch_components: list["Component"], args: Any) -> None:
|
||||
button_layout: ScrollBox = ObjectProperty(None)
|
||||
search_box: MDTextField = ObjectProperty(None)
|
||||
cards: list[LauncherCard]
|
||||
current_filter: Sequence["Type"] | None
|
||||
current_filter: Sequence[str, "Type"] | None
|
||||
failed_worlds: bool = bool(failed_world_loads)
|
||||
|
||||
def __init__(self, ctx=None, components=None, args=None):
|
||||
self.title = self.base_title + " " + Utils.__version__
|
||||
@@ -338,6 +412,39 @@ def run_gui(launch_components: list["Component"], args: Any) -> None:
|
||||
MDSnackbar(MDSnackbarText(text=open_text), y=dp(24), pos_hint={"center_x": 0.5},
|
||||
size_hint_x=0.5).open()
|
||||
|
||||
@staticmethod
|
||||
def copy_to_clipboard(text):
|
||||
from kivy.core.clipboard import Clipboard
|
||||
Clipboard.copy(text)
|
||||
MDSnackbar(MDSnackbarText(text="Copied to clipboard."), y=dp(24), pos_hint={"center_x": 0.5},
|
||||
size_hint_x=0.5).open()
|
||||
|
||||
def display_failed(self):
|
||||
"""Display a dialog showing the exceptions produced by any world that failed to load during
|
||||
initialization."""
|
||||
if not self.failed_worlds:
|
||||
return
|
||||
from kivymd.uix.dialog import MDDialog, MDDialogIcon, MDDialogHeadlineText, MDDialogContentContainer
|
||||
from kivymd.uix.divider import MDDivider
|
||||
from kivymd.uix.list import MDListItem, MDListItemHeadlineText, MDListItemSupportingText
|
||||
entries = []
|
||||
for world, reason in failed_world_loads.items():
|
||||
entries.append(MDListItem(
|
||||
MDListItemHeadlineText(text=world),
|
||||
MDListItemSupportingText(text=reason),
|
||||
on_release=lambda x, r=reason: self.copy_to_clipboard(r)
|
||||
))
|
||||
dialog = MDDialog(
|
||||
MDDialogIcon(icon="alert"),
|
||||
MDDialogHeadlineText(text="Failed World Loads"),
|
||||
MDDialogContentContainer(
|
||||
MDDivider(),
|
||||
*entries,
|
||||
orientation="vertical",
|
||||
)
|
||||
)
|
||||
dialog.open()
|
||||
|
||||
def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None:
|
||||
""" When a patch file is dropped into the window, run the associated component. """
|
||||
from worlds.LauncherComponents import identify
|
||||
|
||||
+2
-2
@@ -241,8 +241,8 @@ async def gba_sync_task(ctx: MMBN3Context):
|
||||
await ctx.server_auth(False)
|
||||
else:
|
||||
if not ctx.version_warning:
|
||||
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}."
|
||||
"Please update to the latest version."
|
||||
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}. "
|
||||
"Please update to the latest version. "
|
||||
"Your connection to the Archipelago server will not be accepted.")
|
||||
ctx.version_warning = True
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
+2
-2
@@ -2633,8 +2633,8 @@ def parse_args() -> argparse.Namespace:
|
||||
goal: !remaining can be used after goal completion
|
||||
''')
|
||||
parser.add_argument('--auto_shutdown', default=defaults["auto_shutdown"], type=int,
|
||||
help="automatically shut down the server after this many minutes without new location checks. "
|
||||
"0 to keep running. Not yet implemented.")
|
||||
help="automatically shut down the server after this many seconds without new location checks. "
|
||||
"0 to keep running.")
|
||||
parser.add_argument('--use_embedded_options', action="store_true",
|
||||
help='retrieve release, remaining and hint options from the multidata file,'
|
||||
' instead of host.yaml')
|
||||
|
||||
@@ -527,7 +527,11 @@ else:
|
||||
except ImportError:
|
||||
pyximport = None
|
||||
try:
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
old_level = logger.level
|
||||
from _speedups import LocationStore
|
||||
logger.setLevel(old_level)
|
||||
except ImportError:
|
||||
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
|
||||
"Install a matching C++ compiler for your platform to compile _speedups.")
|
||||
|
||||
+56
-30
@@ -212,6 +212,13 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
else:
|
||||
return cls.name_lookup[value]
|
||||
|
||||
def __eq__(self, other: typing.Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.value == other.value
|
||||
if isinstance(other, Option):
|
||||
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||
return self.value == other
|
||||
|
||||
def __int__(self) -> T:
|
||||
return self.value
|
||||
|
||||
@@ -930,13 +937,34 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mappin
|
||||
class OptionCounter(OptionDict):
|
||||
min: int | None = None
|
||||
max: int | None = None
|
||||
cull_zeroes: bool = False
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
super(OptionCounter, self).__init__(collections.Counter(value))
|
||||
cleaned_dict = {}
|
||||
|
||||
invalid_value_errors = []
|
||||
for key, value in value.items():
|
||||
if not isinstance(value, (int, float)) or int(value) != value:
|
||||
invalid_value_errors += [f"Invalid value {value} for key {key}, must be an integer."]
|
||||
continue
|
||||
|
||||
if self.cull_zeroes and value == 0:
|
||||
continue
|
||||
|
||||
cleaned_dict[key] = int(value)
|
||||
|
||||
if invalid_value_errors:
|
||||
type_errors = [f"For option {self.__class__.__name__}:"] + invalid_value_errors
|
||||
raise TypeError("\n".join(invalid_value_errors))
|
||||
|
||||
super(OptionCounter, self).__init__(collections.Counter(cleaned_dict))
|
||||
|
||||
def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None:
|
||||
super(OptionCounter, self).verify(world, player_name, plando_options)
|
||||
|
||||
self.verify_values()
|
||||
|
||||
def verify_values(self):
|
||||
range_errors = []
|
||||
|
||||
if self.max is not None:
|
||||
@@ -959,13 +987,8 @@ class OptionCounter(OptionDict):
|
||||
class ItemDict(OptionCounter):
|
||||
verify_item_name = True
|
||||
|
||||
min = 0
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
value = {item_name: amount for item_name, amount in value.items() if amount != 0}
|
||||
|
||||
super(ItemDict, self).__init__(value)
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
cull_zeroes = True
|
||||
|
||||
|
||||
class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
||||
@@ -1446,7 +1469,7 @@ class NonLocalItems(ItemSet):
|
||||
|
||||
|
||||
class StartInventory(ItemDict):
|
||||
"""Start with the specified amount of these items. Example: "Bomb: 1" """
|
||||
"""Start with the specified amount of these items. Example: {Bomb: 1, Arrow: 3} """
|
||||
verify_item_name = True
|
||||
display_name = "Start Inventory"
|
||||
rich_text_doc = True
|
||||
@@ -1454,7 +1477,7 @@ class StartInventory(ItemDict):
|
||||
|
||||
|
||||
class StartInventoryPool(StartInventory):
|
||||
"""Start with the specified amount of these items and don't place them in the world. Example: "Bomb: 1"
|
||||
"""Start with the specified amount of these items and don't place them in the world. Example: {Bomb: 1, Arrow: 3}
|
||||
|
||||
The game decides what the replacement items will be.
|
||||
"""
|
||||
@@ -1833,27 +1856,30 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
|
||||
|
||||
for game_name, world in AutoWorldRegister.world_types.items():
|
||||
if not world.hidden or generate_hidden:
|
||||
presets = world.web.options_presets.copy()
|
||||
presets.update({"": {}})
|
||||
try:
|
||||
presets = world.web.options_presets.copy()
|
||||
presets.update({"": {}})
|
||||
|
||||
option_groups = get_option_groups(world)
|
||||
for name, preset in presets.items():
|
||||
res = template.render(
|
||||
option_groups=option_groups,
|
||||
__version__=__version__,
|
||||
game=game_name,
|
||||
world_version=world.world_version.as_simple_string(),
|
||||
yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
cleandoc=cleandoc,
|
||||
preset_name=name,
|
||||
preset=preset,
|
||||
)
|
||||
preset_name = f" - {name}" if name else ""
|
||||
with open(os.path.join(preset_folder if name else target_folder,
|
||||
get_file_safe_name(game_name + preset_name) + ".yaml"),
|
||||
"w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
option_groups = get_option_groups(world)
|
||||
for name, preset in presets.items():
|
||||
res = template.render(
|
||||
option_groups=option_groups,
|
||||
__version__=__version__,
|
||||
game=game_name,
|
||||
world_version=world.world_version.as_simple_string(),
|
||||
yaml_dump=yaml_dump_scalar,
|
||||
dictify_range=dictify_range,
|
||||
cleandoc=cleandoc,
|
||||
preset_name=name,
|
||||
preset=preset,
|
||||
)
|
||||
preset_name = f" - {name}" if name else ""
|
||||
with open(os.path.join(preset_folder if name else target_folder,
|
||||
get_file_safe_name(game_name + preset_name) + ".yaml"),
|
||||
"w", encoding="utf-8-sig") as f:
|
||||
f.write(res)
|
||||
except Exception as ex:
|
||||
raise Exception(f"Template generation failed for world {game_name}") from ex
|
||||
|
||||
|
||||
def dump_player_options(multiworld: MultiWorld) -> None:
|
||||
|
||||
@@ -24,7 +24,6 @@ Currently, the following games are supported:
|
||||
* The Witness
|
||||
* Sonic Adventure 2: Battle
|
||||
* Starcraft 2
|
||||
* Donkey Kong Country 3
|
||||
* Dark Souls 3
|
||||
* Super Mario World
|
||||
* Pokémon Red and Blue
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class UndertaleCommandProcessor(ClientCommandProcessor):
|
||||
if isinstance(self.ctx, UndertaleContext):
|
||||
os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True)
|
||||
tempInstall = steaminstall
|
||||
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
if tempInstall and not os.path.isfile(os.path.join(tempInstall, "data.win")):
|
||||
tempInstall = None
|
||||
if tempInstall is None:
|
||||
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
|
||||
|
||||
@@ -52,7 +52,7 @@ class Version(typing.NamedTuple):
|
||||
return ".".join(str(item) for item in self)
|
||||
|
||||
|
||||
__version__ = "0.6.7"
|
||||
__version__ = "0.6.8"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
@@ -450,13 +450,10 @@ safe_builtins = frozenset((
|
||||
|
||||
|
||||
class RestrictedUnpickler(pickle.Unpickler):
|
||||
generic_properties_module: Optional[object]
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super(RestrictedUnpickler, self).__init__(*args, **kwargs)
|
||||
self.options_module = importlib.import_module("Options")
|
||||
self.net_utils_module = importlib.import_module("NetUtils")
|
||||
self.generic_properties_module = None
|
||||
|
||||
def find_class(self, module: str, name: str) -> type:
|
||||
if module == "builtins" and name in safe_builtins:
|
||||
@@ -470,10 +467,6 @@ class RestrictedUnpickler(pickle.Unpickler):
|
||||
"SlotType", "NetworkSlot", "HintStatus"}:
|
||||
return getattr(self.net_utils_module, name)
|
||||
# Options and Plando are unpickled by WebHost -> Generate
|
||||
if module == "worlds.generic" and name == "PlandoItem":
|
||||
if not self.generic_properties_module:
|
||||
self.generic_properties_module = importlib.import_module("worlds.generic")
|
||||
return getattr(self.generic_properties_module, name)
|
||||
# pep 8 specifies that modules should have "all-lowercase names" (options, not Options)
|
||||
if module.lower().endswith("options"):
|
||||
if module == "Options":
|
||||
|
||||
@@ -48,6 +48,8 @@ app.config["JOB_THRESHOLD"] = 1
|
||||
app.config["JOB_TIME"] = 600
|
||||
# maximum time in seconds since last activity for a room to be hosted
|
||||
app.config["MAX_ROOM_TIMEOUT"] = 259200
|
||||
# minimum time in days since last activity for a room to be deleted. 0 to disable.
|
||||
app.config["ROOM_AUTO_DELETE"] = 0
|
||||
# memory limit for generator processes in bytes
|
||||
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
|
||||
|
||||
@@ -71,7 +73,9 @@ CLI(app)
|
||||
|
||||
|
||||
def to_python(value: str) -> uuid.UUID:
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=='))
|
||||
if "=" in value or any(c.isspace() for c in value):
|
||||
raise ValueError("Invalid UUID format")
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)))
|
||||
|
||||
|
||||
def to_url(value: uuid.UUID) -> str:
|
||||
|
||||
@@ -100,13 +100,18 @@ def init_generator(config: dict[str, Any]) -> None:
|
||||
db.generate_mapping()
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""delete unowned user-content"""
|
||||
def cleanup(config: dict[str, Any]):
|
||||
"""delete unowned or old user-content"""
|
||||
auto_delete: int = config.get("ROOM_AUTO_DELETE", 0)
|
||||
with db_session:
|
||||
# >>> bool(uuid.UUID(int=0))
|
||||
# True
|
||||
rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True)
|
||||
seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True)
|
||||
if auto_delete > 0:
|
||||
cutoff = utcnow() - timedelta(days=auto_delete)
|
||||
rooms += Room.select(lambda room: room.last_activity < cutoff).delete(bulk=True)
|
||||
seeds += Seed.select(lambda seed: not seed.rooms and seed.creation_time < cutoff).delete(bulk=True)
|
||||
slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True)
|
||||
# Command gets deleted by ponyorm Cascade Delete, as Room is Required
|
||||
if rooms or seeds or slots:
|
||||
@@ -118,7 +123,7 @@ def autohost(config: dict):
|
||||
stop_event = _stop_event
|
||||
try:
|
||||
with Locker("autohost"):
|
||||
cleanup()
|
||||
cleanup(config)
|
||||
hosters = []
|
||||
for x in range(config["HOSTERS"]):
|
||||
hoster = MultiworldInstance(config, x)
|
||||
|
||||
@@ -10,5 +10,5 @@ Flask-Cors==6.0.2
|
||||
bokeh==3.8.2
|
||||
markupsafe==3.0.3
|
||||
setproctitle==1.3.7
|
||||
mistune==3.2.0
|
||||
mistune==3.2.1
|
||||
docutils==0.22.4
|
||||
|
||||
@@ -123,12 +123,26 @@ window.addEventListener('load', () => {
|
||||
});
|
||||
|
||||
const addRangeRow = (optionName) => {
|
||||
const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`;
|
||||
const inputQuery = `input[data-option="${optionName}"]`;
|
||||
const inputTarget = document.querySelector(inputQuery);
|
||||
const newValue = inputTarget.value;
|
||||
if (!/^-?\d+$/.test(newValue)) {
|
||||
alert('Range values must be a positive or negative integer!');
|
||||
return;
|
||||
switch (inputTarget.type) {
|
||||
case 'number':
|
||||
if (!/^-?\d+$/.test(newValue)) {
|
||||
alert('Range values must be a positive or negative integer!');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'text':
|
||||
if (newValue === "") {
|
||||
alert('Range values for text must be a non-empty string!');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error(`Found unsupported input type: ${inputTarget.type}`);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
inputTarget.value = '';
|
||||
const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% block footer %}
|
||||
<footer id="island-footer">
|
||||
<div id="copyright-notice">Copyright 2025 Archipelago</div>
|
||||
<div id="copyright-notice">Copyright 2026 Archipelago</div>
|
||||
<div id="links">
|
||||
<a href="/sitemap">Site Map</a>
|
||||
-
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
<a href="{{ world.web.bug_report_page }}">Report a Bug</a>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% if "authors" in world.manifest %}
|
||||
<p>Authors: {{ world.manifest["authors"] | join(", ") }}</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -71,10 +71,10 @@
|
||||
<div class="hint-text">
|
||||
This option allows custom values only. Please enter your desired values below.
|
||||
<div class="custom-value-wrapper">
|
||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||
<input type="text" class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||
</div>
|
||||
<table>
|
||||
<table class="range-rows" data-option="{{ option_name }}">
|
||||
<tbody>
|
||||
{% if option.default %}
|
||||
{{ RangeRow(option_name, option, option.default, option.default) }}
|
||||
@@ -88,11 +88,11 @@
|
||||
<div class="hint-text">
|
||||
Custom values are also allowed for this option. To create one, enter it into the input box below.
|
||||
<div class="custom-value-wrapper">
|
||||
<input class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" data-option="{{ option_name }}">Add</button>
|
||||
<input type="text" class="custom-value" data-option="{{ option_name }}" placeholder="Custom Value" />
|
||||
<button type="button" class="add-range-option-button" data-option="{{ option_name }}">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<table class="range-rows" data-option="{{ option_name }}">
|
||||
<tbody>
|
||||
{% for id, name in option.name_lookup.items() %}
|
||||
{% if name != 'random' %}
|
||||
|
||||
@@ -140,6 +140,15 @@ MDFloatLayout:
|
||||
|
||||
MDNavigationDrawerDivider:
|
||||
|
||||
MDBoxLayout:
|
||||
orientation: "horizontal"
|
||||
MDIconButton:
|
||||
icon: "alert" if app.failed_worlds else ""
|
||||
theme_text_color: "Custom"
|
||||
text_color: "D23C42"
|
||||
disabled: not app.failed_worlds
|
||||
on_release: app.display_failed()
|
||||
|
||||
|
||||
MDGridLayout:
|
||||
id: main_layout
|
||||
|
||||
@@ -56,9 +56,6 @@
|
||||
# Dark Souls III
|
||||
/worlds/dark_souls_3/ @Marechal-L @nex3
|
||||
|
||||
# Donkey Kong Country 3
|
||||
/worlds/dkc3/ @PoryGone
|
||||
|
||||
# DLCQuest
|
||||
/worlds/dlcquest/ @axe-y @agilbert1412
|
||||
|
||||
|
||||
@@ -92,8 +92,9 @@ for setup).
|
||||
|
||||
The base World class can be found in [AutoWorld](/worlds/AutoWorld.py). Methods available for your world to call
|
||||
during generation can be found in [BaseClasses](/BaseClasses.py) and [Fill](/Fill.py). Some examples and documentation
|
||||
regarding the API can be found in the [world api doc](/docs/world%20api.md). Before publishing, make sure to also
|
||||
check out [world maintainer.md](/docs/world%20maintainer.md).
|
||||
regarding the API can be found in the [world api doc](/docs/world%20api.md), and the [APQuest](/worlds/apquest/) world
|
||||
is a complete world implementation that functions as an introduction to world development. Before publishing, make sure
|
||||
to also check out [world maintainer.md](/docs/world%20maintainer.md).
|
||||
|
||||
### Hard Requirements
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ There are also the following optional fields:
|
||||
* `world_version` - an arbitrary version for that world in order to only load the newest valid world.
|
||||
An APWorld without a world_version is always treated as older than one with a version
|
||||
(**Must** use exactly the format `"major.minor.build"`, e.g. `1.0.0`)
|
||||
* `authors` - a list of authors, to eventually be displayed in various user-facing places such as WebHost and
|
||||
package managers. Should always be a list of strings.
|
||||
* `authors` - a list of authors of the world. Displayed in user-facing places like the Supported Games page
|
||||
on WebHost. Should always be a list of strings.
|
||||
|
||||
If the APWorld is packaged as an `.apworld` zip file, it also needs to have `version` and `compatible_version`,
|
||||
which refer to the version of the APContainer packaging scheme defined in [Files.py](../worlds/Files.py).
|
||||
|
||||
@@ -77,15 +77,6 @@ Changes made to `docker-compose.yaml` can be applied by running `docker compose
|
||||
It is possible to carry out these deployment steps on Windows under [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
|
||||
|
||||
## Optional: A Link to the Past Enemizer
|
||||
|
||||
Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an
|
||||
error if it is required.
|
||||
Enemizer can be enabled on `x86_64` platform architecture, and is included in the image build process. Enemizer requires a version 1.0 Japanese "Zelda no Densetsu" `.sfc` rom file to be placed in the application directory:
|
||||
`docker run archipelago -v "/path/to/zelda.sfc:/app/Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"`.
|
||||
Enemizer is not currently available for `aarch64`.
|
||||
|
||||
|
||||
## Optional: Git
|
||||
|
||||
Building the image requires a local copy of the ArchipelagoMW source code.
|
||||
|
||||
@@ -69,12 +69,6 @@ flowchart LR
|
||||
end
|
||||
SNI <-- Various, depending on SNES device --> SMZ
|
||||
|
||||
%% Donkey Kong Country 3
|
||||
subgraph Donkey Kong Country 3
|
||||
DK3[SNES]
|
||||
end
|
||||
SNI <-- Various, depending on SNES device --> DK3
|
||||
|
||||
%% Super Mario World
|
||||
subgraph Super Mario World
|
||||
SMW[SNES]
|
||||
|
||||
+162
-61
@@ -1,6 +1,7 @@
|
||||
# Rule Builder
|
||||
|
||||
This document describes the API provided for the rule builder. Using this API provides you with with a simple interface to define rules and the following advantages:
|
||||
This document describes the API provided for the rule builder. Using this API provides you with with a simple interface
|
||||
to define rules and the following advantages:
|
||||
|
||||
- Rule classes that avoid all the common pitfalls
|
||||
- Logic optimization
|
||||
@@ -12,13 +13,21 @@ This document describes the API provided for the rule builder. Using this API pr
|
||||
|
||||
The rule builder consists of 3 main parts:
|
||||
|
||||
1. The rules, which are classes that inherit from `rule_builder.rules.Rule`. These are what you write for your logic. They can be combined and take into account your world's options. There are a number of default rules listed below, and you can create as many custom rules for your world as needed. When assigning the rules to a location or entrance they must be resolved.
|
||||
1. Resolved rules, which are classes that inherit from `rule_builder.rules.Rule.Resolved`. These are the optimized rules specific to one player that are set as a location or entrance's access rule. You generally shouldn't be directly creating these but they'll be created when assigning rules to locations or entrances. These are what power the human-readable logic explanations.
|
||||
1. The optional rule builder world subclass `CachedRuleBuilderWorld`, which is a class your world can inherit from instead of `World`. It adds a caching system to the rules that will lazy evaluate and cache the result.
|
||||
1. The rules, which are classes that inherit from `rule_builder.rules.Rule`. These are what you write for your logic.
|
||||
They can be combined and take into account your world's options. There are a number of default rules listed below,
|
||||
and you can create as many custom rules for your world as needed. When assigning the rules to a location or entrance
|
||||
they must be resolved.
|
||||
2. Resolved rules, which are classes that inherit from `rule_builder.rules.Rule.Resolved`. These are the optimized rules
|
||||
specific to one player that are set as a location or entrance's access rule. You generally shouldn't be directly
|
||||
creating these but they'll be created when assigning rules to locations or entrances. These are what power the
|
||||
human-readable logic explanations.
|
||||
3. The optional rule builder world subclass `CachedRuleBuilderWorld`, which is a class your world can inherit from
|
||||
instead of `World`. It adds a caching system to the rules that will lazy evaluate and cache the result.
|
||||
|
||||
## Usage
|
||||
|
||||
For the most part the only difference in usage is instead of writing lambdas for your logic, you write static Rule objects. You then must use `world.set_rule` to assign the rule to a location or entrance.
|
||||
For the most part the only difference in usage is instead of writing lambdas for your logic, you write static Rule
|
||||
objects. You then must use `world.set_rule` to assign the rule to a location or entrance.
|
||||
|
||||
```python
|
||||
# In your world's create_regions method
|
||||
@@ -32,6 +41,7 @@ The rule builder comes with a number of rules by default:
|
||||
- `False_`: Always returns false
|
||||
- `And`: Checks that all child rules are true (also provided by `&` operator)
|
||||
- `Or`: Checks that at least one child rule is true (also provided by `|` operator)
|
||||
- `AtLeast`: Checks that at least some count of rules is true
|
||||
- `Has`: Checks that the player has the given item with the given count (default 1)
|
||||
- `HasAll`: Checks that the player has all given items
|
||||
- `HasAny`: Checks that the player has at least one of the given items
|
||||
@@ -40,18 +50,22 @@ The rule builder comes with a number of rules by default:
|
||||
- `HasFromList`: Checks that the player has some number of given items
|
||||
- `HasFromListUnique`: Checks that the player has some number of given items, ignoring duplicates of the same item
|
||||
- `HasGroup`: Checks that the player has some number of items from a given item group
|
||||
- `HasGroupUnique`: Checks that the player has some number of items from a given item group, ignoring duplicates of the same item
|
||||
- `HasGroupUnique`: Checks that the player has some number of items from a given item group, ignoring duplicates of the
|
||||
same item
|
||||
- `CanReachLocation`: Checks that the player can logically reach the given location
|
||||
- `CanReachRegion`: Checks that the player can logically reach the given region
|
||||
- `CanReachEntrance`: Checks that the player can logically reach the given entrance
|
||||
|
||||
You can combine these rules together to describe the logic required for something. For example, to check if a player either has `Movement ability` or they have both `Key 1` and `Key 2`, you can do:
|
||||
You can combine these rules together to describe the logic required for something. For example, to check if a player
|
||||
either has `Movement ability` or they have both `Key 1` and `Key 2`, you can do:
|
||||
|
||||
```python
|
||||
rule = Has("Movement ability") | HasAll("Key 1", "Key 2")
|
||||
```
|
||||
|
||||
> ⚠️ Composing rules with the `and` and `or` keywords will not work. You must use the bitwise `&` and `|` operators. In order to catch mistakes, the rule builder will not let you do boolean operations. As a consequence, in order to check if a rule is defined you must use `if rule is not None`.
|
||||
> ⚠️ Composing rules with the `and` and `or` keywords will not work. You must use the bitwise `&` and `|` operators. In
|
||||
> order to catch mistakes, the rule builder will not let you do boolean operations. As a consequence, in order to check
|
||||
> if a rule is defined you must use `if rule is not None`.
|
||||
|
||||
### Assigning rules
|
||||
|
||||
@@ -61,13 +75,16 @@ When assigning the rule you must use the `set_rule` helper to correctly resolve
|
||||
self.set_rule(location_or_entrance, rule)
|
||||
```
|
||||
|
||||
There is also a `create_entrance` helper that will resolve the rule, check if it's `False`, and if not create the entrance and set the rule. This allows you to skip creating entrances that will never be valid. You can also specify `force_creation=True` if you would like to create the entrance even if the rule is `False`.
|
||||
There is also a `create_entrance` helper that will resolve the rule, check if it's `False`, and if not create the
|
||||
entrance and set the rule. This allows you to skip creating entrances that will never be valid. You can also specify
|
||||
`force_creation=True` if you would like to create the entrance even if the rule is `False`.
|
||||
|
||||
```python
|
||||
self.create_entrance(from_region, to_region, rule)
|
||||
```
|
||||
|
||||
> ⚠️ If you use a `CanReachLocation` rule on an entrance, you will either have to create the locations first, or specify the location's parent region name with the `parent_region_name` argument of `CanReachLocation`.
|
||||
> ⚠️ If you use a `CanReachLocation` rule on an entrance, you will either have to create the locations first, or specify
|
||||
> the location's parent region name with the `parent_region_name` argument of `CanReachLocation`.
|
||||
|
||||
You can also set a rule for your world's completion condition:
|
||||
|
||||
@@ -77,21 +94,42 @@ self.set_completion_rule(rule)
|
||||
|
||||
### Restricting options
|
||||
|
||||
Every rule allows you to specify which options it's applicable for. You can provide the argument `options` which is an iterable of `OptionFilter` instances. Rules that pass the options check will be resolved as normal, and those that fail will be resolved as `False`.
|
||||
Every rule allows you to specify which options it's applicable for. You can provide the argument `options` which is an
|
||||
iterable of `OptionFilter` instances. When resolved, if no filters are provided or all of them pass then the rule will
|
||||
resolve as normal. Otherwise, the rule will be replaced with `True` or `False` depending on what `filtered_resolution`
|
||||
is set to, which defaults to `False`.
|
||||
|
||||
If you want a comparison that isn't equals, you can specify with the `operator` argument. The following operators are allowed:
|
||||
```python
|
||||
rule1 = Has(
|
||||
"Fast Travel Spell",
|
||||
options=[OptionFilter(RandoFastTravel, RandoFastTravel.option_true)],
|
||||
)
|
||||
rule2 = Has(
|
||||
"Starting Party Member",
|
||||
options=[OptionFilter(RandoParty, 1)], # option attributes are suggested but any value works
|
||||
filtered_resolution=True,
|
||||
)
|
||||
```
|
||||
|
||||
- `eq`: `==`
|
||||
- `ne`: `!=`
|
||||
- `gt`: `>`
|
||||
- `lt`: `<`
|
||||
- `ge`: `>=`
|
||||
- `le`: `<=`
|
||||
- `contains`: `in`
|
||||
If you want a comparison that isn't equals, you can specify with the `operator` argument. The following operators are
|
||||
allowed:
|
||||
|
||||
By default rules that are excluded by their options will default to `False`. If you want to default to `True` instead, you can specify `filtered_resolution=True` on your rule.
|
||||
- `eq`: `option_value == filter_value`
|
||||
- `ne`: `option_value != filter_value`
|
||||
- `gt`: `option_value > filter_value`
|
||||
- `lt`: `option_value < filter_value`
|
||||
- `ge`: `option_value >= filter_value`
|
||||
- `le`: `option_value <= filter_value`
|
||||
- `in`: `option_value in filter_value`
|
||||
- `contains`: `filter_value in option_value` (note reversed operands)
|
||||
|
||||
To check if the player can reach a switch, or if they've received the switch item if switches are randomized:
|
||||
```python
|
||||
rule1 = Has("Movement Ability", options=[OptionFilter(SkipsLevel, SkipsLevel.option_hard, operator="lt")])
|
||||
rule2 = Has("Item", options=[OptionFilter(ChoiceOption, [1, 5], operator="in")])
|
||||
```
|
||||
|
||||
To check if the player has received the switch item if switches are randomized, or if they can reach the switch when not
|
||||
randomized:
|
||||
|
||||
```python
|
||||
rule = (
|
||||
@@ -115,12 +153,12 @@ If you would like to provide option filters when reusing or composing rules, you
|
||||
common_rule = Has("A") | HasAny("B", "C")
|
||||
...
|
||||
rule = (
|
||||
Filtered(common_rule, options=[OptionFilter(Opt, 0)]),
|
||||
| Filtered(Has("X") | CanReachRegion("Y"), options=[OptionFilter(Opt, 1)]),
|
||||
Filtered(common_rule, options=[OptionFilter(Opt, 0)])
|
||||
| Filtered(Has("X") | CanReachRegion("Y"), options=[OptionFilter(Opt, 1)])
|
||||
)
|
||||
```
|
||||
|
||||
You can also use the & and | operators to apply options to rules:
|
||||
For convenience, you can also use the `&` and `|` operators to apply options to rules:
|
||||
|
||||
```python
|
||||
common_rule = Has("A")
|
||||
@@ -129,14 +167,22 @@ common_rule_only_on_easy = common_rule & easy_filter
|
||||
common_rule_skipped_on_easy = common_rule | easy_filter
|
||||
```
|
||||
|
||||
Combining the above, you can easily bypass a requirement based on option choices:
|
||||
|
||||
```python
|
||||
rule = Has("Some Upgrade") | OptionFilter(CombatDifficulty, CombatDifficulty.option_medium, operator="ge")
|
||||
```
|
||||
|
||||
### Field resolvers
|
||||
|
||||
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a `FieldResolver` to define how to populate that field when the rule is being resolved.
|
||||
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a
|
||||
`FieldResolver` to define how to populate that field when the rule is being resolved.
|
||||
|
||||
There are two build-in field resolvers:
|
||||
|
||||
- `FromOption`: Resolves to the value of the given option
|
||||
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get a nested attribute or dict item
|
||||
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get
|
||||
a nested attribute or dict item
|
||||
|
||||
```python
|
||||
world.options.mcguffin_count = 5
|
||||
@@ -148,7 +194,8 @@ rule = (
|
||||
# Results in Has("A", count=5) | HasGroup("Important items", count=99)
|
||||
```
|
||||
|
||||
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and implements a `resolve` function:
|
||||
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and
|
||||
implements a `resolve` function:
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -163,24 +210,30 @@ class FromCustomResolution(FieldResolver, game="MyGame"):
|
||||
rule = Has("Combat Level", count=FromCustomResolution("combat"))
|
||||
```
|
||||
|
||||
If you want to support rule serialization and your resolver contains non-serializable properties you may need to override `to_dict` or `from_dict`.
|
||||
If you want to support rule serialization and your resolver contains non-serializable properties you may need to
|
||||
override `to_dict` or `from_dict`.
|
||||
|
||||
## Enabling caching
|
||||
|
||||
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your rules.
|
||||
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your
|
||||
rules.
|
||||
|
||||
```python
|
||||
class MyWorld(CachedRuleBuilderWorld):
|
||||
game = "My Game"
|
||||
```
|
||||
|
||||
If your world's logic is very simple and you don't have many nested rules, the caching system may have more overhead cost than time it saves. You'll have to benchmark your own world to see if it should be enabled or not.
|
||||
If your world's logic is very simple and you don't have many nested rules, the caching system may have more overhead
|
||||
cost than time it saves. You'll have to benchmark your own world to see if it should be enabled or not.
|
||||
|
||||
### Item name mapping
|
||||
|
||||
If you have multiple real items that map to a single logic item, add a `item_mapping` class dict to your world that maps actual item names to real item names so the cache system knows what to invalidate.
|
||||
If you have multiple real items that map to a single logic item, add a `item_mapping` class dict to your world that maps
|
||||
actual item names to real item names so the cache system knows what to invalidate.
|
||||
|
||||
For example, if you have multiple `Currency x<num>` items on locations, but your rules only check a singular logical `Currency` item, eg `Has("Currency", 1000)`, you'll want to map each numerical currency item to the single logical `Currency`.
|
||||
For example, if you have multiple `Currency x<num>` items on locations, but your rules only check a singular logical
|
||||
`Currency` item, eg `Has("Currency", 1000)`, you'll want to map each numerical currency item to the single logical
|
||||
`Currency`.
|
||||
|
||||
```python
|
||||
class MyWorld(CachedRuleBuilderWorld):
|
||||
@@ -194,9 +247,13 @@ class MyWorld(CachedRuleBuilderWorld):
|
||||
|
||||
## Defining custom rules
|
||||
|
||||
You can create a custom rule by creating a class that inherits from `Rule` or any of the default rules. You must provide the game name as an argument to the class. It's recommended to use the `@dataclass` decorator to reduce boilerplate, and to also provide your world as a type argument to add correct type checking to the `_instantiate` method.
|
||||
You can create a custom rule by creating a class that inherits from `Rule` or any of the default rules. You must provide
|
||||
the game name as an argument to the class. It's recommended to use the `@dataclass` decorator to reduce boilerplate, and
|
||||
to also provide your world as a type argument to add correct type checking to the `_instantiate` method.
|
||||
|
||||
You must provide or inherit a `Resolved` child class that defines an `_evaluate` method. This class will automatically be converted into a frozen `dataclass`. If your world has caching enabled you may need to define one or more dependencies functions as outlined below.
|
||||
You must provide or inherit a `Resolved` child class that defines an `_evaluate` method. This class will automatically
|
||||
be converted into a frozen `dataclass`. If your world has caching enabled you may need to define one or more
|
||||
dependencies functions as outlined below.
|
||||
|
||||
To add a rule that checks if the user has enough mcguffins to goal, with a randomized requirement:
|
||||
|
||||
@@ -245,7 +302,10 @@ class ComplicatedFilter(Rule["MyWorld"], game="My Game"):
|
||||
|
||||
### Item dependencies
|
||||
|
||||
If your world inherits from `CachedRuleBuilderWorld` and there are items that when collected will affect the result of your rule evaluation, it must define an `item_dependencies` function that returns a mapping of the item name to the id of your rule. These dependencies will be combined to inform the caching system. It may be worthwhile to define this function even when caching is disabled as more things may use it in the future.
|
||||
If your world inherits from `CachedRuleBuilderWorld` and there are items that when collected will affect the result of
|
||||
your rule evaluation, it must define an `item_dependencies` function that returns a mapping of the item name to the id
|
||||
of your rule. These dependencies will be combined to inform the caching system. It may be worthwhile to define this
|
||||
function even when caching is disabled as more things may use it in the future.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -262,7 +322,10 @@ All of the default `Has*` rules define this function already.
|
||||
|
||||
### Region dependencies
|
||||
|
||||
If your custom rule references other regions, it must define a `region_dependencies` function that returns a mapping of region names to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
If your custom rule references other regions, it must define a `region_dependencies` function that returns a mapping of
|
||||
region names to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -279,7 +342,10 @@ The default `CanReachLocation`, `CanReachRegion`, and `CanReachEntrance` rules d
|
||||
|
||||
### Location dependencies
|
||||
|
||||
If your custom rule references other locations, it must define a `location_dependencies` function that returns a mapping of the location name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
If your custom rule references other locations, it must define a `location_dependencies` function that returns a mapping
|
||||
of the location name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -296,7 +362,10 @@ The default `CanReachLocation` rule defines this function already.
|
||||
|
||||
### Entrance dependencies
|
||||
|
||||
If your custom rule references other entrances, it must define a `entrance_dependencies` function that returns a mapping of the entrance name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the caching system if applicable.
|
||||
If your custom rule references other entrances, it must define a `entrance_dependencies` function that returns a mapping
|
||||
of the entrance name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
||||
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
||||
caching system if applicable.
|
||||
|
||||
```python
|
||||
@dataclasses.dataclass()
|
||||
@@ -313,9 +382,13 @@ The default `CanReachEntrance` rule defines this function already.
|
||||
|
||||
### Rule explanations
|
||||
|
||||
Resolved rules have a default implementation for `explain_json` and `explain_str` functions. The former optionally accepts a `CollectionState` and returns a list of `JSONMessagePart` appropriate for `print_json` in a client. It will display a human-readable message that explains what the rule requires. The latter is similar but returns a string. It is useful when debugging. There is also a `__str__` method defined to check what a rule is without a state.
|
||||
Resolved rules have a default implementation for `explain_json` and `explain_str` functions. The former optionally
|
||||
accepts a `CollectionState` and returns a list of `JSONMessagePart` appropriate for `print_json` in a client. It will
|
||||
display a human-readable message that explains what the rule requires. The latter is similar but returns a string. It is
|
||||
useful when debugging. There is also a `__str__` method defined to check what a rule is without a state.
|
||||
|
||||
To implement a custom message with a custom rule, override the `explain_json` and/or `explain_str` method on your `Resolved` class:
|
||||
To implement a custom message with a custom rule, override the `explain_json` and/or `explain_str` method on your
|
||||
`Resolved` class:
|
||||
|
||||
```python
|
||||
class MyRule(Rule, game="My Game"):
|
||||
@@ -352,22 +425,35 @@ class MyRule(Rule, game="My Game"):
|
||||
|
||||
### Cache control
|
||||
|
||||
By default your custom rule will work through the cache system as any other rule if caching is enabled. There are two class attributes on the `Resolved` class you can override to change this behavior.
|
||||
By default your custom rule will work through the cache system as any other rule if caching is enabled. There are two
|
||||
class attributes on the `Resolved` class you can override to change this behavior.
|
||||
|
||||
- `force_recalculate`: Setting this to `True` will cause your custom rule to skip going through the caching system and always recalculate when being evaluated. When a rule with this flag enabled is composed with `And` or `Or` it will cause any parent rules to always force recalculate as well. Use this flag when it's difficult to determine when your rule should be marked as stale.
|
||||
- `skip_cache`: Setting this to `True` will also cause your custom rule to skip going through the caching system when being evaluated. However, it will **not** affect any other rules when composed with `And` or `Or`, so it must still define its `*_dependencies` functions as required. Use this flag when the evaluation of this rule is trivial and the overhead of the caching system will slow it down.
|
||||
- `force_recalculate`: Setting this to `True` will cause your custom rule to skip going through the caching system and
|
||||
always recalculate when being evaluated. When a rule with this flag enabled is composed with `And` or `Or` it will
|
||||
cause any parent rules to always force recalculate as well. Use this flag when it's difficult to determine when your
|
||||
rule should be marked as stale.
|
||||
- `skip_cache`: Setting this to `True` will also cause your custom rule to skip going through the caching system when
|
||||
being evaluated. However, it will **not** affect any other rules when composed with `And` or `Or`, so it must still
|
||||
define its `*_dependencies` functions as required. Use this flag when the evaluation of this rule is trivial and the
|
||||
overhead of the caching system will slow it down.
|
||||
|
||||
### Caveats
|
||||
|
||||
- Ensure you are passing `caching_enabled=True` in your `_instantiate` function when creating resolved rule instances if your world has opted into caching.
|
||||
- Ensure you are passing `caching_enabled=True` in your `_instantiate` function when creating resolved rule instances if
|
||||
your world has opted into caching.
|
||||
- Resolved rules are forced to be frozen dataclasses. They and all their attributes must be immutable and hashable.
|
||||
- If your rule creates child rules ensure they are being resolved through the world rather than creating `Resolved` instances directly.
|
||||
- If your rule creates child rules ensure they are being resolved through the world rather than creating `Resolved`
|
||||
instances directly.
|
||||
|
||||
## Serialization
|
||||
|
||||
The rule builder is intended to be written first in Python for optimization and type safety. To facilitate exporting the rules to a client or tracker, rules have a `to_dict` method that returns a JSON-compatible dict. Since the location and entrance logic structure varies greatly from world to world, the actual JSON dumping is left up to the world dev.
|
||||
The rule builder is intended to be written first in Python for optimization and type safety. To facilitate exporting the
|
||||
rules to a client or tracker, rules have a `to_dict` method that returns a JSON-compatible dict. Since the location and
|
||||
entrance logic structure varies greatly from world to world, the actual JSON dumping is left up to the world dev.
|
||||
|
||||
The dict contains a `rule` key with the name of the rule, an `options` key with the rule's list of option filters, and an `args` key that contains any other arguments the individual rule has. For example, this is what a simple `Has` rule would look like:
|
||||
The dict contains a `rule` key with the name of the rule, an `options` key with the rule's list of option filters, and
|
||||
an `args` key that contains any other arguments the individual rule has. For example, this is what a simple `Has` rule
|
||||
would look like:
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -380,7 +466,8 @@ The dict contains a `rule` key with the name of the rule, an `options` key with
|
||||
}
|
||||
```
|
||||
|
||||
For `And` and `Or` rules, instead of an `args` key, they have a `children` key containing a list of their child rules in the same serializable format:
|
||||
For `And` and `Or` rules, instead of an `args` key, they have a `children` key containing a list of their child rules in
|
||||
the same serializable format:
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -464,7 +551,8 @@ class BasicLogicRule(Rule, game="My Game"):
|
||||
}
|
||||
```
|
||||
|
||||
If your logic has been done in custom JSON first, you can define a `from_dict` class method on your rules to parse it correctly:
|
||||
If your logic has been done in custom JSON first, you can define a `from_dict` class method on your rules to parse it
|
||||
correctly:
|
||||
|
||||
```python
|
||||
class BasicLogicRule(Rule, game="My Game"):
|
||||
@@ -485,10 +573,14 @@ These are properties and helpers that are available to you in your world.
|
||||
#### Methods
|
||||
|
||||
- `rule_from_dict(data)`: Create a rule instance from a deserialized dict representation
|
||||
- `register_rule_builder_dependencies()`: Register all rules that depend on location or entrance access with the inherited dependencies, gets called automatically after set_rules
|
||||
- `set_rule(spot: Location | Entrance, rule: Rule)`: Resolve a rule, register its dependencies, and set it on the given location or entrance
|
||||
- `register_rule_builder_dependencies()`: Register all rules that depend on location or entrance access with the
|
||||
inherited dependencies, gets called automatically after set_rules
|
||||
- `set_rule(spot: Location | Entrance, rule: Rule)`: Resolve a rule, register its dependencies, and set it on the given
|
||||
location or entrance
|
||||
- `set_completion_rule(rule: Rule)`: Sets the completion condition for this world
|
||||
- `create_entrance(from_region: Region, to_region: Region, rule: Rule | None, name: str | None = None, force_creation: bool = False)`: Attempt to create an entrance from `from_region` to `to_region`, skipping creation if `rule` is defined and evaluates to `False_()` unless force_creation is `True`
|
||||
- `create_entrance(from_region: Region, to_region: Region, rule: Rule | None, name: str | None = None, force_creation: bool = False)`:
|
||||
Attempt to create an entrance from `from_region` to `to_region`, skipping creation if `rule` is defined and evaluates
|
||||
to `False_()` unless force_creation is `True`
|
||||
|
||||
#### CachedRuleBuilderWorld Properties
|
||||
|
||||
@@ -501,18 +593,27 @@ The following property is only available when inheriting from `CachedRuleBuilder
|
||||
These are properties and helpers that you can use or override for custom rules.
|
||||
|
||||
- `_instantiate(world: World)`: Create a new resolved rule instance, override for custom rules as required
|
||||
- `to_dict()`: Create a JSON-compatible dict representation of this rule, override if you want to customize your rule's serialization
|
||||
- `from_dict(data, world_cls: type[World])`: Return a new rule instance from a deserialized representation, override if you've overridden `to_dict`
|
||||
- `to_dict()`: Create a JSON-compatible dict representation of this rule, override if you want to customize your rule's
|
||||
serialization
|
||||
- `from_dict(data, world_cls: type[World])`: Return a new rule instance from a deserialized representation, override if
|
||||
you've overridden `to_dict`
|
||||
- `__str__()`: Basic string representation of a rule, useful for debugging
|
||||
|
||||
#### Resolved rule API
|
||||
|
||||
- `player: int`: The slot this rule is resolved for
|
||||
- `_evaluate(state: CollectionState)`: Evaluate this rule against the given state, override this to define the logic for this rule
|
||||
- `item_dependencies()`: A mapping of item name to set of ids, override this if your custom rule depends on item collection
|
||||
- `region_dependencies()`: A mapping of region name to set of ids, override this if your custom rule depends on reaching regions
|
||||
- `location_dependencies()`: A mapping of location name to set of ids, override this if your custom rule depends on reaching locations
|
||||
- `entrance_dependencies()`: A mapping of entrance name to set of ids, override this if your custom rule depends on reaching entrances
|
||||
- `explain_json(state: CollectionState | None = None)`: Return a list of printJSON messages describing this rule's logic (and if state is defined its evaluation) in a human readable way, override to explain custom rules
|
||||
- `explain_str(state: CollectionState | None = None)`: Return a string describing this rule's logic (and if state is defined its evaluation) in a human readable way, override to explain custom rules, more useful for debugging
|
||||
- `_evaluate(state: CollectionState)`: Evaluate this rule against the given state, override this to define the logic for
|
||||
this rule
|
||||
- `item_dependencies()`: A mapping of item name to set of ids, override this if your custom rule depends on item
|
||||
collection
|
||||
- `region_dependencies()`: A mapping of region name to set of ids, override this if your custom rule depends on reaching
|
||||
regions
|
||||
- `location_dependencies()`: A mapping of location name to set of ids, override this if your custom rule depends on
|
||||
reaching locations
|
||||
- `entrance_dependencies()`: A mapping of entrance name to set of ids, override this if your custom rule depends on
|
||||
reaching entrances
|
||||
- `explain_json(state: CollectionState | None = None)`: Return a list of printJSON messages describing this rule's logic
|
||||
(and if state is defined its evaluation) in a human readable way, override to explain custom rules
|
||||
- `explain_str(state: CollectionState | None = None)`: Return a string describing this rule's logic (and if state is
|
||||
defined its evaluation) in a human readable way, override to explain custom rules, more useful for debugging
|
||||
- `__str__()`: A string describing this rule's logic without its evaluation, override to explain custom rules
|
||||
|
||||
@@ -78,16 +78,6 @@ first generate the binary distribution and then run `python setup.py bdist_appim
|
||||
put an `appimagetool` into the directory you run the command from, rename it to `appimagetool` and make it executable.
|
||||
|
||||
|
||||
## Optional: A Link to the Past Enemizer
|
||||
|
||||
Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an
|
||||
error if it is required.
|
||||
|
||||
You can get the latest Enemizer release at [Enemizer Github releases](https://github.com/Ijwu/Enemizer/releases).
|
||||
It should be dropped as "EnemizerCLI" into the root folder of the project. Alternatively, you can point the Enemizer
|
||||
setting in host.yaml at your Enemizer executable.
|
||||
|
||||
|
||||
## Optional: SNI
|
||||
|
||||
[SNI](https://github.com/alttpo/sni/blob/main/README.md) is required to use SNIClient. If not integrated into the project, it has to be started manually.
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ Unless you configured PyCharm to use pytest as a test runner, you may get import
|
||||
edit the run configuration, and set the working directory to the Archipelago directory which contains all the project files.
|
||||
|
||||
If you only want to run your world's defined tests, repeat the steps for the test directory within your world.
|
||||
Your working directory should be the directory of your world in the worlds directory and the script should be the
|
||||
Your working directory should be the root Archipelago directory and the script should be the
|
||||
tests folder within your world.
|
||||
|
||||
You can also find the 'Archipelago Unittests' as an option in the dropdown at the top of the window
|
||||
|
||||
@@ -108,7 +108,6 @@ Example:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"Donkey Kong Country 3":"f90acedcd958213f483a6a4c238e2a3faf92165e",
|
||||
"Factorio":"a699194a9589db3ebc0d821915864b422c782f44",
|
||||
...
|
||||
}
|
||||
|
||||
@@ -327,6 +327,11 @@ reject the placement of an item there.
|
||||
|
||||
### Events (or "generation-only items/locations")
|
||||
|
||||
> **Warning:** If you're trying to tell the Archipelago server that the player has achieved their goal, you want to send
|
||||
a [StatusUpdate packet](network%20protocol.md#statusupdate), or however [your client library](network%20protocol.md)
|
||||
wraps it. Despite the popularity of "victory events" during generation, events have nothing to do with how goals are
|
||||
triggered during gameplay.
|
||||
|
||||
An event item or location is one that only exists during multiworld generation; the server is never made aware of them.
|
||||
Event locations can never be checked by the player, and event items cannot be received during play.
|
||||
|
||||
|
||||
+1
-8
@@ -57,9 +57,8 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom
|
||||
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify;
|
||||
|
||||
[Files]
|
||||
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs;
|
||||
Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs;
|
||||
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
|
||||
|
||||
[Icons]
|
||||
@@ -83,7 +82,6 @@ Type: files; Name: "{app}\*.exe"
|
||||
Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua"
|
||||
Type: files; Name: "{app}\data\lua\connector_ff1.lua"
|
||||
Type: filesandordirs; Name: "{app}\SNI\lua*"
|
||||
Type: filesandordirs; Name: "{app}\EnemizerCLI*"
|
||||
#include "installdelete.iss"
|
||||
|
||||
[Registry]
|
||||
@@ -98,11 +96,6 @@ Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Arc
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: "";
|
||||
|
||||
Root: HKCR; Subkey: ".apdkc3"; ValueData: "{#MyAppName}dkc3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}dkc3patch"; ValueData: "Archipelago Donkey Kong Country 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}dkc3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}dkc3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: "";
|
||||
|
||||
Root: HKCR; Subkey: ".apsmw"; ValueData: "{#MyAppName}smwpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}smwpatch"; ValueData: "Archipelago Super Mario World Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
|
||||
Root: HKCR; Subkey: "{#MyAppName}smwpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";
|
||||
|
||||
@@ -379,15 +379,16 @@ class ServerLabel(HoverBehavior, MDTooltip, MDBoxLayout):
|
||||
text += "\nPermissions:"
|
||||
for permission_name, permission_data in ctx.permissions.items():
|
||||
text += f"\n {permission_name}: {permission_data}"
|
||||
if ctx.hint_cost is not None and ctx.total_locations:
|
||||
min_cost = int(ctx.server_version >= (0, 3, 9))
|
||||
text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \
|
||||
f"For you this means every " \
|
||||
f"{max(min_cost, int(ctx.hint_cost * 0.01 * ctx.total_locations))} " \
|
||||
"location checks." \
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
elif ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
if ctx.total_locations and ctx.hint_cost is not None:
|
||||
if ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
else:
|
||||
min_cost = int(ctx.server_version >= (0, 3, 9))
|
||||
text += f"\nA new !hint <itemname> costs {ctx.hint_cost}% of checks made. " \
|
||||
f"For you this means every " \
|
||||
f"{max(min_cost, int(ctx.hint_cost * 0.01 * ctx.total_locations))} " \
|
||||
"location checks." \
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
if ctx.stored_data and "_read_race_mode" in ctx.stored_data:
|
||||
text += "\nRace mode is enabled." \
|
||||
if ctx.stored_data["_read_race_mode"] else "\nRace mode is disabled."
|
||||
|
||||
+138
-7
@@ -36,7 +36,7 @@ def _create_hash_fn(resolved_rule_cls: "CustomRuleRegister") -> Callable[..., in
|
||||
class CustomRuleRegister(type):
|
||||
"""A metaclass to contain world custom rules and automatically convert resolved rules to frozen dataclasses"""
|
||||
|
||||
resolved_rules: ClassVar[dict[int, "Rule.Resolved"]] = {}
|
||||
resolved_rules: ClassVar[dict["Rule.Resolved", "Rule.Resolved"]] = {}
|
||||
"""A cached of resolved rules to turn each unique one into a singleton"""
|
||||
|
||||
custom_rules: ClassVar[dict[str, dict[str, type["Rule[Any]"]]]] = {}
|
||||
@@ -64,10 +64,9 @@ class CustomRuleRegister(type):
|
||||
@override
|
||||
def __call__(cls, *args: Any, **kwds: Any) -> Any:
|
||||
rule = super().__call__(*args, **kwds)
|
||||
rule_hash = hash(rule)
|
||||
if rule_hash in cls.resolved_rules:
|
||||
return cls.resolved_rules[rule_hash]
|
||||
cls.resolved_rules[rule_hash] = rule
|
||||
if rule in cls.resolved_rules:
|
||||
return cls.resolved_rules[rule]
|
||||
cls.resolved_rules[rule] = rule
|
||||
return rule
|
||||
|
||||
@classmethod
|
||||
@@ -426,13 +425,142 @@ class NestedRule(Rule[TWorld], game="Archipelago"):
|
||||
return combined_deps
|
||||
|
||||
|
||||
class AtLeast(NestedRule[TWorld], game="Archipelago"):
|
||||
"""A rule that returns true when at least N child rules evaluate as true"""
|
||||
|
||||
count: int | FieldResolver
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
count: int | FieldResolver,
|
||||
*children: Rule[TWorld],
|
||||
options: Iterable[OptionFilter] = (),
|
||||
filtered_resolution: bool = False,
|
||||
) -> None:
|
||||
super().__init__(*children, options=options, filtered_resolution=filtered_resolution)
|
||||
self.count = count
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
count = resolve_field(self.count, world, int)
|
||||
if count == 0:
|
||||
return True_().resolve(world)
|
||||
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
return AtLeast.from_resolved(count, world, children_to_process)
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, count: int, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
clauses: list[Rule.Resolved] = []
|
||||
|
||||
while children_to_process:
|
||||
child = children_to_process.pop(0)
|
||||
if child.always_true:
|
||||
if count == 1:
|
||||
return child
|
||||
count -= 1
|
||||
continue
|
||||
if child.always_false:
|
||||
# falses can be ignored
|
||||
continue
|
||||
|
||||
clauses.append(child)
|
||||
|
||||
if len(clauses) < count:
|
||||
return False_().resolve(world)
|
||||
if count == 1:
|
||||
# Switch to Or which has more optimized handling
|
||||
return Or.from_resolved(world, clauses)
|
||||
if count == len(clauses):
|
||||
# Switch to And which has more optimized handling
|
||||
return And.from_resolved(world, clauses)
|
||||
return AtLeast.Resolved(
|
||||
tuple(clauses),
|
||||
count=count,
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
|
||||
@override
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
output = super().to_dict()
|
||||
count = self.count
|
||||
output["count"] = count.to_dict() if isinstance(count, FieldResolver) else count
|
||||
return output
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
|
||||
args = cls._parse_field_resolvers(data, world_cls.game)
|
||||
options = OptionFilter.multiple_from_dict(data.get("options", ()))
|
||||
children = [world_cls.rule_from_dict(c) for c in data.get("children", ())]
|
||||
return cls(
|
||||
args.pop("count"),
|
||||
*children,
|
||||
options=options,
|
||||
filtered_resolution=data.get("filtered_resolution", False),
|
||||
)
|
||||
|
||||
class Resolved(NestedRule.Resolved):
|
||||
count: int
|
||||
|
||||
@override
|
||||
def _evaluate(self, state: CollectionState) -> bool:
|
||||
count = self.count
|
||||
for rule in self.children:
|
||||
if rule(state):
|
||||
if count == 1:
|
||||
return True
|
||||
count -= 1
|
||||
return False
|
||||
|
||||
@override
|
||||
def explain_json(self, state: CollectionState | None = None) -> list[JSONMessagePart]:
|
||||
messages: list[JSONMessagePart] = []
|
||||
if state is None:
|
||||
messages = [
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": str(self.count)},
|
||||
{"type": "text", "text": " of ("},
|
||||
]
|
||||
else:
|
||||
satisfied_count = sum(1 if child(state) else 0 for child in self.children)
|
||||
messages = [
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": f"{satisfied_count}/{self.count}"},
|
||||
{"type": "text", "text": " of ("},
|
||||
]
|
||||
for i, child in enumerate(self.children):
|
||||
if i > 0:
|
||||
messages.append({"type": "text", "text": ", "})
|
||||
messages.extend(child.explain_json(state))
|
||||
messages.append({"type": "text", "text": ")"})
|
||||
return messages
|
||||
|
||||
@override
|
||||
def explain_str(self, state: CollectionState | None = None) -> str:
|
||||
clauses = ", ".join([c.explain_str(state) for c in self.children])
|
||||
if state is None:
|
||||
return f"At least {self.count} of ({clauses})"
|
||||
satisfied_count = sum(1 if child(state) else 0 for child in self.children)
|
||||
return f"At least {satisfied_count}/{self.count} of ({clauses})"
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
clauses = ", ".join([str(c) for c in self.children])
|
||||
return f"At least {self.count} of ({clauses})"
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False)
|
||||
class And(NestedRule[TWorld], game="Archipelago"):
|
||||
"""A rule that only returns true when all child rules evaluate as true"""
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
return And.from_resolved(world, [c.resolve(world) for c in self.children])
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
clauses: list[Rule.Resolved] = []
|
||||
items: dict[str, int] = {}
|
||||
true_rule: Rule.Resolved | None = None
|
||||
@@ -519,7 +647,10 @@ class Or(NestedRule[TWorld], game="Archipelago"):
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
children_to_process = [c.resolve(world) for c in self.children]
|
||||
return Or.from_resolved(world, [c.resolve(world) for c in self.children])
|
||||
|
||||
@classmethod
|
||||
def from_resolved(cls, world: TWorld, children_to_process: list[Rule.Resolved]) -> Rule.Resolved:
|
||||
clauses: list[Rule.Resolved] = []
|
||||
items: dict[str, int] = {}
|
||||
|
||||
|
||||
+9
-5
@@ -98,6 +98,8 @@ class Group:
|
||||
self._changed = True
|
||||
attr = new
|
||||
# resolve the path immediately when accessing it
|
||||
if attr.exists():
|
||||
attr.__class__.validate(attr.resolve())
|
||||
return attr.__class__(attr.resolve())
|
||||
return attr
|
||||
|
||||
@@ -633,10 +635,6 @@ class ServerOptions(Group):
|
||||
class GeneratorOptions(Group):
|
||||
"""Options for Generation"""
|
||||
|
||||
class EnemizerPath(LocalFilePath):
|
||||
"""Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases"""
|
||||
is_exe = True
|
||||
|
||||
class PlayerFilesPath(OptionalUserFolderPath):
|
||||
"""Folder from which the player yaml files are pulled from"""
|
||||
# created on demand, so marked as optional
|
||||
@@ -644,6 +642,12 @@ class GeneratorOptions(Group):
|
||||
class Players(int):
|
||||
"""amount of players, 0 to infer from player files"""
|
||||
|
||||
class AllowQuantity(Bool):
|
||||
"""
|
||||
allow players to set an individual quantity for their yaml settings
|
||||
with 'false' any amounts from the players will be ignored and set to 1
|
||||
"""
|
||||
|
||||
class WeightsFilePath(str):
|
||||
"""
|
||||
general weights file, within the stated player_files_path location
|
||||
@@ -687,9 +691,9 @@ class GeneratorOptions(Group):
|
||||
start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
|
||||
"""
|
||||
|
||||
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
|
||||
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
|
||||
players: Players = Players(0)
|
||||
allow_quantity: AllowQuantity | bool = False
|
||||
weights_file_path: WeightsFilePath = WeightsFilePath("weights.yaml")
|
||||
meta_file_path: MetaFilePath = MetaFilePath("meta.yaml")
|
||||
spoiler: Spoiler = Spoiler(3)
|
||||
|
||||
@@ -201,7 +201,7 @@ if is_windows:
|
||||
icon=resolve_icon(c.icon),
|
||||
))
|
||||
|
||||
extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"]
|
||||
extra_data = ["LICENSE", "data", "SNI"]
|
||||
extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else []
|
||||
|
||||
|
||||
@@ -456,9 +456,8 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
for world_directory in folders_to_remove)
|
||||
else:
|
||||
# make sure extra programs are executable
|
||||
enemizer_exe = self.buildfolder / 'EnemizerCLI/EnemizerCLI.Core'
|
||||
sni_exe = self.buildfolder / 'SNI/sni'
|
||||
extra_exes = (enemizer_exe, sni_exe)
|
||||
extra_exes = (sni_exe,)
|
||||
for extra_exe in extra_exes:
|
||||
if extra_exe.is_file():
|
||||
extra_exe.chmod(0o755)
|
||||
|
||||
@@ -54,7 +54,7 @@ class TestImplemented(unittest.TestCase):
|
||||
|
||||
def test_no_failed_world_loads(self):
|
||||
if failed_world_loads:
|
||||
self.fail(f"The following worlds failed to load: {failed_world_loads}")
|
||||
self.fail(f"The following worlds failed to load: {failed_world_loads.keys()}")
|
||||
|
||||
def test_prefill_items(self):
|
||||
"""Test that every world can reach every location from allstate before pre_fill."""
|
||||
|
||||
@@ -12,6 +12,7 @@ from rule_builder.field_resolvers import FieldResolver, FromOption, FromWorldAtt
|
||||
from rule_builder.options import Operator, OptionFilter
|
||||
from rule_builder.rules import (
|
||||
And,
|
||||
AtLeast,
|
||||
CanReachEntrance,
|
||||
CanReachLocation,
|
||||
CanReachRegion,
|
||||
@@ -250,6 +251,40 @@ class CachedRuleBuilderTestCase(RuleBuilderTestCase):
|
||||
Or(HasAnyCount({"A": 1, "B": 2}), HasAnyCount({"A": 2, "B": 2})),
|
||||
HasAnyCount.Resolved((("A", 1), ("B", 2)), player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(0, Has("A")),
|
||||
True_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(3, True_(), Has("A"), Has("B"), Has("C")),
|
||||
AtLeast.Resolved(
|
||||
(Has.Resolved("A", player=1), Has.Resolved("B", player=1), Has.Resolved("C", player=1)), 2, player=1
|
||||
),
|
||||
),
|
||||
(
|
||||
AtLeast(2, False_(), Has("A"), Has("B"), Has("C")),
|
||||
AtLeast.Resolved(
|
||||
(Has.Resolved("A", player=1), Has.Resolved("B", player=1), Has.Resolved("C", player=1)), 2, player=1
|
||||
),
|
||||
),
|
||||
(
|
||||
AtLeast(2, True_(), True_(), Has("A")),
|
||||
True_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
AtLeast(3, Has("A"), Has("B")),
|
||||
False_.Resolved(player=1),
|
||||
),
|
||||
(
|
||||
# This test will fail when Or(Rule, Rule) will be optimized to Rule
|
||||
AtLeast(1, Rule(), Rule()),
|
||||
Or.Resolved((Rule.Resolved(player=1), Rule.Resolved(player=1)), player=1),
|
||||
),
|
||||
(
|
||||
# This test will fail when And(Rule, Rule) will be optimized to Rule
|
||||
AtLeast(2, Rule(), Rule()),
|
||||
And.Resolved((Rule.Resolved(player=1), Rule.Resolved(player=1)), player=1),
|
||||
),
|
||||
)
|
||||
)
|
||||
class TestSimplify(RuleBuilderTestCase):
|
||||
@@ -416,6 +451,15 @@ class TestHashes(RuleBuilderTestCase):
|
||||
rule2 = HasAll("2", "2", "2", "1")
|
||||
self.assertEqual(hash(rule1.resolve(world)), hash(rule2.resolve(world)))
|
||||
|
||||
def test_hash_collision(self) -> None:
|
||||
multiworld = setup_solo_multiworld(self.world_cls, steps=("generate_early",), seed=0)
|
||||
world = multiworld.worlds[1]
|
||||
rule1 = Has("A", count=1).resolve(world)
|
||||
rule2 = Has("A", count=1 << 61).resolve(world)
|
||||
self.assertEqual(hash(rule1), hash(rule2))
|
||||
self.assertNotEqual(rule1, rule2)
|
||||
self.assertNotEqual(id(rule1), id(rule2))
|
||||
|
||||
|
||||
class TestCaching(CachedRuleBuilderTestCase):
|
||||
multiworld: MultiWorld # pyright: ignore[reportUninitializedInstanceVariable]
|
||||
@@ -622,6 +666,24 @@ class TestRules(RuleBuilderTestCase):
|
||||
self.state.remove(item)
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
|
||||
def test_at_least(self) -> None:
|
||||
# Has has to be relied on as True_ and False_ would be optimized out
|
||||
rule = AtLeast(2, Has("Item 1"), Has("Item 1"), Has("Item 2"), Has("Item 3"))
|
||||
resolved_rule = rule.resolve(self.world)
|
||||
self.world.register_rule_dependencies(resolved_rule)
|
||||
item1 = self.world.create_item("Item 1")
|
||||
item2 = self.world.create_item("Item 2")
|
||||
item3 = self.world.create_item("Item 3")
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
self.state.collect(item1)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
self.state.collect(item2)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
self.state.remove(item1)
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
self.state.collect(item3)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
|
||||
def test_has_all(self) -> None:
|
||||
rule = HasAll("Item 1", "Item 2")
|
||||
resolved_rule = rule.resolve(self.world)
|
||||
@@ -797,8 +859,13 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
OptionFilter(ChoiceOption, ChoiceOption.option_second, "ge"),
|
||||
],
|
||||
),
|
||||
AtLeast(
|
||||
FromWorldAttr("instance_data.at_least_requirement"),
|
||||
Has("i15", count=2),
|
||||
HasGroup("g2", count=3),
|
||||
),
|
||||
CanReachEntrance("e1"),
|
||||
HasGroupUnique("g2", count=5),
|
||||
HasGroupUnique("g3", count=5),
|
||||
)
|
||||
|
||||
rule_dict: ClassVar[dict[str, Any]] = {
|
||||
@@ -922,6 +989,29 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"rule": "AtLeast",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"count": {"resolver": "FromWorldAttr", "name": "instance_data.at_least_requirement"},
|
||||
"children": [
|
||||
{
|
||||
"rule": "Has",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {
|
||||
"item_name": "i15",
|
||||
"count": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"rule": "HasGroup",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {"item_name_group": "g2", "count": 3},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"rule": "CanReachEntrance",
|
||||
"options": [],
|
||||
@@ -932,7 +1022,7 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
"rule": "HasGroupUnique",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {"item_name_group": "g2", "count": 5},
|
||||
"args": {"item_name_group": "g3", "count": 5},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -964,9 +1054,15 @@ class TestExplain(RuleBuilderTestCase):
|
||||
),
|
||||
player=1,
|
||||
),
|
||||
HasAllCounts.Resolved((("Item 6", 1), ("Item 7", 5)), player=1),
|
||||
HasAnyCount.Resolved((("Item 8", 2), ("Item 9", 3)), player=1),
|
||||
HasFromList.Resolved(("Item 10", "Item 11", "Item 12"), count=2, player=1),
|
||||
AtLeast.Resolved(
|
||||
children=(
|
||||
HasAllCounts.Resolved((("Item 6", 1), ("Item 7", 5)), player=1),
|
||||
HasAnyCount.Resolved((("Item 8", 2), ("Item 9", 3)), player=1),
|
||||
HasFromList.Resolved(("Item 10", "Item 11", "Item 12"), count=2, player=1),
|
||||
),
|
||||
count=2,
|
||||
player=1,
|
||||
),
|
||||
HasFromListUnique.Resolved(("Item 13", "Item 14"), player=1),
|
||||
HasGroup.Resolved("Group 1", ("Item 15", "Item 16", "Item 17"), player=1),
|
||||
HasGroupUnique.Resolved("Group 2", ("Item 18", "Item 19"), count=2, player=1),
|
||||
@@ -1031,6 +1127,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "0/2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Missing "},
|
||||
{"type": "color", "color": "cyan", "text": "some"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1041,7 +1140,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "Item 7"},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Missing "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1052,7 +1151,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "Item 9"},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "salmon", "text": "0/2"},
|
||||
{"type": "text", "text": " items from ("},
|
||||
@@ -1063,6 +1162,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "color", "color": "salmon", "text": "Item 12"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "salmon", "text": "0/1"},
|
||||
@@ -1129,6 +1229,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "3/2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1139,7 +1242,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "green", "text": "Item 7"},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "some"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1150,7 +1253,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "green", "text": "Item 9"},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "green", "text": "30/2"},
|
||||
{"type": "text", "text": " items from ("},
|
||||
@@ -1161,6 +1264,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "color", "color": "green", "text": "Item 12"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "green", "text": "2/1"},
|
||||
@@ -1195,7 +1299,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "False"},
|
||||
{"type": "text", "text": ")"},
|
||||
]
|
||||
assert self.resolved_rule.explain_json(self.state) == expected
|
||||
self.assertEqual(self.resolved_rule.explain_json(self.state), expected)
|
||||
|
||||
def test_explain_json_without_state(self) -> None:
|
||||
expected: list[JSONMessagePart] = [
|
||||
@@ -1223,6 +1327,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "At least "},
|
||||
{"type": "color", "color": "cyan", "text": "2"},
|
||||
{"type": "text", "text": " of ("},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "all"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1232,7 +1339,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "item_name", "flags": 1, "text": "Item 7", "player": 1},
|
||||
{"type": "text", "text": " x5"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "any"},
|
||||
{"type": "text", "text": " of ("},
|
||||
@@ -1242,7 +1349,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "item_name", "flags": 1, "text": "Item 9", "player": 1},
|
||||
{"type": "text", "text": " x3"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "2"},
|
||||
{"type": "text", "text": "x items from ("},
|
||||
@@ -1252,6 +1359,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "text", "text": ", "},
|
||||
{"type": "item_name", "flags": 1, "text": "Item 12", "player": 1},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": ")"},
|
||||
{"type": "text", "text": " & "},
|
||||
{"type": "text", "text": "Has "},
|
||||
{"type": "color", "color": "cyan", "text": "1"},
|
||||
@@ -1285,16 +1393,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
{"type": "color", "color": "salmon", "text": "False"},
|
||||
{"type": "text", "text": ")"},
|
||||
]
|
||||
assert self.resolved_rule.explain_json() == expected
|
||||
self.assertEqual(self.resolved_rule.explain_json(), expected)
|
||||
|
||||
def test_explain_str_with_state_no_items(self) -> None:
|
||||
expected = (
|
||||
"((Missing 4x Item 1",
|
||||
"| Missing some of (Missing: Item 2, Item 3)",
|
||||
"| Missing all of (Missing: Item 4, Item 5))",
|
||||
"& Missing some of (Missing: Item 6 x1, Item 7 x5)",
|
||||
"& Missing all of (Missing: Item 8 x2, Item 9 x3)",
|
||||
"& Has 0/2 items from (Missing: Item 10, Item 11, Item 12)",
|
||||
"& At least 0/2 of (Missing some of (Missing: Item 6 x1, Item 7 x5),",
|
||||
"Missing all of (Missing: Item 8 x2, Item 9 x3),",
|
||||
"Has 0/2 items from (Missing: Item 10, Item 11, Item 12))",
|
||||
"& Has 0/1 unique items from (Missing: Item 13, Item 14)",
|
||||
"& Has 0/1 items from Group 1",
|
||||
"& Has 0/2 unique items from Group 2",
|
||||
@@ -1304,7 +1412,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
assert self.resolved_rule.explain_str(self.state) == " ".join(expected)
|
||||
self.assertEqual(self.resolved_rule.explain_str(self.state), " ".join(expected))
|
||||
|
||||
def test_explain_str_with_state_all_items(self) -> None:
|
||||
self._collect_all()
|
||||
@@ -1313,9 +1421,9 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Found: Item 2, Item 3)",
|
||||
"| Has some of (Found: Item 4, Item 5))",
|
||||
"& Has all of (Found: Item 6 x1, Item 7 x5)",
|
||||
"& Has some of (Found: Item 8 x2, Item 9 x3)",
|
||||
"& Has 30/2 items from (Found: Item 10, Item 11, Item 12)",
|
||||
"& At least 3/2 of (Has all of (Found: Item 6 x1, Item 7 x5),",
|
||||
"Has some of (Found: Item 8 x2, Item 9 x3),",
|
||||
"Has 30/2 items from (Found: Item 10, Item 11, Item 12))",
|
||||
"& Has 2/1 unique items from (Found: Item 13, Item 14)",
|
||||
"& Has 30/1 items from Group 1",
|
||||
"& Has 2/2 unique items from Group 2",
|
||||
@@ -1325,16 +1433,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
assert self.resolved_rule.explain_str(self.state) == " ".join(expected)
|
||||
self.assertEqual(self.resolved_rule.explain_str(self.state), " ".join(expected))
|
||||
|
||||
def test_explain_str_without_state(self) -> None:
|
||||
expected = (
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Item 2, Item 3)",
|
||||
"| Has any of (Item 4, Item 5))",
|
||||
"& Has all of (Item 6 x1, Item 7 x5)",
|
||||
"& Has any of (Item 8 x2, Item 9 x3)",
|
||||
"& Has 2x items from (Item 10, Item 11, Item 12)",
|
||||
"& At least 2 of (Has all of (Item 6 x1, Item 7 x5),",
|
||||
"Has any of (Item 8 x2, Item 9 x3),",
|
||||
"Has 2x items from (Item 10, Item 11, Item 12))",
|
||||
"& Has a unique item from (Item 13, Item 14)",
|
||||
"& Has an item from Group 1",
|
||||
"& Has 2x unique items from Group 2",
|
||||
@@ -1344,16 +1452,16 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
assert self.resolved_rule.explain_str() == " ".join(expected)
|
||||
self.assertEqual(self.resolved_rule.explain_str(), " ".join(expected))
|
||||
|
||||
def test_str(self) -> None:
|
||||
expected = (
|
||||
"((Has 4x Item 1",
|
||||
"| Has all of (Item 2, Item 3)",
|
||||
"| Has any of (Item 4, Item 5))",
|
||||
"& Has all of (Item 6 x1, Item 7 x5)",
|
||||
"& Has any of (Item 8 x2, Item 9 x3)",
|
||||
"& Has 2x items from (Item 10, Item 11, Item 12)",
|
||||
"& At least 2 of (Has all of (Item 6 x1, Item 7 x5),",
|
||||
"Has any of (Item 8 x2, Item 9 x3),",
|
||||
"Has 2x items from (Item 10, Item 11, Item 12))",
|
||||
"& Has a unique item from (Item 13, Item 14)",
|
||||
"& Has an item from Group 1",
|
||||
"& Has 2x unique items from Group 2",
|
||||
@@ -1363,7 +1471,7 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& True",
|
||||
"& False)",
|
||||
)
|
||||
assert str(self.resolved_rule) == " ".join(expected)
|
||||
self.assertEqual(str(self.resolved_rule), " ".join(expected))
|
||||
|
||||
|
||||
@classvar_matrix(
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Verify that NetUtils' enums work correctly with all supported Python versions."""
|
||||
|
||||
import pickle
|
||||
import unittest
|
||||
from enum import Enum
|
||||
from typing import Type
|
||||
|
||||
from NetUtils import ClientStatus, HintStatus, SlotType
|
||||
from Utils import restricted_loads
|
||||
|
||||
|
||||
class Base:
|
||||
class DataEnumTest(unittest.TestCase):
|
||||
type: Type[Enum]
|
||||
value: Enum
|
||||
|
||||
def test_unpickle(self) -> None:
|
||||
"""Tests that enums used in multidata or multisave can be pickled and unpickled."""
|
||||
pickled = pickle.dumps(self.value)
|
||||
unpickled = restricted_loads(pickled)
|
||||
self.assertEqual(unpickled, self.value)
|
||||
self.assertIsInstance(unpickled, self.type)
|
||||
|
||||
|
||||
class HintStatusTest(Base.DataEnumTest):
|
||||
type = HintStatus
|
||||
value = HintStatus.HINT_AVOID
|
||||
|
||||
|
||||
class ClientStatusTest(Base.DataEnumTest):
|
||||
type = ClientStatus
|
||||
value = ClientStatus.CLIENT_GOAL
|
||||
|
||||
|
||||
class SlotTypeTest(Base.DataEnumTest):
|
||||
type = SlotType
|
||||
value = SlotType.player
|
||||
@@ -1,6 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from Options import Choice, DefaultOnToggle, Toggle
|
||||
from collections import Counter
|
||||
|
||||
from Options import Choice, DefaultOnToggle, Toggle, OptionDict, OptionError, OptionSet, OptionList, OptionCounter
|
||||
|
||||
|
||||
class TestNumericOptions(unittest.TestCase):
|
||||
@@ -74,3 +76,97 @@ class TestNumericOptions(unittest.TestCase):
|
||||
self.assertTrue(toggle_string)
|
||||
self.assertTrue(toggle_int)
|
||||
self.assertTrue(toggle_alias)
|
||||
|
||||
|
||||
class TestContainerOptions(unittest.TestCase):
|
||||
def test_option_dict(self):
|
||||
class TestOptionDict(OptionDict):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_dict = {"D": "Foo"}
|
||||
test_option_dict = TestOptionDict(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_dict.verify_keys)
|
||||
|
||||
init_dict = {"A": "foo", "B": "bar"}
|
||||
test_option_dict = TestOptionDict(init_dict)
|
||||
|
||||
self.assertEqual(test_option_dict, init_dict) # Implicit value comparison
|
||||
self.assertEqual(test_option_dict["A"], "foo")
|
||||
self.assertIn("B", test_option_dict)
|
||||
self.assertNotIn("C", test_option_dict)
|
||||
self.assertRaises(KeyError, lambda: test_option_dict["C"])
|
||||
|
||||
def test_option_set(self):
|
||||
class TestOptionSet(OptionSet):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_set = {"D"}
|
||||
test_option_set = TestOptionSet(unknown_key_init_set)
|
||||
self.assertRaises(OptionError, test_option_set.verify_keys)
|
||||
|
||||
init_set = {"A", "B"}
|
||||
test_option_set = TestOptionSet(init_set)
|
||||
|
||||
self.assertEqual(test_option_set, init_set) # Implicit value comparison
|
||||
self.assertIn("B", test_option_set)
|
||||
self.assertNotIn("C", test_option_set)
|
||||
|
||||
def test_option_list(self):
|
||||
class TestOptionList(OptionList):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_list = ["D"]
|
||||
test_option_list = TestOptionList(unknown_key_init_list)
|
||||
self.assertRaises(OptionError, test_option_list.verify_keys)
|
||||
|
||||
init_list = ["A", "B"]
|
||||
test_option_list = TestOptionList(init_list)
|
||||
|
||||
self.assertEqual(test_option_list, init_list)
|
||||
self.assertIn("B", test_option_list)
|
||||
self.assertNotIn("C", test_option_list)
|
||||
|
||||
|
||||
def test_option_counter(self):
|
||||
class TestOptionCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
max = 10
|
||||
min = 0
|
||||
|
||||
unknown_key_init_dict = {"D": 5}
|
||||
test_option_counter = TestOptionCounter(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_keys)
|
||||
|
||||
wrong_value_type_init_dict = {"A": "B"}
|
||||
self.assertRaises(TypeError, TestOptionCounter, wrong_value_type_init_dict)
|
||||
|
||||
violates_max_init_dict = {"A": 5, "B": 11}
|
||||
test_option_counter = TestOptionCounter(violates_max_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
violates_min_init_dict = {"A": -1, "B": 5}
|
||||
test_option_counter = TestOptionCounter(violates_min_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestOptionCounter(init_dict)
|
||||
self.assertEqual(test_option_counter, Counter(init_dict))
|
||||
self.assertIn("A", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0)
|
||||
self.assertEqual(test_option_counter["B"], 10)
|
||||
self.assertEqual(test_option_counter["C"], 0)
|
||||
|
||||
def test_culling_option_counter(self):
|
||||
class TestCullingCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
cull_zeroes = True
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestCullingCounter(init_dict)
|
||||
self.assertNotIn("A", test_option_counter)
|
||||
self.assertIn("B", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0) # It's still a Counter! cull_zeroes is about "in" checks.
|
||||
self.assertEqual(test_option_counter, Counter({"B": 10}))
|
||||
|
||||
@@ -33,4 +33,9 @@ class TestBase(unittest.TestCase):
|
||||
cls.app = raw_app
|
||||
|
||||
def setUp(self) -> None:
|
||||
from WebHostLib.models import db
|
||||
from pony.orm import db_session
|
||||
with db_session:
|
||||
for entity in db.entities.values():
|
||||
entity.select().delete(bulk=True)
|
||||
self.client = self.app.test_client()
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
from datetime import timedelta
|
||||
from uuid import UUID, uuid4
|
||||
from pony.orm import db_session, commit
|
||||
|
||||
from Utils import utcnow
|
||||
from WebHostLib.autolauncher import cleanup
|
||||
from WebHostLib.models import Room, Seed, Slot
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestCleanup(TestBase):
|
||||
def test_cleanup_unowned(self) -> None:
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=UUID(int=0))
|
||||
Room(id=uuid4(), owner=UUID(int=0), seed=s1)
|
||||
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4()) # Owned
|
||||
Room(id=uuid4(), owner=UUID(int=0), seed=s2) # Unowned room of owned seed
|
||||
|
||||
Seed(id=uuid4(), multidata=b"", owner=UUID(int=0)) # Unowned seed with no rooms
|
||||
|
||||
commit()
|
||||
|
||||
cleanup({"ROOM_AUTO_DELETE": 0})
|
||||
|
||||
with db_session:
|
||||
self.assertEqual(Room.select().count(), 0) # Both rooms were unowned
|
||||
self.assertEqual(Seed.select().count(), 1) # s2 is owned
|
||||
self.assertIsNotNone(Seed.get(id=s2.id))
|
||||
|
||||
def test_cleanup_auto_delete(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
recent_time = now - timedelta(days=2)
|
||||
|
||||
with db_session:
|
||||
# Case 1: Old room, owned
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r1 = Room(id=uuid4(), owner=uuid4(), seed=s1, last_activity=old_time)
|
||||
|
||||
# Case 2: Recent room, owned
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r2 = Room(id=uuid4(), owner=uuid4(), seed=s2, last_activity=recent_time)
|
||||
|
||||
# Case 3: Old seed, no rooms, owned
|
||||
s3 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
|
||||
# Case 4: Recent seed, no rooms, owned
|
||||
s4 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=recent_time)
|
||||
|
||||
# Case 5: Old seed with recent room (should not be deleted)
|
||||
s5 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r5 = Room(id=uuid4(), owner=uuid4(), seed=s5, last_activity=recent_time)
|
||||
|
||||
commit()
|
||||
|
||||
# Delete items older than 5 days
|
||||
cleanup({"ROOM_AUTO_DELETE": 5})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNone(Room.get(id=r1.id), "Old room should be deleted")
|
||||
self.assertIsNotNone(Room.get(id=r2.id), "Recent room should NOT be deleted")
|
||||
self.assertIsNone(Seed.get(id=s3.id), "Old seed without rooms should be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s4.id), "Recent seed without rooms should NOT be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s5.id), "Old seed with recent room should NOT be deleted")
|
||||
self.assertIsNotNone(Room.get(id=r5.id), "Recent room for old seed should NOT be deleted")
|
||||
|
||||
# Seeds are deleted if they have NO rooms AND are old.
|
||||
# After r1 is deleted, s1 has no rooms. Since it's old, it should be deleted.
|
||||
self.assertIsNone(Seed.get(id=s1.id), "Old seed whose only room was deleted should be deleted")
|
||||
|
||||
def test_cleanup_disabled(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
r1 = Room(id=uuid4(), owner=uuid4(), seed=s1, last_activity=old_time)
|
||||
commit()
|
||||
|
||||
cleanup({"ROOM_AUTO_DELETE": 0})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNotNone(Room.get(id=r1.id), "Room should NOT be deleted when auto-delete is 0")
|
||||
self.assertIsNotNone(Seed.get(id=s1.id), "Seed should NOT be deleted when auto-delete is 0")
|
||||
|
||||
def test_cleanup_slots(self) -> None:
|
||||
now = utcnow()
|
||||
old_time = now - timedelta(days=10)
|
||||
|
||||
with db_session:
|
||||
s1 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=old_time)
|
||||
slot1 = Slot(player_id=1, player_name="P1", seed=s1, game="TestGame")
|
||||
|
||||
s2 = Seed(id=uuid4(), multidata=b"", owner=uuid4(), creation_time=now)
|
||||
slot2 = Slot(player_id=2, player_name="P2", seed=s2, game="TestGame")
|
||||
|
||||
commit()
|
||||
|
||||
# Delete items older than 5 days
|
||||
cleanup({"ROOM_AUTO_DELETE": 5})
|
||||
|
||||
with db_session:
|
||||
self.assertIsNone(Seed.get(id=s1.id), "Old seed should be deleted")
|
||||
self.assertIsNone(Slot.get(id=slot1.id), "Slot of deleted seed should be deleted")
|
||||
self.assertIsNotNone(Seed.get(id=s2.id), "Recent seed should NOT be deleted")
|
||||
self.assertIsNotNone(Slot.get(id=slot2.id), "Slot of recent seed should NOT be deleted")
|
||||
@@ -0,0 +1,76 @@
|
||||
import math
|
||||
from typing import Any, Callable
|
||||
from typing_extensions import override
|
||||
from uuid import uuid4
|
||||
|
||||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestSUUID(TestBase):
|
||||
converter: BaseConverter
|
||||
filter: Callable[[Any], str]
|
||||
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
from werkzeug.routing import Map
|
||||
|
||||
super().setUp()
|
||||
self.converter = self.app.url_map.converters["suuid"](Map())
|
||||
self.filter = self.app.jinja_env.filters["suuid"] # type: ignore # defines how we use it, not what it can be
|
||||
|
||||
def test_is_reversible(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(u, self.converter.to_python(self.converter.to_url(u)))
|
||||
s = "A" * 22 # uuid with all zeros
|
||||
self.assertEqual(s, self.converter.to_url(self.converter.to_python(s)))
|
||||
|
||||
def test_uuid_length(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("AAAA")
|
||||
|
||||
def test_padding(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==") # converter should not allow padding
|
||||
|
||||
def test_empty(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("")
|
||||
|
||||
def test_stray_equal_signs(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==" + "AA") # the "==AA" should not be ignored, but error out
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 20 + "==" + "AA") # the final "A"s should not be appended to the first "A"s
|
||||
|
||||
def test_stray_whitespace(self) -> None:
|
||||
s = "A" * 22
|
||||
self.converter.to_python(s) # check that the correct value works
|
||||
for char in " \t\r\n\v":
|
||||
for pos in (0, 11, 22):
|
||||
with self.subTest(char=char, pos=pos):
|
||||
s_with_whitespace = s[0:pos] + char * 4 + s[pos:] # insert 4 to make padding correct
|
||||
# check that the constructed s_with_whitespace is correct
|
||||
self.assertEqual(len(s_with_whitespace), len(s) + 4)
|
||||
self.assertEqual(s_with_whitespace[pos], char)
|
||||
# s_with_whitespace should be invalid as SUUID
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python(s_with_whitespace)
|
||||
|
||||
def test_filter_returns_valid_string(self) -> None:
|
||||
u = uuid4()
|
||||
s = self.filter(u)
|
||||
self.assertIsInstance(s, str)
|
||||
self.assertNotIn("=", s)
|
||||
self.assertEqual(len(s), math.ceil(len(u.bytes) * 4 / 3))
|
||||
|
||||
def test_filter_is_same_as_converter(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(self.filter(u), self.converter.to_url(u))
|
||||
|
||||
def test_filter_bad_type(self) -> None:
|
||||
with self.assertRaises(Exception): # currently the type is not checked directly, so any exception is valid
|
||||
self.filter(None)
|
||||
+5
-1
@@ -353,6 +353,8 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""path it was loaded from"""
|
||||
world_version: ClassVar[Version] = Version(0, 0, 0)
|
||||
"""Optional world version loaded from archipelago.json"""
|
||||
manifest: ClassVar[dict[str, Any]] = {}
|
||||
"""Mapping of the world's archipelago.json manifest. Use game and world_version attrs instead for those values."""
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
assert multiworld is not None
|
||||
@@ -512,7 +514,9 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
"""
|
||||
Called when the item pool needs to be filled with additional items to match location count.
|
||||
If core AP removes an item from your item pool, this method is called to choose a replacement item
|
||||
so item count and location count remain equal.
|
||||
For example: plando, item_links and start_inventory_from_pool are features that may cause this.
|
||||
|
||||
Any returned item name must be for a "repeatable" item, i.e. one that it's okay to generate arbitrarily many of.
|
||||
For most worlds this will be one or more of your filler items, but the classification of these items
|
||||
|
||||
+16
-5
@@ -11,7 +11,7 @@ import json
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import List, Sequence
|
||||
from zipfile import BadZipFile
|
||||
from zipfile import ZipFile, BadZipFile
|
||||
|
||||
from NetUtils import DataPackage
|
||||
from Utils import local_path, user_path, Version, version_tuple, tuplize_version, messagebox
|
||||
@@ -33,7 +33,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
failed_world_loads: List[str] = []
|
||||
failed_world_loads: dict[str, str] = {}
|
||||
|
||||
logger = logging.getLogger("Worlds")
|
||||
logger.propagate = False
|
||||
@@ -71,8 +71,9 @@ class WorldSource:
|
||||
print(f"Could not load world {self}:", file=file_like)
|
||||
traceback.print_exc(file=file_like)
|
||||
file_like.seek(0)
|
||||
logging.exception(file_like.read())
|
||||
failed_world_loads.append(os.path.basename(self.path).rsplit(".", 1)[0])
|
||||
reason = file_like.read()
|
||||
logging.exception(reason)
|
||||
failed_world_loads[os.path.basename(self.path).rsplit(".", 1)[0]] = reason
|
||||
return False
|
||||
|
||||
@property
|
||||
@@ -127,6 +128,7 @@ for world_source in world_sources:
|
||||
game = manifest.get("game")
|
||||
if game in AutoWorldRegister.world_types:
|
||||
AutoWorldRegister.world_types[game].world_version = tuplize_version(manifest.get("world_version", "0.0.0"))
|
||||
AutoWorldRegister.world_types[game].manifest = manifest
|
||||
|
||||
if apworlds:
|
||||
# encapsulation for namespace / gc purposes
|
||||
@@ -137,7 +139,7 @@ if apworlds:
|
||||
|
||||
def fail_world(game_name: str, reason: str, add_as_failed_to_load: bool = True) -> None:
|
||||
if add_as_failed_to_load:
|
||||
failed_world_loads.append(game_name)
|
||||
failed_world_loads[game_name] = reason
|
||||
logging.warning(reason)
|
||||
|
||||
for apworld_source in apworlds:
|
||||
@@ -209,6 +211,15 @@ if apworlds:
|
||||
# world could fail to load at this point
|
||||
if apworld.world_version:
|
||||
AutoWorldRegister.world_types[apworld.game].world_version = apworld.world_version
|
||||
|
||||
assert apworld.path
|
||||
with ZipFile(apworld.path, "r") as zf:
|
||||
manifest = apworld.read_contents(zf)
|
||||
# version/compatible_version shouldn't be needed by world, makes it consistent with folder world
|
||||
manifest.pop("version", None)
|
||||
manifest.pop("compatible_version", None)
|
||||
AutoWorldRegister.world_types[apworld.game].manifest = manifest
|
||||
|
||||
load_apworlds()
|
||||
del load_apworlds
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ from Options import PerGameCommonOptions
|
||||
from .Locations import location_table, AdventureLocation, dragon_room_to_region
|
||||
|
||||
|
||||
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
def connect(multiworld: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
one_way=False, name=None):
|
||||
source_region = world.get_region(source, player)
|
||||
target_region = world.get_region(target, player)
|
||||
source_region = multiworld.get_region(source, player)
|
||||
target_region = multiworld.get_region(target, player)
|
||||
|
||||
if name is None:
|
||||
name = source + " to " + target
|
||||
@@ -22,7 +22,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
if not one_way:
|
||||
connect(world, player, target, source, rule, True)
|
||||
connect(multiworld, player, target, source, rule, True)
|
||||
|
||||
|
||||
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||
|
||||
+25
-25
@@ -3,47 +3,47 @@ from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
world = self.multiworld
|
||||
multiworld = self.multiworld
|
||||
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
|
||||
|
||||
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("YellowCastlePort", self.player),
|
||||
lambda state: state.has("Yellow Key", self.player))
|
||||
set_rule(world.get_entrance("BlackCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastlePort", self.player),
|
||||
lambda state: state.has("Black Key", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePort", self.player),
|
||||
lambda state: state.has("White Key", self.player))
|
||||
|
||||
# a future thing would be to make the bat an actual item, or at least allow it to
|
||||
# be placed in a castle, which would require some additions to the rules when
|
||||
# use_bat_logic is true
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
set_rule(world.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
|
||||
dragon_slay_check = self.options.dragon_slay_check.value
|
||||
if dragon_slay_check:
|
||||
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
else:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
|
||||
# really this requires getting the dot item, and having another item or enemy
|
||||
@@ -51,37 +51,37 @@ def set_rules(self) -> None:
|
||||
# to actually make randomized, since it is invisible. May add some options
|
||||
# for how that works in the distant future, but for now, just say you need
|
||||
# the bridge and black key to get to it, as that simplifies things a lot
|
||||
set_rule(world.get_entrance("CreditsWall", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsWall", self.player),
|
||||
lambda state: state.has("Bridge", self.player) and
|
||||
state.has("Black Key", self.player))
|
||||
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("CreditsToFarSide", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsToFarSide", self.player),
|
||||
lambda state: state.has("Magnet", self.player))
|
||||
|
||||
# bridge literally does not fit in this space, I think. I'll just exclude it
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Bridge", self.player)
|
||||
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Bridge", self.player)
|
||||
# don't put magnet in locations that can pull in-logic items out of reach unless the bat is in play
|
||||
if not use_bat_logic:
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
|
||||
# and obviously we don't want to start with the game already won
|
||||
forbid_item(world.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = world.get_region("Overworld", self.player)
|
||||
forbid_item(multiworld.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = multiworld.get_region("Overworld", self.player)
|
||||
|
||||
for loc in overworld.locations:
|
||||
forbid_item(loc, "Chalice", self.player)
|
||||
|
||||
add_rule(world.get_location("Chalice Home", self.player),
|
||||
add_rule(multiworld.get_location("Chalice Home", self.player),
|
||||
lambda state: state.has("Chalice", self.player) and state.has("Yellow Key", self.player))
|
||||
|
||||
# world.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
# multiworld.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
# all_locations = world.get_locations(self.player).copy()
|
||||
# all_locations = multiworld.get_locations(self.player).copy()
|
||||
# while priority_count < get_num_items():
|
||||
# loc = world.random.choice(all_locations)
|
||||
# loc = multiworld.random.choice(all_locations)
|
||||
# if loc.progress_type == LocationProgressType.DEFAULT:
|
||||
# loc.progress_type = LocationProgressType.PRIORITY
|
||||
# priority_count += 1
|
||||
|
||||
@@ -105,8 +105,8 @@ class AdventureWorld(World):
|
||||
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
||||
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.rom_name: Optional[bytearray] = bytearray("", "utf8" )
|
||||
self.dragon_rooms: [int] = [0x14, 0x19, 0x4]
|
||||
self.dragon_slay_check: Optional[int] = 0
|
||||
|
||||
@@ -50,16 +50,17 @@ make sure ***Enable Developer Console*** is checked in Game Settings and press t
|
||||
|
||||
## FAQ/Common Issues
|
||||
|
||||
### The game is not connecting when starting a new save!
|
||||
For unknown reasons, the mod will randomly disable itself in the mod menu. To fix this, go to the Mods menu
|
||||
(rocket icon) in-game, and re-enable the mod.
|
||||
### The game is crashing on startup repeatedly!
|
||||
This is a common issue on older versions of the game, caused by the game failing to interface with the Steam Workshop.
|
||||
To fix it you can try the following (from least to most effort required)
|
||||
- Subscribe to any random workshop mod, then unsubscribe from it
|
||||
- Restart Steam
|
||||
- Restart your computer
|
||||
- Delete the game's config directory from the files `steamapps/common/HatinTime/HatinTimeGame/Config` then verify the game files
|
||||
- Reinstall the game
|
||||
|
||||
### Why do relics disappear from the stands in the Spaceship after they're completed?
|
||||
This is intentional behaviour. Because of how randomizer logic works, there is no way to predict the order that
|
||||
a player will place their relics. Since there are a limited amount of relic stands in the Spaceship, relics are removed
|
||||
after being completed to allow for the placement of more relics without being potentially locked out.
|
||||
The level that the relic set unlocked will stay unlocked.
|
||||
|
||||
### When I start a new save file, the intro cinematic doesn't get skipped, Hat Kid's body is missing and the mod doesn't work!
|
||||
There is a bug on older versions of A Hat in Time that causes save file creation to fail to work properly
|
||||
if you have too many save files. Delete them and it should fix the problem.
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from Utils import pc_to_snes, snes_to_pc
|
||||
from .enemizer_data.base_patch_data import ENEMIZER_BASE_PATCHES
|
||||
from .enemizer_data.symbols import ENEMIZER_SYMBOLS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ALTTPWorld
|
||||
from .Rom import LocalRom
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BossPatchData:
|
||||
pointer: tuple[int, int]
|
||||
graphics: int
|
||||
sprite_array: tuple[int, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DungeonBossPatchData:
|
||||
room_id: int
|
||||
sprite_pointer_address: int
|
||||
shell_x: int
|
||||
shell_y: int
|
||||
clear_layer2: bool = False
|
||||
extra_sprites: tuple[int, ...] = ()
|
||||
gt_sprite_write_address: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomObjectTable:
|
||||
header_byte_0: int
|
||||
header_byte_1: int
|
||||
layer_1_objects: list[bytes] = field(default_factory=list)
|
||||
layer_1_doors: list[bytes] = field(default_factory=list)
|
||||
layer_2_objects: list[bytes] = field(default_factory=list)
|
||||
layer_2_doors: list[bytes] = field(default_factory=list)
|
||||
layer_3_objects: list[bytes] = field(default_factory=list)
|
||||
layer_3_doors: list[bytes] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_rom(cls, rom: "LocalRom", start_address: int) -> "RoomObjectTable":
|
||||
table = cls(rom.read_byte(start_address), rom.read_byte(start_address + 1))
|
||||
layers = (
|
||||
(table.layer_1_objects, table.layer_1_doors),
|
||||
(table.layer_2_objects, table.layer_2_doors),
|
||||
(table.layer_3_objects, table.layer_3_doors),
|
||||
)
|
||||
index = start_address + 2
|
||||
|
||||
for objects, doors in layers:
|
||||
is_door = False
|
||||
while True:
|
||||
if rom.read_bytes(index, 2) == bytearray((0xF0, 0xFF)):
|
||||
is_door = True
|
||||
index += 2
|
||||
continue
|
||||
if rom.read_bytes(index, 2) == bytearray((0xFF, 0xFF)):
|
||||
index += 2
|
||||
break
|
||||
if is_door:
|
||||
doors.append(bytes(rom.read_bytes(index, 2)))
|
||||
index += 2
|
||||
else:
|
||||
objects.append(bytes(rom.read_bytes(index, 3)))
|
||||
index += 3
|
||||
|
||||
return table
|
||||
|
||||
def add_shell(self, x: int, y: int, clear_layer_2: bool, shell_id: int) -> None:
|
||||
self.header_byte_0 = 0xF0
|
||||
if clear_layer_2:
|
||||
self.layer_2_objects.clear()
|
||||
self.layer_2_objects.append(_build_subtype_3_object(x, y, shell_id))
|
||||
|
||||
def remove_shell(self, shell_id: int) -> None:
|
||||
self.layer_2_objects = [obj for obj in self.layer_2_objects if _object_id(obj) != shell_id]
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
output = bytearray((self.header_byte_0, self.header_byte_1))
|
||||
output.extend(self._serialize_layer(self.layer_1_objects, self.layer_1_doors, is_last_layer=False))
|
||||
output.extend(self._serialize_layer(self.layer_2_objects, self.layer_2_doors, is_last_layer=False))
|
||||
output.extend(self._serialize_layer(self.layer_3_objects, self.layer_3_doors, is_last_layer=True))
|
||||
return bytes(output)
|
||||
|
||||
@staticmethod
|
||||
def _serialize_layer(objects: list[bytes], doors: list[bytes], is_last_layer: bool) -> bytes:
|
||||
output = bytearray()
|
||||
for obj in objects:
|
||||
output.extend(obj)
|
||||
if is_last_layer or doors:
|
||||
output.extend((0xF0, 0xFF))
|
||||
for door in doors:
|
||||
output.extend(door)
|
||||
output.extend((0xFF, 0xFF))
|
||||
return bytes(output)
|
||||
|
||||
|
||||
BOSS_PATCH_DATA: dict[str, BossPatchData] = {
|
||||
"Armos": BossPatchData((0x87, 0xE8), 9, (0x05, 0x04, 0x53, 0x05, 0x07, 0x53, 0x05, 0x0A, 0x53,
|
||||
0x08, 0x0A, 0x53, 0x08, 0x07, 0x53, 0x08, 0x04, 0x53,
|
||||
0x08, 0xE7, 0x19)),
|
||||
"Arrghus": BossPatchData((0x97, 0xD9), 20, (0x07, 0x07, 0x8C, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D,
|
||||
0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D)),
|
||||
"Blind": BossPatchData((0x54, 0xE6), 32, (0x05, 0x09, 0xCE)),
|
||||
"Helmasaur": BossPatchData((0x49, 0xE0), 21, (0x06, 0x07, 0x92)),
|
||||
"Kholdstare": BossPatchData((0x01, 0xEA), 22, (0x05, 0x07, 0xA3, 0x05, 0x07, 0xA4, 0x05, 0x07, 0xA2)),
|
||||
"Lanmola": BossPatchData((0xCB, 0xDC), 11, (0x07, 0x06, 0x54, 0x07, 0x09, 0x54, 0x09, 0x07, 0x54)),
|
||||
"Moldorm": BossPatchData((0xC3, 0xD9), 12, (0x09, 0x09, 0x09)),
|
||||
"Mothula": BossPatchData((0x31, 0xDC), 26, (0x06, 0x08, 0x88)),
|
||||
"Trinexx": BossPatchData((0xBA, 0xE5), 23, (0x05, 0x07, 0xCB, 0x05, 0x07, 0xCC, 0x05, 0x07, 0xCD)),
|
||||
"Vitreous": BossPatchData((0x57, 0xE4), 22, (0x05, 0x07, 0xBD)),
|
||||
}
|
||||
|
||||
DUNGEON_BOSS_PATCH_DATA: dict[tuple[str, Optional[str]], DungeonBossPatchData] = {
|
||||
("Eastern Palace", None): DungeonBossPatchData(200, 0x04D7BE, 0x2B, 0x28),
|
||||
("Desert Palace", None): DungeonBossPatchData(51, 0x04D694, 0x0B, 0x28),
|
||||
("Tower of Hera", None): DungeonBossPatchData(7, 0x04D63C, 0x18, 0x16),
|
||||
("Palace of Darkness", None): DungeonBossPatchData(90, 0x04D6E2, 0x2B, 0x28),
|
||||
("Swamp Palace", None): DungeonBossPatchData(6, 0x04D63A, 0x0B, 0x28),
|
||||
("Skull Woods", None): DungeonBossPatchData(41, 0x04D680, 0x2B, 0x28),
|
||||
("Thieves Town", None): DungeonBossPatchData(172, 0x04D786, 0x2B, 0x28, clear_layer2=True),
|
||||
("Ice Palace", None): DungeonBossPatchData(222, 0x04D7EA, 0x2B, 0x08, clear_layer2=True),
|
||||
("Misery Mire", None): DungeonBossPatchData(144, 0x04D74E, 0x0B, 0x28, clear_layer2=True),
|
||||
("Turtle Rock", None): DungeonBossPatchData(164, 0x04D776, 0x0B, 0x28, clear_layer2=True),
|
||||
("Ganons Tower", "bottom"): DungeonBossPatchData(
|
||||
28, 0x04D666, 0x2B, 0x28, extra_sprites=(0x07, 0x07, 0xE3, 0x07, 0x08, 0xE3, 0x08, 0x07, 0xE3, 0x08, 0x08, 0xE3),
|
||||
gt_sprite_write_address=0x04D87E,
|
||||
),
|
||||
("Ganons Tower", "middle"): DungeonBossPatchData(
|
||||
108, 0x04D706, 0x0B, 0x28, extra_sprites=(0x18, 0x17, 0xD1, 0x1C, 0x03, 0xC5), gt_sprite_write_address=0x04D8B6,
|
||||
),
|
||||
("Ganons Tower", "top"): DungeonBossPatchData(77, 0x04D6C8, 0x18, 0x16),
|
||||
}
|
||||
|
||||
TRINEXX_SHELL_OBJECT_ID = 0xFF2
|
||||
KHOLDSTARE_SHELL_OBJECT_ID = 0xF95
|
||||
TRINEXX_VANILLA_ROOM_ID = 164
|
||||
KHOLDSTARE_VANILLA_ROOM_ID = 222
|
||||
ENEMY_HP_TABLE_ADDRESS = 0x6B173
|
||||
ENEMY_DAMAGE_TABLE_ADDRESS = 0x6B266
|
||||
HIDDEN_ENEMY_CHANCE_POOL_ADDRESS = 0xD7BBB
|
||||
DAMAGE_GROUP_TABLE_ADDRESS = 0x3742D
|
||||
RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS = 0x301FC
|
||||
RETRO_RUPEE_REPLACEMENT_SPRITE_ID = 0xDA
|
||||
ARROW_REFILL_5_SPRITE_ID = 0xE1
|
||||
THIEF_SPRITE_ID = 0xC4
|
||||
THIEF_DEFAULT_HP = 4
|
||||
VANILLA_HIDDEN_ENEMY_CHANCE_POOL = (
|
||||
0x01, 0x01, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x12,
|
||||
0x10, 0x01, 0x01, 0x01, 0x11, 0x01, 0x01, 0x03,
|
||||
)
|
||||
RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL = (
|
||||
0x01, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x12,
|
||||
0x0F, 0x01, 0x0F, 0x0F, 0x11, 0x0F, 0x0F, 0x03,
|
||||
)
|
||||
EXCLUDED_ENEMY_TABLE_SPRITE_IDS = frozenset({
|
||||
0x09, 0x53, 0x54, 0x70, 0x7A, 0x7B, 0x88, 0x89, 0x8C, 0x8D, 0x92,
|
||||
0xA2, 0xA3, 0xA4, 0xBD, 0xBE, 0xBF, 0xCB, 0xCC, 0xCD, 0xCE, 0xD6, 0xD7,
|
||||
})
|
||||
ENEMY_HEALTH_RANGE_BY_KEY = {
|
||||
"easy": (1, 4),
|
||||
"normal": (2, 15),
|
||||
"hard": (2, 25),
|
||||
"expert": (4, 50),
|
||||
}
|
||||
|
||||
_ENEMIZER_SYMBOLS: Optional[dict[str, int]] = None
|
||||
|
||||
BOSS_GFX_SHEET_INDEXES = {
|
||||
"Agahnim1": 0x8D,
|
||||
"Agahnim2": 0xB5,
|
||||
"Agahnim3": 0xC8,
|
||||
"Agahnim4": 0xB6,
|
||||
"ArmosKnight1": 0x90,
|
||||
"Ganon1": 0x94,
|
||||
"Ganon2": 0xA6,
|
||||
"Ganon3": 0xB4,
|
||||
"Ganon4": 0xB8,
|
||||
"Moldorm1": 0xA3,
|
||||
"Lanmola1": 0xA4,
|
||||
"Arrghus1": 0xAC,
|
||||
"Mothula1": 0xAB,
|
||||
"Helmasaure1": 0xAD,
|
||||
"Helmasaure2": 0xB1,
|
||||
"Blind1": 0xAE,
|
||||
"Kholdstare1": 0xAF,
|
||||
"Vitreous1": 0xB0,
|
||||
"Trinexx1": 0xB2,
|
||||
"Trinexx2": 0xB3,
|
||||
}
|
||||
|
||||
BOSS_GFX_TABLE = {
|
||||
"Agahnim1": (21, 190, 228),
|
||||
"Agahnim2": (22, 255, 135),
|
||||
"Agahnim3": (23, 220, 101),
|
||||
"Agahnim4": (23, 132, 92),
|
||||
"ArmosKnight1": (21, 206, 27),
|
||||
"Ganon1": (21, 227, 160),
|
||||
"Ganon2": (22, 186, 55),
|
||||
"Ganon3": (22, 250, 199),
|
||||
"Ganon4": (23, 142, 33),
|
||||
"Moldorm1": (22, 175, 152),
|
||||
"Lanmola1": (22, 180, 23),
|
||||
"Arrghus1": (22, 214, 147),
|
||||
"Mothula1": (22, 210, 84),
|
||||
"Helmasaure1": (22, 219, 114),
|
||||
"Helmasaure2": (22, 239, 177),
|
||||
"Blind1": (22, 224, 90),
|
||||
"Kholdstare1": (22, 230, 31),
|
||||
"Vitreous1": (22, 235, 9),
|
||||
"Trinexx1": (22, 243, 89),
|
||||
"Trinexx2": (22, 246, 35),
|
||||
}
|
||||
|
||||
TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS = 0x04B37E
|
||||
TRINEXX_ICE_PROJECTILE_TILE_ADDRESS = 0xE7A5
|
||||
TILE_TRAP_FLOOR_TILE_ADDRESS = 0xF3BED
|
||||
|
||||
|
||||
def apply_enemizer_base_patch(rom: "LocalRom") -> None:
|
||||
for address, patch_data in _load_enemizer_base_patches():
|
||||
rom.write_bytes(address, patch_data)
|
||||
_apply_trinexx_room_fixes(rom)
|
||||
|
||||
def patch_bosses(world: "ALTTPWorld", rom: "LocalRom") -> None:
|
||||
dungeon_header_base = _get_enemizer_symbol("room_header_table")
|
||||
moved_room_object_base = _get_enemizer_symbol("modified_room_object_table")
|
||||
gt_dungeon_name = "Ganons Tower" if world.options.mode != "inverted" else "Inverted Ganons Tower"
|
||||
gt_dungeon = world.dungeons[gt_dungeon_name]
|
||||
|
||||
placements = (
|
||||
(world.dungeons["Eastern Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Eastern Palace", None)]),
|
||||
(world.dungeons["Desert Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Desert Palace", None)]),
|
||||
(world.dungeons["Tower of Hera"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Tower of Hera", None)]),
|
||||
(world.dungeons["Palace of Darkness"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Palace of Darkness", None)]),
|
||||
(world.dungeons["Swamp Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Swamp Palace", None)]),
|
||||
(world.dungeons["Skull Woods"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Skull Woods", None)]),
|
||||
(world.dungeons["Thieves Town"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Thieves Town", None)]),
|
||||
(world.dungeons["Ice Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ice Palace", None)]),
|
||||
(world.dungeons["Misery Mire"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Misery Mire", None)]),
|
||||
(world.dungeons["Turtle Rock"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Turtle Rock", None)]),
|
||||
(gt_dungeon.bosses["bottom"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "bottom")]),
|
||||
(gt_dungeon.bosses["middle"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "middle")]),
|
||||
(gt_dungeon.bosses["top"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "top")]),
|
||||
)
|
||||
|
||||
modified_room_tables: dict[int, RoomObjectTable] = {}
|
||||
|
||||
for boss_name, dungeon_data in placements:
|
||||
boss_data = BOSS_PATCH_DATA[boss_name]
|
||||
rom.write_bytes(dungeon_data.sprite_pointer_address, boss_data.pointer)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 3, boss_data.graphics)
|
||||
|
||||
if boss_name == "Trinexx" and dungeon_data.room_id != TRINEXX_VANILLA_ROOM_ID:
|
||||
room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id)
|
||||
room_table.add_shell(
|
||||
dungeon_data.shell_x,
|
||||
dungeon_data.shell_y - 2,
|
||||
dungeon_data.clear_layer2,
|
||||
TRINEXX_SHELL_OBJECT_ID,
|
||||
)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0x60)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x04)
|
||||
|
||||
if boss_name == "Kholdstare" and dungeon_data.room_id != KHOLDSTARE_VANILLA_ROOM_ID:
|
||||
room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id)
|
||||
room_table.add_shell(
|
||||
dungeon_data.shell_x,
|
||||
dungeon_data.shell_y,
|
||||
dungeon_data.clear_layer2,
|
||||
KHOLDSTARE_SHELL_OBJECT_ID,
|
||||
)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0xE0)
|
||||
rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x01)
|
||||
|
||||
if boss_name != "Trinexx" and dungeon_data.room_id == TRINEXX_VANILLA_ROOM_ID:
|
||||
_get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(TRINEXX_SHELL_OBJECT_ID)
|
||||
|
||||
if boss_name != "Kholdstare" and dungeon_data.room_id == KHOLDSTARE_VANILLA_ROOM_ID:
|
||||
_get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(KHOLDSTARE_SHELL_OBJECT_ID)
|
||||
|
||||
if dungeon_data.gt_sprite_write_address is not None:
|
||||
_write_gt_boss_sprite_block(rom, dungeon_data, boss_data)
|
||||
|
||||
write_address = moved_room_object_base
|
||||
for room_id in sorted(modified_room_tables):
|
||||
table_bytes = modified_room_tables[room_id].to_bytes()
|
||||
_write_room_object_pointer(rom, room_id, write_address)
|
||||
rom.write_bytes(write_address, table_bytes)
|
||||
write_address += len(table_bytes)
|
||||
|
||||
rom.write_byte(0x1B0101, 0x01)
|
||||
rom.write_byte(0x04DE81, 0x00)
|
||||
if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
rom.write_byte(0x04DE81, 0x06)
|
||||
rom.write_byte(0x1B0101, 0x00)
|
||||
|
||||
|
||||
def _get_room_object_table(rom: "LocalRom", cache: dict[int, RoomObjectTable], room_id: int) -> RoomObjectTable:
|
||||
room_table = cache.get(room_id)
|
||||
if room_table is not None:
|
||||
return room_table
|
||||
|
||||
pointer_address = 0xF8000 + (room_id * 3)
|
||||
snes_address_bytes = rom.read_bytes(pointer_address, 3)
|
||||
snes_address = (snes_address_bytes[2] << 16) | (snes_address_bytes[1] << 8) | snes_address_bytes[0]
|
||||
room_table = RoomObjectTable.from_rom(rom, snes_to_pc(snes_address))
|
||||
cache[room_id] = room_table
|
||||
return room_table
|
||||
|
||||
|
||||
def _write_gt_boss_sprite_block(rom: "LocalRom", dungeon_data: DungeonBossPatchData, boss_data: BossPatchData) -> None:
|
||||
assert dungeon_data.gt_sprite_write_address is not None
|
||||
rom.write_int16(dungeon_data.sprite_pointer_address, dungeon_data.gt_sprite_write_address)
|
||||
|
||||
sprite_block = bytearray((0x00,))
|
||||
sprite_block.extend(boss_data.sprite_array)
|
||||
if dungeon_data.room_id == 28 and boss_data.pointer == BOSS_PATCH_DATA["Arrghus"].pointer:
|
||||
sprite_block.extend(dungeon_data.extra_sprites[:6])
|
||||
else:
|
||||
sprite_block.extend(dungeon_data.extra_sprites)
|
||||
sprite_block.append(0xFF)
|
||||
rom.write_bytes(dungeon_data.gt_sprite_write_address, sprite_block)
|
||||
|
||||
|
||||
def _write_room_object_pointer(rom: "LocalRom", room_id: int, pc_address: int) -> None:
|
||||
snes_address = pc_to_snes(pc_address)
|
||||
pointer_address = 0xF8000 + (room_id * 3)
|
||||
rom.write_bytes(pointer_address, (
|
||||
snes_address & 0xFF,
|
||||
(snes_address >> 8) & 0xFF,
|
||||
(snes_address >> 16) & 0xFF,
|
||||
))
|
||||
|
||||
|
||||
def _build_subtype_3_object(x: int, y: int, object_id: int) -> bytes:
|
||||
return bytes((
|
||||
((x << 2) & 0xFC) | (object_id & 0x03),
|
||||
((y << 2) & 0xFC) | ((object_id >> 2) & 0x03),
|
||||
0xF0 | ((object_id >> 4) & 0x0F),
|
||||
))
|
||||
|
||||
|
||||
def _object_id(object_bytes: bytes) -> Optional[int]:
|
||||
if len(object_bytes) != 3:
|
||||
return None
|
||||
if object_bytes[0] >= 0xFC:
|
||||
return (object_bytes[2] & 0x3F) + 0x100
|
||||
if object_bytes[2] >= 0xF8:
|
||||
return 0xF00 | ((object_bytes[2] & 0x0F) << 4) | ((object_bytes[1] & 0x03) << 2) | (object_bytes[0] & 0x03)
|
||||
return object_bytes[2]
|
||||
|
||||
|
||||
def _set_enemizer_flag(rom: "LocalRom", symbol_name: str, enabled: bool) -> None:
|
||||
rom.write_byte(_get_enemizer_symbol(symbol_name), 0x01 if enabled else 0x00)
|
||||
|
||||
|
||||
def _apply_killable_thief(rom: "LocalRom") -> None:
|
||||
rom.write_byte(_get_enemizer_symbol("notItemSprite_Mimic") + 4, THIEF_SPRITE_ID)
|
||||
thief_hp_address = ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID
|
||||
if rom.read_byte(thief_hp_address) != 0xFF:
|
||||
rom.write_byte(thief_hp_address, THIEF_DEFAULT_HP)
|
||||
|
||||
|
||||
def _randomize_enemy_health(rom: "LocalRom", rng: random.Random, enemy_health_key: str) -> None:
|
||||
min_hp, max_hp = ENEMY_HEALTH_RANGE_BY_KEY[enemy_health_key]
|
||||
for sprite_id in range(0xF3):
|
||||
hp_address = ENEMY_HP_TABLE_ADDRESS + sprite_id
|
||||
if rom.read_byte(hp_address) == 0xFF or sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS:
|
||||
continue
|
||||
rom.write_byte(hp_address, rng.randrange(min_hp, max_hp))
|
||||
|
||||
|
||||
def _randomize_enemy_damage(rom: "LocalRom", rng: random.Random, allow_zero_damage: bool) -> None:
|
||||
for sprite_id in range(0xF3):
|
||||
if sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS:
|
||||
continue
|
||||
new_damage = rng.randrange(8)
|
||||
if not allow_zero_damage and new_damage == 2:
|
||||
continue
|
||||
rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + sprite_id, new_damage)
|
||||
|
||||
|
||||
def _shuffle_damage_groups(
|
||||
rom: "LocalRom",
|
||||
rng: random.Random,
|
||||
*,
|
||||
chaos_mode: bool,
|
||||
allow_zero_damage: bool,
|
||||
) -> None:
|
||||
min_damage = 0 if allow_zero_damage else 4
|
||||
max_damage = 64 if chaos_mode else 32
|
||||
|
||||
for group_id in range(10):
|
||||
green_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
if chaos_mode:
|
||||
blue_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
red_mail_damage = rng.randrange(min_damage, max_damage)
|
||||
else:
|
||||
blue_mail_damage = green_mail_damage * 3 // 4
|
||||
red_mail_damage = green_mail_damage * 3 // 8
|
||||
group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3)
|
||||
rom.write_bytes(group_address, (green_mail_damage, blue_mail_damage, red_mail_damage))
|
||||
|
||||
|
||||
def _update_hidden_enemy_item_table_for_retro_mode(rom: "LocalRom") -> None:
|
||||
if rom.read_byte(RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS) != RETRO_RUPEE_REPLACEMENT_SPRITE_ID:
|
||||
return
|
||||
|
||||
item_table_address = _get_enemizer_symbol("sprite_bush_spawn_item_table")
|
||||
for index in range(22):
|
||||
if rom.read_byte(item_table_address + index) == ARROW_REFILL_5_SPRITE_ID:
|
||||
rom.write_byte(item_table_address + index, RETRO_RUPEE_REPLACEMENT_SPRITE_ID)
|
||||
|
||||
|
||||
def _apply_trinexx_room_fixes(rom: "LocalRom") -> None:
|
||||
# Match original Enemizer's unconditional Trinexx ice-floor removal so
|
||||
# blue-head projectiles do not create solid walls in non-vanilla rooms.
|
||||
rom.write_bytes(TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, (0xEA, 0xEA, 0xEA, 0xEA))
|
||||
|
||||
|
||||
def _apply_randomized_tile_trap_floor_tile(rom: "LocalRom") -> None:
|
||||
# Original Enemizer's RandomizeTileTrapFloorTile option changes the tile
|
||||
# left behind by flying floor tile traps. AP does not currently expose or
|
||||
# call this option, so keep the implementation isolated and unused.
|
||||
rom.write_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, (0x88, 0x01))
|
||||
rom.write_byte(TILE_TRAP_FLOOR_TILE_ADDRESS, 0x12)
|
||||
|
||||
|
||||
def _make_native_enemizer_rng(world: "ALTTPWorld") -> random.Random:
|
||||
seed_material = "|".join((
|
||||
str(world.multiworld.seed),
|
||||
world.multiworld.seed_name,
|
||||
str(world.player),
|
||||
_option_key(world.options.enemy_health),
|
||||
_option_key(world.options.enemy_damage),
|
||||
str(int(bool(world.options.enemy_shuffle))),
|
||||
str(int(bool(world.options.bush_shuffle))),
|
||||
str(int(bool(world.options.killable_thieves))),
|
||||
))
|
||||
seed = int.from_bytes(hashlib.sha256(seed_material.encode("utf-8")).digest()[:8], "big")
|
||||
return random.Random(seed)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_enemizer_base_patches() -> tuple[tuple[int, bytes], ...]:
|
||||
return tuple(
|
||||
(entry.address, entry.patch_data)
|
||||
for entry in ENEMIZER_BASE_PATCHES
|
||||
)
|
||||
|
||||
|
||||
def _option_key(option: object) -> str:
|
||||
return str(getattr(option, "current_key", option))
|
||||
|
||||
|
||||
def _get_enemizer_symbol(symbol_name: str) -> int:
|
||||
global _ENEMIZER_SYMBOLS
|
||||
if _ENEMIZER_SYMBOLS is None:
|
||||
_ENEMIZER_SYMBOLS = _load_enemizer_symbols()
|
||||
return _ENEMIZER_SYMBOLS[symbol_name]
|
||||
|
||||
|
||||
def _load_enemizer_symbols() -> dict[str, int]:
|
||||
return {
|
||||
name: snes_to_pc(snes_address)
|
||||
for name, snes_address in ENEMIZER_SYMBOLS.items()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType
|
||||
from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType
|
||||
from .Bosses import place_bosses
|
||||
from .Dungeons import get_dungeon_item_pool_player
|
||||
from .EnemyShuffle import generate_enemy_shuffle_state
|
||||
from .EntranceShuffle import connect_entrance
|
||||
from .Items import item_factory, GetBeemizerItem, trap_replaceable, item_name_groups
|
||||
from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode, LTTPBosses
|
||||
@@ -511,6 +512,8 @@ def generate_itempool(world: "ALTTPWorld"):
|
||||
world.options.turtle_rock_medallion.current_key.title())
|
||||
|
||||
place_bosses(world)
|
||||
if world.options.enemy_shuffle:
|
||||
world.enemy_shuffle_state = generate_enemy_shuffle_state(world)
|
||||
|
||||
multiworld.itempool += items
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from Utils import snes_to_pc
|
||||
from .enemizer_data.pot_shuffle_data import POT_ROOMS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ALTTPWorld
|
||||
from .Rom import LocalRom
|
||||
|
||||
|
||||
POT_ITEM_POINTER_TABLE = 0xDB67
|
||||
POT_KEY = 0x08
|
||||
POT_ARROW = 0x09
|
||||
POT_BLUE_RUPEE = 0x07
|
||||
POT_SWITCH = 0x88
|
||||
POT_HOLE = 0x80
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PotData:
|
||||
x: int
|
||||
y: int
|
||||
reserved: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PotRoomData:
|
||||
room_id: int
|
||||
pots: tuple[PotData, ...]
|
||||
items: tuple[int, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FilledPot:
|
||||
x: int
|
||||
y: int
|
||||
item: int
|
||||
|
||||
|
||||
def generate_pot_shuffle(world: "ALTTPWorld") -> dict[int, tuple[FilledPot, ...]]:
|
||||
room_data = _load_pot_room_data()
|
||||
shuffled_pots: dict[int, tuple[FilledPot, ...]] = {}
|
||||
|
||||
for room in room_data:
|
||||
room_items = [item for item in room.items if item != POT_HOLE]
|
||||
if world.options.retro_bow:
|
||||
room_items = [POT_BLUE_RUPEE if item == POT_ARROW else item for item in room_items]
|
||||
|
||||
empty_pots: list[PotData] = []
|
||||
filled_pots: list[FilledPot] = []
|
||||
|
||||
for pot in room.pots:
|
||||
if pot.reserved == 3:
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_HOLE))
|
||||
else:
|
||||
empty_pots.append(pot)
|
||||
|
||||
while POT_KEY in room_items:
|
||||
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 1]
|
||||
if not candidate_indices:
|
||||
break
|
||||
pot_index = world.random.choice(candidate_indices)
|
||||
pot = empty_pots.pop(pot_index)
|
||||
room_items.remove(POT_KEY)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_KEY))
|
||||
|
||||
while POT_SWITCH in room_items:
|
||||
candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 2]
|
||||
if not candidate_indices:
|
||||
break
|
||||
pot_index = world.random.choice(candidate_indices)
|
||||
pot = empty_pots.pop(pot_index)
|
||||
room_items.remove(POT_SWITCH)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, POT_SWITCH))
|
||||
|
||||
while room_items and empty_pots:
|
||||
pot_index = world.random.randrange(len(empty_pots))
|
||||
item_index = world.random.randrange(len(room_items))
|
||||
pot = empty_pots.pop(pot_index)
|
||||
item = room_items.pop(item_index)
|
||||
filled_pots.append(FilledPot(pot.x, pot.y, item))
|
||||
|
||||
shuffled_pots[room.room_id] = tuple(filled_pots)
|
||||
|
||||
return shuffled_pots
|
||||
|
||||
|
||||
def apply_pot_shuffle(rom: "LocalRom", shuffled_pots: dict[int, tuple[FilledPot, ...]]) -> None:
|
||||
for room_id, pots in shuffled_pots.items():
|
||||
pointer_address = POT_ITEM_POINTER_TABLE + (room_id * 2)
|
||||
snes_address = rom.read_byte(pointer_address) | (rom.read_byte(pointer_address + 1) << 8) | (0x01 << 16)
|
||||
address = snes_to_pc(snes_address)
|
||||
for index, pot in enumerate(pots):
|
||||
rom.write_bytes(address + (index * 3), (pot.x, pot.y, pot.item))
|
||||
|
||||
|
||||
def get_unique_pot_item_position(
|
||||
shuffled_pots: dict[int, tuple[FilledPot, ...]],
|
||||
room_id: int,
|
||||
item: int,
|
||||
) -> tuple[int, int]:
|
||||
positions = [
|
||||
(pot.x, pot.y)
|
||||
for pot in shuffled_pots.get(room_id, ())
|
||||
if pot.item == item
|
||||
]
|
||||
if len(positions) != 1:
|
||||
raise ValueError(
|
||||
f"Expected exactly one pot item {hex(item)} in room {hex(room_id)}, found {len(positions)}"
|
||||
)
|
||||
return positions[0]
|
||||
|
||||
|
||||
def _load_pot_room_data() -> tuple[PotRoomData, ...]:
|
||||
return tuple(
|
||||
PotRoomData(
|
||||
room_id=room.room_id,
|
||||
pots=tuple(PotData(x=pot.x, y=pot.y, reserved=pot.reserved) for pot in room.pots),
|
||||
items=room.items,
|
||||
)
|
||||
for room in POT_ROOMS
|
||||
)
|
||||
+132
-225
@@ -15,7 +15,6 @@ import logging
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
import subprocess
|
||||
import threading
|
||||
import concurrent.futures
|
||||
import bsdiff4
|
||||
@@ -53,9 +52,6 @@ try:
|
||||
except:
|
||||
xxtea = None
|
||||
|
||||
enemizer_logger = logging.getLogger("Enemizer")
|
||||
|
||||
|
||||
class LocalRom:
|
||||
|
||||
def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None):
|
||||
@@ -179,43 +175,6 @@ class LocalRom:
|
||||
self.write_int32(startaddress + (i * 4), value)
|
||||
|
||||
|
||||
check_lock = threading.Lock()
|
||||
|
||||
|
||||
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. "
|
||||
f"Such as https://github.com/Ijwu/Enemizer/releases")
|
||||
|
||||
with check_lock:
|
||||
# some time may have passed since the lock was acquired, as such a quick re-check doesn't hurt
|
||||
if getattr(check_enemizer, "done", None):
|
||||
return
|
||||
wanted_version = (7, 1, 0)
|
||||
# version info is saved on the lib, for some reason
|
||||
library_info = os.path.join(os.path.dirname(enemizercli), "EnemizerCLI.Core.deps.json")
|
||||
with open(library_info) as f:
|
||||
info = json.load(f)
|
||||
|
||||
for lib in info["libraries"]:
|
||||
if lib.startswith("EnemizerLibrary/"):
|
||||
version = lib.split("/")[-1]
|
||||
version = tuple(int(element) for element in version.split("."))
|
||||
enemizer_logger.debug(f"Found Enemizer version {version}")
|
||||
if version < wanted_version:
|
||||
raise Exception(
|
||||
f"Enemizer found at {enemizercli} is outdated ({version}) < ({wanted_version}), "
|
||||
f"please update your Enemizer. "
|
||||
f"Such as from https://github.com/Ijwu/Enemizer/releases")
|
||||
break
|
||||
else:
|
||||
raise Exception(f"Could not find Enemizer library version information in {library_info}")
|
||||
|
||||
check_enemizer.done = True
|
||||
|
||||
|
||||
def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_random_on_event, sprite_pool):
|
||||
userandomsprites = False
|
||||
if sprite and not isinstance(sprite, Sprite):
|
||||
@@ -282,174 +241,6 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand
|
||||
rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette)
|
||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||
|
||||
|
||||
def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
|
||||
player = world.player
|
||||
check_enemizer(enemizercli)
|
||||
randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc'))
|
||||
options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json'))
|
||||
enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{player}.sfc'))
|
||||
|
||||
# write options file for enemizer
|
||||
options = {
|
||||
'RandomizeEnemies': world.options.enemy_shuffle.value,
|
||||
'RandomizeEnemiesType': 3,
|
||||
'RandomizeBushEnemyChance': world.options.bush_shuffle.value,
|
||||
'RandomizeEnemyHealthRange': world.options.enemy_health != 'default',
|
||||
'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[
|
||||
world.options.enemy_health.current_key],
|
||||
'OHKO': False,
|
||||
'RandomizeEnemyDamage': world.options.enemy_damage != 'default',
|
||||
'AllowEnemyZeroDamage': True,
|
||||
'ShuffleEnemyDamageGroups': world.options.enemy_damage != 'default',
|
||||
'EnemyDamageChaosMode': world.options.enemy_damage == 'chaos',
|
||||
'EasyModeEscape': world.options.mode == "standard",
|
||||
'EnemiesAbsorbable': False,
|
||||
'AbsorbableSpawnRate': 10,
|
||||
'AbsorbableTypes': {
|
||||
'FullMagic': True, 'SmallMagic': True, 'Bomb_1': True, 'BlueRupee': True, 'Heart': True, 'BigKey': True,
|
||||
'Key': True,
|
||||
'Fairy': True, 'Arrow_10': True, 'Arrow_5': True, 'Bomb_8': True, 'Bomb_4': True, 'GreenRupee': True,
|
||||
'RedRupee': True
|
||||
},
|
||||
'BossMadness': False,
|
||||
'RandomizeBosses': True,
|
||||
'RandomizeBossesType': 0,
|
||||
'RandomizeBossHealth': False,
|
||||
'RandomizeBossHealthMinAmount': 0,
|
||||
'RandomizeBossHealthMaxAmount': 300,
|
||||
'RandomizeBossDamage': False,
|
||||
'RandomizeBossDamageMinAmount': 0,
|
||||
'RandomizeBossDamageMaxAmount': 200,
|
||||
'RandomizeBossBehavior': False,
|
||||
'RandomizeDungeonPalettes': False,
|
||||
'SetBlackoutMode': False,
|
||||
'RandomizeOverworldPalettes': False,
|
||||
'RandomizeSpritePalettes': False,
|
||||
'SetAdvancedSpritePalettes': False,
|
||||
'PukeMode': False,
|
||||
'NegativeMode': False,
|
||||
'GrayscaleMode': False,
|
||||
'GenerateSpoilers': False,
|
||||
'RandomizeLinkSpritePalette': False,
|
||||
'RandomizePots': world.options.pot_shuffle.value,
|
||||
'ShuffleMusic': False,
|
||||
'BootlegMagic': True,
|
||||
'CustomBosses': False,
|
||||
'AndyMode': False,
|
||||
'HeartBeepSpeed': 0,
|
||||
'AlternateGfx': False,
|
||||
'ShieldGraphics': "shield_gfx/normal.gfx",
|
||||
'SwordGraphics': "sword_gfx/normal.gfx",
|
||||
'BeeMizer': False,
|
||||
'BeesLevel': 0,
|
||||
'RandomizeTileTrapPattern': False,
|
||||
'RandomizeTileTrapFloorTile': False,
|
||||
'AllowKillableThief': world.options.killable_thieves.value,
|
||||
'RandomizeSpriteOnHit': False,
|
||||
'DebugMode': False,
|
||||
'DebugForceEnemy': False,
|
||||
'DebugForceEnemyId': 0,
|
||||
'DebugForceBoss': False,
|
||||
'DebugForceBossId': 0,
|
||||
'DebugOpenShutterDoors': False,
|
||||
'DebugForceEnemyDamageZero': False,
|
||||
'DebugShowRoomIdInRupeeCounter': False,
|
||||
'UseManualBosses': True,
|
||||
'ManualBosses': {
|
||||
'EasternPalace': world.dungeons["Eastern Palace"].boss.enemizer_name,
|
||||
'DesertPalace': world.dungeons["Desert Palace"].boss.enemizer_name,
|
||||
'TowerOfHera': world.dungeons["Tower of Hera"].boss.enemizer_name,
|
||||
'AgahnimsTower': 'Agahnim',
|
||||
'PalaceOfDarkness': world.dungeons["Palace of Darkness"].boss.enemizer_name,
|
||||
'SwampPalace': world.dungeons["Swamp Palace"].boss.enemizer_name,
|
||||
'SkullWoods': world.dungeons["Skull Woods"].boss.enemizer_name,
|
||||
'ThievesTown': world.dungeons["Thieves Town"].boss.enemizer_name,
|
||||
'IcePalace': world.dungeons["Ice Palace"].boss.enemizer_name,
|
||||
'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name,
|
||||
'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name,
|
||||
'GanonsTower1':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['bottom'].enemizer_name,
|
||||
'GanonsTower2':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['middle'].enemizer_name,
|
||||
'GanonsTower3':
|
||||
world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else
|
||||
"Inverted Ganons Tower"].bosses['top'].enemizer_name,
|
||||
'GanonsTower4': 'Agahnim2',
|
||||
'Ganon': 'Ganon',
|
||||
}
|
||||
}
|
||||
|
||||
rom.write_to_file(randopatch_path)
|
||||
|
||||
with open(options_path, 'w') as f:
|
||||
json.dump(options, f)
|
||||
|
||||
max_enemizer_tries = 5
|
||||
for i in range(max_enemizer_tries):
|
||||
enemizer_seed = str(world.random.randint(0, 999999999))
|
||||
enemizer_command = [os.path.abspath(enemizercli),
|
||||
'--rom', randopatch_path,
|
||||
'--seed', enemizer_seed,
|
||||
'--binary',
|
||||
'--enemizer', options_path,
|
||||
'--output', enemizer_output_path]
|
||||
|
||||
p_open = subprocess.Popen(enemizer_command,
|
||||
cwd=os.path.dirname(enemizercli),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True)
|
||||
|
||||
enemizer_logger.debug(
|
||||
f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}")
|
||||
for stdout_line in iter(p_open.stdout.readline, ""):
|
||||
if i == max_enemizer_tries - 1:
|
||||
enemizer_logger.warning(stdout_line.rstrip())
|
||||
else:
|
||||
enemizer_logger.debug(stdout_line.rstrip())
|
||||
p_open.stdout.close()
|
||||
|
||||
return_code = p_open.wait()
|
||||
if return_code:
|
||||
if i == max_enemizer_tries - 1:
|
||||
raise subprocess.CalledProcessError(return_code, enemizer_command)
|
||||
continue
|
||||
|
||||
for j in range(i + 1, max_enemizer_tries):
|
||||
world.random.randint(0, 999999999)
|
||||
# Sacrifice all remaining random numbers that would have been used for unused enemizer tries.
|
||||
# This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness
|
||||
break
|
||||
|
||||
rom.read_from_file(enemizer_output_path)
|
||||
os.remove(enemizer_output_path)
|
||||
|
||||
if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
rom.write_byte(0x04DE81, 6)
|
||||
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry.
|
||||
|
||||
# Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on.
|
||||
# Replace them with a Slime enemy if they are placed.
|
||||
if world.options.key_drop_shuffle:
|
||||
key_drop_enemies = {
|
||||
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
|
||||
0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA
|
||||
}
|
||||
for enemy in key_drop_enemies:
|
||||
if rom.read_byte(enemy) == 0x12:
|
||||
logging.debug(f"Moblin found and replaced at {enemy} in world {player}")
|
||||
rom.write_byte(enemy, 0x8F)
|
||||
|
||||
for used in (randopatch_path, options_path):
|
||||
try:
|
||||
os.remove(used)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
tile_list_lock = threading.Lock()
|
||||
_tile_collection_table = []
|
||||
|
||||
@@ -795,9 +586,13 @@ def get_nonnative_item_sprite(code: int) -> int:
|
||||
# https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886
|
||||
|
||||
|
||||
def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int):
|
||||
local_random = multiworld.worlds[player].random
|
||||
local_world = multiworld.worlds[player]
|
||||
enemized = bool(local_world.options.boss_shuffle or local_world.options.enemy_shuffle
|
||||
or local_world.options.enemy_health != 'default' or local_world.options.enemy_damage != 'default'
|
||||
or local_world.options.pot_shuffle or local_world.options.bush_shuffle
|
||||
or local_world.options.killable_thieves)
|
||||
|
||||
# patch items
|
||||
|
||||
@@ -1331,6 +1126,13 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool
|
||||
starting_max_arrows = 30
|
||||
|
||||
startingstate = CollectionState(multiworld)
|
||||
has_blue_shield = False
|
||||
has_red_shield = False
|
||||
has_mirror_shield = False
|
||||
progressive_shields = 0
|
||||
has_blue_mail = False
|
||||
has_red_mail = False
|
||||
progressive_mail = 0
|
||||
|
||||
if startingstate.has('Silver Bow', player):
|
||||
equip[0x340] = 1
|
||||
@@ -1359,18 +1161,6 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool
|
||||
elif startingstate.has('Fighter Sword', player):
|
||||
equip[0x359] = 1
|
||||
|
||||
if startingstate.has('Mirror Shield', player):
|
||||
equip[0x35A] = 3
|
||||
elif startingstate.has('Red Shield', player):
|
||||
equip[0x35A] = 2
|
||||
elif startingstate.has('Blue Shield', player):
|
||||
equip[0x35A] = 1
|
||||
|
||||
if startingstate.has('Red Mail', player):
|
||||
equip[0x35B] = 2
|
||||
elif startingstate.has('Blue Mail', player):
|
||||
equip[0x35B] = 1
|
||||
|
||||
if startingstate.has('Magic Upgrade (1/4)', player):
|
||||
equip[0x37B] = 2
|
||||
equip[0x36E] = 0x80
|
||||
@@ -1383,8 +1173,6 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool
|
||||
if item.name in {'Bow', 'Silver Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
|
||||
'Titans Mitts', 'Power Glove', 'Progressive Glove',
|
||||
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
|
||||
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
|
||||
'Red Mail', 'Blue Mail', 'Progressive Mail',
|
||||
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', 'Triforce Piece'}:
|
||||
continue
|
||||
|
||||
@@ -1489,9 +1277,63 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool
|
||||
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
|
||||
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
|
||||
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
|
||||
elif item.name == 'Blue Shield':
|
||||
has_blue_shield = True
|
||||
continue
|
||||
elif item.name == 'Red Shield':
|
||||
has_red_shield = True
|
||||
continue
|
||||
elif item.name == 'Mirror Shield':
|
||||
has_mirror_shield = True
|
||||
continue
|
||||
elif item.name == 'Progressive Shield':
|
||||
progressive_shields += 1
|
||||
continue
|
||||
elif item.name == 'Blue Mail':
|
||||
has_blue_mail = True
|
||||
continue
|
||||
elif item.name == 'Red Mail':
|
||||
has_red_mail = True
|
||||
continue
|
||||
elif item.name == 'Progressive Mail':
|
||||
progressive_mail += 1
|
||||
continue
|
||||
else:
|
||||
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
|
||||
|
||||
for _ in range(progressive_shields):
|
||||
if has_mirror_shield:
|
||||
continue
|
||||
if has_red_shield and local_world.difficulty_requirements.progressive_shield_limit >= 3:
|
||||
has_mirror_shield = True
|
||||
continue
|
||||
if has_blue_shield and local_world.difficulty_requirements.progressive_shield_limit >= 2:
|
||||
has_red_shield = True
|
||||
continue
|
||||
if local_world.difficulty_requirements.progressive_shield_limit >= 1:
|
||||
has_blue_shield = True
|
||||
|
||||
for _ in range(progressive_mail):
|
||||
if has_red_mail:
|
||||
continue
|
||||
if has_blue_mail and local_world.difficulty_requirements.progressive_armor_limit >= 2:
|
||||
has_red_mail = True
|
||||
continue
|
||||
if local_world.difficulty_requirements.progressive_armor_limit >= 1:
|
||||
has_blue_mail = True
|
||||
|
||||
if has_mirror_shield:
|
||||
equip[0x35A] = 3
|
||||
elif has_red_shield:
|
||||
equip[0x35A] = 2
|
||||
elif has_blue_shield:
|
||||
equip[0x35A] = 1
|
||||
|
||||
if has_red_mail:
|
||||
equip[0x35B] = 2
|
||||
elif has_blue_mail:
|
||||
equip[0x35B] = 1
|
||||
|
||||
equip[0x343] = min(equip[0x343], starting_max_bombs)
|
||||
rom.write_byte(0x180034, starting_max_bombs)
|
||||
equip[0x377] = min(equip[0x377], starting_max_arrows)
|
||||
@@ -1710,6 +1552,71 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool
|
||||
if encoded_players > ROM_PLAYER_LIMIT:
|
||||
rom.write_bytes(0x195FFC + ((ROM_PLAYER_LIMIT - 1) * 32), hud_format_text("Archipelago"))
|
||||
|
||||
if enemized:
|
||||
from . import EnemizerPatches as enemizer_patches
|
||||
from .EnemyShuffle import apply_enemy_shuffle
|
||||
from .PotShuffle import apply_pot_shuffle
|
||||
|
||||
enemizer_patches.apply_enemizer_base_patch(rom)
|
||||
|
||||
enemy_shuffle_enabled = bool(local_world.options.enemy_shuffle)
|
||||
bush_shuffle_enabled = bool(local_world.options.bush_shuffle)
|
||||
enemy_health_key = enemizer_patches._option_key(local_world.options.enemy_health)
|
||||
enemy_damage_key = enemizer_patches._option_key(local_world.options.enemy_damage)
|
||||
|
||||
if enemy_shuffle_enabled or bush_shuffle_enabled:
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_bushes", True)
|
||||
hidden_enemy_chance_pool = (
|
||||
enemizer_patches.RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL
|
||||
if bush_shuffle_enabled
|
||||
else enemizer_patches.VANILLA_HIDDEN_ENEMY_CHANCE_POOL
|
||||
)
|
||||
rom.write_bytes(enemizer_patches.HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, hidden_enemy_chance_pool)
|
||||
enemizer_patches._update_hidden_enemy_item_table_for_retro_mode(rom)
|
||||
|
||||
if enemy_shuffle_enabled:
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_sprites", True)
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_mimic_override", True)
|
||||
enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_terrorpin_ai_fix", True)
|
||||
rom.write_bytes(0x1F2D5, (0x54, 0x9C))
|
||||
rom.write_byte(0x1F2E5, 0xB0)
|
||||
rom.write_byte(0x1F2EB, 0xD0)
|
||||
|
||||
if local_world.options.killable_thieves:
|
||||
enemizer_patches._apply_killable_thief(rom)
|
||||
|
||||
if enemy_health_key != "default" or enemy_damage_key != "default":
|
||||
rng = enemizer_patches._make_native_enemizer_rng(local_world)
|
||||
else:
|
||||
rng = None
|
||||
|
||||
if enemy_health_key != "default":
|
||||
assert rng is not None
|
||||
enemizer_patches._randomize_enemy_health(rom, rng, enemy_health_key)
|
||||
|
||||
if enemy_damage_key != "default":
|
||||
assert rng is not None
|
||||
enemizer_patches._randomize_enemy_damage(rom, rng, allow_zero_damage=True)
|
||||
enemizer_patches._shuffle_damage_groups(
|
||||
rom,
|
||||
rng,
|
||||
chaos_mode=enemy_damage_key == "chaos",
|
||||
allow_zero_damage=True,
|
||||
)
|
||||
|
||||
enemy_shuffle_state = getattr(local_world, "enemy_shuffle_state", None)
|
||||
if local_world.options.enemy_shuffle and enemy_shuffle_state is not None:
|
||||
apply_enemy_shuffle(rom, enemy_shuffle_state)
|
||||
|
||||
if local_world.options.boss_shuffle:
|
||||
# Boss shuffle must run after enemy shuffle so boss room sprite pointers
|
||||
# and graphics block IDs are not restored to the enemy-shuffled room values.
|
||||
enemizer_patches.patch_bosses(local_world, rom)
|
||||
|
||||
pot_shuffle_state = getattr(local_world, "pot_shuffle_state", None)
|
||||
if local_world.options.pot_shuffle and pot_shuffle_state is not None:
|
||||
apply_pot_shuffle(rom, pot_shuffle_state)
|
||||
|
||||
# Write title screen Code
|
||||
hashint = int(rom.get_hash(), 16)
|
||||
code = [
|
||||
@@ -1860,7 +1767,7 @@ def apply_oof_sfx(rom: LocalRom, oof: str):
|
||||
rom.write_bytes(0x12803A, oof_bytes)
|
||||
rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB])
|
||||
|
||||
# Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
|
||||
# Preserve SPC $3188 instead of writing the unused "WHAT" sound effect there.
|
||||
rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08])
|
||||
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from .ItemPool import generate_itempool, difficulties
|
||||
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
|
||||
from .Options import ALTTPOptions, small_key_shuffle
|
||||
from .PotShuffle import generate_pot_shuffle
|
||||
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
|
||||
is_main_entrance, key_drop_data
|
||||
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
|
||||
from .Rom import LocalRom, patch_rom, patch_race_rom, apply_rom_settings, \
|
||||
get_hash_string, get_base_rom_path, LttPDeltaPatch
|
||||
from .Rules import set_rules
|
||||
from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name
|
||||
@@ -253,17 +254,6 @@ class ALTTPWorld(World):
|
||||
|
||||
create_items = generate_itempool
|
||||
|
||||
_enemizer_path: typing.ClassVar[typing.Optional[str]] = None
|
||||
|
||||
@property
|
||||
def enemizer_path(self) -> str:
|
||||
# TODO: directly use settings
|
||||
cls = self.__class__
|
||||
if cls._enemizer_path is None:
|
||||
cls._enemizer_path = settings.get_settings().generator.enemizer_path
|
||||
assert isinstance(cls._enemizer_path, str)
|
||||
return cls._enemizer_path
|
||||
|
||||
# custom instance vars
|
||||
dungeon_local_item_names: typing.Set[str]
|
||||
dungeon_specific_item_names: typing.Set[str]
|
||||
@@ -305,6 +295,8 @@ class ALTTPWorld(World):
|
||||
self.required_medallions = ["Ether", "Quake"]
|
||||
self.escape_assist = []
|
||||
self.shops = []
|
||||
self.enemy_shuffle_state = None
|
||||
self.pot_shuffle_state = None
|
||||
self.logical_heart_containers = 10
|
||||
self.logical_heart_pieces = 24
|
||||
super(ALTTPWorld, self).__init__(*args, **kwargs)
|
||||
@@ -316,10 +308,6 @@ class ALTTPWorld(World):
|
||||
raise FileNotFoundError(rom_file)
|
||||
if multiworld.is_race:
|
||||
import xxtea # noqa
|
||||
for player in multiworld.get_game_players(cls.game):
|
||||
if multiworld.worlds[player].use_enemizer:
|
||||
check_enemizer(multiworld.worlds[player].enemizer_path)
|
||||
break
|
||||
|
||||
def generate_early(self):
|
||||
multiworld = self.multiworld
|
||||
@@ -339,6 +327,9 @@ class ALTTPWorld(World):
|
||||
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
|
||||
if self.options.pot_shuffle:
|
||||
self.pot_shuffle_state = generate_pot_shuffle(self)
|
||||
|
||||
if self.options.mode == 'standard':
|
||||
if self.options.small_key_shuffle:
|
||||
if (self.options.small_key_shuffle not in
|
||||
@@ -564,13 +555,6 @@ class ALTTPWorld(World):
|
||||
def stage_generate_output(cls, multiworld, output_directory):
|
||||
push_shop_inventories(multiworld)
|
||||
|
||||
@property
|
||||
def use_enemizer(self) -> bool:
|
||||
return bool(self.options.boss_shuffle or self.options.enemy_shuffle
|
||||
or self.options.enemy_health != 'default' or self.options.enemy_damage != 'default'
|
||||
or self.options.pot_shuffle or self.options.bush_shuffle
|
||||
or self.options.killable_thieves)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
multiworld = self.multiworld
|
||||
player = self.player
|
||||
@@ -578,14 +562,9 @@ class ALTTPWorld(World):
|
||||
self.pushed_shop_inventories.wait()
|
||||
|
||||
try:
|
||||
use_enemizer = self.use_enemizer
|
||||
|
||||
rom = LocalRom(get_base_rom_path())
|
||||
|
||||
patch_rom(multiworld, rom, player, use_enemizer)
|
||||
|
||||
if use_enemizer:
|
||||
patch_enemizer(self, rom, self.enemizer_path, output_directory)
|
||||
patch_rom(multiworld, rom, player)
|
||||
|
||||
if multiworld.is_race:
|
||||
patch_race_rom(rom, multiworld, player)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
These modules are vendored/generated from the upstream Enemizer compiled release and source that were already present
|
||||
locally in `/home/alchav/PycharmProjects/Archipelago/EnemizerCLI` and `/home/alchav/PycharmProjects/Archipelago/Enemizer`.
|
||||
|
||||
Source details:
|
||||
|
||||
- Upstream project: `Ijwu/Enemizer`
|
||||
- Release family: `7.1`
|
||||
- Library version from `EnemizerCLI/EnemizerCLI.Core.deps.json`: `EnemizerLibrary/7.1.0`
|
||||
|
||||
Vendored data modules:
|
||||
|
||||
- `base_patch_data.py`
|
||||
- `symbols.py`
|
||||
- `enemy_room_metadata.py`
|
||||
- `enemy_sprite_requirements.py`
|
||||
- `overworld_enemy_metadata.py`
|
||||
- `dungeon_sprite_addresses.py`
|
||||
- `pot_shuffle_data.py`
|
||||
|
||||
Purpose:
|
||||
|
||||
- `base_patch_data.py` contains the generated base patch Enemizer applies before feature-specific randomization.
|
||||
- `symbols.py` contains the assembled symbol map consumed by Enemizer's runtime code for ROM addresses.
|
||||
- `enemy_room_metadata.py` and `overworld_enemy_metadata.py` contain room and area grouping/randomization constraints.
|
||||
- `enemy_sprite_requirements.py` contains the sprite metadata used by the native enemy shuffle implementation.
|
||||
- `dungeon_sprite_addresses.py` contains dungeon sprite slot metadata derived from Enemizer's source tables and keyed-enemy address list.
|
||||
- `pot_shuffle_data.py` contains the native pot shuffle room/item source data.
|
||||
@@ -0,0 +1 @@
|
||||
"""Native ALTTP Enemizer data modules."""
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,202 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class DungeonSpriteAddressData(NamedTuple):
|
||||
room_id: int
|
||||
sprite_id_addresses: tuple[int, ...]
|
||||
|
||||
DUNGEON_SPRITE_ADDRESSES = (
|
||||
DungeonSpriteAddressData(room_id=2, sprite_id_addresses=(317750, 317753, 317756, 317759, 317762, 317792, 317795)),
|
||||
DungeonSpriteAddressData(room_id=4, sprite_id_addresses=(317803, 317806, 317809, 317812, 317827, 317839, 317842, 317845)),
|
||||
DungeonSpriteAddressData(room_id=9, sprite_id_addresses=(317904, 317907, 317910)),
|
||||
DungeonSpriteAddressData(room_id=10, sprite_id_addresses=(317915, 317918, 317921, 317924, 317930, 317933)),
|
||||
DungeonSpriteAddressData(room_id=11, sprite_id_addresses=(317941, 317944, 317947, 317950, 317953, 317956, 317959, 317962, 317965)),
|
||||
DungeonSpriteAddressData(room_id=14, sprite_id_addresses=(317978, 317981, 317984)),
|
||||
DungeonSpriteAddressData(room_id=17, sprite_id_addresses=(317992, 317995, 317998, 318001, 318004, 318007, 318010, 318013)),
|
||||
DungeonSpriteAddressData(room_id=19, sprite_id_addresses=(318029, 318032, 318035, 318038, 318044, 318056, 318053, 318041)),
|
||||
DungeonSpriteAddressData(room_id=21, sprite_id_addresses=(318105, 318108, 318111, 318114, 318117, 318120)),
|
||||
DungeonSpriteAddressData(room_id=22, sprite_id_addresses=(318125, 318128, 318131, 318134, 318137, 318140, 318143)),
|
||||
DungeonSpriteAddressData(room_id=23, sprite_id_addresses=(318157, 318160, 318163, 318166, 318169, 318172)),
|
||||
DungeonSpriteAddressData(room_id=25, sprite_id_addresses=(318177, 318180, 318183, 318186)),
|
||||
DungeonSpriteAddressData(room_id=26, sprite_id_addresses=(318191, 318194, 318197, 318200, 318203, 318206, 318209, 318212, 318218)),
|
||||
DungeonSpriteAddressData(room_id=27, sprite_id_addresses=(318232, 318235, 318238, 318241)),
|
||||
DungeonSpriteAddressData(room_id=30, sprite_id_addresses=(318284, 318287, 318290, 318293, 318296, 318299)),
|
||||
DungeonSpriteAddressData(room_id=31, sprite_id_addresses=(318304, 318307, 318310, 318313, 318316, 318319, 318322, 318325)),
|
||||
DungeonSpriteAddressData(room_id=33, sprite_id_addresses=(318335, 318341, 318344, 318347, 318350, 318353, 318356, 318359, 318362, 318365, 318368)),
|
||||
DungeonSpriteAddressData(room_id=34, sprite_id_addresses=(318373, 318376, 318379, 318382, 318385, 318388, 318391)),
|
||||
DungeonSpriteAddressData(room_id=36, sprite_id_addresses=(318413, 318416, 318419, 318422, 318425, 318428, 318431)),
|
||||
DungeonSpriteAddressData(room_id=38, sprite_id_addresses=(318471, 318438, 318441, 318444, 318447, 318450, 318453, 318459, 318462, 318465, 318468)),
|
||||
DungeonSpriteAddressData(room_id=39, sprite_id_addresses=(318476, 318479, 318482, 318485, 318488, 318491, 318494)),
|
||||
DungeonSpriteAddressData(room_id=40, sprite_id_addresses=(318511,)),
|
||||
DungeonSpriteAddressData(room_id=42, sprite_id_addresses=(318530, 318533, 318536, 318539, 318542, 318545)),
|
||||
DungeonSpriteAddressData(room_id=43, sprite_id_addresses=(318556, 318559, 318562, 318565, 318568, 318571)),
|
||||
DungeonSpriteAddressData(room_id=46, sprite_id_addresses=(318590, 318593, 318596, 318599, 318602, 318605)),
|
||||
DungeonSpriteAddressData(room_id=49, sprite_id_addresses=(318621, 318624, 318627, 318630, 318633, 318636, 318639, 318642, 318645, 318648)),
|
||||
DungeonSpriteAddressData(room_id=50, sprite_id_addresses=(318653, 318656, 318659, 318662, 318665)),
|
||||
DungeonSpriteAddressData(room_id=52, sprite_id_addresses=(318681, 318684, 318687, 318693, 318696, 318699, 318690)),
|
||||
DungeonSpriteAddressData(room_id=53, sprite_id_addresses=(318710, 318713, 318716, 318719, 318722, 318728, 318731, 318734, 318725)),
|
||||
DungeonSpriteAddressData(room_id=54, sprite_id_addresses=(318742, 318745, 318754, 318760, 318763)),
|
||||
DungeonSpriteAddressData(room_id=55, sprite_id_addresses=(318777, 318780, 318783, 318786, 318792, 318795, 318798, 318801, 318789)),
|
||||
DungeonSpriteAddressData(room_id=56, sprite_id_addresses=(318806, 318809, 318812, 318815, 318818, 318821, 318824)),
|
||||
DungeonSpriteAddressData(room_id=57, sprite_id_addresses=(318829, 318835, 318841, 318844, 318847, 318850)),
|
||||
DungeonSpriteAddressData(room_id=58, sprite_id_addresses=(318855, 318858, 318861, 318864, 318867, 318870)),
|
||||
DungeonSpriteAddressData(room_id=59, sprite_id_addresses=(318875, 318878, 318881, 318884, 318887, 318890, 318893)),
|
||||
DungeonSpriteAddressData(room_id=60, sprite_id_addresses=(318898, 318901, 318904)),
|
||||
DungeonSpriteAddressData(room_id=61, sprite_id_addresses=(318915, 318921, 318924, 318927, 318930, 318933, 318939, 318942, 318945, 318948, 318951)),
|
||||
DungeonSpriteAddressData(room_id=62, sprite_id_addresses=(318959, 318962, 318980, 318983, 318989, 318992)),
|
||||
DungeonSpriteAddressData(room_id=63, sprite_id_addresses=(319000, 319006, 319009)),
|
||||
DungeonSpriteAddressData(room_id=64, sprite_id_addresses=(319014, 319017, 319023, 319026, 319029)),
|
||||
DungeonSpriteAddressData(room_id=65, sprite_id_addresses=(319036, 319039, 319042, 319045)),
|
||||
DungeonSpriteAddressData(room_id=66, sprite_id_addresses=(319050, 319053, 319056, 319059, 319062, 319065)),
|
||||
DungeonSpriteAddressData(room_id=67, sprite_id_addresses=(319070, 319073)),
|
||||
DungeonSpriteAddressData(room_id=68, sprite_id_addresses=(319084, 319087, 319090, 319093, 319096, 319102)),
|
||||
DungeonSpriteAddressData(room_id=69, sprite_id_addresses=(319110, 319116, 319119, 319131, 319134, 319137, 319113, 319122, 319125, 319128)),
|
||||
DungeonSpriteAddressData(room_id=70, sprite_id_addresses=(319142, 319148, 319154)),
|
||||
DungeonSpriteAddressData(room_id=73, sprite_id_addresses=(319161, 319164, 319167, 319170, 319173, 319176, 319182, 319185, 319188, 319191, 319194, 319197)),
|
||||
DungeonSpriteAddressData(room_id=74, sprite_id_addresses=(319205, 319208)),
|
||||
DungeonSpriteAddressData(room_id=75, sprite_id_addresses=(319213, 319216, 319219, 319222, 319225, 319228, 319231, 319234)),
|
||||
DungeonSpriteAddressData(room_id=76, sprite_id_addresses=(319245, 319248, 319251, 319254, 319257, 319260)),
|
||||
DungeonSpriteAddressData(room_id=78, sprite_id_addresses=(319270, 319273, 319276, 319279)),
|
||||
DungeonSpriteAddressData(room_id=80, sprite_id_addresses=(319295, 319298, 319301)),
|
||||
DungeonSpriteAddressData(room_id=81, sprite_id_addresses=(319309, 319312)),
|
||||
DungeonSpriteAddressData(room_id=82, sprite_id_addresses=(319317, 319320, 319323)),
|
||||
DungeonSpriteAddressData(room_id=83, sprite_id_addresses=(319328, 319331, 319334, 319337, 319340, 319343, 319346, 319349, 319352, 319355, 319358, 319361, 319364)),
|
||||
DungeonSpriteAddressData(room_id=84, sprite_id_addresses=(319369, 319372, 319375, 319378, 319381, 319384, 319387, 319390)),
|
||||
DungeonSpriteAddressData(room_id=85, sprite_id_addresses=(319398, 319401)),
|
||||
DungeonSpriteAddressData(room_id=86, sprite_id_addresses=(319415, 319418, 319421, 319424, 319430, 319433, 319436, 319442, 319439)),
|
||||
DungeonSpriteAddressData(room_id=87, sprite_id_addresses=(319447, 319450, 319453, 319456, 319459, 319462, 319468, 319471, 319474, 319477, 319480, 319483, 319486, 319489)),
|
||||
DungeonSpriteAddressData(room_id=88, sprite_id_addresses=(319497, 319500, 319506, 319509, 319515, 319518, 319521)),
|
||||
DungeonSpriteAddressData(room_id=89, sprite_id_addresses=(319526, 319529, 319538, 319544, 319547, 319550, 319553, 319556, 319559, 319541)),
|
||||
DungeonSpriteAddressData(room_id=91, sprite_id_addresses=(319575, 319578, 319581, 319584)),
|
||||
DungeonSpriteAddressData(room_id=93, sprite_id_addresses=(319615, 319618, 319621, 319624, 319627, 319633, 319636, 319639, 319651, 319642, 319645, 319648, 319630)),
|
||||
DungeonSpriteAddressData(room_id=94, sprite_id_addresses=(319659, 319662, 319665, 319668)),
|
||||
DungeonSpriteAddressData(room_id=95, sprite_id_addresses=(319673, 319676, 319679)),
|
||||
DungeonSpriteAddressData(room_id=96, sprite_id_addresses=(319684,)),
|
||||
DungeonSpriteAddressData(room_id=97, sprite_id_addresses=(319689, 319692, 319695)),
|
||||
DungeonSpriteAddressData(room_id=98, sprite_id_addresses=(319700, 319703, 319706)),
|
||||
DungeonSpriteAddressData(room_id=99, sprite_id_addresses=(319714, 319711)),
|
||||
DungeonSpriteAddressData(room_id=100, sprite_id_addresses=(319719, 319725, 319728, 319731, 319734, 319737)),
|
||||
DungeonSpriteAddressData(room_id=101, sprite_id_addresses=(319760, 319763, 319766, 319769, 319772)),
|
||||
DungeonSpriteAddressData(room_id=102, sprite_id_addresses=(319777, 319783, 319786, 319795, 319798, 319801, 319804, 319810)),
|
||||
DungeonSpriteAddressData(room_id=103, sprite_id_addresses=(319818, 319821, 319824, 319827, 319830, 319833, 319836, 319839, 319842)),
|
||||
DungeonSpriteAddressData(room_id=104, sprite_id_addresses=(319859, 319865, 319868)),
|
||||
DungeonSpriteAddressData(room_id=106, sprite_id_addresses=(319873, 319876, 319879, 319882, 319885, 319888)),
|
||||
DungeonSpriteAddressData(room_id=107, sprite_id_addresses=(319899, 319902, 319905, 319911, 319914, 319917, 319920, 319923, 319926, 319929, 319932)),
|
||||
DungeonSpriteAddressData(room_id=109, sprite_id_addresses=(319954, 319957, 319960, 319963, 319966, 319969, 319972, 319975, 319978)),
|
||||
DungeonSpriteAddressData(room_id=110, sprite_id_addresses=(319983, 319986, 319989, 319992, 319995)),
|
||||
DungeonSpriteAddressData(room_id=113, sprite_id_addresses=(320000, 320003)),
|
||||
DungeonSpriteAddressData(room_id=114, sprite_id_addresses=(320011, 320017)),
|
||||
DungeonSpriteAddressData(room_id=115, sprite_id_addresses=(320022, 320025, 320028, 320031, 320034, 320037)),
|
||||
DungeonSpriteAddressData(room_id=116, sprite_id_addresses=(320045, 320048, 320051, 320054, 320057, 320060, 320063, 320066)),
|
||||
DungeonSpriteAddressData(room_id=117, sprite_id_addresses=(320071, 320074, 320077, 320080, 320083, 320086, 320095, 320098)),
|
||||
DungeonSpriteAddressData(room_id=118, sprite_id_addresses=(320106, 320109, 320112, 320115, 320121)),
|
||||
DungeonSpriteAddressData(room_id=119, sprite_id_addresses=(320126, 320138, 320141)),
|
||||
DungeonSpriteAddressData(room_id=123, sprite_id_addresses=(320146, 320149, 320152, 320155, 320158, 320161, 320167, 320170, 320173, 320176)),
|
||||
DungeonSpriteAddressData(room_id=124, sprite_id_addresses=(320181, 320184, 320187, 320190, 320193, 320196)),
|
||||
DungeonSpriteAddressData(room_id=125, sprite_id_addresses=(320216, 320219, 320225, 320228, 320234, 320222, 320231, 320204, 320207, 320210, 320213, 320222, 320231)),
|
||||
DungeonSpriteAddressData(room_id=126, sprite_id_addresses=(320242, 320245, 320254, 320257)),
|
||||
DungeonSpriteAddressData(room_id=128, sprite_id_addresses=(320291, 320294)),
|
||||
DungeonSpriteAddressData(room_id=129, sprite_id_addresses=(320302, 320305)),
|
||||
DungeonSpriteAddressData(room_id=130, sprite_id_addresses=(320310, 320313, 320316)),
|
||||
DungeonSpriteAddressData(room_id=131, sprite_id_addresses=(320321, 320324, 320327, 320330, 320333, 320336, 320339, 320342, 320345, 320348)),
|
||||
DungeonSpriteAddressData(room_id=132, sprite_id_addresses=(320353, 320356, 320359, 320362, 320365, 320368, 320371)),
|
||||
DungeonSpriteAddressData(room_id=133, sprite_id_addresses=(320376, 320379, 320382, 320385, 320388, 320391, 320394, 320397, 320400, 320403)),
|
||||
DungeonSpriteAddressData(room_id=135, sprite_id_addresses=(320410, 320413, 320416, 320419, 320434, 320437, 320440, 320446, 320422)),
|
||||
DungeonSpriteAddressData(room_id=139, sprite_id_addresses=(320468, 320471, 320474, 320477, 320480)),
|
||||
DungeonSpriteAddressData(room_id=140, sprite_id_addresses=(320503, 320506, 320509, 320512, 320518, 320521, 320527, 320524, 320515)),
|
||||
DungeonSpriteAddressData(room_id=141, sprite_id_addresses=(320538, 320541, 320544, 320547, 320550, 320556, 320559, 320562, 320565, 320568, 320571, 320535)),
|
||||
DungeonSpriteAddressData(room_id=142, sprite_id_addresses=(320579, 320582, 320585, 320588, 320591, 320594, 320597)),
|
||||
DungeonSpriteAddressData(room_id=145, sprite_id_addresses=(320610, 320616, 320619, 320622, 320625, 320613)),
|
||||
DungeonSpriteAddressData(room_id=146, sprite_id_addresses=(320636, 320639, 320642, 320645, 320648, 320654, 320657, 320660, 320663)),
|
||||
DungeonSpriteAddressData(room_id=147, sprite_id_addresses=(320668, 320671, 320674, 320677, 320680, 320683, 320686, 320689)),
|
||||
DungeonSpriteAddressData(room_id=149, sprite_id_addresses=(320694, 320697, 320700, 320703)),
|
||||
DungeonSpriteAddressData(room_id=151, sprite_id_addresses=(320728,)),
|
||||
DungeonSpriteAddressData(room_id=152, sprite_id_addresses=(320733, 320736, 320739, 320742, 320745)),
|
||||
DungeonSpriteAddressData(room_id=153, sprite_id_addresses=(320750, 320753, 320756, 320759, 320765, 320768, 320771, 320774, 320777, 320780)),
|
||||
DungeonSpriteAddressData(room_id=155, sprite_id_addresses=(320794, 320797, 320800, 320803, 320806, 320809, 320812, 320815, 320818, 320821)),
|
||||
DungeonSpriteAddressData(room_id=156, sprite_id_addresses=(320826, 320829, 320832, 320835, 320838, 320841)),
|
||||
DungeonSpriteAddressData(room_id=157, sprite_id_addresses=(320852, 320855, 320858, 320861, 320864, 320867, 320870, 320873)),
|
||||
DungeonSpriteAddressData(room_id=158, sprite_id_addresses=(320878, 320881, 320884, 320887)),
|
||||
DungeonSpriteAddressData(room_id=159, sprite_id_addresses=(320907, 320910)),
|
||||
DungeonSpriteAddressData(room_id=160, sprite_id_addresses=(320915, 320918, 320921)),
|
||||
DungeonSpriteAddressData(room_id=161, sprite_id_addresses=(320929, 320932, 320935, 320938, 320941, 320944, 320947, 320950)),
|
||||
DungeonSpriteAddressData(room_id=165, sprite_id_addresses=(320968, 320971, 320974, 320977, 320980, 320983, 320986, 320989, 320998, 321001)),
|
||||
DungeonSpriteAddressData(room_id=167, sprite_id_addresses=(321014, 321017)),
|
||||
DungeonSpriteAddressData(room_id=168, sprite_id_addresses=(321022, 321025, 321028, 321031, 321034)),
|
||||
DungeonSpriteAddressData(room_id=169, sprite_id_addresses=(321039, 321042, 321057, 321060, 321045, 321048, 321051, 321054)),
|
||||
DungeonSpriteAddressData(room_id=170, sprite_id_addresses=(321065, 321068, 321071, 321074, 321077, 321080)),
|
||||
DungeonSpriteAddressData(room_id=171, sprite_id_addresses=(321088, 321091, 321094, 321097, 321100, 321103, 321106)),
|
||||
DungeonSpriteAddressData(room_id=174, sprite_id_addresses=(321116, 321119)),
|
||||
DungeonSpriteAddressData(room_id=176, sprite_id_addresses=(321129, 321132, 321135, 321138, 321141, 321144, 321147, 321150, 321153, 321156, 321159, 321165, 321168)),
|
||||
DungeonSpriteAddressData(room_id=177, sprite_id_addresses=(321173, 321176, 321179, 321182, 321185, 321188, 321191, 321194, 321197, 321200)),
|
||||
DungeonSpriteAddressData(room_id=178, sprite_id_addresses=(321205, 321208, 321211, 321214, 321217, 321220, 321223, 321226, 321229, 321232, 321235, 321238, 321241, 321244)),
|
||||
DungeonSpriteAddressData(room_id=179, sprite_id_addresses=(321249, 321252, 321255, 321258, 321261)),
|
||||
DungeonSpriteAddressData(room_id=182, sprite_id_addresses=(321277, 321280, 321289, 321292, 321301, 321304)),
|
||||
DungeonSpriteAddressData(room_id=183, sprite_id_addresses=(321309, 321312)),
|
||||
DungeonSpriteAddressData(room_id=184, sprite_id_addresses=(321317, 321320, 321323, 321326, 321329, 321332)),
|
||||
DungeonSpriteAddressData(room_id=186, sprite_id_addresses=(321342, 321345, 321348, 321351, 321354, 321357, 321360)),
|
||||
DungeonSpriteAddressData(room_id=187, sprite_id_addresses=(321365, 321368, 321371, 321374, 321377, 321380, 321386, 321389, 321392, 321395, 321383)),
|
||||
DungeonSpriteAddressData(room_id=188, sprite_id_addresses=(321403, 321406, 321409, 321412, 321418, 321421, 321424, 321433, 321400, 321415, 321427, 321430)),
|
||||
DungeonSpriteAddressData(room_id=190, sprite_id_addresses=(321440, 321446, 321449, 321452, 321455, 321458)),
|
||||
DungeonSpriteAddressData(room_id=192, sprite_id_addresses=(321471, 321474, 321477, 321480, 321486, 321489, 321492, 321495)),
|
||||
DungeonSpriteAddressData(room_id=193, sprite_id_addresses=(321503, 321506, 321509, 321512, 321518, 321524, 321527, 321530, 321521, 321515, 321536)),
|
||||
DungeonSpriteAddressData(room_id=194, sprite_id_addresses=(321547, 321550, 321553, 321556, 321562, 321559, 321544, 321541)),
|
||||
DungeonSpriteAddressData(room_id=195, sprite_id_addresses=(321567, 321585, 321588)),
|
||||
DungeonSpriteAddressData(room_id=196, sprite_id_addresses=(321605, 321608, 321611, 321614, 321617, 321620)),
|
||||
DungeonSpriteAddressData(room_id=201, sprite_id_addresses=(321697, 321700, 321703)),
|
||||
DungeonSpriteAddressData(room_id=203, sprite_id_addresses=(321708, 321717, 321720, 321723, 321726, 321729, 321732, 321735, 321738, 321741, 321714, 321711)),
|
||||
DungeonSpriteAddressData(room_id=204, sprite_id_addresses=(321746, 321749, 321755, 321758, 321761, 321770, 321773, 321776, 321779, 321782, 321785, 321752, 321764, 321767)),
|
||||
DungeonSpriteAddressData(room_id=206, sprite_id_addresses=(321790, 321793, 321799, 321802, 321805, 321808, 321811)),
|
||||
DungeonSpriteAddressData(room_id=208, sprite_id_addresses=(321816, 321819, 321822, 321825, 321828, 321831, 321834, 321837, 321840, 321843, 321846)),
|
||||
DungeonSpriteAddressData(room_id=209, sprite_id_addresses=(321851, 321854, 321857, 321860, 321863, 321866, 321869, 321872)),
|
||||
DungeonSpriteAddressData(room_id=210, sprite_id_addresses=(321877, 321880, 321883, 321886, 321889, 321892, 321895, 321898, 321901, 321904)),
|
||||
DungeonSpriteAddressData(room_id=216, sprite_id_addresses=(321937, 321940, 321943, 321946, 321949, 321952, 321955, 321958, 321961, 321964, 321967)),
|
||||
DungeonSpriteAddressData(room_id=217, sprite_id_addresses=(321975, 321978, 321981, 321972)),
|
||||
DungeonSpriteAddressData(room_id=218, sprite_id_addresses=(321986, 321989)),
|
||||
DungeonSpriteAddressData(room_id=219, sprite_id_addresses=(321994, 321997, 322000, 322006, 322003, 322012, 322009)),
|
||||
DungeonSpriteAddressData(room_id=220, sprite_id_addresses=(322020, 322023, 322026, 322029, 322032, 322035, 322047, 322017, 322038, 322041, 322044)),
|
||||
DungeonSpriteAddressData(room_id=223, sprite_id_addresses=(322063, 322066)),
|
||||
DungeonSpriteAddressData(room_id=224, sprite_id_addresses=(322071, 322074, 322077, 322080)),
|
||||
DungeonSpriteAddressData(room_id=232, sprite_id_addresses=(322189, 322192, 322195, 322198)),
|
||||
DungeonSpriteAddressData(room_id=238, sprite_id_addresses=(322213, 322216, 322219, 322222, 322225)),
|
||||
DungeonSpriteAddressData(room_id=239, sprite_id_addresses=(322230, 322233, 322236)),
|
||||
DungeonSpriteAddressData(room_id=249, sprite_id_addresses=(322323, 322326, 322329, 322332)),
|
||||
DungeonSpriteAddressData(room_id=254, sprite_id_addresses=(322378, 322381, 322384, 322387, 322390)),
|
||||
DungeonSpriteAddressData(room_id=263, sprite_id_addresses=(322444, 322447)),
|
||||
DungeonSpriteAddressData(room_id=264, sprite_id_addresses=(322452, 322455, 322458, 322461)),
|
||||
DungeonSpriteAddressData(room_id=267, sprite_id_addresses=(322494,)),
|
||||
DungeonSpriteAddressData(room_id=269, sprite_id_addresses=(322525, 322528)),
|
||||
DungeonSpriteAddressData(room_id=291, sprite_id_addresses=(322671, 322674, 322677, 322680)),
|
||||
)
|
||||
|
||||
KEYED_SPRITE_ID_ADDRESSES = frozenset((317984,
|
||||
318044,
|
||||
318335,
|
||||
318835,
|
||||
318915,
|
||||
318983,
|
||||
320003,
|
||||
320011,
|
||||
320294,
|
||||
320759,
|
||||
321292,
|
||||
321480,
|
||||
321530,
|
||||
320000,
|
||||
321159,
|
||||
321937,
|
||||
321940,
|
||||
321943,
|
||||
321946,
|
||||
321949,
|
||||
321952,
|
||||
321955,
|
||||
321958,
|
||||
321961,
|
||||
321964,
|
||||
321967,
|
||||
321424,
|
||||
321421,
|
||||
321418))
|
||||
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class RoomGroupRequirementData(NamedTuple):
|
||||
group_id: Optional[int]
|
||||
subgroup_0: Optional[int]
|
||||
subgroup_1: Optional[int]
|
||||
subgroup_2: Optional[int]
|
||||
subgroup_3: Optional[int]
|
||||
rooms: tuple[int, ...]
|
||||
|
||||
SHUTTER_ROOM_IDS = frozenset((184,
|
||||
11,
|
||||
27,
|
||||
75,
|
||||
4,
|
||||
36,
|
||||
182,
|
||||
40,
|
||||
14,
|
||||
46,
|
||||
62,
|
||||
110,
|
||||
49,
|
||||
135,
|
||||
68,
|
||||
69,
|
||||
83,
|
||||
117,
|
||||
133,
|
||||
61,
|
||||
93,
|
||||
107,
|
||||
109,
|
||||
123,
|
||||
125,
|
||||
141,
|
||||
150,
|
||||
165,
|
||||
113,
|
||||
168,
|
||||
216,
|
||||
176,
|
||||
192,
|
||||
224,
|
||||
178,
|
||||
210,
|
||||
239,
|
||||
268,
|
||||
291))
|
||||
WATER_ROOM_IDS = frozenset((22, 40, 52, 54, 56, 70, 102))
|
||||
DONT_RANDOMIZE_ROOM_IDS = frozenset((0, 1, 3, 13, 20, 32, 48, 127))
|
||||
NO_SPECIAL_ENEMIES_STANDARD_ROOM_IDS = frozenset((1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 85, 96, 97, 98, 112, 113, 114, 128, 129, 130))
|
||||
BOSS_ROOM_IDS = frozenset((200, 51, 108, 7, 77, 90, 6, 41, 172, 222, 144, 164, 32, 13, 0))
|
||||
|
||||
ROOM_GROUP_REQUIREMENTS = (
|
||||
RoomGroupRequirementData(group_id=1, subgroup_0=70, subgroup_1=73, subgroup_2=28, subgroup_3=82, rooms=(228, 240)),
|
||||
RoomGroupRequirementData(group_id=5, subgroup_0=75, subgroup_1=77, subgroup_2=74, subgroup_3=90, rooms=(243, 265, 270, 271, 272, 273, 282, 284, 290)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=75, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(255, 274, 287)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, rooms=(289,)),
|
||||
RoomGroupRequirementData(group_id=7, subgroup_0=75, subgroup_1=77, subgroup_2=57, subgroup_3=54, rooms=(8, 44, 276, 277)),
|
||||
RoomGroupRequirementData(group_id=13, subgroup_0=81, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(85, 258, 260)),
|
||||
RoomGroupRequirementData(group_id=14, subgroup_0=71, subgroup_1=73, subgroup_2=76, subgroup_3=80, rooms=(18, 261, 266)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=80, rooms=(264,)),
|
||||
RoomGroupRequirementData(group_id=15, subgroup_0=79, subgroup_1=77, subgroup_2=74, subgroup_3=80, rooms=(244, 245, 257, 259, 262, 280, 281)),
|
||||
RoomGroupRequirementData(group_id=18, subgroup_0=85, subgroup_1=61, subgroup_2=66, subgroup_3=67, rooms=(32, 48)),
|
||||
RoomGroupRequirementData(group_id=24, subgroup_0=85, subgroup_1=26, subgroup_2=66, subgroup_3=67, rooms=(13,)),
|
||||
RoomGroupRequirementData(group_id=34, subgroup_0=33, subgroup_1=65, subgroup_2=69, subgroup_3=51, rooms=(0,)),
|
||||
RoomGroupRequirementData(group_id=40, subgroup_0=14, subgroup_1=None, subgroup_2=74, subgroup_3=80, rooms=(225, 256, 293, 292, 294)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=14, subgroup_1=30, subgroup_2=None, subgroup_3=None, rooms=(291,)),
|
||||
RoomGroupRequirementData(group_id=23, subgroup_0=64, subgroup_1=None, subgroup_2=None, subgroup_3=63, rooms=()),
|
||||
RoomGroupRequirementData(group_id=9, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=29, rooms=(227,)),
|
||||
RoomGroupRequirementData(group_id=11, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=61, rooms=()),
|
||||
RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=49, rooms=()),
|
||||
RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=60, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=21, subgroup_0=None, subgroup_1=None, subgroup_2=58, subgroup_3=62, rooms=()),
|
||||
RoomGroupRequirementData(group_id=28, subgroup_0=None, subgroup_1=None, subgroup_2=38, subgroup_3=82, rooms=(14, 126, 142, 158, 190)),
|
||||
RoomGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=48, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=26, subgroup_0=None, subgroup_1=None, subgroup_2=56, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=20, subgroup_0=None, subgroup_1=None, subgroup_2=57, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=32, subgroup_0=None, subgroup_1=44, subgroup_2=59, subgroup_3=None, rooms=()),
|
||||
RoomGroupRequirementData(group_id=3, subgroup_0=93, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(81,)),
|
||||
RoomGroupRequirementData(group_id=42, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(286,)),
|
||||
RoomGroupRequirementData(group_id=10, subgroup_0=47, subgroup_1=None, subgroup_2=46, subgroup_3=None, rooms=(92, 117, 185, 217)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(54, 70, 102, 118)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=32, subgroup_2=None, subgroup_3=None, rooms=(62, 159)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=31, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(127,)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=35, subgroup_3=None, rooms=(57, 73, 86, 87, 104, 141)),
|
||||
RoomGroupRequirementData(group_id=37, subgroup_0=31, subgroup_1=None, subgroup_2=39, subgroup_3=82, rooms=(36, 180, 181, 198, 199, 214)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(23, 42, 68, 76, 86, 88, 89, 103, 104, 126, 139, 235, 251)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(23, 42, 76, 89, 103, 104, 126, 139, 235, 251)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(11, 19, 27, 30, 42, 43, 49, 61, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(11, 19, 27, 30, 42, 43, 49, 53, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(19, 35, 150, 165, 195, 197, 213)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(19, 35, 150, 165, 197, 213)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 38, 43, 64, 74, 87, 107, 123)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(38, 43, 64, 74, 87, 107, 123, 206)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(2, 88, 100, 140, 267)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 61, 68, 86, 94, 124, 149, 195)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(4, 63, 206)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(53, 55, 118)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(40,)),
|
||||
RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=37, subgroup_3=None, rooms=(151,)),
|
||||
)
|
||||
@@ -0,0 +1,295 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class EnemySpriteRequirementData(NamedTuple):
|
||||
sprite_name: str
|
||||
sprite_id: int
|
||||
boss: bool
|
||||
overlord: bool
|
||||
do_not_randomize: bool
|
||||
killable: bool
|
||||
npc: bool
|
||||
never_use_dungeon: bool
|
||||
never_use_overworld: bool
|
||||
cannot_have_key: bool
|
||||
is_object: bool
|
||||
absorbable: bool
|
||||
is_water_sprite: bool
|
||||
is_enemy_sprite: bool
|
||||
group_ids: tuple[int, ...]
|
||||
subgroup_0: tuple[int, ...]
|
||||
subgroup_1: tuple[int, ...]
|
||||
subgroup_2: tuple[int, ...]
|
||||
subgroup_3: tuple[int, ...]
|
||||
parameters: Optional[int]
|
||||
special_glitched: bool
|
||||
excluded_rooms: tuple[int, ...]
|
||||
dont_randomize_rooms: tuple[int, ...]
|
||||
spawnable_rooms: tuple[int, ...]
|
||||
|
||||
ENEMY_SPRITE_REQUIREMENTS = (
|
||||
EnemySpriteRequirementData(sprite_name='RavenSprite', sprite_id=0, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17, 25), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VultureSprite', sprite_id=1, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EmptySprite', sprite_id=3, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullSwitch_GoodSprite', sprite_id=4, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullSwitch_TrapSprite', sprite_id=6, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Octorok_OneWaySprite', sprite_id=8, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MoldormSprite', sprite_id=9, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(48,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Octorok_FourWaySprite', sprite_id=10, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ChickenSprite', sprite_id=11, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21, 80), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BuzzblobSprite', sprite_id=13, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SnapdragonSprite', sprite_id=14, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OctoballoonSprite', sprite_id=15, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OctoballoonHatchlingsSprite', sprite_id=16, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HinoxSprite', sprite_id=17, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MoblinSprite', sprite_id=18, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MiniHelmasaurSprite', sprite_id=19, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GargoylesDomainGateSprite', sprite_id=20, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AntifairySprite', sprite_id=21, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(64, 210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SahasrahlaAginahSprite', sprite_id=22, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BushHoarderSprite', sprite_id=23, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MiniMoldormSprite', sprite_id=24, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PoeSprite', sprite_id=25, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(14, 21), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DwarvesSprite', sprite_id=26, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowInWall_MaybeSprite', sprite_id=27, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StatueSprite', sprite_id=28, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268, 63), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WeathervaneSprite', sprite_id=29, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CrystalSwitchSprite', sprite_id=30, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BugCatchingKidSprite', sprite_id=31, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(81,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SluggulaSprite', sprite_id=32, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PushSwitchSprite', sprite_id=33, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(83,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RopaSprite', sprite_id=34, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedBariSprite', sprite_id=35, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueBariSprite', sprite_id=36, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TalkingTreeSprite', sprite_id=37, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HardhatBeetleSprite', sprite_id=38, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DeadrockSprite', sprite_id=39, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(127, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StorytellersSprite', sprite_id=40, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlindHideoutAttendantSprite', sprite_id=41, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14, 79), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SweepingLadySprite', sprite_id=42, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MultipurposeSpriteSprite', sprite_id=43, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LumberjacksSprite', sprite_id=44, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TelepathicStones_NoIdeaWhatThisActuallyIsLikelyUnusedSprite', sprite_id=45, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FluteBoysNotesSprite', sprite_id=46, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RaceHPNPCsSprite', sprite_id=47, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Person_MaybeSprite', sprite_id=48, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FortuneTellerSprite', sprite_id=49, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AngryBrothersSprite', sprite_id=50, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PullForRupeesSpriteSprite', sprite_id=51, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ScaredGirl2Sprite', sprite_id=52, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='InnkeeperSprite', sprite_id=53, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WitchSprite', sprite_id=54, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WaterfallSprite', sprite_id=55, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowTargetSprite', sprite_id=56, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AverageMiddleAgedManSprite', sprite_id=57, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HalfMagicBatSprite', sprite_id=58, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DashItemSprite', sprite_id=59, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VillageKidSprite', sprite_id=60, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Signs_ChickenLadyAlsoShowedUp_ScaredLadiesOutsideHousesSprite', sprite_id=61, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RockHoarderSprite', sprite_id=62, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TutorialSoldierSprite', sprite_id=63, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LightningLockSprite', sprite_id=64, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueSwordSoldier_DetectPlayerSprite', sprite_id=65, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSwordSoldierSprite', sprite_id=66, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedSpearSoldierSprite', sprite_id=67, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AssaultSwordSoldierSprite', sprite_id=68, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSpearSoldierSprite', sprite_id=69, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueArcherSprite', sprite_id=70, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenArcherSprite', sprite_id=71, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedJavelinSoldierSprite', sprite_id=72, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedJavelinSoldier2Sprite', sprite_id=73, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedBombSoldiersSprite', sprite_id=74, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenSoldierRecruits_HMKnightSprite', sprite_id=75, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(19,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GeldmanSprite', sprite_id=76, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RabbitSprite', sprite_id=77, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PopoSprite', sprite_id=78, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Popo2Sprite', sprite_id=79, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CannonBallsSprite', sprite_id=80, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArmosSprite', sprite_id=81, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GiantZoraSprite', sprite_id=82, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArmosKnightsSprite', sprite_id=83, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LanmolasSprite', sprite_id=84, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(49,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FireballZoraSprite', sprite_id=85, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WalkingZoraSprite', sprite_id=86, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DesertPalaceBarriersSprite', sprite_id=87, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CrabSprite', sprite_id=88, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BirdSprite', sprite_id=89, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SquirrelSprite', sprite_id=90, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Spark_LeftToRightSprite', sprite_id=91, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Spark_RightToLeftSprite', sprite_id=92, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_VerticalMovingSprite', sprite_id=93, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_VerticalMoving2Sprite', sprite_id=94, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RollerSprite', sprite_id=95, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Roller_HorizontalMovingSprite', sprite_id=96, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BeamosSprite', sprite_id=97, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MasterSwordSprite', sprite_id=98, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Devalant_NonShooterSprite', sprite_id=99, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Devalant_ShooterSprite', sprite_id=100, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ShootingGalleryProprietorSprite', sprite_id=101, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_RightSprite', sprite_id=102, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_LeftSprite', sprite_id=103, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_DownSprite', sprite_id=104, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_UpSprite', sprite_id=105, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BallNChainTrooperSprite', sprite_id=106, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CannonSoldierSprite', sprite_id=107, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MirrorPortalSprite', sprite_id=108, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RatSprite', sprite_id=109, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RopeSprite', sprite_id=110, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KeeseSprite', sprite_id=111, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LeeverSprite', sprite_id=113, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ActivatoForThePonds_WhereYouThrowInItemsSprite', sprite_id=114, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='UnclePriestSprite', sprite_id=115, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(71, 81), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RunningManSprite', sprite_id=116, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BottleSalesmanSprite', sprite_id=117, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PrincessZeldaSprite', sprite_id=118, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VillageElderSprite', sprite_id=120, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimSprite', sprite_id=122, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(26, 61), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimEnergyBallSprite', sprite_id=123, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FloatingStalfosHeadSprite', sprite_id=124, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigSpikeTrapSprite', sprite_id=125, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GuruguruBar_ClockwiseSprite', sprite_id=126, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GuruguruBar_CounterClockwiseSprite', sprite_id=127, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WinderSprite', sprite_id=128, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WaterTektiteSprite', sprite_id=129, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(40,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AntifairyCircleSprite', sprite_id=130, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenEyegoreSprite', sprite_id=131, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedEyegoreSprite', sprite_id=132, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KodongosSprite', sprite_id=134, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MothulaSprite', sprite_id=136, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MothulasBeamSprite', sprite_id=137, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SpikeTrapSprite', sprite_id=138, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(40, 11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GibdoSprite', sprite_id=139, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrghusSprite', sprite_id=140, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrghusSpawnSprite', sprite_id=141, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TerrorpinSprite', sprite_id=142, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SlimeSprite_JumpsOutOfTheFloor', sprite_id=143, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WallmasterSprite', sprite_id=144, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 96, 97, 98, 112, 113, 114, 128, 129, 130, 137, 153, 168, 169, 170, 184, 185, 186, 200, 201, 216, 217, 218, 51, 67, 83, 99, 115, 116, 117, 131, 132, 133, 7, 23, 39, 49, 119, 135, 167, 32, 48, 64, 176, 192, 208, 224, 9, 10, 11, 25, 26, 27, 42, 43, 58, 59, 74, 75, 90, 106, 6, 22, 38, 40, 52, 53, 54, 55, 56, 70, 84, 102, 118, 41, 57, 73, 86, 87, 88, 89, 103, 104, 68, 69, 100, 101, 171, 172, 187, 188, 203, 204, 219, 220, 14, 30, 31, 46, 62, 63, 78, 79, 94, 95, 110, 126, 127, 142, 158, 159, 174, 175, 190, 191, 206, 222, 144, 145, 146, 147, 151, 152, 160, 161, 162, 163, 177, 178, 179, 193, 194, 195, 209, 210, 4, 19, 20, 21, 35, 36, 164, 180, 181, 182, 183, 196, 197, 198, 199, 213, 214, 12, 13, 28, 29, 61, 76, 77, 91, 92, 93, 107, 108, 109, 123, 124, 125, 139, 140, 141, 149, 150, 155, 156, 157, 165, 166)),
|
||||
EnemySpriteRequirementData(sprite_name='StalfosKnightSprite', sprite_id=145, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HelmasaurKingSprite', sprite_id=146, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(58,), subgroup_3=(62,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BumperSprite', sprite_id=147, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SwimmersEvilSprite', sprite_id=148, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_RightSprite', sprite_id=149, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_LeftSprite', sprite_id=150, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_DownSprite', sprite_id=151, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='EyeLaser_UpSprite', sprite_id=152, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PengatorSprite', sprite_id=153, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KyameronWaterSplashSprite', sprite_id=154, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(40,), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WizzrobeSprite', sprite_id=155, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VerminHorizontalSprite', sprite_id=156, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VerminVerticalSprite', sprite_id=157, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Ostrich_HauntedGroveSprite', sprite_id=158, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FluteSprite', sprite_id=159, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Birds_HauntedGroveSprite', sprite_id=160, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FreezorSprite', sprite_id=161, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KholdstareSprite', sprite_id=162, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KholdstaresShellSprite', sprite_id=163, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FallingIceSprite', sprite_id=164, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueZazakSprite', sprite_id=165, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedZazakSprite', sprite_id=166, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StalfosSprite', sprite_id=167, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworldSprite', sprite_id=168, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworld2Sprite', sprite_id=169, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='PikitSprite', sprite_id=170, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MaidenSprite', sprite_id=171, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AppleSprite', sprite_id=172, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LostOldManSprite', sprite_id=173, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(28,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DownPipeSprite', sprite_id=174, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='UpPipeSprite', sprite_id=175, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RightPipeSprite', sprite_id=176, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LeftPipeSprite', sprite_id=177, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GoodBee_AgainMaybeSprite', sprite_id=178, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HylianInscriptionSprite', sprite_id=179, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ThiefsChestSprite', sprite_id=180, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombSalesmanSprite', sprite_id=181, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KikiSprite', sprite_id=182, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MaidenInBlindDungeonSprite', sprite_id=183, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MimicSprite', sprite_id=184, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FeudingFriendsOnDeathMountainSprite', sprite_id=185, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='WhirlpoolSprite', sprite_id=186, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(255, 274, 287)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(271, 272)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(272,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(280,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(293,)),
|
||||
EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(286,)),
|
||||
EnemySpriteRequirementData(sprite_name='DrunkInTheInnSprite', sprite_id=188, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Vitreous_LargeEyeballSprite', sprite_id=189, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Vitreous_SmallEyeballSprite', sprite_id=190, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='VitreousLightningSprite', sprite_id=191, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CatFish_QuakeMedallionSprite', sprite_id=192, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(24,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AgahnimTeleportingZeldaToDarkworldSprite', sprite_id=193, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(61,), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BouldersSprite', sprite_id=194, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='Gibo_FloatingBlobSprite', sprite_id=195, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ThiefSprite', sprite_id=196, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(14, 21), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MedusaSprite', sprite_id=197, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FourWayFireballSpittersSprite', sprite_id=198, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HokkuBokkuSprite', sprite_id=199, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigFairyWhoHealsYouSprite', sprite_id=200, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TektiteSprite', sprite_id=201, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ChainChompSprite', sprite_id=202, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='TrinexxSprite', sprite_id=203, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='AnotherPartOfTrinexxSprite', sprite_id=204, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='YetAnotherPartOfTrinexxSprite', sprite_id=205, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlindTheThiefSprite', sprite_id=206, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(59,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SwamolaSprite', sprite_id=207, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LynelSprite', sprite_id=208, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BunnyBeamSprite', sprite_id=209, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FloppingFishSprite', sprite_id=210, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='StalSprite', sprite_id=211, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='LandmineSprite', sprite_id=212, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='DiggingGameProprietorSprite', sprite_id=213, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(42,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GanonSprite', sprite_id=214, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(33,), subgroup_1=(65,), subgroup_2=(69,), subgroup_3=(51,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CopyOfGanon_ExceptInvincibleSprite', sprite_id=215, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartSprite', sprite_id=216, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='GreenRupeeSprite', sprite_id=217, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BlueRupeeSprite', sprite_id=218, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='RedRupeeSprite', sprite_id=219, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill1Sprite', sprite_id=220, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill4Sprite', sprite_id=221, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BombRefill8Sprite', sprite_id=222, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='SmallMagicRefillSprite', sprite_id=223, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FullMagicRefillSprite', sprite_id=224, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowRefill5Sprite', sprite_id=225, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ArrowRefill10Sprite', sprite_id=226, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FairySprite', sprite_id=227, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='KeySprite', sprite_id=228, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BigKeySprite', sprite_id=229, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='ShieldEaterSprite', sprite_id=230, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MushroomSprite', sprite_id=231, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='FakeMasterSwordSprite', sprite_id=232, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MagicShopDude_HisItemsIncludingTheMagicPowderSprite', sprite_id=233, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartContainerSprite', sprite_id=234, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='HeartPieceSprite', sprite_id=235, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='BushesSprite', sprite_id=236, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatformSprite', sprite_id=237, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MantleSprite', sprite_id=238, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(93,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused1Sprite', sprite_id=239, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused2Sprite', sprite_id=240, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused3Sprite', sprite_id=241, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='MedallionTabletSprite', sprite_id=242, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OW_OL_FallingRocks', sprite_id=244, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EP4Walls', sprite_id=258, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EPEntrance', sprite_id=259, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_StalfosHeadTrap', sprite_id=261, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BombDrop_RopeTrap', sprite_id=262, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_MovingFloor', sprite_id=263, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_SlimeDropper', sprite_id=264, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_Wallmaster', sprite_id=265, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Square', sprite_id=266, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Path', sprite_id=267, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_RightEvil_PirogusuSpawner', sprite_id=272, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_LeftEvil_PirogusuSpawner', sprite_id=273, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_DownEvil_PirogusuSpawner', sprite_id=274, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_UpEvil_PirogusuSpawner', sprite_id=275, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_FlyingFloorTileTrap', sprite_id=276, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_WizzrobeSpawner', sprite_id=277, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BlackSpawn_Zoro_BombHole', sprite_id=278, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_4Skull_Trap_Pot', sprite_id=279, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_Stalfos_Spawn_Trap_EP', sprite_id=280, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_ArmosKnight_Trigger', sprite_id=281, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
EnemySpriteRequirementData(sprite_name='OL_BombDrop_BombTrap', sprite_id=282, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()),
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class OverworldGroupRequirementData(NamedTuple):
|
||||
group_id: Optional[int]
|
||||
subgroup_0: Optional[int]
|
||||
subgroup_1: Optional[int]
|
||||
subgroup_2: Optional[int]
|
||||
subgroup_3: Optional[int]
|
||||
areas: tuple[int, ...]
|
||||
|
||||
AREA_IDS = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207)
|
||||
DO_NOT_RANDOMIZE_AREA_IDS = frozenset((1,
|
||||
4,
|
||||
6,
|
||||
8,
|
||||
9,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
25,
|
||||
28,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
35,
|
||||
36,
|
||||
38,
|
||||
39,
|
||||
49,
|
||||
54,
|
||||
56,
|
||||
57,
|
||||
61,
|
||||
62,
|
||||
65,
|
||||
68,
|
||||
70,
|
||||
72,
|
||||
73,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
78,
|
||||
89,
|
||||
92,
|
||||
95,
|
||||
96,
|
||||
97,
|
||||
99,
|
||||
100,
|
||||
102,
|
||||
103,
|
||||
113,
|
||||
118,
|
||||
120,
|
||||
121,
|
||||
125,
|
||||
126,
|
||||
42,
|
||||
106,
|
||||
130,
|
||||
131,
|
||||
132,
|
||||
133,
|
||||
134,
|
||||
135,
|
||||
136,
|
||||
137,
|
||||
138,
|
||||
139,
|
||||
140,
|
||||
141,
|
||||
142,
|
||||
143,
|
||||
186,
|
||||
250,
|
||||
145,
|
||||
148,
|
||||
150,
|
||||
152,
|
||||
153,
|
||||
155,
|
||||
156,
|
||||
158,
|
||||
169,
|
||||
172,
|
||||
175,
|
||||
176,
|
||||
177,
|
||||
179,
|
||||
180,
|
||||
182,
|
||||
183,
|
||||
193,
|
||||
198,
|
||||
200,
|
||||
274,
|
||||
275,
|
||||
276,
|
||||
277,
|
||||
278,
|
||||
279,
|
||||
281,
|
||||
288))
|
||||
|
||||
FORCED_GROUP_REQUIREMENTS = (
|
||||
OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=74, subgroup_3=None, areas=(2,)),
|
||||
OverworldGroupRequirementData(group_id=16, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=16, areas=(3, 147)),
|
||||
OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=17, areas=(10, 154)),
|
||||
OverworldGroupRequirementData(group_id=4, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(15, 159)),
|
||||
OverworldGroupRequirementData(group_id=3, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=14, areas=(20, 164)),
|
||||
OverworldGroupRequirementData(group_id=1, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=63, areas=(27, 171)),
|
||||
OverworldGroupRequirementData(group_id=6, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(34, 40, 178, 184)),
|
||||
OverworldGroupRequirementData(group_id=8, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=None, areas=(48, 192)),
|
||||
OverworldGroupRequirementData(group_id=10, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(58, 202)),
|
||||
OverworldGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=24, subgroup_3=None, areas=(79, 223)),
|
||||
OverworldGroupRequirementData(group_id=21, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=21, areas=(98, 242)),
|
||||
OverworldGroupRequirementData(group_id=27, subgroup_0=None, subgroup_1=42, subgroup_2=None, subgroup_3=None, areas=(104, 248)),
|
||||
OverworldGroupRequirementData(group_id=13, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=None, areas=(22, 166)),
|
||||
OverworldGroupRequirementData(group_id=29, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, areas=(105, 249)),
|
||||
OverworldGroupRequirementData(group_id=15, subgroup_0=None, subgroup_1=None, subgroup_2=78, subgroup_3=None, areas=(42, 186)),
|
||||
OverworldGroupRequirementData(group_id=17, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=76, areas=(106, 250)),
|
||||
OverworldGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=55, subgroup_3=54, areas=(128, 272)),
|
||||
OverworldGroupRequirementData(group_id=14, subgroup_0=None, subgroup_1=None, subgroup_2=12, subgroup_3=68, areas=(129, 273)),
|
||||
OverworldGroupRequirementData(group_id=26, subgroup_0=15, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(146,)),
|
||||
OverworldGroupRequirementData(group_id=23, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=25, areas=(94, 238)),
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class PotDataRecord(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
reserved: int
|
||||
|
||||
|
||||
class PotRoomDataRecord(NamedTuple):
|
||||
room_id: int
|
||||
pots: tuple[PotDataRecord, ...]
|
||||
items: tuple[int, ...]
|
||||
|
||||
POT_ROOMS = (
|
||||
PotRoomDataRecord(room_id=4, pots=(PotDataRecord(x=162, y=25, reserved=0), PotDataRecord(x=152, y=25, reserved=0), PotDataRecord(x=152, y=22, reserved=0), PotDataRecord(x=162, y=22, reserved=0), PotDataRecord(x=240, y=19, reserved=0), PotDataRecord(x=204, y=19, reserved=0),), items=(10, 10)),
|
||||
PotRoomDataRecord(room_id=9, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(1, 11, 136)),
|
||||
PotRoomDataRecord(room_id=10, pots=(PotDataRecord(x=204, y=11, reserved=0), PotDataRecord(x=156, y=17, reserved=0), PotDataRecord(x=96, y=8, reserved=0), PotDataRecord(x=100, y=7, reserved=0), PotDataRecord(x=160, y=17, reserved=0), PotDataRecord(x=104, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0),), items=(11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=17, pots=(PotDataRecord(x=152, y=19, reserved=0), PotDataRecord(x=152, y=15, reserved=0), PotDataRecord(x=144, y=15, reserved=0), PotDataRecord(x=10, y=15, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=160, y=19, reserved=0),), items=(11, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=21, pots=(PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=100, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0), PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=12, y=6, reserved=0), PotDataRecord(x=16, y=6, reserved=0), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=70, y=11, reserved=0),), items=(1, 7, 9, 9, 10, 11, 12, 12, 13)),
|
||||
PotRoomDataRecord(room_id=22, pots=(PotDataRecord(x=188, y=3, reserved=0), PotDataRecord(x=192, y=3, reserved=0), PotDataRecord(x=188, y=4, reserved=0), PotDataRecord(x=192, y=4, reserved=0), PotDataRecord(x=188, y=5, reserved=0), PotDataRecord(x=192, y=5, reserved=0), PotDataRecord(x=188, y=6, reserved=0), PotDataRecord(x=192, y=6, reserved=0), PotDataRecord(x=240, y=19, reserved=0),), items=(8, 9, 9, 10, 10, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=26, pots=(PotDataRecord(x=232, y=19, reserved=0), PotDataRecord(x=212, y=19, reserved=0), PotDataRecord(x=28, y=5, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(10, 10, 10, 10)),
|
||||
PotRoomDataRecord(room_id=33, pots=(PotDataRecord(x=100, y=28, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=48, y=28, reserved=0), PotDataRecord(x=82, y=28, reserved=0), PotDataRecord(x=160, y=20, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=35, pots=(PotDataRecord(x=86, y=26, reserved=0), PotDataRecord(x=90, y=26, reserved=0), PotDataRecord(x=94, y=26, reserved=0), PotDataRecord(x=98, y=26, reserved=0), PotDataRecord(x=102, y=26, reserved=0),), items=(1, 10, 11)),
|
||||
PotRoomDataRecord(room_id=36, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(1, 11, 12, 7)),
|
||||
PotRoomDataRecord(room_id=38, pots=(PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=150, y=19, reserved=2), PotDataRecord(x=22, y=26, reserved=2), PotDataRecord(x=220, y=26, reserved=0),), items=(7, 9, 10, 12, 136)),
|
||||
PotRoomDataRecord(room_id=39, pots=(PotDataRecord(x=214, y=19, reserved=0), PotDataRecord(x=214, y=20, reserved=0), PotDataRecord(x=166, y=20, reserved=0), PotDataRecord(x=214, y=21, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=102, y=17, reserved=0), PotDataRecord(x=98, y=17, reserved=0), PotDataRecord(x=106, y=17, reserved=0), PotDataRecord(x=166, y=21, reserved=0), PotDataRecord(x=166, y=19, reserved=0), PotDataRecord(x=92, y=12, reserved=0), PotDataRecord(x=160, y=12, reserved=0),), items=(1, 1, 10, 11, 7, 7)),
|
||||
PotRoomDataRecord(room_id=43, pots=(PotDataRecord(x=16, y=5, reserved=2), PotDataRecord(x=44, y=5, reserved=2), PotDataRecord(x=16, y=6, reserved=2), PotDataRecord(x=44, y=6, reserved=2), PotDataRecord(x=16, y=7, reserved=2), PotDataRecord(x=44, y=7, reserved=2), PotDataRecord(x=146, y=21, reserved=0), PotDataRecord(x=170, y=21, reserved=0), PotDataRecord(x=146, y=22, reserved=0), PotDataRecord(x=170, y=22, reserved=0),), items=(9, 9, 10, 10, 10, 10, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=47, pots=(PotDataRecord(x=28, y=7, reserved=0), PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=28, y=9, reserved=0), PotDataRecord(x=32, y=9, reserved=0), PotDataRecord(x=172, y=19, reserved=0), PotDataRecord(x=180, y=19, reserved=0), PotDataRecord(x=104, y=27, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(7, 7, 7, 7, 11, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=53, pots=(PotDataRecord(x=60, y=6, reserved=1), PotDataRecord(x=20, y=8, reserved=0), PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=28, y=8, reserved=0), PotDataRecord(x=32, y=8, reserved=0), PotDataRecord(x=36, y=8, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=76, y=23, reserved=1), PotDataRecord(x=88, y=23, reserved=1), PotDataRecord(x=100, y=27, reserved=1), PotDataRecord(x=242, y=28, reserved=1), PotDataRecord(x=240, y=22, reserved=1), PotDataRecord(x=76, y=28, reserved=1),), items=(7, 7, 7, 7, 7, 8, 11)),
|
||||
PotRoomDataRecord(room_id=54, pots=(PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=10, y=16, reserved=0), PotDataRecord(x=114, y=16, reserved=0),), items=(8, 10, 11)),
|
||||
PotRoomDataRecord(room_id=55, pots=(PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=60, y=6, reserved=0),), items=(8,)),
|
||||
PotRoomDataRecord(room_id=56, pots=(PotDataRecord(x=164, y=12, reserved=0), PotDataRecord(x=164, y=13, reserved=0), PotDataRecord(x=164, y=18, reserved=0), PotDataRecord(x=164, y=19, reserved=0),), items=(8, 7, 10, 10)),
|
||||
PotRoomDataRecord(room_id=57, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=100, y=22, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 9, 11, 12)),
|
||||
PotRoomDataRecord(room_id=60, pots=(PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=64, y=12, reserved=0), PotDataRecord(x=20, y=14, reserved=0), PotDataRecord(x=68, y=18, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=64, y=20, reserved=0), PotDataRecord(x=64, y=26, reserved=0),), items=(1, 7, 7, 7, 7, 11, 12)),
|
||||
PotRoomDataRecord(room_id=61, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=24, y=22, reserved=0), PotDataRecord(x=40, y=22, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0),), items=(9, 7, 10, 10, 11, 11, 13)),
|
||||
PotRoomDataRecord(room_id=62, pots=(PotDataRecord(x=96, y=6, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=92, y=10, reserved=0),), items=(10, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=63, pots=(PotDataRecord(x=12, y=25, reserved=0), PotDataRecord(x=20, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=12, y=27, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=28, y=23, reserved=0),), items=(1, 1, 8, 10, 10, 11, 136)),
|
||||
PotRoomDataRecord(room_id=65, pots=(PotDataRecord(x=100, y=10, reserved=0), PotDataRecord(x=52, y=15, reserved=0), PotDataRecord(x=52, y=16, reserved=0), PotDataRecord(x=148, y=22, reserved=0),), items=(1, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=67, pots=(PotDataRecord(x=112, y=28, reserved=1), PotDataRecord(x=76, y=28, reserved=1), PotDataRecord(x=76, y=20, reserved=1), PotDataRecord(x=66, y=4, reserved=0), PotDataRecord(x=78, y=4, reserved=0), PotDataRecord(x=66, y=9, reserved=0), PotDataRecord(x=78, y=9, reserved=0), PotDataRecord(x=112, y=20, reserved=1),), items=(8, 9, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=69, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=108, y=11, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=220, y=16, reserved=0), PotDataRecord(x=236, y=16, reserved=0),), items=(9, 9, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=73, pots=(PotDataRecord(x=156, y=27, reserved=0), PotDataRecord(x=172, y=24, reserved=0), PotDataRecord(x=172, y=23, reserved=0), PotDataRecord(x=144, y=20, reserved=0), PotDataRecord(x=104, y=15, reserved=0), PotDataRecord(x=104, y=16, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=172, y=20, reserved=0), PotDataRecord(x=144, y=27, reserved=0), PotDataRecord(x=172, y=28, reserved=0), PotDataRecord(x=160, y=27, reserved=0),), items=(11, 11, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=78, pots=(PotDataRecord(x=48, y=10, reserved=2), PotDataRecord(x=140, y=11, reserved=2), PotDataRecord(x=28, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=0),), items=(136, 11, 12)),
|
||||
PotRoomDataRecord(room_id=83, pots=(PotDataRecord(x=92, y=11, reserved=0), PotDataRecord(x=96, y=11, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=11, reserved=0),), items=(8, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=84, pots=(PotDataRecord(x=186, y=25, reserved=0), PotDataRecord(x=186, y=26, reserved=0), PotDataRecord(x=186, y=27, reserved=0), PotDataRecord(x=186, y=28, reserved=0),), items=(7, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=86, pots=(PotDataRecord(x=100, y=6, reserved=1), PotDataRecord(x=96, y=10, reserved=1), PotDataRecord(x=92, y=10, reserved=1), PotDataRecord(x=48, y=20, reserved=1), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=40, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=36, y=7, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=24, y=9, reserved=0), PotDataRecord(x=36, y=9, reserved=0), PotDataRecord(x=20, y=10, reserved=0), PotDataRecord(x=40, y=10, reserved=0), PotDataRecord(x=12, y=20, reserved=1),), items=(7, 7, 11, 11, 8, 12, 12, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=87, pots=(PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=12, y=20, reserved=2), PotDataRecord(x=92, y=23, reserved=0), PotDataRecord(x=100, y=23, reserved=0), PotDataRecord(x=84, y=25, reserved=0), PotDataRecord(x=76, y=27, reserved=0), PotDataRecord(x=48, y=20, reserved=2), PotDataRecord(x=30, y=22, reserved=2),), items=(7, 10, 11, 12, 12, 12, 13, 136)),
|
||||
PotRoomDataRecord(room_id=88, pots=(PotDataRecord(x=96, y=9, reserved=0), PotDataRecord(x=92, y=8, reserved=0), PotDataRecord(x=108, y=8, reserved=0), PotDataRecord(x=108, y=6, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=92, y=6, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=16, y=7, reserved=0), PotDataRecord(x=96, y=5, reserved=0), PotDataRecord(x=100, y=5, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=108, y=7, reserved=0), PotDataRecord(x=16, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0), PotDataRecord(x=104, y=9, reserved=0),), items=(10, 10, 11, 11, 12, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=91, pots=(PotDataRecord(x=218, y=37, reserved=0), PotDataRecord(x=222, y=37, reserved=0), PotDataRecord(x=226, y=37, reserved=0),), items=(136,)),
|
||||
PotRoomDataRecord(room_id=92, pots=(PotDataRecord(x=228, y=25, reserved=0), PotDataRecord(x=104, y=24, reserved=0), PotDataRecord(x=228, y=22, reserved=0), PotDataRecord(x=216, y=25, reserved=0), PotDataRecord(x=84, y=24, reserved=0), PotDataRecord(x=216, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=94, y=26, reserved=0),), items=(10, 13)),
|
||||
PotRoomDataRecord(room_id=93, pots=(PotDataRecord(x=16, y=5, reserved=0), PotDataRecord(x=44, y=5, reserved=0), PotDataRecord(x=16, y=11, reserved=0), PotDataRecord(x=44, y=11, reserved=0), PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(1, 7, 9, 9, 10, 10, 10, 12)),
|
||||
PotRoomDataRecord(room_id=94, pots=(PotDataRecord(x=92, y=4, reserved=0), PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=112, y=8, reserved=0),), items=(11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=99, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(8, 11)),
|
||||
PotRoomDataRecord(room_id=100, pots=(PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=16, y=22, reserved=0), PotDataRecord(x=20, y=22, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(10, 10, 10, 10, 12, 12, 136)),
|
||||
PotRoomDataRecord(room_id=102, pots=(PotDataRecord(x=48, y=37, reserved=0), PotDataRecord(x=52, y=37, reserved=0), PotDataRecord(x=56, y=37, reserved=0), PotDataRecord(x=84, y=5, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=48, y=38, reserved=0), PotDataRecord(x=52, y=38, reserved=0), PotDataRecord(x=56, y=38, reserved=0), PotDataRecord(x=84, y=6, reserved=0), PotDataRecord(x=104, y=6, reserved=0),), items=(7, 7, 9, 9, 9, 10, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=103, pots=(PotDataRecord(x=22, y=26, reserved=0), PotDataRecord(x=18, y=22, reserved=0), PotDataRecord(x=92, y=9, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=48, y=7, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=74, y=20, reserved=0), PotDataRecord(x=18, y=23, reserved=0), PotDataRecord(x=18, y=26, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(9, 11, 11, 11, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=104, pots=(PotDataRecord(x=84, y=14, reserved=0), PotDataRecord(x=84, y=13, reserved=0), PotDataRecord(x=88, y=12, reserved=0), PotDataRecord(x=88, y=6, reserved=0), PotDataRecord(x=88, y=5, reserved=0), PotDataRecord(x=88, y=4, reserved=0), PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=64, y=15, reserved=0), PotDataRecord(x=64, y=7, reserved=0), PotDataRecord(x=88, y=7, reserved=0), PotDataRecord(x=64, y=16, reserved=0), PotDataRecord(x=64, y=24, reserved=0), PotDataRecord(x=64, y=25, reserved=0),), items=(11, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=115, pots=(PotDataRecord(x=154, y=21, reserved=0), PotDataRecord(x=158, y=21, reserved=0), PotDataRecord(x=20, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=144, y=24, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0), PotDataRecord(x=154, y=27, reserved=0), PotDataRecord(x=158, y=27, reserved=0),), items=(1, 1, 11, 11, 7, 7, 9, 9, 12, 136)),
|
||||
PotRoomDataRecord(room_id=116, pots=(PotDataRecord(x=30, y=5, reserved=0), PotDataRecord(x=62, y=5, reserved=0), PotDataRecord(x=94, y=5, reserved=0), PotDataRecord(x=14, y=11, reserved=0), PotDataRecord(x=46, y=11, reserved=0), PotDataRecord(x=78, y=11, reserved=0), PotDataRecord(x=110, y=11, reserved=0),), items=(9, 9, 11, 11, 12, 12, 136)),
|
||||
PotRoomDataRecord(room_id=117, pots=(PotDataRecord(x=148, y=22, reserved=0), PotDataRecord(x=160, y=22, reserved=0), PotDataRecord(x=172, y=22, reserved=0),), items=(9, 11, 12)),
|
||||
PotRoomDataRecord(room_id=123, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=76, y=7, reserved=0), PotDataRecord(x=60, y=4, reserved=0), PotDataRecord(x=64, y=4, reserved=0),), items=(11, 8)),
|
||||
PotRoomDataRecord(room_id=124, pots=(PotDataRecord(x=36, y=21, reserved=0), PotDataRecord(x=24, y=11, reserved=0), PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=32, y=4, reserved=0),), items=(11, 11)),
|
||||
PotRoomDataRecord(room_id=125, pots=(PotDataRecord(x=44, y=12, reserved=0), PotDataRecord(x=44, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=108, y=20, reserved=0), PotDataRecord(x=114, y=20, reserved=0), PotDataRecord(x=76, y=28, reserved=0),), items=(9, 10, 10, 11)),
|
||||
PotRoomDataRecord(room_id=126, pots=(PotDataRecord(x=86, y=15, reserved=0), PotDataRecord(x=82, y=26, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=104, y=26, reserved=0),), items=(11, 12, 136)),
|
||||
PotRoomDataRecord(room_id=130, pots=(PotDataRecord(x=50, y=5, reserved=0), PotDataRecord(x=50, y=10, reserved=0), PotDataRecord(x=76, y=50, reserved=0),), items=(11,)),
|
||||
PotRoomDataRecord(room_id=131, pots=(PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=80, y=4, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0),), items=(1, 7, 9, 9)),
|
||||
PotRoomDataRecord(room_id=132, pots=(PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=60, y=17, reserved=0), PotDataRecord(x=80, y=14, reserved=0), PotDataRecord(x=44, y=14, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=24, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=100, y=7, reserved=0),), items=(9, 9)),
|
||||
PotRoomDataRecord(room_id=135, pots=(PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=112, y=20, reserved=0), PotDataRecord(x=16, y=12, reserved=0), PotDataRecord(x=40, y=12, reserved=0), PotDataRecord(x=32, y=12, reserved=0), PotDataRecord(x=24, y=12, reserved=0), PotDataRecord(x=16, y=11, reserved=0),), items=(12, 13)),
|
||||
PotRoomDataRecord(room_id=139, pots=(PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=76, y=12, reserved=1), PotDataRecord(x=32, y=23, reserved=1), PotDataRecord(x=28, y=23, reserved=1), PotDataRecord(x=112, y=12, reserved=1), PotDataRecord(x=32, y=9, reserved=1), PotDataRecord(x=76, y=28, reserved=0),), items=(8, 11, 12)),
|
||||
PotRoomDataRecord(room_id=140, pots=(PotDataRecord(x=76, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=2), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=92, y=20, reserved=0), PotDataRecord(x=100, y=21, reserved=0), PotDataRecord(x=104, y=26, reserved=0), PotDataRecord(x=88, y=27, reserved=0),), items=(9, 10, 10, 10, 10, 12, 136)),
|
||||
PotRoomDataRecord(room_id=145, pots=(PotDataRecord(x=84, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0),), items=(11, 12)),
|
||||
PotRoomDataRecord(room_id=150, pots=(PotDataRecord(x=14, y=18, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=32, y=17, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=76, y=21, reserved=0), PotDataRecord(x=112, y=21, reserved=0), PotDataRecord(x=14, y=24, reserved=0),), items=(11, 12, 12, 13)),
|
||||
PotRoomDataRecord(room_id=155, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(8, 12)),
|
||||
PotRoomDataRecord(room_id=157, pots=(PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=40, y=9, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=84, y=4, reserved=0),), items=(10, 12)),
|
||||
PotRoomDataRecord(room_id=159, pots=(PotDataRecord(x=138, y=20, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=138, y=21, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=138, y=27, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=21, reserved=0), PotDataRecord(x=178, y=20, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=178, y=27, reserved=0), PotDataRecord(x=178, y=26, reserved=0), PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=138, y=26, reserved=0), PotDataRecord(x=20, y=21, reserved=0),), items=(8, 11, 11, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=161, pots=(PotDataRecord(x=96, y=27, reserved=0), PotDataRecord(x=92, y=21, reserved=0), PotDataRecord(x=150, y=6, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=12, reserved=0), PotDataRecord(x=108, y=13, reserved=0), PotDataRecord(x=112, y=14, reserved=0), PotDataRecord(x=96, y=23, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(8, 11, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=168, pots=(PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=30, y=24, reserved=0),), items=(1, 11)),
|
||||
PotRoomDataRecord(room_id=169, pots=(PotDataRecord(x=12, y=19, reserved=0), PotDataRecord(x=112, y=19, reserved=0), PotDataRecord(x=144, y=43, reserved=0), PotDataRecord(x=236, y=43, reserved=0), PotDataRecord(x=144, y=44, reserved=0), PotDataRecord(x=236, y=44, reserved=0), PotDataRecord(x=16, y=20, reserved=0), PotDataRecord(x=108, y=20, reserved=0),), items=(11, 11, 11, 9, 9, 9)),
|
||||
PotRoomDataRecord(room_id=170, pots=(PotDataRecord(x=212, y=10, reserved=2), PotDataRecord(x=232, y=10, reserved=2), PotDataRecord(x=232, y=5, reserved=2), PotDataRecord(x=212, y=5, reserved=2), PotDataRecord(x=94, y=8, reserved=2), PotDataRecord(x=108, y=55, reserved=0), PotDataRecord(x=108, y=56, reserved=0), PotDataRecord(x=108, y=57, reserved=0),), items=(11, 11, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=176, pots=(PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=24, y=24, reserved=0), PotDataRecord(x=44, y=25, reserved=0), PotDataRecord(x=20, y=21, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=16, y=23, reserved=0), PotDataRecord(x=44, y=23, reserved=0), PotDataRecord(x=36, y=24, reserved=0), PotDataRecord(x=16, y=25, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(1, 1, 7, 7, 9, 9, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=179, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(8, 12, 136)),
|
||||
PotRoomDataRecord(room_id=180, pots=(PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(11, 13)),
|
||||
PotRoomDataRecord(room_id=181, pots=(PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=112, y=15, reserved=0), PotDataRecord(x=76, y=16, reserved=0), PotDataRecord(x=112, y=16, reserved=0), PotDataRecord(x=112, y=17, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 10, 11, 11, 13, 136)),
|
||||
PotRoomDataRecord(room_id=184, pots=(PotDataRecord(x=96, y=13, reserved=0), PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=104, y=16, reserved=0),), items=(11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=185, pots=(PotDataRecord(x=92, y=18, reserved=0), PotDataRecord(x=96, y=18, reserved=0), PotDataRecord(x=104, y=18, reserved=0), PotDataRecord(x=108, y=18, reserved=0),), items=(1, 1, 7, 7)),
|
||||
PotRoomDataRecord(room_id=186, pots=(PotDataRecord(x=100, y=8, reserved=0), PotDataRecord(x=88, y=8, reserved=0), PotDataRecord(x=94, y=4, reserved=0), PotDataRecord(x=76, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=76, y=10, reserved=0), PotDataRecord(x=112, y=10, reserved=0), PotDataRecord(x=94, y=12, reserved=0),), items=(1, 1, 8, 11, 11, 12)),
|
||||
PotRoomDataRecord(room_id=188, pots=(PotDataRecord(x=138, y=3, reserved=2), PotDataRecord(x=178, y=3, reserved=2), PotDataRecord(x=86, y=4, reserved=1), PotDataRecord(x=102, y=4, reserved=1), PotDataRecord(x=138, y=12, reserved=2), PotDataRecord(x=178, y=12, reserved=2), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(7, 7, 7, 7, 8, 10, 10, 10, 10, 10, 11, 11, 136)),
|
||||
PotRoomDataRecord(room_id=191, pots=(PotDataRecord(x=40, y=20, reserved=0), PotDataRecord(x=44, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 10, 11, 12, 12, 12)),
|
||||
PotRoomDataRecord(room_id=192, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=12, y=14, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=28, y=27, reserved=0),), items=(1, 7, 10, 11)),
|
||||
PotRoomDataRecord(room_id=194, pots=(PotDataRecord(x=180, y=7, reserved=0), PotDataRecord(x=100, y=46, reserved=0), PotDataRecord(x=68, y=48, reserved=0), PotDataRecord(x=64, y=52, reserved=0),), items=(1, 9, 12, 136)),
|
||||
PotRoomDataRecord(room_id=196, pots=(PotDataRecord(x=84, y=9, reserved=0), PotDataRecord(x=24, y=14, reserved=0), PotDataRecord(x=56, y=17, reserved=0), PotDataRecord(x=84, y=17, reserved=0), PotDataRecord(x=12, y=21, reserved=0), PotDataRecord(x=76, y=23, reserved=0), PotDataRecord(x=48, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0),), items=(1, 9, 12, 7, 10, 10, 11, 11)),
|
||||
PotRoomDataRecord(room_id=199, pots=(PotDataRecord(x=12, y=10, reserved=0), PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(9, 12, 11, 13)),
|
||||
PotRoomDataRecord(room_id=201, pots=(PotDataRecord(x=30, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=60, y=22, reserved=0),), items=(1, 1, 136)),
|
||||
PotRoomDataRecord(room_id=203, pots=(PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=88, y=28, reserved=0),), items=(7, 11)),
|
||||
PotRoomDataRecord(room_id=204, pots=(PotDataRecord(x=36, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 11, 7, 10)),
|
||||
PotRoomDataRecord(room_id=206, pots=(PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=80, y=8, reserved=0), PotDataRecord(x=108, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=204, y=11, reserved=3),), items=(9, 12, 12, 10, 128)),
|
||||
PotRoomDataRecord(room_id=208, pots=(PotDataRecord(x=158, y=5, reserved=0), PotDataRecord(x=140, y=11, reserved=0), PotDataRecord(x=42, y=13, reserved=0), PotDataRecord(x=48, y=16, reserved=0), PotDataRecord(x=176, y=20, reserved=0), PotDataRecord(x=146, y=23, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(1, 1, 7, 11, 11, 12, 12)),
|
||||
PotRoomDataRecord(room_id=209, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=168, y=7, reserved=0), PotDataRecord(x=112, y=12, reserved=0),), items=(9, 1, 1, 1, 13)),
|
||||
PotRoomDataRecord(room_id=214, pots=(PotDataRecord(x=92, y=22, reserved=0), PotDataRecord(x=96, y=22, reserved=0),), items=(10, 13)),
|
||||
PotRoomDataRecord(room_id=216, pots=(PotDataRecord(x=202, y=8, reserved=0), PotDataRecord(x=242, y=8, reserved=0), PotDataRecord(x=202, y=10, reserved=0), PotDataRecord(x=242, y=10, reserved=0), PotDataRecord(x=202, y=12, reserved=0), PotDataRecord(x=242, y=12, reserved=0), PotDataRecord(x=92, y=24, reserved=0), PotDataRecord(x=96, y=24, reserved=0),), items=(9, 9, 9, 9, 9, 11, 11, 11)),
|
||||
PotRoomDataRecord(room_id=218, pots=(PotDataRecord(x=24, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=24, y=25, reserved=0), PotDataRecord(x=36, y=25, reserved=0),), items=(9, 9, 11, 136)),
|
||||
PotRoomDataRecord(room_id=219, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=62, y=19, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=88, y=16, reserved=0),), items=(7, 11)),
|
||||
PotRoomDataRecord(room_id=220, pots=(PotDataRecord(x=56, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=68, y=16, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(7, 9, 10, 11)),
|
||||
PotRoomDataRecord(room_id=235, pots=(PotDataRecord(x=206, y=8, reserved=0), PotDataRecord(x=210, y=8, reserved=0), PotDataRecord(x=88, y=14, reserved=0), PotDataRecord(x=92, y=14, reserved=0), PotDataRecord(x=96, y=14, reserved=0),), items=(7, 7, 11, 12, 12)),
|
||||
)
|
||||
@@ -0,0 +1,171 @@
|
||||
from __future__ import annotations
|
||||
|
||||
ENEMIZER_SYMBOLS = {
|
||||
':pos_1_0': 0x36975E,
|
||||
':pos_1_1': 0x36976D,
|
||||
':pos_1_10': 0x369819,
|
||||
':pos_1_11': 0x369828,
|
||||
':pos_1_12': 0x369837,
|
||||
':pos_1_13': 0x369845,
|
||||
':pos_1_14': 0x36984B,
|
||||
':pos_1_15': 0x369851,
|
||||
':pos_1_16': 0x369873,
|
||||
':pos_1_17': 0x369898,
|
||||
':pos_1_18': 0x36989E,
|
||||
':pos_1_19': 0x3698A4,
|
||||
':pos_1_2': 0x369787,
|
||||
':pos_1_20': 0x3698C6,
|
||||
':pos_1_21': 0x3698EB,
|
||||
':pos_1_22': 0x3698F1,
|
||||
':pos_1_23': 0x3698F7,
|
||||
':pos_1_24': 0x369919,
|
||||
':pos_1_25': 0x36993E,
|
||||
':pos_1_26': 0x369944,
|
||||
':pos_1_27': 0x36994A,
|
||||
':pos_1_28': 0x36996C,
|
||||
':pos_1_29': 0x369AA5,
|
||||
':pos_1_3': 0x369796,
|
||||
':pos_1_30': 0x36B796,
|
||||
':pos_1_4': 0x3697A5,
|
||||
':pos_1_5': 0x3697B4,
|
||||
':pos_1_6': 0x3697D5,
|
||||
':pos_1_7': 0x3697E8,
|
||||
':pos_1_8': 0x3697F7,
|
||||
':pos_1_9': 0x369806,
|
||||
'CheckIfLinkShouldDie': 0x3699B2,
|
||||
'CheckIfLinkShouldDie_dead': 0x3699BB,
|
||||
'CheckIfLinkShouldDie_done': 0x3699BD,
|
||||
'Check_for_Blind_Fight': 0x1DA085,
|
||||
'CopyShield': 0x36B713,
|
||||
'CopyShield_loop_copy': 0x36B729,
|
||||
'CopyShield_shield_positon_gfx': 0x36B74B,
|
||||
'CopySword': 0x36B6D1,
|
||||
'CopySword_loop_copy': 0x36B6E7,
|
||||
'CopySword_sword_positon_gfx': 0x36B709,
|
||||
'DMAKholdstare': 0x3695A5,
|
||||
'DMATrinexx': 0x369617,
|
||||
'Dungeon_ResetSprites': 0x09C114,
|
||||
'EnemizerCodeStart': 0x3694F5,
|
||||
'EnemizerFlags': 0x368100,
|
||||
'EnemizerFlags_agahnim_fun_balls': 0x368104,
|
||||
'EnemizerFlags_close_blind_door': 0x368101,
|
||||
'EnemizerFlags_enable_mimic_override': 0x368105,
|
||||
'EnemizerFlags_enable_terrorpin_ai_fix': 0x368106,
|
||||
'EnemizerFlags_moldorm_eye_count': 0x368102,
|
||||
'EnemizerFlags_randomize_bushes': 0x368100,
|
||||
'EnemizerFlags_randomize_sprites': 0x368103,
|
||||
'EnemizerTablesStart': 0x368000,
|
||||
'Ext_OnBossDeath': 0x29803C,
|
||||
'Ext_OnDungeonCompleted': 0x29804B,
|
||||
'Ext_OnDungeonEnter': 0x298041,
|
||||
'Ext_OnDungeonExit': 0x298046,
|
||||
'Ext_OnFairyRevive': 0x298019,
|
||||
'Ext_OnFileCreate': 0x298000,
|
||||
'Ext_OnFileLoad': 0x298005,
|
||||
'Ext_OnFileSave': 0x29800A,
|
||||
'Ext_OnIemMenuOpen': 0x298023,
|
||||
'Ext_OnItemChange': 0x29802D,
|
||||
'Ext_OnItemMenuClose': 0x298028,
|
||||
'Ext_OnMapUse': 0x298014,
|
||||
'Ext_OnPlayerAttack': 0x298037,
|
||||
'Ext_OnPlayerDamaged': 0x298032,
|
||||
'Ext_OnPlayerDeath': 0x29800F,
|
||||
'Ext_OnYItemUse': 0x29801E,
|
||||
'Ext_OnZeldaRescued': 0x298050,
|
||||
'FixTerrorpin': 0x36971A,
|
||||
'FixTerrorpin_new': 0x369728,
|
||||
'GFX_Kholdstare_Shell': 0x36C79A,
|
||||
'GFX_Trinexx_Shell': 0x36D79A,
|
||||
'GFX_Trinexx_Shell2': 0x36DF9A,
|
||||
'GetRandomInt': 0x0DBA71,
|
||||
'Initialize_Blind_Fight': 0x1DA090,
|
||||
'Kholdstare_Draw': 0x0DD97F,
|
||||
'LoadFile': 0x369A87,
|
||||
'LoadNewSoundFx': 0x369A9E,
|
||||
'LoadOverworldSprites': 0x36B753,
|
||||
'Module_LoadFile_indoors': 0x028118,
|
||||
'Moldorm_UpdateOamPosition': 0x3699E7,
|
||||
'Moldorm_UpdateOamPosition_more_eyes': 0x3699ED,
|
||||
'NMIHookAction': 0x36956C,
|
||||
'NMIHookAction_loadKholdstare': 0x369584,
|
||||
'NMIHookAction_loadTrinexx': 0x369590,
|
||||
'NMIHookAction_return': 0x36959A,
|
||||
'NMIHookReturn': 0x0080D5,
|
||||
'NewLoadSoundBank': 0x369A98,
|
||||
'NewLoadSoundBank_Intro': 0x369A92,
|
||||
'OnInitFileSelect': 0x3696FA,
|
||||
'OnInitFileSelect_continue': 0x36970F,
|
||||
'Player_Main': 0x078000,
|
||||
'Sound_LoadSongBank': 0x008888,
|
||||
'Sound_SetSfx3PanLong': 0x0DBB8A,
|
||||
'Sound_SetSfxPanWithPlayerCoords': 0x0DBB67,
|
||||
'Spawn_Bees': 0x36B75D,
|
||||
'Spawn_Bees_done': 0x36B779,
|
||||
'SpritePrep_Eyegore': 0x1EC6FA,
|
||||
'SpritePrep_EyegoreNew': 0x369A1A,
|
||||
'SpritePrep_EyegoreNew_mimic': 0x369A31,
|
||||
'SpritePrep_EyegoreNew_new': 0x369A25,
|
||||
'Sprite_ResetAll': 0x09C44E,
|
||||
'Sprite_SpawnDynamically': 0x1DF65D,
|
||||
'VitreousKeyReset': 0x36B77A,
|
||||
'boss_move': 0x369743,
|
||||
'boss_move_loop_bottom_left': 0x369935,
|
||||
'boss_move_loop_bottom_left2': 0x369963,
|
||||
'boss_move_loop_bottom_right': 0x3698E2,
|
||||
'boss_move_loop_bottom_right2': 0x369910,
|
||||
'boss_move_loop_middle': 0x36983C,
|
||||
'boss_move_loop_middle2': 0x36986A,
|
||||
'boss_move_loop_top_right': 0x36988F,
|
||||
'boss_move_loop_top_right2': 0x3698BD,
|
||||
'boss_move_move_to_bottom_left': 0x369933,
|
||||
'boss_move_move_to_bottom_right': 0x3698E0,
|
||||
'boss_move_move_to_middle': 0x36983A,
|
||||
'boss_move_move_to_top_right': 0x36988D,
|
||||
'boss_move_no_blind_door': 0x3697D2,
|
||||
'boss_move_no_change': 0x369863,
|
||||
'boss_move_no_change2': 0x3698B6,
|
||||
'boss_move_no_change3': 0x369909,
|
||||
'boss_move_no_change4': 0x36995C,
|
||||
'boss_move_no_change_ov': 0x369885,
|
||||
'boss_move_no_change_ov2': 0x3698D8,
|
||||
'boss_move_no_change_ov3': 0x36992B,
|
||||
'boss_move_no_change_ov4': 0x36997E,
|
||||
'boss_move_return': 0x369986,
|
||||
'change_heartcontainer_position': 0x3699BE,
|
||||
'change_heartcontainer_position_not_moldorm_room': 0x3699E1,
|
||||
'check_blind_boss_room': 0x36B782,
|
||||
'check_special_action': 0x369731,
|
||||
'check_special_action_no_special_action': 0x36973E,
|
||||
'enemizer_info_table': 0x368000,
|
||||
'linkIsDead': 0x0780D5,
|
||||
'linkNotDead': 0x0780F7,
|
||||
'modified_room_object_table': 0x36B79A,
|
||||
'moved_room_header_bank_value_address': 0x368374,
|
||||
'newKodongoCollision': 0x369A02,
|
||||
'newKodongoCollision_continue': 0x369A19,
|
||||
'new_kholdstare_code': 0x369987,
|
||||
'new_kholdstare_code_already_iced': 0x369997,
|
||||
'new_trinexx_code': 0x36999C,
|
||||
'new_trinexx_code_already_rocked': 0x3699AC,
|
||||
'notItemSprite_Mimic': 0x369A66,
|
||||
'notItemSprite_Mimic_changeSpriteId': 0x369A7A,
|
||||
'notItemSprite_Mimic_continue': 0x369A82,
|
||||
'notItemSprite_Mimic_reloadSpriteIdAndSkipMimic': 0x369A7F,
|
||||
'resetSprite_Mimic': 0x369A4E,
|
||||
'resetSprite_Mimic_notMimic': 0x369A60,
|
||||
'room_header_table': 0x368375,
|
||||
'shieldgfx': 0x36AAD1,
|
||||
'sprite_bush_spawn': 0x3694F5,
|
||||
'sprite_bush_spawn_continue': 0x36950F,
|
||||
'sprite_bush_spawn_dontGoPhase2': 0x369565,
|
||||
'sprite_bush_spawn_item_table': 0x369525,
|
||||
'sprite_bush_spawn_newSpriteSpawn': 0x36954C,
|
||||
'sprite_bush_spawn_not_random': 0x36953B,
|
||||
'sprite_bush_spawn_not_random_old': 0x36950B,
|
||||
'sprite_bush_spawn_return': 0x369568,
|
||||
'sprite_bush_spawn_table': 0x368120,
|
||||
'sprite_bush_spawn_table_dungeons': 0x368248,
|
||||
'sprite_bush_spawn_table_overworld': 0x368120,
|
||||
'sprite_bush_spawn_table_random_sprites': 0x368370,
|
||||
'swordgfx': 0x369AD1,
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from worlds.alttp.EnemizerPatches import (
|
||||
ARROW_REFILL_5_SPRITE_ID,
|
||||
BOSS_GFX_SHEET_INDEXES,
|
||||
BOSS_PATCH_DATA,
|
||||
DAMAGE_GROUP_TABLE_ADDRESS,
|
||||
DUNGEON_BOSS_PATCH_DATA,
|
||||
ENEMY_DAMAGE_TABLE_ADDRESS,
|
||||
ENEMY_HP_TABLE_ADDRESS,
|
||||
EXCLUDED_ENEMY_TABLE_SPRITE_IDS,
|
||||
HIDDEN_ENEMY_CHANCE_POOL_ADDRESS,
|
||||
RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL,
|
||||
RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS,
|
||||
RETRO_RUPEE_REPLACEMENT_SPRITE_ID,
|
||||
THIEF_DEFAULT_HP,
|
||||
THIEF_SPRITE_ID,
|
||||
TILE_TRAP_FLOOR_TILE_ADDRESS,
|
||||
TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS,
|
||||
TRINEXX_ICE_PROJECTILE_TILE_ADDRESS,
|
||||
VANILLA_HIDDEN_ENEMY_CHANCE_POOL,
|
||||
_apply_killable_thief,
|
||||
_apply_randomized_tile_trap_floor_tile,
|
||||
_get_enemizer_symbol,
|
||||
_make_native_enemizer_rng,
|
||||
_option_key,
|
||||
patch_bosses,
|
||||
_randomize_enemy_damage,
|
||||
_randomize_enemy_health,
|
||||
_set_enemizer_flag,
|
||||
_shuffle_damage_groups,
|
||||
_update_hidden_enemy_item_table_for_retro_mode,
|
||||
apply_enemizer_base_patch,
|
||||
)
|
||||
|
||||
|
||||
class FakeRom:
|
||||
def __init__(self, size: int = 0x400000) -> None:
|
||||
self.buffer = bytearray(size)
|
||||
|
||||
def read_byte(self, address: int) -> int:
|
||||
return self.buffer[address]
|
||||
|
||||
def read_bytes(self, startaddress: int, length: int) -> bytearray:
|
||||
return self.buffer[startaddress:startaddress + length]
|
||||
|
||||
def write_byte(self, address: int, value: int) -> None:
|
||||
self.buffer[address] = value
|
||||
|
||||
def write_bytes(self, startaddress: int, values) -> None:
|
||||
self.buffer[startaddress:startaddress + len(values)] = values
|
||||
|
||||
def write_int16(self, address: int, value: int) -> None:
|
||||
self.write_bytes(address, (value & 0xFF, (value >> 8) & 0xFF))
|
||||
|
||||
|
||||
class TestEnemizerPatches(unittest.TestCase):
|
||||
def test_enemizer_base_patch_applies_mimic_hooks(self) -> None:
|
||||
rom = FakeRom()
|
||||
|
||||
apply_enemizer_base_patch(rom)
|
||||
|
||||
self.assertEqual(tuple(rom.read_bytes(0x307CB, 2)), (0xB6, 0x91))
|
||||
self.assertEqual(tuple(rom.read_bytes(0x311B6, 4)), (0x22, 0x1A, 0x9A, 0x36))
|
||||
self.assertEqual(tuple(rom.read_bytes(0x36C08, 5)), (0x22, 0x4E, 0x9A, 0x36, 0xEA))
|
||||
self.assertEqual(tuple(rom.read_bytes(0x36DA6, 4)), (0x22, 0x66, 0x9A, 0x36))
|
||||
self.assertEqual(tuple(rom.read_bytes(0xF0BB1, 2)), (0x95, 0xC7))
|
||||
self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, 4)), (0xEA, 0xEA, 0xEA, 0xEA))
|
||||
self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, 2)), (0x00, 0x00))
|
||||
self.assertEqual(rom.read_byte(TILE_TRAP_FLOOR_TILE_ADDRESS), 0x00)
|
||||
|
||||
def test_randomized_tile_trap_floor_tile_patch_is_separate(self) -> None:
|
||||
rom = FakeRom()
|
||||
|
||||
_apply_randomized_tile_trap_floor_tile(rom)
|
||||
|
||||
self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, 2)), (0x88, 0x01))
|
||||
self.assertEqual(rom.read_byte(TILE_TRAP_FLOOR_TILE_ADDRESS), 0x12)
|
||||
|
||||
def test_enemy_shuffle_enables_hidden_enemy_and_mimic_support(self) -> None:
|
||||
rom = FakeRom()
|
||||
world = self._build_world(enemy_shuffle=True, bush_shuffle=False)
|
||||
|
||||
self._apply_native_enemizer_features(world, rom)
|
||||
|
||||
self.assertEqual(
|
||||
tuple(rom.read_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, len(VANILLA_HIDDEN_ENEMY_CHANCE_POOL))),
|
||||
VANILLA_HIDDEN_ENEMY_CHANCE_POOL,
|
||||
)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_bushes")), 0x01)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_sprites")), 0x01)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_mimic_override")), 0x01)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_terrorpin_ai_fix")), 0x01)
|
||||
self.assertEqual(tuple(rom.read_bytes(0x1F2D5, 2)), (0x54, 0x9C))
|
||||
self.assertEqual(rom.read_byte(0x1F2E5), 0xB0)
|
||||
self.assertEqual(rom.read_byte(0x1F2EB), 0xD0)
|
||||
|
||||
def test_bush_shuffle_and_remaining_tables_are_patched_natively(self) -> None:
|
||||
rom = FakeRom()
|
||||
item_table_address = _get_enemizer_symbol("sprite_bush_spawn_item_table")
|
||||
not_item_sprite_address = _get_enemizer_symbol("notItemSprite_Mimic")
|
||||
rom.write_byte(RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS, RETRO_RUPEE_REPLACEMENT_SPRITE_ID)
|
||||
rom.write_byte(item_table_address + 5, ARROW_REFILL_5_SPRITE_ID)
|
||||
rom.write_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID, 0x08)
|
||||
|
||||
included_hp_sprite_id = 0x01
|
||||
included_damage_sprite_id = 0x02
|
||||
excluded_sprite_id = min(EXCLUDED_ENEMY_TABLE_SPRITE_IDS)
|
||||
rom.write_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id, 0x06)
|
||||
rom.write_byte(ENEMY_HP_TABLE_ADDRESS + excluded_sprite_id, 0x07)
|
||||
rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + included_damage_sprite_id, 0x06)
|
||||
rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + excluded_sprite_id, 0x05)
|
||||
|
||||
world = self._build_world(
|
||||
bush_shuffle=True,
|
||||
killable_thieves=True,
|
||||
enemy_health="hard",
|
||||
enemy_damage="chaos",
|
||||
)
|
||||
|
||||
self._apply_native_enemizer_features(world, rom)
|
||||
|
||||
self.assertEqual(
|
||||
tuple(rom.read_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, len(RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL))),
|
||||
RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL,
|
||||
)
|
||||
self.assertEqual(rom.read_byte(item_table_address + 5), RETRO_RUPEE_REPLACEMENT_SPRITE_ID)
|
||||
self.assertEqual(rom.read_byte(not_item_sprite_address + 4), THIEF_SPRITE_ID)
|
||||
self.assertNotEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 0x08)
|
||||
self.assertGreaterEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 2)
|
||||
self.assertLess(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 25)
|
||||
self.assertGreaterEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id), 2)
|
||||
self.assertLess(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id), 25)
|
||||
self.assertEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + excluded_sprite_id), 0x07)
|
||||
self.assertIn(rom.read_byte(ENEMY_DAMAGE_TABLE_ADDRESS + included_damage_sprite_id), range(8))
|
||||
self.assertEqual(rom.read_byte(ENEMY_DAMAGE_TABLE_ADDRESS + excluded_sprite_id), 0x05)
|
||||
for group_id in range(10):
|
||||
group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3)
|
||||
green_mail, blue_mail, red_mail = rom.read_bytes(group_address, 3)
|
||||
self.assertIn(green_mail, range(64))
|
||||
self.assertIn(blue_mail, range(64))
|
||||
self.assertIn(red_mail, range(64))
|
||||
|
||||
def test_killable_thief_sets_default_hp_without_enemy_health_shuffle(self) -> None:
|
||||
rom = FakeRom()
|
||||
rom.write_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID, 0x08)
|
||||
|
||||
world = self._build_world(killable_thieves=True)
|
||||
|
||||
self._apply_native_enemizer_features(world, rom)
|
||||
|
||||
self.assertEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), THIEF_DEFAULT_HP)
|
||||
|
||||
def test_bush_shuffle_without_enemy_shuffle_does_not_enable_sprite_randomization_flags(self) -> None:
|
||||
rom = FakeRom()
|
||||
|
||||
self._apply_native_enemizer_features(self._build_world(bush_shuffle=True), rom)
|
||||
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_bushes")), 0x01)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_sprites")), 0x00)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_mimic_override")), 0x00)
|
||||
self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_terrorpin_ai_fix")), 0x00)
|
||||
self.assertEqual(tuple(rom.read_bytes(0x1F2D5, 2)), (0x00, 0x00))
|
||||
self.assertEqual(rom.read_byte(0x1F2E5), 0x00)
|
||||
self.assertEqual(rom.read_byte(0x1F2EB), 0x00)
|
||||
|
||||
def test_non_chaos_enemy_damage_uses_expected_mail_scaling(self) -> None:
|
||||
rom = FakeRom()
|
||||
|
||||
self._apply_native_enemizer_features(self._build_world(enemy_damage="hard"), rom)
|
||||
|
||||
for group_id in range(10):
|
||||
group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3)
|
||||
green_mail, blue_mail, red_mail = rom.read_bytes(group_address, 3)
|
||||
self.assertEqual(blue_mail, green_mail * 3 // 4)
|
||||
self.assertEqual(red_mail, green_mail * 3 // 8)
|
||||
|
||||
def test_patch_bosses_overwrites_enemy_shuffle_boss_room_graphics(self) -> None:
|
||||
rom = FakeRom()
|
||||
dungeon_header_base = _get_enemizer_symbol("room_header_table")
|
||||
eastern_dungeon_data = DUNGEON_BOSS_PATCH_DATA[("Eastern Palace", None)]
|
||||
rom.write_byte(dungeon_header_base + (eastern_dungeon_data.room_id * 14) + 3, BOSS_PATCH_DATA["Armos"].graphics)
|
||||
|
||||
for table_index in BOSS_GFX_SHEET_INDEXES.values():
|
||||
rom.write_byte(0x4FC0 + table_index, 0xAA)
|
||||
rom.write_byte(0x509F + table_index, 0xBB)
|
||||
rom.write_byte(0x517E + table_index, 0xCC)
|
||||
|
||||
patch_bosses(self._build_boss_world({"Eastern Palace": "Vitreous"}), rom)
|
||||
|
||||
eastern_boss_data = BOSS_PATCH_DATA["Vitreous"]
|
||||
self.assertEqual(
|
||||
tuple(rom.read_bytes(eastern_dungeon_data.sprite_pointer_address, 2)),
|
||||
eastern_boss_data.pointer,
|
||||
)
|
||||
self.assertEqual(
|
||||
rom.read_byte(dungeon_header_base + (eastern_dungeon_data.room_id * 14) + 3),
|
||||
eastern_boss_data.graphics,
|
||||
)
|
||||
|
||||
for table_index in BOSS_GFX_SHEET_INDEXES.values():
|
||||
self.assertEqual(rom.read_byte(0x4FC0 + table_index), 0xAA)
|
||||
self.assertEqual(rom.read_byte(0x509F + table_index), 0xBB)
|
||||
self.assertEqual(rom.read_byte(0x517E + table_index), 0xCC)
|
||||
|
||||
def test_native_enemizer_rng_is_deterministic_for_same_world_settings(self) -> None:
|
||||
world = self._build_world(enemy_health="hard", enemy_damage="chaos", bush_shuffle=True)
|
||||
|
||||
rng_a = _make_native_enemizer_rng(world)
|
||||
rng_b = _make_native_enemizer_rng(world)
|
||||
|
||||
self.assertEqual([rng_a.randrange(256) for _ in range(8)], [rng_b.randrange(256) for _ in range(8)])
|
||||
|
||||
@staticmethod
|
||||
def _apply_native_enemizer_features(world: SimpleNamespace, rom: FakeRom) -> None:
|
||||
enemy_shuffle_enabled = bool(world.options.enemy_shuffle)
|
||||
bush_shuffle_enabled = bool(world.options.bush_shuffle)
|
||||
enemy_health_key = _option_key(world.options.enemy_health)
|
||||
enemy_damage_key = _option_key(world.options.enemy_damage)
|
||||
|
||||
if enemy_shuffle_enabled or bush_shuffle_enabled:
|
||||
_set_enemizer_flag(rom, "EnemizerFlags_randomize_bushes", True)
|
||||
hidden_enemy_chance_pool = (
|
||||
RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL if bush_shuffle_enabled else VANILLA_HIDDEN_ENEMY_CHANCE_POOL
|
||||
)
|
||||
rom.write_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, hidden_enemy_chance_pool)
|
||||
_update_hidden_enemy_item_table_for_retro_mode(rom)
|
||||
|
||||
if enemy_shuffle_enabled:
|
||||
_set_enemizer_flag(rom, "EnemizerFlags_randomize_sprites", True)
|
||||
_set_enemizer_flag(rom, "EnemizerFlags_enable_mimic_override", True)
|
||||
_set_enemizer_flag(rom, "EnemizerFlags_enable_terrorpin_ai_fix", True)
|
||||
rom.write_bytes(0x1F2D5, (0x54, 0x9C))
|
||||
rom.write_byte(0x1F2E5, 0xB0)
|
||||
rom.write_byte(0x1F2EB, 0xD0)
|
||||
|
||||
if world.options.killable_thieves:
|
||||
_apply_killable_thief(rom)
|
||||
|
||||
if enemy_health_key != "default" or enemy_damage_key != "default":
|
||||
rng = _make_native_enemizer_rng(world)
|
||||
else:
|
||||
rng = None
|
||||
|
||||
if enemy_health_key != "default":
|
||||
assert rng is not None
|
||||
_randomize_enemy_health(rom, rng, enemy_health_key)
|
||||
|
||||
if enemy_damage_key != "default":
|
||||
assert rng is not None
|
||||
_randomize_enemy_damage(rom, rng, allow_zero_damage=True)
|
||||
_shuffle_damage_groups(rom, rng, chaos_mode=enemy_damage_key == "chaos", allow_zero_damage=True)
|
||||
|
||||
@staticmethod
|
||||
def _build_world(
|
||||
*,
|
||||
enemy_shuffle: bool = False,
|
||||
bush_shuffle: bool = False,
|
||||
killable_thieves: bool = False,
|
||||
enemy_health: str = "default",
|
||||
enemy_damage: str = "default",
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
player=1,
|
||||
multiworld=SimpleNamespace(seed=12345, seed_name="native-enemizer-test"),
|
||||
options=SimpleNamespace(
|
||||
enemy_shuffle=enemy_shuffle,
|
||||
bush_shuffle=bush_shuffle,
|
||||
killable_thieves=killable_thieves,
|
||||
enemy_health=SimpleNamespace(current_key=enemy_health),
|
||||
enemy_damage=SimpleNamespace(current_key=enemy_damage),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_boss_world(boss_overrides: dict[str, str] | None = None) -> SimpleNamespace:
|
||||
boss_overrides = boss_overrides or {}
|
||||
|
||||
def boss(name: str) -> SimpleNamespace:
|
||||
return SimpleNamespace(enemizer_name=name)
|
||||
|
||||
return SimpleNamespace(
|
||||
options=SimpleNamespace(mode="open"),
|
||||
dungeons={
|
||||
"Eastern Palace": SimpleNamespace(boss=boss(boss_overrides.get("Eastern Palace", "Armos"))),
|
||||
"Desert Palace": SimpleNamespace(boss=boss(boss_overrides.get("Desert Palace", "Lanmola"))),
|
||||
"Tower of Hera": SimpleNamespace(boss=boss(boss_overrides.get("Tower of Hera", "Moldorm"))),
|
||||
"Palace of Darkness": SimpleNamespace(boss=boss(boss_overrides.get("Palace of Darkness", "Helmasaur"))),
|
||||
"Swamp Palace": SimpleNamespace(boss=boss(boss_overrides.get("Swamp Palace", "Arrghus"))),
|
||||
"Skull Woods": SimpleNamespace(boss=boss(boss_overrides.get("Skull Woods", "Mothula"))),
|
||||
"Thieves Town": SimpleNamespace(boss=boss(boss_overrides.get("Thieves Town", "Blind"))),
|
||||
"Ice Palace": SimpleNamespace(boss=boss(boss_overrides.get("Ice Palace", "Kholdstare"))),
|
||||
"Misery Mire": SimpleNamespace(boss=boss(boss_overrides.get("Misery Mire", "Vitreous"))),
|
||||
"Turtle Rock": SimpleNamespace(boss=boss(boss_overrides.get("Turtle Rock", "Trinexx"))),
|
||||
"Ganons Tower": SimpleNamespace(
|
||||
bosses={
|
||||
"bottom": boss(boss_overrides.get("Ganons Tower Bottom", "Armos")),
|
||||
"middle": boss(boss_overrides.get("Ganons Tower Middle", "Lanmola")),
|
||||
"top": boss(boss_overrides.get("Ganons Tower Top", "Moldorm")),
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,834 @@
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
import random
|
||||
|
||||
from worlds.alttp.EnemyShuffle import (
|
||||
DungeonEnemyRoom,
|
||||
DungeonEnemySprite,
|
||||
DungeonSpriteGroup,
|
||||
EnemyShuffleState,
|
||||
EnemySpriteRequirement,
|
||||
OverworldEnemyArea,
|
||||
OverworldEnemySprite,
|
||||
RandomizedDungeonEnemyRoom,
|
||||
RandomizedDungeonEnemySprite,
|
||||
RandomizedOverworldEnemyArea,
|
||||
RandomizedOverworldEnemySprite,
|
||||
WALLMASTER_SPRITE_ID,
|
||||
_load_dungeon_sprite_metadata,
|
||||
_read_room_sprites,
|
||||
get_possible_dungeon_sprite_groups,
|
||||
_get_requirements_for_usable_dungeon_enemies,
|
||||
_get_requirements_for_usable_overworld_enemies,
|
||||
_get_randomizable_sprites_in_room,
|
||||
_apply_selected_boss_group_requirements,
|
||||
_randomize_overworld_groups,
|
||||
_randomize_room_sprites,
|
||||
_setup_required_overworld_groups,
|
||||
can_spawn_in_room,
|
||||
validate_enemy_shuffle_state,
|
||||
)
|
||||
|
||||
|
||||
class TestEnemyShuffleValidation(unittest.TestCase):
|
||||
def test_curated_room_sprite_addresses_exclude_hera_basement_key_slot(self) -> None:
|
||||
room_id = 135
|
||||
sprite_table_address = 0x4E397
|
||||
rom_bytes = bytearray(0x4E3C0)
|
||||
rom_bytes[sprite_table_address] = 0
|
||||
room_135_sprite_records = (
|
||||
(0x4E398, 0x05, 0x14, 0x18),
|
||||
(0x4E39B, 0x07, 0x1A, 0x18),
|
||||
(0x4E39E, 0x0B, 0x13, 0x18),
|
||||
(0x4E3A1, 0x19, 0x06, 0x18),
|
||||
(0x4E3A4, 0x08, 0xE7, 0x14),
|
||||
(0x4E3A7, 0x04, 0x17, 0x1E),
|
||||
(0x4E3AA, 0x0C, 0x03, 0x1E),
|
||||
(0x4E3AD, 0x15, 0x04, 0x1E),
|
||||
(0x4E3B0, 0x17, 0x0B, 0xA7),
|
||||
(0x4E3B3, 0x18, 0x19, 0xA7),
|
||||
(0x4E3B6, 0x19, 0x04, 0xA7),
|
||||
(0x4E3B9, 0x1A, 0x08, 0xE4),
|
||||
(0x4E3BC, 0x1C, 0x15, 0xA7),
|
||||
)
|
||||
for address, byte_0, byte_1, sprite_id in room_135_sprite_records:
|
||||
rom_bytes[address] = byte_0
|
||||
rom_bytes[address + 1] = byte_1
|
||||
rom_bytes[address + 2] = sprite_id
|
||||
rom_bytes[0x4E3BF] = 0xFF
|
||||
|
||||
sprites = _read_room_sprites(rom_bytes, room_id, sprite_table_address, _load_dungeon_sprite_metadata())
|
||||
sprite_addresses = {sprite.address for sprite in sprites}
|
||||
|
||||
self.assertNotIn(0x4E3B9, sprite_addresses)
|
||||
self.assertIn(0x4E3B6, sprite_addresses)
|
||||
self.assertFalse(any(sprite.has_key for sprite in sprites))
|
||||
|
||||
def test_curated_room_sprite_addresses_deduplicate_duplicate_slots(self) -> None:
|
||||
room_id = 125
|
||||
sprite_table_address = 0x4E2CA
|
||||
metadata = _load_dungeon_sprite_metadata()
|
||||
max_sprite_id_address = max(metadata["room_sprite_id_addresses"][room_id])
|
||||
rom_bytes = bytearray(max_sprite_id_address + 2)
|
||||
rom_bytes[sprite_table_address] = 0
|
||||
for offset, sprite_id_address in enumerate(metadata["room_sprite_id_addresses"][room_id]):
|
||||
address = sprite_id_address - 2
|
||||
sprite_id = 0x80 if offset % 2 == 0 else 0x81
|
||||
rom_bytes[address] = 0
|
||||
rom_bytes[address + 1] = 0
|
||||
rom_bytes[address + 2] = sprite_id
|
||||
|
||||
sprites = _read_room_sprites(rom_bytes, room_id, sprite_table_address, metadata)
|
||||
sprite_addresses = [sprite.address for sprite in sprites]
|
||||
|
||||
self.assertEqual(len(sprite_addresses), len(set(sprite_addresses)))
|
||||
|
||||
def test_rejects_non_killable_shutter_room(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=1,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x10, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=True,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={1: room},
|
||||
randomized_dungeon_rooms={
|
||||
1: RandomizedDungeonEnemyRoom(
|
||||
room_id=1,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
original_graphics_block_id=1,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
RandomizedDungeonEnemySprite(
|
||||
address=0x1000,
|
||||
byte_0=0,
|
||||
byte_1=0,
|
||||
original_sprite_id=0x10,
|
||||
sprite_id=0x11,
|
||||
is_overlord=False,
|
||||
has_key=False,
|
||||
),
|
||||
),
|
||||
skipped_randomization=False,
|
||||
)
|
||||
},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x10, killable=True, subgroup_0=(1,)),
|
||||
self._requirement(0x11, killable=False, subgroup_0=(1,)),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
validate_enemy_shuffle_state(state, is_standard_mode=False)
|
||||
|
||||
def test_rejects_water_enemy_in_non_water_room(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=165,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=True,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={165: room},
|
||||
randomized_dungeon_rooms={
|
||||
165: RandomizedDungeonEnemyRoom(
|
||||
room_id=165,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
original_graphics_block_id=1,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
RandomizedDungeonEnemySprite(
|
||||
address=0x1000,
|
||||
byte_0=0,
|
||||
byte_1=0,
|
||||
original_sprite_id=0x20,
|
||||
sprite_id=0x81,
|
||||
is_overlord=False,
|
||||
has_key=False,
|
||||
),
|
||||
),
|
||||
skipped_randomization=False,
|
||||
)
|
||||
},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, killable=True, subgroup_0=(1,)),
|
||||
self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "water enemy"):
|
||||
validate_enemy_shuffle_state(state, is_standard_mode=False)
|
||||
|
||||
def test_rejects_multiple_flopping_fish(self) -> None:
|
||||
area = OverworldEnemyArea(
|
||||
area_id=0x10,
|
||||
sprite_table_address=0,
|
||||
graphics_block_address=0,
|
||||
graphics_block_id=1,
|
||||
bush_sprite_id=0x20,
|
||||
sprites=(
|
||||
OverworldEnemySprite(address=0x2000, y_coord=0, x_coord=0, sprite_id=0x20),
|
||||
OverworldEnemySprite(address=0x2003, y_coord=0, x_coord=0, sprite_id=0x21),
|
||||
),
|
||||
do_not_randomize=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
overworld_areas={0x10: area},
|
||||
randomized_overworld_areas={
|
||||
0x10: RandomizedOverworldEnemyArea(
|
||||
area_id=0x10,
|
||||
sprite_table_address=0,
|
||||
graphics_block_address=0,
|
||||
original_graphics_block_id=1,
|
||||
graphics_block_id=1,
|
||||
original_bush_sprite_id=0x20,
|
||||
bush_sprite_id=0xD2,
|
||||
sprites=(
|
||||
RandomizedOverworldEnemySprite(
|
||||
address=0x2000,
|
||||
y_coord=0,
|
||||
x_coord=0,
|
||||
original_sprite_id=0x20,
|
||||
sprite_id=0xD2,
|
||||
),
|
||||
RandomizedOverworldEnemySprite(
|
||||
address=0x2003,
|
||||
y_coord=0,
|
||||
x_coord=0,
|
||||
original_sprite_id=0x21,
|
||||
sprite_id=0xD2,
|
||||
),
|
||||
),
|
||||
skipped_randomization=False,
|
||||
)
|
||||
},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, group_ids=(1,)),
|
||||
self._requirement(0x21, group_ids=(1,)),
|
||||
self._requirement(0x22, group_ids=(1,)),
|
||||
self._requirement(0xD2, group_ids=(1,)),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
validate_enemy_shuffle_state(state, is_standard_mode=False)
|
||||
|
||||
def test_allows_multiple_flopping_fish_when_no_other_sprite_is_possible(self) -> None:
|
||||
area = OverworldEnemyArea(
|
||||
area_id=0x10,
|
||||
sprite_table_address=0,
|
||||
graphics_block_address=0,
|
||||
graphics_block_id=1,
|
||||
bush_sprite_id=0x20,
|
||||
sprites=(
|
||||
OverworldEnemySprite(address=0x2000, y_coord=0, x_coord=0, sprite_id=0x20),
|
||||
OverworldEnemySprite(address=0x2003, y_coord=0, x_coord=0, sprite_id=0x21),
|
||||
),
|
||||
do_not_randomize=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
overworld_areas={0x10: area},
|
||||
randomized_overworld_areas={
|
||||
0x10: RandomizedOverworldEnemyArea(
|
||||
area_id=0x10,
|
||||
sprite_table_address=0,
|
||||
graphics_block_address=0,
|
||||
original_graphics_block_id=1,
|
||||
graphics_block_id=1,
|
||||
original_bush_sprite_id=0x20,
|
||||
bush_sprite_id=0xD2,
|
||||
sprites=(
|
||||
RandomizedOverworldEnemySprite(
|
||||
address=0x2000,
|
||||
y_coord=0,
|
||||
x_coord=0,
|
||||
original_sprite_id=0x20,
|
||||
sprite_id=0xD2,
|
||||
),
|
||||
RandomizedOverworldEnemySprite(
|
||||
address=0x2003,
|
||||
y_coord=0,
|
||||
x_coord=0,
|
||||
original_sprite_id=0x21,
|
||||
sprite_id=0xD2,
|
||||
),
|
||||
),
|
||||
skipped_randomization=False,
|
||||
)
|
||||
},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, group_ids=(2,)),
|
||||
self._requirement(0x21, group_ids=(2,)),
|
||||
self._requirement(0xD2, group_ids=(1,)),
|
||||
),
|
||||
)
|
||||
|
||||
validate_enemy_shuffle_state(state, is_standard_mode=False)
|
||||
|
||||
def test_excludes_absorbables_from_usable_enemy_pools(self) -> None:
|
||||
state = self._build_state(
|
||||
sprite_requirements=(
|
||||
self._requirement(0x10, subgroup_0=(1,)),
|
||||
self._requirement(0xE3, subgroup_0=(1,), absorbable=True),
|
||||
self._requirement(0x20, subgroup_0=(1,), never_use_dungeon=True),
|
||||
self._requirement(0x21, subgroup_0=(1,), never_use_overworld=True),
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[requirement.sprite_id for requirement in _get_requirements_for_usable_dungeon_enemies(state)],
|
||||
[0x10, 0x21],
|
||||
)
|
||||
self.assertEqual(
|
||||
[requirement.sprite_id for requirement in _get_requirements_for_usable_overworld_enemies(state)],
|
||||
[0x10, 0x20],
|
||||
)
|
||||
|
||||
def test_key_enemy_replacements_exclude_moblins(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=1,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x12, is_overlord=False, has_key=True),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={1: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x12, killable=True, subgroup_0=(1,), cannot_have_key=True),
|
||||
self._requirement(0x13, killable=True, subgroup_0=(1,)),
|
||||
),
|
||||
)
|
||||
selected_group = state.sprite_groups[0x41]
|
||||
|
||||
randomized_room = _randomize_room_sprites(
|
||||
SimpleNamespace(random=random.Random(0)),
|
||||
state,
|
||||
room,
|
||||
selected_group,
|
||||
False,
|
||||
)
|
||||
|
||||
self.assertEqual(randomized_room.sprites[0].sprite_id, 0x13)
|
||||
|
||||
def test_shutter_water_room_prefers_killable_water_enemy(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=40,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x8A, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=True,
|
||||
is_water_room=True,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={40: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x8A, killable=False, subgroup_2=(34,)),
|
||||
self._requirement(0x81, killable=True, subgroup_2=(34,), is_water_sprite=True),
|
||||
self._requirement(0x9A, killable=False, subgroup_2=(34,), is_water_sprite=True),
|
||||
),
|
||||
)
|
||||
selected_group = state.sprite_groups[0x41]
|
||||
selected_group.subgroup_2 = 34
|
||||
|
||||
randomized_room = _randomize_room_sprites(
|
||||
SimpleNamespace(random=random.Random(0)),
|
||||
state,
|
||||
room,
|
||||
selected_group,
|
||||
False,
|
||||
)
|
||||
|
||||
self.assertEqual(randomized_room.sprites[0].sprite_id, 0x81)
|
||||
|
||||
def test_non_water_shutter_room_replacements_exclude_water_enemies(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=165,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=True,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={165: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, killable=False, subgroup_0=(1,)),
|
||||
self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True),
|
||||
self._requirement(0x22, killable=True, subgroup_0=(1,)),
|
||||
),
|
||||
)
|
||||
|
||||
randomized_room = _randomize_room_sprites(
|
||||
SimpleNamespace(random=random.Random(1)),
|
||||
state,
|
||||
room,
|
||||
state.sprite_groups[0x41],
|
||||
False,
|
||||
)
|
||||
|
||||
self.assertEqual(randomized_room.sprites[0].sprite_id, 0x22)
|
||||
|
||||
def test_non_water_shutter_group_selection_requires_non_water_killable_enemy(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=165,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=True,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={165: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, killable=False, subgroup_0=(1,)),
|
||||
self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True),
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(get_possible_dungeon_sprite_groups(state, room), tuple())
|
||||
|
||||
def test_wallmaster_cannot_spawn_in_high_room_ids(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=0x100,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=tuple(),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
|
||||
self.assertFalse(can_spawn_in_room(self._requirement(WALLMASTER_SPRITE_ID), room))
|
||||
|
||||
def test_room_specific_do_not_randomize_sprites_are_not_updated(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=7,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x30, is_overlord=False, has_key=False),
|
||||
DungeonEnemySprite(address=0x1003, byte_0=0, byte_1=0, sprite_id=0x31, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={7: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x30, subgroup_0=(1,), dont_randomize_rooms=(7,)),
|
||||
self._requirement(0x31, subgroup_0=(1,)),
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[sprite.sprite_id for sprite in _get_randomizable_sprites_in_room(state, room)],
|
||||
[0x31],
|
||||
)
|
||||
|
||||
def test_water_rooms_only_use_water_enemies(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=1,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=True,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={1: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, subgroup_0=(1,)),
|
||||
self._requirement(0x21, subgroup_0=(1,), is_water_sprite=True),
|
||||
self._requirement(0x22, subgroup_0=(1,), is_water_sprite=True),
|
||||
),
|
||||
)
|
||||
|
||||
randomized_room = _randomize_room_sprites(
|
||||
SimpleNamespace(random=random.Random(0)),
|
||||
state,
|
||||
room,
|
||||
state.sprite_groups[0x41],
|
||||
False,
|
||||
)
|
||||
|
||||
self.assertIn(randomized_room.sprites[0].sprite_id, {0x21, 0x22})
|
||||
|
||||
def test_dungeon_group_selection_excludes_groups_without_enemy_requirements(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=1,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={1: room},
|
||||
sprite_requirements=(self._requirement(0x20, subgroup_0=(1,)),),
|
||||
)
|
||||
state.sprite_groups[0x42] = DungeonSpriteGroup(
|
||||
group_id=0x42,
|
||||
dungeon_group_id=2,
|
||||
subgroup_0=0,
|
||||
subgroup_1=0,
|
||||
subgroup_2=0,
|
||||
subgroup_3=0,
|
||||
)
|
||||
|
||||
possible_groups = get_possible_dungeon_sprite_groups(state, room)
|
||||
|
||||
self.assertEqual([group.group_id for group in possible_groups], [0x41])
|
||||
|
||||
def test_key_room_group_selection_excludes_groups_without_room_spawnable_key_enemies(self) -> None:
|
||||
room = DungeonEnemyRoom(
|
||||
room_id=61,
|
||||
room_header_address=0,
|
||||
sprite_table_address=0,
|
||||
graphics_block_id=1,
|
||||
tag_1=0,
|
||||
tag_2=0,
|
||||
sort_sprites_value=0,
|
||||
sprites=(
|
||||
DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=True),
|
||||
),
|
||||
required_group_id=None,
|
||||
required_subgroup_0=tuple(),
|
||||
required_subgroup_1=tuple(),
|
||||
required_subgroup_2=tuple(),
|
||||
required_subgroup_3=tuple(),
|
||||
is_shutter_room=False,
|
||||
is_water_room=False,
|
||||
do_not_randomize=False,
|
||||
no_special_enemies_standard=False,
|
||||
)
|
||||
state = self._build_state(
|
||||
dungeon_rooms={61: room},
|
||||
sprite_requirements=(
|
||||
self._requirement(0x20, subgroup_0=(1,)),
|
||||
self._requirement(0x50, killable=True, subgroup_1=(32,), excluded_rooms=(61,)),
|
||||
self._requirement(0x9C, killable=True, subgroup_1=(32,), cannot_have_key=True),
|
||||
self._requirement(0x51, killable=True, subgroup_1=(33,)),
|
||||
),
|
||||
)
|
||||
state.sprite_groups[0x41] = DungeonSpriteGroup(
|
||||
group_id=0x41,
|
||||
dungeon_group_id=1,
|
||||
subgroup_0=1,
|
||||
subgroup_1=32,
|
||||
subgroup_2=1,
|
||||
subgroup_3=1,
|
||||
)
|
||||
state.sprite_groups[0x42] = DungeonSpriteGroup(
|
||||
group_id=0x42,
|
||||
dungeon_group_id=2,
|
||||
subgroup_0=1,
|
||||
subgroup_1=33,
|
||||
subgroup_2=1,
|
||||
subgroup_3=1,
|
||||
)
|
||||
|
||||
possible_groups = get_possible_dungeon_sprite_groups(state, room)
|
||||
|
||||
self.assertEqual([group.group_id for group in possible_groups], [0x42])
|
||||
|
||||
def test_overworld_group_randomization_preserves_forced_subgroups(self) -> None:
|
||||
sprite_groups = {
|
||||
7: DungeonSpriteGroup(group_id=7, dungeon_group_id=-57, subgroup_0=1, subgroup_1=2, subgroup_2=3, subgroup_3=4),
|
||||
}
|
||||
|
||||
_setup_required_overworld_groups(
|
||||
sprite_groups,
|
||||
(
|
||||
SimpleNamespace(
|
||||
group_id=7,
|
||||
subgroup_0=None,
|
||||
subgroup_1=None,
|
||||
subgroup_2=None,
|
||||
subgroup_3=17,
|
||||
areas=(0x02,),
|
||||
),
|
||||
),
|
||||
)
|
||||
_randomize_overworld_groups(SimpleNamespace(random=random.Random(0)), sprite_groups)
|
||||
|
||||
group = sprite_groups[7]
|
||||
self.assertEqual(group.subgroup_3, 17)
|
||||
self.assertIn(group.subgroup_0, {22, 31, 47, 14})
|
||||
self.assertIn(group.subgroup_1, {44, 30, 32})
|
||||
self.assertIn(group.subgroup_2, {12, 18, 23, 24, 28, 46, 34, 35, 39, 40, 38, 41, 36, 37, 42})
|
||||
|
||||
def test_selected_boss_group_requirements_override_shared_boss_graphics_group(self) -> None:
|
||||
sprite_groups = {
|
||||
0x56: DungeonSpriteGroup(
|
||||
group_id=0x56,
|
||||
dungeon_group_id=22,
|
||||
subgroup_0=1,
|
||||
subgroup_1=1,
|
||||
subgroup_2=60,
|
||||
subgroup_3=49,
|
||||
),
|
||||
}
|
||||
sprite_requirements = (
|
||||
self._requirement(162, subgroup_2=(60,)),
|
||||
self._requirement(189, subgroup_3=(61,)),
|
||||
)
|
||||
|
||||
_apply_selected_boss_group_requirements(
|
||||
self._build_boss_world({"Eastern Palace": "Vitreous"}),
|
||||
sprite_groups,
|
||||
sprite_requirements,
|
||||
)
|
||||
|
||||
group = sprite_groups[0x56]
|
||||
self.assertEqual(group.subgroup_2, 60)
|
||||
self.assertEqual(group.subgroup_3, 61)
|
||||
self.assertTrue(group.preserve_subgroup_2)
|
||||
self.assertTrue(group.preserve_subgroup_3)
|
||||
|
||||
@staticmethod
|
||||
def _requirement(
|
||||
sprite_id: int,
|
||||
*,
|
||||
killable: bool = False,
|
||||
subgroup_0: tuple[int, ...] = tuple(),
|
||||
subgroup_1: tuple[int, ...] = tuple(),
|
||||
subgroup_2: tuple[int, ...] = tuple(),
|
||||
subgroup_3: tuple[int, ...] = tuple(),
|
||||
group_ids: tuple[int, ...] = tuple(),
|
||||
absorbable: bool = False,
|
||||
never_use_dungeon: bool = False,
|
||||
never_use_overworld: bool = False,
|
||||
cannot_have_key: bool = False,
|
||||
is_water_sprite: bool = False,
|
||||
excluded_rooms: tuple[int, ...] = tuple(),
|
||||
dont_randomize_rooms: tuple[int, ...] = tuple(),
|
||||
) -> EnemySpriteRequirement:
|
||||
return EnemySpriteRequirement(
|
||||
sprite_name=f"sprite_{sprite_id:02x}",
|
||||
sprite_id=sprite_id,
|
||||
boss=False,
|
||||
overlord=False,
|
||||
do_not_randomize=False,
|
||||
killable=killable,
|
||||
npc=False,
|
||||
never_use_dungeon=never_use_dungeon,
|
||||
never_use_overworld=never_use_overworld,
|
||||
cannot_have_key=cannot_have_key,
|
||||
is_object=False,
|
||||
absorbable=absorbable,
|
||||
is_water_sprite=is_water_sprite,
|
||||
is_enemy_sprite=True,
|
||||
group_ids=group_ids,
|
||||
subgroup_0=subgroup_0,
|
||||
subgroup_1=subgroup_1,
|
||||
subgroup_2=subgroup_2,
|
||||
subgroup_3=subgroup_3,
|
||||
parameters=None,
|
||||
special_glitched=False,
|
||||
excluded_rooms=excluded_rooms,
|
||||
dont_randomize_rooms=dont_randomize_rooms,
|
||||
spawnable_rooms=tuple(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_state(
|
||||
*,
|
||||
dungeon_rooms=None,
|
||||
overworld_areas=None,
|
||||
randomized_dungeon_rooms=None,
|
||||
randomized_overworld_areas=None,
|
||||
sprite_requirements=tuple(),
|
||||
) -> EnemyShuffleState:
|
||||
sprite_groups = {
|
||||
1: DungeonSpriteGroup(group_id=1, dungeon_group_id=-63, subgroup_0=1, subgroup_1=1, subgroup_2=1, subgroup_3=1),
|
||||
0x41: DungeonSpriteGroup(group_id=0x41, dungeon_group_id=1, subgroup_0=1, subgroup_1=1, subgroup_2=1, subgroup_3=1),
|
||||
}
|
||||
return EnemyShuffleState(
|
||||
dungeon_rooms=dungeon_rooms or {},
|
||||
overworld_areas=overworld_areas or {},
|
||||
sprite_groups=sprite_groups,
|
||||
sprite_requirements=sprite_requirements,
|
||||
room_group_requirements=tuple(),
|
||||
overworld_group_requirements=tuple(),
|
||||
shutter_room_ids=frozenset(),
|
||||
water_room_ids=frozenset(),
|
||||
dont_randomize_room_ids=frozenset(),
|
||||
no_special_enemies_standard_room_ids=frozenset(),
|
||||
boss_room_ids=frozenset(),
|
||||
dont_randomize_overworld_area_ids=frozenset(),
|
||||
randomized_dungeon_rooms=randomized_dungeon_rooms or {},
|
||||
randomized_overworld_areas=randomized_overworld_areas or {},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_boss_world(boss_overrides: dict[str, str] | None = None) -> SimpleNamespace:
|
||||
boss_overrides = boss_overrides or {}
|
||||
|
||||
def boss(name: str) -> SimpleNamespace:
|
||||
return SimpleNamespace(enemizer_name=name)
|
||||
|
||||
return SimpleNamespace(
|
||||
options=SimpleNamespace(mode="open"),
|
||||
dungeons={
|
||||
"Eastern Palace": SimpleNamespace(boss=boss(boss_overrides.get("Eastern Palace", "Armos"))),
|
||||
"Desert Palace": SimpleNamespace(boss=boss(boss_overrides.get("Desert Palace", "Lanmola"))),
|
||||
"Tower of Hera": SimpleNamespace(boss=boss(boss_overrides.get("Tower of Hera", "Moldorm"))),
|
||||
"Palace of Darkness": SimpleNamespace(boss=boss(boss_overrides.get("Palace of Darkness", "Helmasaur"))),
|
||||
"Swamp Palace": SimpleNamespace(boss=boss(boss_overrides.get("Swamp Palace", "Arrghus"))),
|
||||
"Skull Woods": SimpleNamespace(boss=boss(boss_overrides.get("Skull Woods", "Mothula"))),
|
||||
"Thieves Town": SimpleNamespace(boss=boss(boss_overrides.get("Thieves Town", "Blind"))),
|
||||
"Ice Palace": SimpleNamespace(boss=boss(boss_overrides.get("Ice Palace", "Kholdstare"))),
|
||||
"Misery Mire": SimpleNamespace(boss=boss(boss_overrides.get("Misery Mire", "Vitreous"))),
|
||||
"Turtle Rock": SimpleNamespace(boss=boss(boss_overrides.get("Turtle Rock", "Trinexx"))),
|
||||
"Ganons Tower": SimpleNamespace(
|
||||
bosses={
|
||||
"bottom": boss(boss_overrides.get("Ganons Tower Bottom", "Armos")),
|
||||
"middle": boss(boss_overrides.get("Ganons Tower Middle", "Lanmola")),
|
||||
"top": boss(boss_overrides.get("Ganons Tower Top", "Moldorm")),
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,56 @@
|
||||
import random
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from worlds.alttp.PotShuffle import (
|
||||
POT_KEY,
|
||||
POT_HOLE,
|
||||
generate_pot_shuffle,
|
||||
get_unique_pot_item_position,
|
||||
)
|
||||
|
||||
|
||||
class TestPotShuffle(unittest.TestCase):
|
||||
def test_reserved_key_rooms_only_place_actual_keys(self) -> None:
|
||||
for seed in range(10):
|
||||
world = SimpleNamespace(
|
||||
random=random.Random(seed),
|
||||
options=SimpleNamespace(retro_bow=False),
|
||||
)
|
||||
shuffled_pots = generate_pot_shuffle(world)
|
||||
conveyor_cross_keys = [
|
||||
pot for pot in shuffled_pots[0x8B]
|
||||
if pot.item == POT_KEY
|
||||
]
|
||||
self.assertEqual(len(conveyor_cross_keys), 1)
|
||||
|
||||
def test_get_unique_pot_item_position_returns_single_match(self) -> None:
|
||||
world = SimpleNamespace(
|
||||
random=random.Random(0),
|
||||
options=SimpleNamespace(retro_bow=False),
|
||||
)
|
||||
shuffled_pots = generate_pot_shuffle(world)
|
||||
|
||||
self.assertEqual(
|
||||
get_unique_pot_item_position(shuffled_pots, 0x36, POT_KEY),
|
||||
(114, 16),
|
||||
)
|
||||
|
||||
def test_reserved_hole_room_keeps_hole_fixed(self) -> None:
|
||||
for seed in range(25):
|
||||
world = SimpleNamespace(
|
||||
random=random.Random(seed),
|
||||
options=SimpleNamespace(retro_bow=False),
|
||||
)
|
||||
shuffled_pots = generate_pot_shuffle(world)
|
||||
hole_positions = [
|
||||
(pot.x, pot.y)
|
||||
for pot in shuffled_pots[206]
|
||||
if pot.item == POT_HOLE
|
||||
]
|
||||
|
||||
self.assertEqual(hole_positions, [(204, 11)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"game": "APQuest",
|
||||
"minimum_ap_version": "0.6.4",
|
||||
"world_version": "1.0.1",
|
||||
"minimum_ap_version": "0.6.7",
|
||||
"world_version": "2.0.0",
|
||||
"authors": ["NewSoupVi"]
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ class APQuestContext(CommonContext):
|
||||
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.ui.render(self.ap_quest_game, self.player_sprite, self.hard_mode)
|
||||
self.handle_game_events()
|
||||
|
||||
def location_checked_side_effects(self, location: int) -> None:
|
||||
|
||||
@@ -88,23 +88,23 @@ class APQuestManager(GameManager):
|
||||
self.game_view.force_focus()
|
||||
self.sound_manager.game_started = True
|
||||
|
||||
def render(self, game: Game, player_sprite: PlayerSprite) -> None:
|
||||
def render(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> None:
|
||||
self.setup_game_grid_if_not_setup(game)
|
||||
|
||||
# This calls game.render(), which needs to happen to update the state of math traps
|
||||
self.render_gameboard(game, player_sprite)
|
||||
self.render_gameboard(game, player_sprite, hard_mode)
|
||||
# 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:
|
||||
def render_gameboard(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> 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)
|
||||
texture = get_texture(graphic, player_sprite, hard_mode)
|
||||
|
||||
if texture is None:
|
||||
image.opacity = 0
|
||||
|
||||
@@ -29,6 +29,7 @@ class RelatedTexture(NamedTuple):
|
||||
|
||||
|
||||
IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
# Inanimates
|
||||
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),
|
||||
@@ -37,29 +38,25 @@ IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
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),
|
||||
|
||||
# Enemies
|
||||
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),
|
||||
|
||||
# Items
|
||||
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",
|
||||
|
||||
# Numbers
|
||||
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),
|
||||
@@ -70,26 +67,29 @@ IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
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),
|
||||
|
||||
# Letters
|
||||
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),
|
||||
|
||||
# Mathematical symbols
|
||||
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),
|
||||
# Other visual-only elements
|
||||
Graphic.ITEMS_TEXT: "items_text.png",
|
||||
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)
|
||||
|
||||
EASY_MODE_BOSS_2_HEALTH = RelatedTexture("boss.png", 16, 0, 16, 16)
|
||||
|
||||
|
||||
class PlayerSprite(Enum):
|
||||
HUMAN = 0
|
||||
@@ -160,13 +160,18 @@ def get_texture_by_identifier(texture_identifier: str | RelatedTexture) -> Textu
|
||||
return sub_texture
|
||||
|
||||
|
||||
def get_texture(graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None) -> Texture | None:
|
||||
def get_texture(
|
||||
graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None, hard_mode: bool = False
|
||||
) -> Texture | None:
|
||||
if graphic == Graphic.EMPTY:
|
||||
return None
|
||||
|
||||
if graphic == "Grass":
|
||||
return get_texture_by_identifier(BACKGROUND_TILE)
|
||||
|
||||
if graphic == Graphic.BOSS_2_HEALTH and not hard_mode:
|
||||
return get_texture_by_identifier(EASY_MODE_BOSS_2_HEALTH)
|
||||
|
||||
if graphic in IMAGE_GRAPHICS:
|
||||
return get_texture_by_identifier(IMAGE_GRAPHICS[graphic])
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ def create_gameboard(hard_mode: bool, hammer_exists: bool, extra_chest: bool) ->
|
||||
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)
|
||||
boss = FinalBoss(5 if hard_mode else 2)
|
||||
|
||||
gameboard = (
|
||||
(Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 754 B |
+92
-71
@@ -2,12 +2,16 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
from rule_builder.options import OptionFilter
|
||||
from rule_builder.rules import Has, HasAll, Rule
|
||||
|
||||
from .options import HardMode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import APQuestWorld
|
||||
|
||||
HAS_KEY = Has("Key") # Hmm, what could this be? A little foreshadowing perhaps? :) You'll find out if you keep reading!
|
||||
|
||||
|
||||
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,
|
||||
@@ -26,36 +30,46 @@ def set_all_entrance_rules(world: APQuestWorld) -> None:
|
||||
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, let's make some rules!
|
||||
# First, let's handle the transition from the overworld to the bottom right room,
|
||||
# which requires slashing a bush with the Sword.
|
||||
# For this, we need a rule that says "player has a Sword".
|
||||
# We can use a "Has"-type rule from the rule_builder module for this.
|
||||
can_destroy_bush = Has("Sword")
|
||||
|
||||
# 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)
|
||||
# Now we can set our "can_destroy_bush" rule to the entrance which requires slashing a bush to clear the path.
|
||||
# The easiest way to do this is by calling world.set_rule, which works for both Locations and Entrances.
|
||||
world.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))
|
||||
# Conditions can also depend on event items.
|
||||
button_pressed = Has("Top Left Room Button Pressed")
|
||||
world.set_rule(right_room_to_final_boss_room, button_pressed)
|
||||
|
||||
# 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))
|
||||
can_smash_brick = Has("Hammer")
|
||||
world.set_rule(overworld_to_top_middle_room, can_smash_brick)
|
||||
|
||||
# So far, we've been using "Has" from the Rule Builder to make our rules.
|
||||
# There is another way to make rules that you will see in a lot of older worlds.
|
||||
# A rule can just be a function that takes a "state" argument and returns a bool.
|
||||
# As a demonstration of what that looks like, let's do it with our final Entrance rule:
|
||||
world.set_rule(overworld_to_top_left_room, lambda state: state.has("Key", world.player))
|
||||
# This style is not really recommended anymore, though.
|
||||
# Notice how you have to explicitly capture world.player here so that the rule applies to the correct player?
|
||||
# Well, Rule Builder does this part for you, inside of world.set_rule.
|
||||
# This doesn't just result in shorter code, it also means you can define rules statically (at the module level).
|
||||
# APQuest opts to create its Rule objects locally, but just to show what this would look like,
|
||||
# we'll re-set the "Overworld to Top Left Room" rule to a constant defined at the top of this file:
|
||||
world.set_rule(overworld_to_top_left_room, HAS_KEY)
|
||||
|
||||
# Beyond these structural advantages,
|
||||
# Rule Builder also allows the core AP code to do a lot of under-the-hood optimizations.
|
||||
# Rule Builder is quite comprehensive, and even if you have really esoteric rules,
|
||||
# you can make custom rules by subclassing CustomRule.
|
||||
|
||||
def set_all_location_rules(world: APQuestWorld) -> None:
|
||||
# Location rules work no differently from Entrance rules.
|
||||
@@ -67,65 +81,72 @@ def set_all_location_rules(world: APQuestWorld) -> None:
|
||||
# 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.
|
||||
# In "set_all_entrance_rules", we had a rule for a location that doesn't always exist.
|
||||
# In this case, we had to check for its existence (by checking the player's chosen options) before setting the rule.
|
||||
# Other times, you may have a situation where a location can have two different rules depending on the options.
|
||||
# In our case, the enemy in the right room has more health if hard mode is selected,
|
||||
# so ontop of the Sword, the player will either need one more health or a Shield in hard mode.
|
||||
# First, let's make our sword condition.
|
||||
can_defeat_basic_enemy: Rule = Has("Sword")
|
||||
|
||||
# Next, we'll check whether hard mode has been chosen in the player options.
|
||||
if world.options.hard_mode:
|
||||
# We'll make the condition for "Has a Shield or a Health Upgrade".
|
||||
# We can chain two "Has" conditions together with the | operator to make "Has Shield or has Health Upgrade".
|
||||
can_withstand_a_hit = Has("Shield") | Has("Health Upgrade")
|
||||
|
||||
# Now, we chain this rule to our Sword rule.
|
||||
# Since we want both conditions to be true, in this case, we have to chain them in an "and" way.
|
||||
# For this, we can use the & operator.
|
||||
can_defeat_basic_enemy = can_defeat_basic_enemy & can_withstand_a_hit
|
||||
|
||||
# Finally, we set our rule onto the Right Room Eney Drop location.
|
||||
right_room_enemy = world.get_location("Right Room Enemy Drop")
|
||||
world.set_rule(right_room_enemy, can_defeat_basic_enemy)
|
||||
|
||||
# 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!!!!
|
||||
# For the final boss, we also need to chain multiple conditions.
|
||||
# First of all, you always need a Sword and a Shield.
|
||||
# So far, we used the | and & operators to chain "Has" rules.
|
||||
# Instead, we can also use HasAny for an or-chain of items, or HasAll for an and-chain of items.
|
||||
has_sword_and_shield: Rule = HasAll("Sword", "Shield")
|
||||
|
||||
# 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.
|
||||
# In hard mode, the player also needs both Health Upgrades to survive long enough to defeat the boss.
|
||||
# For this, we can use the optional "count" parameter for "Has".
|
||||
has_both_health_upgrades = Has("Health Upgrade", count=2)
|
||||
|
||||
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))
|
||||
# Previously, we used an "if world.options.hard_mode" condition to check if we should apply the extra requirement.
|
||||
# However, if you're comfortable with boolean logic, there is another way.
|
||||
# OptionFilter is a rule component which isn't a "Rule" on its own, but when used in a boolean expression with
|
||||
# rules, it acts like True if the option has the specified value, and acts like False otherwise.
|
||||
hard_mode_is_off = OptionFilter(HardMode, False)
|
||||
|
||||
# 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.
|
||||
# So with this option-checking rule component in hand, we can write our boss condition like this:
|
||||
can_defeat_final_boss = has_sword_and_shield & (hard_mode_is_off | has_both_health_upgrades)
|
||||
# If you're not as comfortable with boolean logic, it might be somewhat confusing why this is correct.
|
||||
# There is nothing wrong with using "if" conditions to check for options, if you find that easier to understand.
|
||||
|
||||
# Finally, we apply the rule to our "Final Boss Defeated" event location.
|
||||
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))
|
||||
world.set_rule(final_boss, can_defeat_final_boss)
|
||||
|
||||
|
||||
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.
|
||||
# For this, we can use world.set_completion_rule.
|
||||
# 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)
|
||||
world.set_completion_rule(HasAll("Sword", "Shield"))
|
||||
|
||||
# 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)
|
||||
world.set_completion_rule(Has("Victory"))
|
||||
|
||||
|
||||
# One final comment about rules:
|
||||
# If your world exclusively uses Rule Builder rules (like APQuest), it's worth trying CachedRuleBuilderWorld.
|
||||
# CachedRuleBuilderWorld is a subclass of World that has a bunch of caching magic to make rules faster.
|
||||
# Just have your world class subclass CachedRuleBuilderWorld instead of World:
|
||||
# class APQuestWorld(CachedRuleBuilderWorld): ...
|
||||
# This may speed up your world, or it may make it slower.
|
||||
# The exact factors are complex and not well understood, but there is no harm in trying it.
|
||||
# Generate a few seeds and see if there is a noticeable difference!
|
||||
# If you're wondering, author has checked: APQuest is too simple to see any benefits, so we'll stick with "World".
|
||||
|
||||
@@ -739,6 +739,9 @@ group_table: Dict[str, Set[str]] = {
|
||||
"Broken Left Eye of the Traitor"}
|
||||
}
|
||||
|
||||
# Because each item is only in a single group, a reverse lookup table from each item to its group can be created.
|
||||
group_table_reverse: Dict[str, str] = {item: group for group, items in group_table.items() for item in items}
|
||||
|
||||
tears_list: List[str] = [
|
||||
"Tears of Atonement (500)",
|
||||
"Tears of Atonement (625)",
|
||||
|
||||
+377
-262
@@ -1,5 +1,6 @@
|
||||
from typing import Dict, List, Tuple, Any, Callable, TYPE_CHECKING
|
||||
from typing import Dict, List, Tuple, Any, Callable, TYPE_CHECKING, Mapping
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import BlasphemousWorld
|
||||
@@ -7,30 +8,145 @@ else:
|
||||
BlasphemousWorld = object
|
||||
|
||||
|
||||
# Depending on a player's options, some logic can either always be True, or always be False.
|
||||
# When combining rules together in load_rule(), optimizations can be made by checking whether a rule being combined is
|
||||
# _always or _never.
|
||||
def _always(state: CollectionState):
|
||||
return True
|
||||
|
||||
|
||||
def _never(state: CollectionState):
|
||||
return False
|
||||
|
||||
|
||||
def _bool_rule(b) -> CollectionRule:
|
||||
"""Small helper to return the appropriate rule function for a rule that can be pre-calculated"""
|
||||
if b:
|
||||
return _always
|
||||
else:
|
||||
return _never
|
||||
|
||||
|
||||
# Player strengths required to logically beat bosses.
|
||||
# Mapping is an immutable type, so type hints should warn if attempts are made to modify it.
|
||||
BOSS_STRENGTHS: Mapping[str, float] = {
|
||||
"warden": -0.10,
|
||||
"ten-piedad": 0.05,
|
||||
"charred-visage": 0.20,
|
||||
"tres-angustias": 0.15,
|
||||
"esdras": 0.25,
|
||||
"melquiades": 0.25,
|
||||
"exposito": 0.30,
|
||||
"quirce": 0.35,
|
||||
"crisanta": 0.50,
|
||||
"isidora": 0.70,
|
||||
"sierpes": 0.70,
|
||||
"amanecida": 0.60,
|
||||
"laudes": 0.60,
|
||||
"perpetua": -0.05,
|
||||
"legionary": 0.20
|
||||
}
|
||||
|
||||
|
||||
class BlasRules:
|
||||
player: int
|
||||
world: BlasphemousWorld
|
||||
string_rules: Dict[str, Callable[[CollectionState], bool]]
|
||||
|
||||
upwarp_skips_allowed: bool
|
||||
mourning_skip_allowed: bool
|
||||
enemy_skips_allowed: bool
|
||||
obscure_skips_allowed: bool
|
||||
precise_skips_allowed: bool
|
||||
can_enemy_bounce: bool
|
||||
|
||||
# Player strengths required to logically beat bosses, adjusted by the player's difficulty option.
|
||||
boss_strengths: Mapping[str, float]
|
||||
|
||||
can_enemy_upslash: CollectionRule
|
||||
can_air_stall: CollectionRule
|
||||
can_dawn_jump: CollectionRule
|
||||
can_dive_laser: CollectionRule
|
||||
can_survive_poison_1: CollectionRule
|
||||
can_survive_poison_2: CollectionRule
|
||||
can_survive_poison_3: CollectionRule
|
||||
|
||||
def __init__(self, world: "BlasphemousWorld") -> None:
|
||||
self.player = world.player
|
||||
self.world = world
|
||||
self.multiworld = world.multiworld
|
||||
self.indirect_conditions: List[Tuple[str, str]] = []
|
||||
|
||||
difficulty = world.options.difficulty.value
|
||||
|
||||
# Rules that can be fully or partially pre-calculated based on world.options.
|
||||
|
||||
# Special Skips
|
||||
self.upwarp_skips_allowed = difficulty >= 2
|
||||
self.mourning_skip_allowed = difficulty >= 2
|
||||
self.enemy_skips_allowed = difficulty >= 2 and not world.options.enemy_randomizer.value
|
||||
self.obscure_skips_allowed = difficulty >= 2
|
||||
self.precise_skips_allowed = difficulty >= 2
|
||||
|
||||
if difficulty >= 2:
|
||||
# Beating bosses ends up in logic earlier.
|
||||
self.boss_strengths = {boss: strength - 0.1 for boss, strength in BOSS_STRENGTHS.items()}
|
||||
elif difficulty >= 1:
|
||||
self.boss_strengths = BOSS_STRENGTHS
|
||||
else:
|
||||
# Beating bosses ends up in logic later.
|
||||
self.boss_strengths = {boss: strength + 0.1 for boss, strength in BOSS_STRENGTHS.items()}
|
||||
|
||||
# Enemy tech
|
||||
if self.enemy_skips_allowed:
|
||||
self.can_enemy_bounce = True
|
||||
self.can_enemy_upslash = lambda state: self.combo(state) >= 2
|
||||
else:
|
||||
self.can_enemy_bounce = False
|
||||
self.can_enemy_upslash = _never
|
||||
|
||||
# Movement tech
|
||||
if difficulty >= 1:
|
||||
self.can_air_stall = self.ranged
|
||||
self.can_dawn_jump = lambda state: self.dawn_heart(state) and self.dash(state)
|
||||
else:
|
||||
self.can_air_stall = _never
|
||||
self.can_dawn_jump = _never
|
||||
|
||||
# Breakable tech
|
||||
if difficulty >= 2:
|
||||
self.can_dive_laser = lambda state: self.dive(state) >= 3
|
||||
else:
|
||||
self.can_dive_laser = _never
|
||||
|
||||
# Lung tech
|
||||
if difficulty >= 2:
|
||||
self.can_survive_poison_1 = _always
|
||||
self.can_survive_poison_2 = lambda state: self.lung(state) or self.tiento(state)
|
||||
self.can_survive_poison_3 = lambda state: self.lung(state) or (self.tiento(state)
|
||||
and self.total_fervour(state) >= 120)
|
||||
elif difficulty >= 1:
|
||||
self.can_survive_poison_1 = lambda state: self.lung(state) or self.tiento(state)
|
||||
self.can_survive_poison_2 = lambda state: self.lung(state) or self.tiento(state)
|
||||
self.can_survive_poison_3 = self.lung
|
||||
else:
|
||||
self.can_survive_poison_1 = self.lung
|
||||
self.can_survive_poison_2 = self.lung
|
||||
self.can_survive_poison_3 = self.lung
|
||||
|
||||
|
||||
# BrandenEK/Blasphemous.Randomizer/ItemRando/BlasphemousInventory.cs
|
||||
self.string_rules = {
|
||||
self.string_rules: dict[str, CollectionRule] = {
|
||||
# Visibility flags
|
||||
"DoubleJump": lambda state: bool(self.world.options.purified_hand),
|
||||
"NormalLogic": lambda state: self.world.options.difficulty >= 1,
|
||||
"NormalLogicAndDoubleJump": lambda state: self.world.options.difficulty >= 1 \
|
||||
and bool(self.world.options.purified_hand),
|
||||
"HardLogic": lambda state: self.world.options.difficulty >= 2,
|
||||
"HardLogicAndDoubleJump": lambda state: self.world.options.difficulty >= 2 \
|
||||
and bool(self.world.options.purified_hand),
|
||||
"EnemySkips": self.enemy_skips_allowed,
|
||||
"EnemySkipsAndDoubleJump": lambda state: self.enemy_skips_allowed(state) \
|
||||
and bool(self.world.options.purified_hand),
|
||||
"DoubleJump": _bool_rule(self.world.options.purified_hand.value),
|
||||
"NormalLogic": _bool_rule(self.world.options.difficulty.value >= 1),
|
||||
"NormalLogicAndDoubleJump": _bool_rule(self.world.options.difficulty.value >= 1
|
||||
and bool(self.world.options.purified_hand.value)),
|
||||
"HardLogic": _bool_rule(self.world.options.difficulty.value >= 2),
|
||||
"HardLogicAndDoubleJump": _bool_rule(self.world.options.difficulty.value >= 2
|
||||
and bool(self.world.options.purified_hand.value)),
|
||||
"EnemySkips": _bool_rule(self.enemy_skips_allowed),
|
||||
"EnemySkipsAndDoubleJump": _bool_rule(self.enemy_skips_allowed and self.world.options.purified_hand.value),
|
||||
|
||||
# Relics
|
||||
"blood": self.blood,
|
||||
@@ -52,20 +168,20 @@ class BlasRules:
|
||||
"cherubs20": lambda state: self.cherubs(state) >= 20,
|
||||
"cherubs38": lambda state: self.cherubs(state) >= 38,
|
||||
|
||||
"bones4": lambda state: self.bones(state) >= 4,
|
||||
"bones8": lambda state: self.bones(state) >= 8,
|
||||
"bones12": lambda state: self.bones(state) >= 12,
|
||||
"bones16": lambda state: self.bones(state) >= 16,
|
||||
"bones20": lambda state: self.bones(state) >= 20,
|
||||
"bones24": lambda state: self.bones(state) >= 24,
|
||||
"bones28": lambda state: self.bones(state) >= 28,
|
||||
"bones30": lambda state: self.bones(state) >= 30,
|
||||
"bones32": lambda state: self.bones(state) >= 32,
|
||||
"bones36": lambda state: self.bones(state) >= 36,
|
||||
"bones40": lambda state: self.bones(state) >= 40,
|
||||
"bones44": lambda state: self.bones(state) >= 44,
|
||||
"bones4": lambda state: self.bones(state, 4),
|
||||
"bones8": lambda state: self.bones(state, 8),
|
||||
"bones12": lambda state: self.bones(state, 12),
|
||||
"bones16": lambda state: self.bones(state, 16),
|
||||
"bones20": lambda state: self.bones(state, 20),
|
||||
"bones24": lambda state: self.bones(state, 24),
|
||||
"bones28": lambda state: self.bones(state, 28),
|
||||
"bones30": lambda state: self.bones(state, 30),
|
||||
"bones32": lambda state: self.bones(state, 32),
|
||||
"bones36": lambda state: self.bones(state, 36),
|
||||
"bones40": lambda state: self.bones(state, 40),
|
||||
"bones44": lambda state: self.bones(state, 44),
|
||||
|
||||
"tears0": lambda state: True,
|
||||
"tears0": _always,
|
||||
|
||||
# Special items
|
||||
"dash": self.dash,
|
||||
@@ -118,13 +234,13 @@ class BlasRules:
|
||||
# skip "dive"
|
||||
# skip "lunge"
|
||||
"chargeBeam": self.charge_beam,
|
||||
"rangedAttack": lambda state: self.ranged(state) > 0,
|
||||
"rangedAttack": self.ranged,
|
||||
|
||||
# Main quest
|
||||
"holyWounds3": lambda state: self.holy_wounds(state) >= 3,
|
||||
"masks1": lambda state: self.masks(state) >= 1,
|
||||
"masks2": lambda state: self.masks(state) >= 2,
|
||||
"masks3": lambda state: self.masks(state) >= 3,
|
||||
"holyWounds3": lambda state: self.holy_wounds(state, 3),
|
||||
"masks1": lambda state: self.masks(state, 1),
|
||||
"masks2": lambda state: self.masks(state, 2),
|
||||
"masks3": lambda state: self.masks(state, 3),
|
||||
"guiltBead": self.guilt_bead,
|
||||
|
||||
# LOTL quest
|
||||
@@ -133,17 +249,17 @@ class BlasRules:
|
||||
"hatchedEgg": self.hatched_egg,
|
||||
|
||||
# Tirso quest
|
||||
"herbs1": lambda state: self.herbs(state) >= 1,
|
||||
"herbs2": lambda state: self.herbs(state) >= 2,
|
||||
"herbs3": lambda state: self.herbs(state) >= 3,
|
||||
"herbs4": lambda state: self.herbs(state) >= 4,
|
||||
"herbs5": lambda state: self.herbs(state) >= 5,
|
||||
"herbs6": lambda state: self.herbs(state) >= 6,
|
||||
"herbs1": lambda state: self.herbs(state, 1),
|
||||
"herbs2": lambda state: self.herbs(state, 2),
|
||||
"herbs3": lambda state: self.herbs(state, 3),
|
||||
"herbs4": lambda state: self.herbs(state, 4),
|
||||
"herbs5": lambda state: self.herbs(state, 5),
|
||||
"herbs6": lambda state: self.herbs(state, 6),
|
||||
|
||||
# Tentudia quest
|
||||
"tentudiaRemains1": lambda state: self.tentudia_remains(state) >= 1,
|
||||
"tentudiaRemains2": lambda state: self.tentudia_remains(state) >= 2,
|
||||
"tentudiaRemains3": lambda state: self.tentudia_remains(state) >= 3,
|
||||
"tentudiaRemains1": lambda state: self.tentudia_remains(state, 1),
|
||||
"tentudiaRemains2": lambda state: self.tentudia_remains(state, 2),
|
||||
"tentudiaRemains3": lambda state: self.tentudia_remains(state, 3),
|
||||
|
||||
# Gemino quest
|
||||
"emptyThimble": self.empty_thimble,
|
||||
@@ -151,7 +267,7 @@ class BlasRules:
|
||||
"driedFlowers": self.dried_flowers,
|
||||
|
||||
# Altasgracias quest
|
||||
"ceremonyItems3": lambda state: self.ceremony_items(state) >= 3,
|
||||
"ceremonyItems3": lambda state: self.ceremony_items(state, 3),
|
||||
"egg": self.egg,
|
||||
|
||||
# Redento quest
|
||||
@@ -159,13 +275,13 @@ class BlasRules:
|
||||
# skip "knots", not actually used
|
||||
|
||||
# Cleofas quest
|
||||
"marksOfRefuge3": lambda state: self.marks_of_refuge(state) >= 3,
|
||||
"marksOfRefuge3": lambda state: self.marks_of_refuge(state, 3),
|
||||
"cord": self.cord,
|
||||
|
||||
# Crisanta quest
|
||||
"scapular": self.scapular,
|
||||
"trueHeart": self.true_heart,
|
||||
"traitorEyes2": lambda state: self.traitor_eyes(state) >= 2,
|
||||
"traitorEyes2": lambda state: self.traitor_eyes(state, 2),
|
||||
|
||||
# Jibrael quest
|
||||
"bell": self.bell,
|
||||
@@ -190,32 +306,32 @@ class BlasRules:
|
||||
"canSurvivePoison3": self.can_survive_poison_3,
|
||||
|
||||
# Enemy tech
|
||||
"canEnemyBounce": self.can_enemy_bounce,
|
||||
"canEnemyBounce": _bool_rule(self.can_enemy_bounce),
|
||||
"canEnemyUpslash": self.can_enemy_upslash,
|
||||
|
||||
# Reaching rooms
|
||||
"guiltRooms1": lambda state: self.guilt_rooms(state) >= 1,
|
||||
"guiltRooms2": lambda state: self.guilt_rooms(state) >= 2,
|
||||
"guiltRooms3": lambda state: self.guilt_rooms(state) >= 3,
|
||||
"guiltRooms4": lambda state: self.guilt_rooms(state) >= 4,
|
||||
"guiltRooms5": lambda state: self.guilt_rooms(state) >= 5,
|
||||
"guiltRooms6": lambda state: self.guilt_rooms(state) >= 6,
|
||||
"guiltRooms7": lambda state: self.guilt_rooms(state) >= 7,
|
||||
"guiltRooms1": lambda state: self.guilt_rooms(state, 1),
|
||||
"guiltRooms2": lambda state: self.guilt_rooms(state, 2),
|
||||
"guiltRooms3": lambda state: self.guilt_rooms(state, 3),
|
||||
"guiltRooms4": lambda state: self.guilt_rooms(state, 4),
|
||||
"guiltRooms5": lambda state: self.guilt_rooms(state, 5),
|
||||
"guiltRooms6": lambda state: self.guilt_rooms(state, 6),
|
||||
"guiltRooms7": lambda state: self.guilt_rooms(state, 7),
|
||||
|
||||
"swordRooms1": lambda state: self.sword_rooms(state) >= 1,
|
||||
"swordRooms2": lambda state: self.sword_rooms(state) >= 2,
|
||||
"swordRooms3": lambda state: self.sword_rooms(state) >= 3,
|
||||
"swordRooms4": lambda state: self.sword_rooms(state) >= 4,
|
||||
"swordRooms5": lambda state: self.sword_rooms(state) >= 5,
|
||||
"swordRooms6": lambda state: self.sword_rooms(state) >= 6,
|
||||
"swordRooms7": lambda state: self.sword_rooms(state) >= 7,
|
||||
"swordRooms1": lambda state: self.sword_rooms(state, 1),
|
||||
"swordRooms2": lambda state: self.sword_rooms(state, 2),
|
||||
"swordRooms3": lambda state: self.sword_rooms(state, 3),
|
||||
"swordRooms4": lambda state: self.sword_rooms(state, 4),
|
||||
"swordRooms5": lambda state: self.sword_rooms(state, 5),
|
||||
"swordRooms6": lambda state: self.sword_rooms(state, 6),
|
||||
"swordRooms7": lambda state: self.sword_rooms(state, 7),
|
||||
|
||||
"redentoRooms2": lambda state: self.redento_rooms(state) >= 2,
|
||||
"redentoRooms3": lambda state: self.redento_rooms(state) >= 3,
|
||||
"redentoRooms4": lambda state: self.redento_rooms(state) >= 4,
|
||||
"redentoRooms5": lambda state: self.redento_rooms(state) >= 5,
|
||||
"redentoRooms2": lambda state: self.redento_rooms(state, 2),
|
||||
"redentoRooms3": lambda state: self.redento_rooms(state, 3),
|
||||
"redentoRooms4": lambda state: self.redento_rooms(state, 4),
|
||||
"redentoRooms5": lambda state: self.redento_rooms(state, 5),
|
||||
|
||||
"miriamRooms5": lambda state: self.miriam_rooms(state) >= 5,
|
||||
"miriamRooms5": self.all_miriam_rooms,
|
||||
|
||||
"amanecidaRooms1": lambda state: self.amanecida_rooms(state) >= 1,
|
||||
"amanecidaRooms2": lambda state: self.amanecida_rooms(state) >= 2,
|
||||
@@ -254,11 +370,11 @@ class BlasRules:
|
||||
"openedBotSSLadder": self.opened_botss_ladder,
|
||||
|
||||
# Special skips
|
||||
"upwarpSkipsAllowed": self.upwarp_skips_allowed,
|
||||
"mourningSkipAllowed": self.mourning_skip_allowed,
|
||||
"enemySkipsAllowed": self.enemy_skips_allowed,
|
||||
"obscureSkipsAllowed": self.obscure_skips_allowed,
|
||||
"preciseSkipsAllowed": self.precise_skips_allowed,
|
||||
"upwarpSkipsAllowed": _bool_rule(self.upwarp_skips_allowed),
|
||||
"mourningSkipAllowed": _bool_rule(self.mourning_skip_allowed),
|
||||
"enemySkipsAllowed": _bool_rule(self.enemy_skips_allowed),
|
||||
"obscureSkipsAllowed": _bool_rule(self.obscure_skips_allowed),
|
||||
"preciseSkipsAllowed": _bool_rule(self.precise_skips_allowed),
|
||||
|
||||
# Bosses
|
||||
"canBeatBrotherhoodBoss": self.can_beat_brotherhood_boss,
|
||||
@@ -498,30 +614,74 @@ class BlasRules:
|
||||
|
||||
def load_rule(self, obj_is_region: bool, name: str, obj: Dict[str, Any]) -> Callable[[CollectionState], bool]:
|
||||
clauses = []
|
||||
clauses_are_impossible_if_empty = False
|
||||
rule_indirect_conditions = []
|
||||
for clause in obj["logic"]:
|
||||
reqs = []
|
||||
clause_indirect_conditions = []
|
||||
clause_is_impossible = False
|
||||
for req in clause["item_requirements"]:
|
||||
if self.req_is_region(req):
|
||||
if obj_is_region:
|
||||
# add to indirect conditions if object and requirement are doors
|
||||
self.indirect_conditions.append((req, f"{name} -> {obj['target']}"))
|
||||
clause_indirect_conditions.append((req, f"{name} -> {obj['target']}"))
|
||||
reqs.append(lambda state, req=req: state.can_reach_region(req, self.player))
|
||||
else:
|
||||
string_rule = self.string_rules[req]
|
||||
if string_rule is _never:
|
||||
# This clause is not possible with the options this player has chosen.
|
||||
clause_is_impossible = True
|
||||
break
|
||||
elif string_rule is _always:
|
||||
# Don't need to add a rule that is always True with the options this player has chosen.
|
||||
# Continue to the next requirement.
|
||||
continue
|
||||
if obj_is_region and req in self.indirect_regions:
|
||||
# add to indirect conditions if object is door and requirement has list of regions
|
||||
for region in self.indirect_regions[req]:
|
||||
self.indirect_conditions.append((region, f"{name} -> {obj['target']}"))
|
||||
clause_indirect_conditions.append((region, f"{name} -> {obj['target']}"))
|
||||
reqs.append(self.string_rules[req])
|
||||
if clause_is_impossible:
|
||||
# At least one clause was impossible, so if all clauses were impossible, the entire rule is impossible.
|
||||
clauses_are_impossible_if_empty = True
|
||||
# Continue to the next clause.
|
||||
continue
|
||||
rule_indirect_conditions.extend(clause_indirect_conditions)
|
||||
|
||||
# Combine the requirements if there are multiple.
|
||||
# Requirements are AND-ed together.
|
||||
if len(reqs) == 1:
|
||||
clauses.append(reqs[0])
|
||||
else:
|
||||
clauses.append(lambda state, reqs=reqs: all(req(state) for req in reqs))
|
||||
def req_func(state, reqs=reqs):
|
||||
for req in reqs:
|
||||
if not req(state):
|
||||
return False
|
||||
return True
|
||||
clauses.append(req_func)
|
||||
|
||||
# Combine the clauses if there are multiple.
|
||||
# Clauses are OR-ed together.
|
||||
if not clauses:
|
||||
return lambda state: True
|
||||
# There is no need to register the indirect conditions if it turns out the rule is impossible or always
|
||||
# possible.
|
||||
rule_indirect_conditions.clear()
|
||||
if clauses_are_impossible_if_empty:
|
||||
to_return = _never
|
||||
else:
|
||||
to_return = _always
|
||||
elif len(clauses) == 1:
|
||||
return clauses[0]
|
||||
to_return = clauses[0]
|
||||
else:
|
||||
return lambda state: any(clause(state) for clause in clauses)
|
||||
def clause_func(state, clauses=clauses):
|
||||
for clause in clauses:
|
||||
if clause(state):
|
||||
return True
|
||||
return False
|
||||
to_return = clause_func
|
||||
# Update the list of indirect conditions to add.
|
||||
self.indirect_conditions.extend(rule_indirect_conditions)
|
||||
return to_return
|
||||
|
||||
# Relics
|
||||
def blood(self, state: CollectionState) -> bool:
|
||||
@@ -565,8 +725,10 @@ class BlasRules:
|
||||
def cherubs(self, state: CollectionState) -> int:
|
||||
return state.count("Child of Moonlight", self.player)
|
||||
|
||||
def bones(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("bones", self.player)
|
||||
def bones(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "bones" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("bones", self.player, count)
|
||||
|
||||
# def tears():
|
||||
|
||||
@@ -594,7 +756,7 @@ class BlasRules:
|
||||
|
||||
# Health boosts
|
||||
def flasks(self, state: CollectionState) -> int:
|
||||
doors = {
|
||||
doors = (
|
||||
"D01Z05S05[SW]",
|
||||
"D02Z02S04[W]",
|
||||
"D03Z02S08[W]",
|
||||
@@ -602,10 +764,11 @@ class BlasRules:
|
||||
"D04Z02S13[W]",
|
||||
"D05Z01S08[NW]",
|
||||
"D20Z01S07[NE]"
|
||||
}
|
||||
|
||||
return state.count("Empty Bile Vessel", self.player) \
|
||||
if sum(state.can_reach_region(door, self.player) for door in doors) >= 1 else 0
|
||||
)
|
||||
for door in doors:
|
||||
if state.can_reach_region(door, self.player):
|
||||
return state.count("Empty Bile Vessel", self.player)
|
||||
return 0
|
||||
|
||||
def quicksilver(self, state: CollectionState) -> int:
|
||||
return state.count("Quicksilver", self.player) if state.can_reach_region("D01Z05S01[W]", self.player) else 0
|
||||
@@ -613,7 +776,7 @@ class BlasRules:
|
||||
# Puzzles
|
||||
def red_wax(self, state: CollectionState) -> int:
|
||||
return state.count("Bead of Red Wax", self.player)
|
||||
|
||||
|
||||
def blue_wax(self, state: CollectionState) -> int:
|
||||
return state.count("Bead of Blue Wax", self.player)
|
||||
|
||||
@@ -670,7 +833,7 @@ class BlasRules:
|
||||
or self.cante(state)
|
||||
or self.cantina(state)
|
||||
or self.tiento(state)
|
||||
or state.has_any({
|
||||
or state.has_any((
|
||||
"Campanillero to the Sons of the Aurora",
|
||||
"Mirabras of the Return to Port",
|
||||
"Romance to the Crimson Mist",
|
||||
@@ -678,7 +841,7 @@ class BlasRules:
|
||||
"Seguiriya to your Eyes like Stars",
|
||||
"Verdiales of the Forsaken Hamlet",
|
||||
"Zambra to the Resplendent Crown"
|
||||
}, self.player)
|
||||
), self.player)
|
||||
)
|
||||
|
||||
def pillar(self, state: CollectionState) -> bool:
|
||||
@@ -710,8 +873,8 @@ class BlasRules:
|
||||
def charged(self, state: CollectionState) -> int:
|
||||
return state.count("Charged Skill", self.player)
|
||||
|
||||
def ranged(self, state: CollectionState) -> int:
|
||||
return state.count("Ranged Skill", self.player)
|
||||
def ranged(self, state: CollectionState) -> bool:
|
||||
return state.has("Ranged Skill", self.player)
|
||||
|
||||
def dive(self, state: CollectionState) -> int:
|
||||
return state.count("Dive Skill", self.player)
|
||||
@@ -723,11 +886,15 @@ class BlasRules:
|
||||
return self.charged(state) >= 3
|
||||
|
||||
# Main quest
|
||||
def holy_wounds(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("wounds", self.player)
|
||||
def holy_wounds(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "wounds" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("wounds", self.player, count)
|
||||
|
||||
def masks(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("masks", self.player)
|
||||
def masks(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "masks" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("masks", self.player, count)
|
||||
|
||||
def guilt_bead(self, state: CollectionState) -> bool:
|
||||
return state.has("Weight of True Guilt", self.player)
|
||||
@@ -743,12 +910,16 @@ class BlasRules:
|
||||
return state.has("Hatched Egg of Deformity", self.player)
|
||||
|
||||
# Tirso quest
|
||||
def herbs(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("tirso", self.player)
|
||||
def herbs(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "tirso" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("tirso", self.player, count)
|
||||
|
||||
# Tentudia quest
|
||||
def tentudia_remains(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("tentudia", self.player)
|
||||
def tentudia_remains(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "tentudia" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("tentudia", self.player, count)
|
||||
|
||||
# Gemino quest
|
||||
def empty_thimble(self, state: CollectionState) -> bool:
|
||||
@@ -761,23 +932,29 @@ class BlasRules:
|
||||
return state.has("Dried Flowers bathed in Tears", self.player)
|
||||
|
||||
# Altasgracias quest
|
||||
def ceremony_items(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("egg", self.player)
|
||||
def ceremony_items(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "egg" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("egg", self.player, count)
|
||||
|
||||
def egg(self, state: CollectionState) -> bool:
|
||||
return state.has("Egg of Deformity", self.player)
|
||||
|
||||
# Redento quest
|
||||
def limestones(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("toe", self.player)
|
||||
def limestones(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "toe" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("toe", self.player, count)
|
||||
|
||||
def knots(self, state: CollectionState) -> int:
|
||||
return state.count("Knot of Rosary Rope", self.player) if state.can_reach_region("D17Z01S07[NW]", self.player)\
|
||||
else 0
|
||||
|
||||
# Cleofas quest
|
||||
def marks_of_refuge(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("marks", self.player)
|
||||
def marks_of_refuge(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "marks" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("marks", self.player, count)
|
||||
|
||||
def cord(self, state: CollectionState) -> bool:
|
||||
return state.has("Cord of the True Burying", self.player)
|
||||
@@ -789,8 +966,10 @@ class BlasRules:
|
||||
def true_heart(self, state: CollectionState) -> bool:
|
||||
return state.has("Apodictic Heart of Mea Culpa", self.player)
|
||||
|
||||
def traitor_eyes(self, state: CollectionState) -> int:
|
||||
return state.count_group_unique("eye", self.player)
|
||||
def traitor_eyes(self, state: CollectionState, count: int) -> bool:
|
||||
# Count of unique items in the "eye" item group that have been collected into state.
|
||||
# BlasphemousWorld.collect/remove adjust the count when items in the group are collected/removed.
|
||||
return state.has("eye", self.player, count)
|
||||
|
||||
# Jibrael quest
|
||||
def bell(self, state: CollectionState) -> bool:
|
||||
@@ -800,19 +979,6 @@ class BlasRules:
|
||||
return state.count("Verses Spun from Gold", self.player)
|
||||
|
||||
# Movement tech
|
||||
def can_air_stall(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.ranged(state) > 0
|
||||
and self.world.options.difficulty >= 1
|
||||
)
|
||||
|
||||
def can_dawn_jump(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.dawn_heart(state)
|
||||
and self.dash(state)
|
||||
and self.world.options.difficulty >= 1
|
||||
)
|
||||
|
||||
def can_water_jump(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.nail(state)
|
||||
@@ -828,12 +994,6 @@ class BlasRules:
|
||||
or self.can_use_any_prayer(state)
|
||||
)
|
||||
|
||||
def can_dive_laser(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.dive(state) >= 3
|
||||
and self.world.options.difficulty >= 2
|
||||
)
|
||||
|
||||
# Root tech
|
||||
def can_walk_on_root(self, state: CollectionState) -> bool:
|
||||
return self.root(state)
|
||||
@@ -844,40 +1004,6 @@ class BlasRules:
|
||||
and self.wall_climb(state)
|
||||
)
|
||||
|
||||
# Lung tech
|
||||
def can_survive_poison_1(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.lung(state)
|
||||
or self.world.options.difficulty >= 1
|
||||
and self.tiento(state)
|
||||
or self.world.options.difficulty >= 2
|
||||
)
|
||||
|
||||
def can_survive_poison_2(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.lung(state)
|
||||
or self.world.options.difficulty >= 1
|
||||
and self.tiento(state)
|
||||
)
|
||||
|
||||
def can_survive_poison_3(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.lung(state)
|
||||
or self.world.options.difficulty >= 2
|
||||
and self.tiento(state)
|
||||
and self.total_fervour(state) >= 120
|
||||
)
|
||||
|
||||
# Enemy tech
|
||||
def can_enemy_bounce(self, state: CollectionState) -> bool:
|
||||
return self.enemy_skips_allowed(state)
|
||||
|
||||
def can_enemy_upslash(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.combo(state) >= 2
|
||||
and self.enemy_skips_allowed(state)
|
||||
)
|
||||
|
||||
# Crossing gaps
|
||||
def can_cross_gap_1(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
@@ -1021,7 +1147,7 @@ class BlasRules:
|
||||
or state.can_reach_region("D03Z02S03[E]", self.player)
|
||||
and (
|
||||
self.can_cross_gap_5(state)
|
||||
or self.can_enemy_bounce(state)
|
||||
or self.can_enemy_bounce
|
||||
and self.can_cross_gap_3(state)
|
||||
)
|
||||
)
|
||||
@@ -1067,25 +1193,6 @@ class BlasRules:
|
||||
or state.can_reach_region("D17BZ02S01[FrontR]", self.player)
|
||||
)
|
||||
|
||||
# Special skips
|
||||
def upwarp_skips_allowed(self, state: CollectionState) -> bool:
|
||||
return self.world.options.difficulty >= 2
|
||||
|
||||
def mourning_skip_allowed(self, state: CollectionState) -> bool:
|
||||
return self.world.options.difficulty >= 2
|
||||
|
||||
def enemy_skips_allowed(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
self.world.options.difficulty >= 2
|
||||
and not self.world.options.enemy_randomizer
|
||||
)
|
||||
|
||||
def obscure_skips_allowed(self, state: CollectionState) -> bool:
|
||||
return self.world.options.difficulty >= 2
|
||||
|
||||
def precise_skips_allowed(self, state: CollectionState) -> bool:
|
||||
return self.world.options.difficulty >= 2
|
||||
|
||||
# Bosses
|
||||
def can_beat_brotherhood_boss(self, state: CollectionState) -> bool:
|
||||
return (
|
||||
@@ -1183,18 +1290,18 @@ class BlasRules:
|
||||
and state.can_reach_region("D20Z02S07[W]", self.player)
|
||||
)
|
||||
|
||||
def can_beat_graveyard_boss(self, state: CollectionState) -> bool:
|
||||
def can_beat_graveyard_boss(self, state: CollectionState, player_strength: float | None = None) -> bool:
|
||||
return (
|
||||
self.has_boss_strength(state, "amanecida")
|
||||
self.has_boss_strength(state, "amanecida", player_strength)
|
||||
and self.wall_climb(state)
|
||||
and state.can_reach_region("D01Z06S01[Santos]", self.player)
|
||||
and state.can_reach_region("D02Z03S18[NW]", self.player)
|
||||
and state.can_reach_region("D02Z02S03[NE]", self.player)
|
||||
)
|
||||
|
||||
def can_beat_jondo_boss(self, state: CollectionState) -> bool:
|
||||
def can_beat_jondo_boss(self, state: CollectionState, player_strength: float | None = None) -> bool:
|
||||
return (
|
||||
self.has_boss_strength(state, "amanecida")
|
||||
self.has_boss_strength(state, "amanecida", player_strength)
|
||||
and state.can_reach_region("D01Z06S01[Santos]", self.player)
|
||||
and (
|
||||
state.can_reach_region("D20Z01S06[NE]", self.player)
|
||||
@@ -1206,9 +1313,9 @@ class BlasRules:
|
||||
)
|
||||
)
|
||||
|
||||
def can_beat_patio_boss(self, state: CollectionState) -> bool:
|
||||
def can_beat_patio_boss(self, state: CollectionState, player_strength: float | None = None) -> bool:
|
||||
return (
|
||||
self.has_boss_strength(state, "amanecida")
|
||||
self.has_boss_strength(state, "amanecida", player_strength)
|
||||
and state.can_reach_region("D01Z06S01[Santos]", self.player)
|
||||
and state.can_reach_region("D06Z01S02[W]", self.player)
|
||||
and (
|
||||
@@ -1218,9 +1325,9 @@ class BlasRules:
|
||||
)
|
||||
)
|
||||
|
||||
def can_beat_wall_boss(self, state: CollectionState) -> bool:
|
||||
def can_beat_wall_boss(self, state: CollectionState, player_strength: float | None = None) -> bool:
|
||||
return (
|
||||
self.has_boss_strength(state, "amanecida")
|
||||
self.has_boss_strength(state, "amanecida", player_strength)
|
||||
and state.can_reach_region("D01Z06S01[Santos]", self.player)
|
||||
and state.can_reach_region("D09Z01S09[Cell24]", self.player)
|
||||
and (
|
||||
@@ -1244,8 +1351,7 @@ class BlasRules:
|
||||
def can_beat_legionary(self, state: CollectionState) -> bool:
|
||||
return self.has_boss_strength(state, "legionary")
|
||||
|
||||
|
||||
def has_boss_strength(self, state: CollectionState, boss: str) -> bool:
|
||||
def get_player_strength(self, state: CollectionState) -> float:
|
||||
life: int = state.count("Life Upgrade", self.player)
|
||||
sword: int = state.count("Mea Culpa Upgrade", self.player)
|
||||
fervour: int = state.count("Fervour Upgrade", self.player)
|
||||
@@ -1259,30 +1365,16 @@ class BlasRules:
|
||||
+ min(8, flasks) * 0.15 / 8
|
||||
+ min(5, quicksilver) * 0.15 / 5
|
||||
)
|
||||
return player_strength
|
||||
|
||||
bosses: Dict[str, float] = {
|
||||
"warden": -0.10,
|
||||
"ten-piedad": 0.05,
|
||||
"charred-visage": 0.20,
|
||||
"tres-angustias": 0.15,
|
||||
"esdras": 0.25,
|
||||
"melquiades": 0.25,
|
||||
"exposito": 0.30,
|
||||
"quirce": 0.35,
|
||||
"crisanta": 0.50,
|
||||
"isidora": 0.70,
|
||||
"sierpes": 0.70,
|
||||
"amanecida": 0.60,
|
||||
"laudes": 0.60,
|
||||
"perpetua": -0.05,
|
||||
"legionary": 0.20
|
||||
}
|
||||
boss_strength: float = bosses[boss]
|
||||
return player_strength >= (boss_strength - 0.10 if self.world.options.difficulty >= 2 else
|
||||
(boss_strength if self.world.options.difficulty >= 1 else boss_strength + 0.10))
|
||||
def has_boss_strength(self, state: CollectionState, boss: str, player_strength: float | None = None) -> bool:
|
||||
if player_strength is None:
|
||||
return self.get_player_strength(state) >= self.boss_strengths[boss]
|
||||
else:
|
||||
return player_strength >= self.boss_strengths[boss]
|
||||
|
||||
def guilt_rooms(self, state: CollectionState) -> int:
|
||||
doors = [
|
||||
def guilt_rooms(self, state: CollectionState, count: int) -> bool:
|
||||
doors = (
|
||||
"D01Z04S01[NE]",
|
||||
"D02Z02S11[W]",
|
||||
"D03Z03S02[NE]",
|
||||
@@ -1290,20 +1382,25 @@ class BlasRules:
|
||||
"D05Z01S05[NE]",
|
||||
"D09Z01S05[W]",
|
||||
"D17Z01S04[W]",
|
||||
]
|
||||
)
|
||||
|
||||
return sum(state.can_reach_region(door, self.player) for door in doors)
|
||||
total: int = 0
|
||||
for door in doors:
|
||||
total += state.can_reach_region(door, self.player)
|
||||
if total >= count:
|
||||
return True
|
||||
return False
|
||||
|
||||
def sword_rooms(self, state: CollectionState) -> int:
|
||||
doors = [
|
||||
["D01Z02S07[E]", "D01Z02S02[SW]"],
|
||||
["D20Z01S04[E]", "D01Z05S23[W]"],
|
||||
["D02Z03S02[NE]"],
|
||||
["D04Z02S21[NE]"],
|
||||
["D05Z01S21[NW]"],
|
||||
["D06Z01S15[NE]"],
|
||||
["D17Z01S07[SW]"]
|
||||
]
|
||||
def sword_rooms(self, state: CollectionState, count: int) -> bool:
|
||||
doors = (
|
||||
("D01Z02S07[E]", "D01Z02S02[SW]"),
|
||||
("D20Z01S04[E]", "D01Z05S23[W]"),
|
||||
("D02Z03S02[NE]",),
|
||||
("D04Z02S21[NE]",),
|
||||
("D05Z01S21[NW]",),
|
||||
("D06Z01S15[NE]",),
|
||||
("D17Z01S07[SW]",)
|
||||
)
|
||||
|
||||
total: int = 0
|
||||
for subdoors in doors:
|
||||
@@ -1311,72 +1408,90 @@ class BlasRules:
|
||||
if state.can_reach_region(door, self.player):
|
||||
total += 1
|
||||
break
|
||||
if total >= count:
|
||||
return True
|
||||
|
||||
return total
|
||||
return False
|
||||
|
||||
def redento_rooms(self, state: CollectionState) -> int:
|
||||
if (
|
||||
state.can_reach_region("D03Z01S04[E]", self.player)
|
||||
or state.can_reach_region("D03Z02S10[N]", self.player)
|
||||
def redento_rooms(self, state: CollectionState, count: int) -> bool:
|
||||
if not (
|
||||
state.can_reach_region("D03Z01S04[E]", self.player)
|
||||
or state.can_reach_region("D03Z02S10[N]", self.player)
|
||||
):
|
||||
if (
|
||||
# Realistically, count should never be zero or negative.
|
||||
return count < 1
|
||||
|
||||
if count == 1:
|
||||
return True
|
||||
|
||||
if not (
|
||||
state.can_reach_region("D17Z01S05[S]", self.player)
|
||||
or state.can_reach_region("D17BZ02S01[FrontR]", self.player)
|
||||
):
|
||||
if (
|
||||
state.can_reach_region("D01Z03S04[E]", self.player)
|
||||
or state.can_reach_region("D08Z01S01[W]", self.player)
|
||||
):
|
||||
if (
|
||||
state.can_reach_region("D04Z01S03[E]", self.player)
|
||||
or state.can_reach_region("D04Z02S01[W]", self.player)
|
||||
or state.can_reach_region("D06Z01S18[-Cherubs]", self.player)
|
||||
):
|
||||
if (
|
||||
self.knots(state) >= 1
|
||||
and self.limestones(state) >= 3
|
||||
and (
|
||||
state.can_reach_region("D04Z02S08[E]", self.player)
|
||||
or state.can_reach_region("D04BZ02S01[Redento]", self.player)
|
||||
)
|
||||
):
|
||||
return 5
|
||||
return 4
|
||||
return 3
|
||||
return 2
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def miriam_rooms(self, state: CollectionState) -> int:
|
||||
doors = [
|
||||
):
|
||||
return False
|
||||
|
||||
if count == 2:
|
||||
return True
|
||||
|
||||
if not (state.can_reach_region("D01Z03S04[E]", self.player)
|
||||
or state.can_reach_region("D08Z01S01[W]", self.player)):
|
||||
return False
|
||||
|
||||
if count == 3:
|
||||
return True
|
||||
|
||||
if not (state.can_reach_region("D04Z01S03[E]", self.player)
|
||||
or state.can_reach_region("D04Z02S01[W]", self.player)
|
||||
or state.can_reach_region("D06Z01S18[-Cherubs]", self.player)):
|
||||
return False
|
||||
|
||||
if count == 4:
|
||||
return True
|
||||
|
||||
if not (
|
||||
self.knots(state) >= 1
|
||||
and self.limestones(state, 3)
|
||||
and (state.can_reach_region("D04Z02S08[E]", self.player)
|
||||
or state.can_reach_region("D04BZ02S01[Redento]", self.player))
|
||||
):
|
||||
return False
|
||||
|
||||
return count == 5
|
||||
|
||||
def all_miriam_rooms(self, state: CollectionState) -> bool:
|
||||
doors = (
|
||||
"D02Z03S07[NWW]",
|
||||
"D03Z03S07[NW]",
|
||||
"D04Z04S01[E]",
|
||||
"D05Z01S06[W]",
|
||||
"D06Z01S17[E]"
|
||||
]
|
||||
)
|
||||
|
||||
return sum(state.can_reach_region(door, self.player) for door in doors)
|
||||
for door in doors:
|
||||
if not state.can_reach_region(door, self.player):
|
||||
return False
|
||||
return True
|
||||
|
||||
def amanecida_rooms(self, state: CollectionState) -> int:
|
||||
player_strength = self.get_player_strength(state)
|
||||
total: int = 0
|
||||
if self.can_beat_graveyard_boss(state):
|
||||
if self.can_beat_graveyard_boss(state, player_strength):
|
||||
total += 1
|
||||
if self.can_beat_jondo_boss(state):
|
||||
if self.can_beat_jondo_boss(state, player_strength):
|
||||
total += 1
|
||||
if self.can_beat_patio_boss(state):
|
||||
if self.can_beat_patio_boss(state, player_strength):
|
||||
total += 1
|
||||
if self.can_beat_wall_boss(state):
|
||||
if self.can_beat_wall_boss(state, player_strength):
|
||||
total += 1
|
||||
|
||||
return total
|
||||
|
||||
def chalice_rooms(self, state: CollectionState) -> int:
|
||||
doors = [
|
||||
["D03Z01S02[E]", "D01Z05S02[W]", "D20Z01S03[N]"],
|
||||
["D05Z01S11[SE]", "D05Z02S02[NW]"],
|
||||
["D09Z01S09[E]", "D09Z01S10[W]", "D09Z01S08[SE]", "D09Z01S02[SW]"]
|
||||
]
|
||||
doors = (
|
||||
("D03Z01S02[E]", "D01Z05S02[W]", "D20Z01S03[N]"),
|
||||
("D05Z01S11[SE]", "D05Z02S02[NW]"),
|
||||
("D09Z01S09[E]", "D09Z01S10[W]", "D09Z01S08[SE]", "D09Z01S02[SW]")
|
||||
)
|
||||
|
||||
total: int = 0
|
||||
for subdoors in doors:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import Dict, List, Set, Any
|
||||
from collections import Counter
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
|
||||
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, CollectionState
|
||||
from Options import OptionError
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Items import base_id, item_table, group_table, tears_list, reliquary_set
|
||||
from .Items import base_id, item_table, group_table, tears_list, reliquary_set, group_table_reverse
|
||||
from .Locations import location_names
|
||||
from .Rules import BlasRules
|
||||
from worlds.generic.Rules import set_rule
|
||||
@@ -216,6 +216,27 @@ class BlasphemousWorld(World):
|
||||
for loc, item in option_dict.items():
|
||||
self.get_location(loc).place_locked_item(self.create_item(item))
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
changed = super().collect(state, item)
|
||||
if changed:
|
||||
name = item.name
|
||||
if name in group_table_reverse and state.count(name, self.player) == 1:
|
||||
# Count was 0 before super().collect().
|
||||
group_name = group_table_reverse[name]
|
||||
# Increase unique count for items in this group.
|
||||
state.prog_items[self.player][group_name] += 1
|
||||
return changed
|
||||
|
||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||
changed = super().remove(state, item)
|
||||
if changed:
|
||||
name = item.name
|
||||
if name in group_table_reverse and state.count(name, self.player) == 0:
|
||||
# Count was 1 before super().remove().
|
||||
group_name = group_table_reverse[name]
|
||||
# Decrease unique count for items in this group.
|
||||
state.prog_items[self.player][group_name] -= 1
|
||||
return changed
|
||||
|
||||
def create_regions(self) -> None:
|
||||
multiworld = self.multiworld
|
||||
|
||||
@@ -116,7 +116,7 @@ def versum_hill_rave(state: CollectionState, player: int, limit: bool, glitched:
|
||||
else:
|
||||
return (
|
||||
graffitiL(state, player, limit, 85)
|
||||
and graffitiXL(state, player, limit, 48)
|
||||
and graffitiXL(state, player, limit, 49)
|
||||
)
|
||||
else:
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"game": "Bomb Rush Cyberfunk",
|
||||
"world_version": "1.0.6",
|
||||
"authors": ["TRPG"]
|
||||
}
|
||||
+105
-105
@@ -16,213 +16,213 @@ from .Locations import (
|
||||
)
|
||||
|
||||
|
||||
def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
menu_region = Region("Menu", player, world, "Aranearum")
|
||||
world.regions.append(menu_region)
|
||||
def create_regions(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
menu_region = Region("Menu", player, multiworld, "Aranearum")
|
||||
multiworld.regions.append(menu_region)
|
||||
|
||||
start_camp_region = Region("Start Camp", player, world)
|
||||
start_camp_region = Region("Start Camp", player, multiworld)
|
||||
start_camp_region.add_locations(loc_start_camp, CCCharlesLocation)
|
||||
world.regions.append(start_camp_region)
|
||||
multiworld.regions.append(start_camp_region)
|
||||
|
||||
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, world)
|
||||
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, multiworld)
|
||||
tony_tiddle_mission_region.add_locations(loc_tony_tiddle_mission, CCCharlesLocation)
|
||||
world.regions.append(tony_tiddle_mission_region)
|
||||
multiworld.regions.append(tony_tiddle_mission_region)
|
||||
|
||||
barn_region = Region("Barn", player, world)
|
||||
barn_region = Region("Barn", player, multiworld)
|
||||
barn_region.add_locations(loc_barn, CCCharlesLocation)
|
||||
world.regions.append(barn_region)
|
||||
multiworld.regions.append(barn_region)
|
||||
|
||||
candice_mission_region = Region("Candice Mission", player, world)
|
||||
candice_mission_region = Region("Candice Mission", player, multiworld)
|
||||
candice_mission_region.add_locations(loc_candice_mission, CCCharlesLocation)
|
||||
world.regions.append(candice_mission_region)
|
||||
multiworld.regions.append(candice_mission_region)
|
||||
|
||||
tutorial_house_region = Region("Tutorial House", player, world)
|
||||
tutorial_house_region = Region("Tutorial House", player, multiworld)
|
||||
tutorial_house_region.add_locations(loc_tutorial_house, CCCharlesLocation)
|
||||
world.regions.append(tutorial_house_region)
|
||||
multiworld.regions.append(tutorial_house_region)
|
||||
|
||||
swamp_edges_region = Region("Swamp Edges", player, world)
|
||||
swamp_edges_region = Region("Swamp Edges", player, multiworld)
|
||||
swamp_edges_region.add_locations(loc_swamp_edges, CCCharlesLocation)
|
||||
world.regions.append(swamp_edges_region)
|
||||
multiworld.regions.append(swamp_edges_region)
|
||||
|
||||
swamp_mission_region = Region("Swamp Mission", player, world)
|
||||
swamp_mission_region = Region("Swamp Mission", player, multiworld)
|
||||
swamp_mission_region.add_locations(loc_swamp_mission, CCCharlesLocation)
|
||||
world.regions.append(swamp_mission_region)
|
||||
multiworld.regions.append(swamp_mission_region)
|
||||
|
||||
junkyard_area_region = Region("Junkyard Area", player, world)
|
||||
junkyard_area_region = Region("Junkyard Area", player, multiworld)
|
||||
junkyard_area_region.add_locations(loc_junkyard_area, CCCharlesLocation)
|
||||
world.regions.append(junkyard_area_region)
|
||||
multiworld.regions.append(junkyard_area_region)
|
||||
|
||||
south_house_region = Region("South House", player, world)
|
||||
south_house_region = Region("South House", player, multiworld)
|
||||
south_house_region.add_locations(loc_south_house, CCCharlesLocation)
|
||||
world.regions.append(south_house_region)
|
||||
multiworld.regions.append(south_house_region)
|
||||
|
||||
junkyard_shed_region = Region("Junkyard Shed", player, world)
|
||||
junkyard_shed_region = Region("Junkyard Shed", player, multiworld)
|
||||
junkyard_shed_region.add_locations(loc_junkyard_shed, CCCharlesLocation)
|
||||
world.regions.append(junkyard_shed_region)
|
||||
multiworld.regions.append(junkyard_shed_region)
|
||||
|
||||
military_base_region = Region("Military Base", player, world)
|
||||
military_base_region = Region("Military Base", player, multiworld)
|
||||
military_base_region.add_locations(loc_military_base, CCCharlesLocation)
|
||||
world.regions.append(military_base_region)
|
||||
multiworld.regions.append(military_base_region)
|
||||
|
||||
south_mine_outside_region = Region("South Mine Outside", player, world)
|
||||
south_mine_outside_region = Region("South Mine Outside", player, multiworld)
|
||||
south_mine_outside_region.add_locations(loc_south_mine_outside, CCCharlesLocation)
|
||||
world.regions.append(south_mine_outside_region)
|
||||
multiworld.regions.append(south_mine_outside_region)
|
||||
|
||||
south_mine_inside_region = Region("South Mine Inside", player, world)
|
||||
south_mine_inside_region = Region("South Mine Inside", player, multiworld)
|
||||
south_mine_inside_region.add_locations(loc_south_mine_inside, CCCharlesLocation)
|
||||
world.regions.append(south_mine_inside_region)
|
||||
multiworld.regions.append(south_mine_inside_region)
|
||||
|
||||
middle_station_region = Region("Middle Station", player, world)
|
||||
middle_station_region = Region("Middle Station", player, multiworld)
|
||||
middle_station_region.add_locations(loc_middle_station, CCCharlesLocation)
|
||||
world.regions.append(middle_station_region)
|
||||
multiworld.regions.append(middle_station_region)
|
||||
|
||||
canyon_region = Region("Canyon", player, world)
|
||||
canyon_region = Region("Canyon", player, multiworld)
|
||||
canyon_region.add_locations(loc_canyon, CCCharlesLocation)
|
||||
world.regions.append(canyon_region)
|
||||
multiworld.regions.append(canyon_region)
|
||||
|
||||
watchtower_region = Region("Watchtower", player, world)
|
||||
watchtower_region = Region("Watchtower", player, multiworld)
|
||||
watchtower_region.add_locations(loc_watchtower, CCCharlesLocation)
|
||||
world.regions.append(watchtower_region)
|
||||
multiworld.regions.append(watchtower_region)
|
||||
|
||||
boulder_field_region = Region("Boulder Field", player, world)
|
||||
boulder_field_region = Region("Boulder Field", player, multiworld)
|
||||
boulder_field_region.add_locations(loc_boulder_field, CCCharlesLocation)
|
||||
world.regions.append(boulder_field_region)
|
||||
multiworld.regions.append(boulder_field_region)
|
||||
|
||||
haunted_house_region = Region("Haunted House", player, world)
|
||||
haunted_house_region = Region("Haunted House", player, multiworld)
|
||||
haunted_house_region.add_locations(loc_haunted_house, CCCharlesLocation)
|
||||
world.regions.append(haunted_house_region)
|
||||
multiworld.regions.append(haunted_house_region)
|
||||
|
||||
santiago_house_region = Region("Santiago House", player, world)
|
||||
santiago_house_region = Region("Santiago House", player, multiworld)
|
||||
santiago_house_region.add_locations(loc_santiago_house, CCCharlesLocation)
|
||||
world.regions.append(santiago_house_region)
|
||||
multiworld.regions.append(santiago_house_region)
|
||||
|
||||
port_region = Region("Port", player, world)
|
||||
port_region = Region("Port", player, multiworld)
|
||||
port_region.add_locations(loc_port, CCCharlesLocation)
|
||||
world.regions.append(port_region)
|
||||
multiworld.regions.append(port_region)
|
||||
|
||||
trench_house_region = Region("Trench House", player, world)
|
||||
trench_house_region = Region("Trench House", player, multiworld)
|
||||
trench_house_region.add_locations(loc_trench_house, CCCharlesLocation)
|
||||
world.regions.append(trench_house_region)
|
||||
multiworld.regions.append(trench_house_region)
|
||||
|
||||
doll_woods_region = Region("Doll Woods", player, world)
|
||||
doll_woods_region = Region("Doll Woods", player, multiworld)
|
||||
doll_woods_region.add_locations(loc_doll_woods, CCCharlesLocation)
|
||||
world.regions.append(doll_woods_region)
|
||||
multiworld.regions.append(doll_woods_region)
|
||||
|
||||
lost_stairs_region = Region("Lost Stairs", player, world)
|
||||
lost_stairs_region = Region("Lost Stairs", player, multiworld)
|
||||
lost_stairs_region.add_locations(loc_lost_stairs, CCCharlesLocation)
|
||||
world.regions.append(lost_stairs_region)
|
||||
multiworld.regions.append(lost_stairs_region)
|
||||
|
||||
east_house_region = Region("East House", player, world)
|
||||
east_house_region = Region("East House", player, multiworld)
|
||||
east_house_region.add_locations(loc_east_house, CCCharlesLocation)
|
||||
world.regions.append(east_house_region)
|
||||
multiworld.regions.append(east_house_region)
|
||||
|
||||
rockets_testing_ground_region = Region("Rockets Testing Ground", player, world)
|
||||
rockets_testing_ground_region = Region("Rockets Testing Ground", player, multiworld)
|
||||
rockets_testing_ground_region.add_locations(loc_rockets_testing_ground, CCCharlesLocation)
|
||||
world.regions.append(rockets_testing_ground_region)
|
||||
multiworld.regions.append(rockets_testing_ground_region)
|
||||
|
||||
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, world)
|
||||
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, multiworld)
|
||||
rockets_testing_bunker_region.add_locations(loc_rockets_testing_bunker, CCCharlesLocation)
|
||||
world.regions.append(rockets_testing_bunker_region)
|
||||
multiworld.regions.append(rockets_testing_bunker_region)
|
||||
|
||||
workshop_region = Region("Workshop", player, world)
|
||||
workshop_region = Region("Workshop", player, multiworld)
|
||||
workshop_region.add_locations(loc_workshop, CCCharlesLocation)
|
||||
world.regions.append(workshop_region)
|
||||
multiworld.regions.append(workshop_region)
|
||||
|
||||
east_tower_region = Region("East Tower", player, world)
|
||||
east_tower_region = Region("East Tower", player, multiworld)
|
||||
east_tower_region.add_locations(loc_east_tower, CCCharlesLocation)
|
||||
world.regions.append(east_tower_region)
|
||||
multiworld.regions.append(east_tower_region)
|
||||
|
||||
lighthouse_region = Region("Lighthouse", player, world)
|
||||
lighthouse_region = Region("Lighthouse", player, multiworld)
|
||||
lighthouse_region.add_locations(loc_lighthouse, CCCharlesLocation)
|
||||
world.regions.append(lighthouse_region)
|
||||
multiworld.regions.append(lighthouse_region)
|
||||
|
||||
north_mine_outside_region = Region("North Mine Outside", player, world)
|
||||
north_mine_outside_region = Region("North Mine Outside", player, multiworld)
|
||||
north_mine_outside_region.add_locations(loc_north_mine_outside, CCCharlesLocation)
|
||||
world.regions.append(north_mine_outside_region)
|
||||
multiworld.regions.append(north_mine_outside_region)
|
||||
|
||||
north_mine_inside_region = Region("North Mine Inside", player, world)
|
||||
north_mine_inside_region = Region("North Mine Inside", player, multiworld)
|
||||
north_mine_inside_region.add_locations(loc_north_mine_inside, CCCharlesLocation)
|
||||
world.regions.append(north_mine_inside_region)
|
||||
multiworld.regions.append(north_mine_inside_region)
|
||||
|
||||
wood_bridge_region = Region("Wood Bridge", player, world)
|
||||
wood_bridge_region = Region("Wood Bridge", player, multiworld)
|
||||
wood_bridge_region.add_locations(loc_wood_bridge, CCCharlesLocation)
|
||||
world.regions.append(wood_bridge_region)
|
||||
multiworld.regions.append(wood_bridge_region)
|
||||
|
||||
museum_region = Region("Museum", player, world)
|
||||
museum_region = Region("Museum", player, multiworld)
|
||||
museum_region.add_locations(loc_museum, CCCharlesLocation)
|
||||
world.regions.append(museum_region)
|
||||
multiworld.regions.append(museum_region)
|
||||
|
||||
barbed_shelter_region = Region("Barbed Shelter", player, world)
|
||||
barbed_shelter_region = Region("Barbed Shelter", player, multiworld)
|
||||
barbed_shelter_region.add_locations(loc_barbed_shelter, CCCharlesLocation)
|
||||
world.regions.append(barbed_shelter_region)
|
||||
multiworld.regions.append(barbed_shelter_region)
|
||||
|
||||
west_beach_region = Region("West Beach", player, world)
|
||||
west_beach_region = Region("West Beach", player, multiworld)
|
||||
west_beach_region.add_locations(loc_west_beach, CCCharlesLocation)
|
||||
world.regions.append(west_beach_region)
|
||||
multiworld.regions.append(west_beach_region)
|
||||
|
||||
church_region = Region("Church", player, world)
|
||||
church_region = Region("Church", player, multiworld)
|
||||
church_region.add_locations(loc_church, CCCharlesLocation)
|
||||
world.regions.append(church_region)
|
||||
multiworld.regions.append(church_region)
|
||||
|
||||
west_cottage_region = Region("West Cottage", player, world)
|
||||
west_cottage_region = Region("West Cottage", player, multiworld)
|
||||
west_cottage_region.add_locations(loc_west_cottage, CCCharlesLocation)
|
||||
world.regions.append(west_cottage_region)
|
||||
multiworld.regions.append(west_cottage_region)
|
||||
|
||||
caravan_region = Region("Caravan", player, world)
|
||||
caravan_region = Region("Caravan", player, multiworld)
|
||||
caravan_region.add_locations(loc_caravan, CCCharlesLocation)
|
||||
world.regions.append(caravan_region)
|
||||
multiworld.regions.append(caravan_region)
|
||||
|
||||
trailer_cabin_region = Region("Trailer Cabin", player, world)
|
||||
trailer_cabin_region = Region("Trailer Cabin", player, multiworld)
|
||||
trailer_cabin_region.add_locations(loc_trailer_cabin, CCCharlesLocation)
|
||||
world.regions.append(trailer_cabin_region)
|
||||
multiworld.regions.append(trailer_cabin_region)
|
||||
|
||||
towers_region = Region("Towers", player, world)
|
||||
towers_region = Region("Towers", player, multiworld)
|
||||
towers_region.add_locations(loc_towers, CCCharlesLocation)
|
||||
world.regions.append(towers_region)
|
||||
multiworld.regions.append(towers_region)
|
||||
|
||||
north_beach_region = Region("North beach", player, world)
|
||||
north_beach_region = Region("North beach", player, multiworld)
|
||||
north_beach_region.add_locations(loc_north_beach, CCCharlesLocation)
|
||||
world.regions.append(north_beach_region)
|
||||
multiworld.regions.append(north_beach_region)
|
||||
|
||||
mine_shaft_region = Region("Mine Shaft", player, world)
|
||||
mine_shaft_region = Region("Mine Shaft", player, multiworld)
|
||||
mine_shaft_region.add_locations(loc_mine_shaft, CCCharlesLocation)
|
||||
world.regions.append(mine_shaft_region)
|
||||
multiworld.regions.append(mine_shaft_region)
|
||||
|
||||
mob_camp_region = Region("Mob Camp", player, world)
|
||||
mob_camp_region = Region("Mob Camp", player, multiworld)
|
||||
mob_camp_region.add_locations(loc_mob_camp, CCCharlesLocation)
|
||||
world.regions.append(mob_camp_region)
|
||||
multiworld.regions.append(mob_camp_region)
|
||||
|
||||
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, world)
|
||||
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, multiworld)
|
||||
mob_camp_locked_room_region.add_locations(loc_mob_camp_locked_room, CCCharlesLocation)
|
||||
world.regions.append(mob_camp_locked_room_region)
|
||||
multiworld.regions.append(mob_camp_locked_room_region)
|
||||
|
||||
mine_elevator_exit_region = Region("Mine Elevator Exit", player, world)
|
||||
mine_elevator_exit_region = Region("Mine Elevator Exit", player, multiworld)
|
||||
mine_elevator_exit_region.add_locations(loc_mine_elevator_exit, CCCharlesLocation)
|
||||
world.regions.append(mine_elevator_exit_region)
|
||||
multiworld.regions.append(mine_elevator_exit_region)
|
||||
|
||||
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, world)
|
||||
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, multiworld)
|
||||
mountain_ruin_outside_region.add_locations(loc_mountain_ruin_outside, CCCharlesLocation)
|
||||
world.regions.append(mountain_ruin_outside_region)
|
||||
multiworld.regions.append(mountain_ruin_outside_region)
|
||||
|
||||
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, world)
|
||||
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, multiworld)
|
||||
mountain_ruin_inside_region.add_locations(loc_mountain_ruin_inside, CCCharlesLocation)
|
||||
world.regions.append(mountain_ruin_inside_region)
|
||||
multiworld.regions.append(mountain_ruin_inside_region)
|
||||
|
||||
prism_temple_region = Region("Prism Temple", player, world)
|
||||
prism_temple_region = Region("Prism Temple", player, multiworld)
|
||||
prism_temple_region.add_locations(loc_prism_temple, CCCharlesLocation)
|
||||
world.regions.append(prism_temple_region)
|
||||
multiworld.regions.append(prism_temple_region)
|
||||
|
||||
pickle_val_region = Region("Pickle Val", player, world)
|
||||
pickle_val_region = Region("Pickle Val", player, multiworld)
|
||||
pickle_val_region.add_locations(loc_pickle_val, CCCharlesLocation)
|
||||
world.regions.append(pickle_val_region)
|
||||
multiworld.regions.append(pickle_val_region)
|
||||
|
||||
shrine_near_temple_region = Region("Shrine Near Temple", player, world)
|
||||
shrine_near_temple_region = Region("Shrine Near Temple", player, multiworld)
|
||||
shrine_near_temple_region.add_locations(loc_shrine_near_temple, CCCharlesLocation)
|
||||
world.regions.append(shrine_near_temple_region)
|
||||
multiworld.regions.append(shrine_near_temple_region)
|
||||
|
||||
morse_bunker_region = Region("Morse Bunker", player, world)
|
||||
morse_bunker_region = Region("Morse Bunker", player, multiworld)
|
||||
morse_bunker_region.add_locations(loc_morse_bunker, CCCharlesLocation)
|
||||
world.regions.append(morse_bunker_region)
|
||||
multiworld.regions.append(morse_bunker_region)
|
||||
|
||||
# Place "Victory" event at "Final Boss" location
|
||||
loc_final_boss = CCCharlesLocation(player, "Final Boss", None, prism_temple_region)
|
||||
|
||||
+74
-74
@@ -4,212 +4,212 @@ from .Options import CCCharlesOptions
|
||||
|
||||
# Go mode: Green Egg + Blue Egg + Red Egg + Temple Key + Bug Spray (+ Remote Explosive x8 but the base game ignores it)
|
||||
|
||||
def set_rules(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
def set_rules(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
# Tony Tiddle
|
||||
set_rule(world.get_entrance("Barn Door", player),
|
||||
set_rule(multiworld.get_entrance("Barn Door", player),
|
||||
lambda state: state.has("Barn Key", player))
|
||||
|
||||
# Candice
|
||||
set_rule(world.get_entrance("Tutorial House Door", player),
|
||||
set_rule(multiworld.get_entrance("Tutorial House Door", player),
|
||||
lambda state: state.has("Candice's Key", player))
|
||||
|
||||
# Lizbeth Murkwater
|
||||
set_rule(world.get_location("Swamp Lizbeth Murkwater Mission End", player),
|
||||
set_rule(multiworld.get_location("Swamp Lizbeth Murkwater Mission End", player),
|
||||
lambda state: state.has("Dead Fish", player))
|
||||
|
||||
# Daryl
|
||||
set_rule(world.get_location("Junkyard Area Chest Ancient Tablet", player),
|
||||
set_rule(multiworld.get_location("Junkyard Area Chest Ancient Tablet", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Junkyard Area Daryl Mission End", player),
|
||||
set_rule(multiworld.get_location("Junkyard Area Daryl Mission End", player),
|
||||
lambda state: state.has("Ancient Tablet", player))
|
||||
|
||||
# South House
|
||||
set_rule(world.get_location("South House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# South Mine
|
||||
set_rule(world.get_entrance("South Mine Gate", player),
|
||||
set_rule(multiworld.get_entrance("South Mine Gate", player),
|
||||
lambda state: state.has("South Mine Key", player))
|
||||
|
||||
set_rule(world.get_location("South Mine Inside Green Paint Can", player),
|
||||
set_rule(multiworld.get_location("South Mine Inside Green Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Theodore
|
||||
set_rule(world.get_location("Middle Station Theodore Mission End", player),
|
||||
set_rule(multiworld.get_location("Middle Station Theodore Mission End", player),
|
||||
lambda state: state.has("Blue Box", player))
|
||||
|
||||
# Watchtower
|
||||
set_rule(world.get_location("Watchtower Pink Paint Can", player),
|
||||
set_rule(multiworld.get_location("Watchtower Pink Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Sasha
|
||||
set_rule(world.get_location("Haunted House Sasha Mission End", player),
|
||||
set_rule(multiworld.get_location("Haunted House Sasha Mission End", player),
|
||||
lambda state: state.has("Page Drawing", player, 8))
|
||||
|
||||
# Santiago
|
||||
set_rule(world.get_location("Port Santiago Mission End", player),
|
||||
set_rule(multiworld.get_location("Port Santiago Mission End", player),
|
||||
lambda state: state.has("Journal", player))
|
||||
|
||||
# Trench House
|
||||
set_rule(world.get_location("Trench House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# East House
|
||||
set_rule(world.get_location("East House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Rocket Testing Bunker
|
||||
set_rule(world.get_entrance("Stuck Bunker Door", player),
|
||||
set_rule(multiworld.get_entrance("Stuck Bunker Door", player),
|
||||
lambda state: state.has("Timed Dynamite", player))
|
||||
|
||||
# John Smith
|
||||
set_rule(world.get_location("Workshop John Smith Mission End", player),
|
||||
set_rule(multiworld.get_location("Workshop John Smith Mission End", player),
|
||||
lambda state: state.has("Box of Rockets", player))
|
||||
|
||||
# Claire
|
||||
set_rule(world.get_location("Lighthouse Claire Mission End", player),
|
||||
set_rule(multiworld.get_location("Lighthouse Claire Mission End", player),
|
||||
lambda state: state.has("Breaker", player, 4))
|
||||
|
||||
# North Mine
|
||||
set_rule(world.get_entrance("North Mine Gate", player),
|
||||
set_rule(multiworld.get_entrance("North Mine Gate", player),
|
||||
lambda state: state.has("North Mine Key", player))
|
||||
|
||||
set_rule(world.get_location("North Mine Inside Blue Paint Can", player),
|
||||
set_rule(multiworld.get_location("North Mine Inside Blue Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Paul
|
||||
set_rule(world.get_location("Museum Paul Mission End", player),
|
||||
set_rule(multiworld.get_location("Museum Paul Mission End", player),
|
||||
lambda state: state.has("Remote Explosive x8", player))
|
||||
# lambda state: state.has("Remote Explosive", player, 8)) # TODO: Add an option to split remote explosives
|
||||
|
||||
# West Beach
|
||||
set_rule(world.get_location("West Beach Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Caravan
|
||||
set_rule(world.get_location("Caravan Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Ronny
|
||||
set_rule(world.get_location("Towers Ronny Mission End", player),
|
||||
set_rule(multiworld.get_location("Towers Ronny Mission End", player),
|
||||
lambda state: state.has("Employment Contracts", player))
|
||||
|
||||
# North Beach
|
||||
set_rule(world.get_location("North Beach Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Mine Shaft
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 7", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 7", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Mob Camp
|
||||
set_rule(world.get_entrance("Mob Camp Locked Door", player),
|
||||
set_rule(multiworld.get_entrance("Mob Camp Locked Door", player),
|
||||
lambda state: state.has("Mob Camp Key", player))
|
||||
|
||||
set_rule(world.get_location("Mob Camp Locked Room Stolen Bob", player),
|
||||
set_rule(multiworld.get_location("Mob Camp Locked Room Stolen Bob", player),
|
||||
lambda state: state.has("Broken Bob", player))
|
||||
|
||||
# Mountain Ruin
|
||||
set_rule(world.get_entrance("Mountain Ruin Gate", player),
|
||||
set_rule(multiworld.get_entrance("Mountain Ruin Gate", player),
|
||||
lambda state: state.has("Mountain Ruin Key", player))
|
||||
|
||||
set_rule(world.get_location("Mountain Ruin Inside Red Paint Can", player),
|
||||
set_rule(multiworld.get_location("Mountain Ruin Inside Red Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Prism Temple
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Pickle Lady
|
||||
set_rule(world.get_location("Pickle Val Jar of Pickles", player),
|
||||
set_rule(multiworld.get_location("Pickle Val Jar of Pickles", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Pickle Val Pickle Lady Mission End", player),
|
||||
set_rule(multiworld.get_location("Pickle Val Pickle Lady Mission End", player),
|
||||
lambda state: state.has("Jar of Pickles", player))
|
||||
|
||||
# Morse Bunker
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Add rules to reach the "Go mode"
|
||||
set_rule(world.get_location("Final Boss", player),
|
||||
set_rule(multiworld.get_location("Final Boss", player),
|
||||
lambda state: state.has("Temple Key", player)
|
||||
and state.has("Green Egg", player)
|
||||
and state.has("Blue Egg", player)
|
||||
and state.has("Red Egg", player))
|
||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
|
||||
@@ -296,11 +296,9 @@ class ImpatientMimicsOption(Toggle):
|
||||
class RandomEnemyPresetOption(OptionDict):
|
||||
"""The YAML preset for the static enemy randomizer.
|
||||
|
||||
See the static randomizer documentation in `randomizer\\presets\\README.txt` for details.
|
||||
See the online enemy randomization documentation for all available options.
|
||||
Include this as nested YAML. For example:
|
||||
|
||||
.. code-block:: YAML
|
||||
|
||||
random_enemy_preset:
|
||||
RemoveSource: Ancient Wyvern; Darkeater Midir
|
||||
DontRandomize: Iudex Gundyr
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Dark Souls III
|
||||
|
||||
Game Page | [Items] | [Locations]
|
||||
Game Page | [Setup] | [Items] | [Locations] | [Enemy Randomization]
|
||||
|
||||
[Setup]: /tutorial/Dark%20Souls%20III/setup/en
|
||||
[Items]: /tutorial/Dark%20Souls%20III/items/en
|
||||
[Locations]: /tutorial/Dark%20Souls%20III/locations/en
|
||||
[Enemy Randomization]: /tutorial/Dark%20Souls%20III/enemy-randomization/en
|
||||
|
||||
## What do I need to do to randomize DS3?
|
||||
|
||||
@@ -138,6 +140,14 @@ Check out the [item guide], which explains the named groups available for items.
|
||||
|
||||
[item guide]: /tutorial/Dark%20Souls%20III/items/en
|
||||
|
||||
## How can I change what enemies get randomized?
|
||||
|
||||
The [enemy randomization guide] explains how to further customize enemy randomization
|
||||
for challenge runs or convenience. You can target specific enemies or entire
|
||||
categories and even remove annoying enemy types outright.
|
||||
|
||||
[enemy randomization guide]: /tutorial/Dark%20Souls%20III/enemy-randomization/en
|
||||
|
||||
## What's new from 2.x.x?
|
||||
|
||||
Version 3.0.0 of the Dark Souls III Archipelago client has a number of
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
# Dark Souls III Enemy Randomization
|
||||
|
||||
[Game Page] | [Setup] | [Items] | [Locations] | Enemy Randomization
|
||||
|
||||
[Game Page]: /games/Dark%20Souls%20III/info/en
|
||||
[Setup]: /tutorial/Dark%20Souls%20III/setup/en
|
||||
[Items]: /tutorial/Dark%20Souls%20III/items/en
|
||||
[Locations]: /tutorial/Dark%20Souls%20III/locations/en
|
||||
|
||||
If `randomize_enemies` in your Dark Souls 3 player config YAML is enabled, bosses, minibosses and basic enemies will
|
||||
be shuffled with themselves respectively.
|
||||
|
||||
To further customize enemy randomization beyond that, there is a section called `random_enemy_preset`.
|
||||
|
||||
This tutorial will show all the ways how to configure that preset.
|
||||
|
||||
## Table of Contents
|
||||
- [The Basics](#the-basics)
|
||||
- [Individual Assignments](#individual-assignments)
|
||||
- [Pools](#pools)
|
||||
* [Pool Groups](#pool-groups)
|
||||
+ [RandomByType](#randombytype)
|
||||
* [Weights](#weights)
|
||||
- [Settings](#settings)
|
||||
* [Boss](#boss)
|
||||
* [Miniboss](#miniboss)
|
||||
* [Basic](#basic)
|
||||
+ [BuffBasicEnemiesAsBosses](#buffbasicenemiesasbosses)
|
||||
* [Enemies](#enemies)
|
||||
* [DontRandomize](#dontrandomize)
|
||||
* [RemoveSource](#removesource)
|
||||
* [OopsAll](#oopsall)
|
||||
- [Enemy Categories](#enemy-categories)
|
||||
|
||||
## The Basics
|
||||
|
||||
There are two main ways to assign an enemy to be randomized: [individual enemy assignments](#individual-assignments)
|
||||
to target a singular enemy placement and setting up [Pools](#pools) to target a category of enemies.
|
||||
|
||||
Custom pools are recommended unless you specifically want to single out one enemy placement.
|
||||
|
||||
All bosses also have their own category, so individual assignment is not necessary in those cases.
|
||||
|
||||
Be aware of correct indentation of your YAML file. Every example in this document will need to be nested under the
|
||||
`random_enemy_preset:` section.
|
||||
|
||||
Disable the preset by leaving just empty brackets `{}`. Like usual with YAML, you can add comments by using `#`.
|
||||
|
||||
For further examples, check out the "presets" folder of the standalone randomizer.
|
||||
|
||||
## Individual Assignments
|
||||
|
||||
Individual enemy assignment allows you to target individual enemies, rather than a category as under pools.
|
||||
|
||||
This overrides pools and any other configuration, will usually ignore progression, and can possibly cause you to have to
|
||||
fight Yhorm the Giant without Storm Ruler.
|
||||
|
||||
You use it in the [`Enemies`](#enemies) section by selecting a specific enemy using its unique
|
||||
ID, or its specific name followed by its ID.
|
||||
|
||||
See the '/randomizer/preset/Template.txt' file of the static randomizer for all available IDs.
|
||||
|
||||
There are also some special target names available for individual assignments:
|
||||
|
||||
- `any`: This is the default and allows any enemy in the pool to appear there.
|
||||
|
||||
- `norandom`: Assigns an enemy to itself. This has the same effect as adding the enemy name to [`DontRandomize`](#dontrandomize).
|
||||
|
||||
## Pools
|
||||
|
||||
A pool is a collection of enemies. A pool can both be a randomization target and an eligible group of random enemies to
|
||||
be drawn from for randomization. See [Enemy Categories](#enemy-categories) for all available pools.
|
||||
|
||||
Pool assignment generally respects progression, like requiring Storm Ruler to be accessible before Yhorm the Giant.
|
||||
|
||||
By default, using a boss as another boss, or a miniboss as another miniboss, takes the source enemy out of the default
|
||||
pool for that category, so each enemy will still be used once if possible. However, the enemy can still appear more
|
||||
than once if used in a custom pool.
|
||||
|
||||
### Pool Groups
|
||||
|
||||
Pools can be joined into a pool group by joining several names, separated by a semicolon.
|
||||
|
||||
```yaml
|
||||
# All basic enemies are just different hollows now
|
||||
Basic:
|
||||
- Weight: 100
|
||||
Pool: Hollow Soldiers; Large Hollow Soldiers
|
||||
```
|
||||
|
||||
#### RandomByType
|
||||
|
||||
By default, selection will be random across all eligible enemies. In our example above it would select from:
|
||||
|
||||
- Hollow Soldier
|
||||
- Road of Sacrifices Hollow Soldier
|
||||
- Cathedral Hollow Soldier
|
||||
- Lothric Castle Hollow Soldier
|
||||
- Grand Archives Hollow Soldier
|
||||
|
||||
and
|
||||
|
||||
- Large Hollow Soldier
|
||||
- Cathedral Large Hollow Soldier
|
||||
- Lothric Castle Large Hollow Soldier
|
||||
|
||||
However, this would make it more likely to select a regular soldier instead of a large one (5 out of 8), just because
|
||||
there are fewer entries in the latter category.
|
||||
|
||||
You can specify `RandomByType: true` to select randomly from the list itself (Hollow Soldiers, Large Hollow Soldiers)
|
||||
and make our previous example a true 50/50 split.
|
||||
|
||||
```yaml
|
||||
# All basic enemies are just different hollows now
|
||||
Basic:
|
||||
- Weight: 100
|
||||
Pool: Hollow Soldiers; Large Hollow Soldiers
|
||||
RandomByType: true # To make it truly 50/50 between the categories
|
||||
```
|
||||
|
||||
### Weights
|
||||
|
||||
Weights can be used to select multiple different outcomes within a pool, weighted to give different probabilities each.
|
||||
|
||||
Weights don't necessarily have to add up to 100, but doing it that way makes estimating probabilities very intuitive.
|
||||
|
||||
```yaml
|
||||
Boss:
|
||||
- Weight: 79 # 79% of bosses will still be bosses
|
||||
Pool: default
|
||||
- Weight: 20 # Replace 20% of all bosses with minibosses
|
||||
Pool: Miniboss
|
||||
- Weight: 1 # Replace 1% of all bosses with regular enemies. It's always funny
|
||||
Pool: Basic
|
||||
```
|
||||
|
||||
Be aware that weights will not work in the [`Enemies`](#enemies) section.
|
||||
|
||||
## Settings
|
||||
|
||||
### Boss
|
||||
|
||||
This setting indicates which enemies can be used as replacements for bosses.
|
||||
By default, this is the pool of all 29 bosses.
|
||||
|
||||
```yaml
|
||||
Boss:
|
||||
- Weight: 80
|
||||
Pool: default
|
||||
- Weight: 20 # Replace 20% of all bosses with minibosses
|
||||
Pool: Miniboss
|
||||
```
|
||||
|
||||
### Miniboss
|
||||
|
||||
This setting indicates which enemies can be used as replacements for minibosses.
|
||||
By default, this is the pool of all 32 minibosses (including duplicates).
|
||||
|
||||
```yaml
|
||||
Miniboss:
|
||||
- Weight: 80
|
||||
Pool: default
|
||||
- Weight: 20 # Replace 20% of all minibosses with bosses
|
||||
Pool: Boss
|
||||
```
|
||||
|
||||
### Basic
|
||||
|
||||
This setting indicates which enemies can be used as replacements for all other enemies, so non-bosses and non-minibosses.
|
||||
By default, this is the pool of all ~2000 basic enemies (including duplicates).
|
||||
|
||||
```yaml
|
||||
Basic:
|
||||
- Weight: 94
|
||||
Pool: default
|
||||
- Weight: 5 # Replace 5% of all basic enemies with minibosses
|
||||
Pool: Miniboss
|
||||
- Weight: 1 # Replace 1% of all basic enemies with bosses
|
||||
Pool: Boss
|
||||
```
|
||||
|
||||
#### BuffBasicEnemiesAsBosses
|
||||
|
||||
If enabled, this causes basic enemies to become a lot stronger when randomized into the slot of a boss.
|
||||
|
||||
```yaml
|
||||
Boss:
|
||||
- Weight: 100 # All bosses are just basic enemies...
|
||||
Pool: Basic
|
||||
|
||||
BuffBasicEnemiesAsBosses: true # ...but they are strong
|
||||
```
|
||||
|
||||
### Enemies
|
||||
|
||||
Under the `Enemies:` setting you can add more nuanced replacements of random enemies.
|
||||
There are two ways you can adjust enemies:
|
||||
- Assign to a group of enemies using their category pool (see [Enemy Categories](#enemy-categories))
|
||||
- Assign to one specifc enemy by using its number (see [Individual Assignments](#individual-assignments))
|
||||
|
||||
```yaml
|
||||
Enemies:
|
||||
# Replace only the very first Ravenous Crystal Lizard with the final boss
|
||||
Ravenous Crystal Lizard 4000380: Lords of Cinder
|
||||
|
||||
# Replace all regular soldiers with skeletons or small crabs
|
||||
Hollow Soldiers: Skeletons; Lesser Crab
|
||||
|
||||
# Knights remain knights, but variants (i.e. weapons) are still shuffled within the category
|
||||
High Wall Lothric Knight: High Wall Lothric Knight
|
||||
```
|
||||
|
||||
### DontRandomize
|
||||
|
||||
A semicolon-separated list of enemies or enemy types to not randomize (assign to themselves).
|
||||
It is taken out of its default pool and also custom pools in this case, but it can still be assigned to
|
||||
[individual enemies](#individual-assignments).
|
||||
|
||||
```yaml
|
||||
DontRandomize: Iudex Gundyr # Iudex Gundyr will be at his vanilla location
|
||||
|
||||
Boss:
|
||||
- Weight: 100
|
||||
Pool: default # Boss slots other than Iudex Gundyr will never become him
|
||||
```
|
||||
|
||||
### RemoveSource
|
||||
|
||||
A semicolon-separated list of enemies or enemy types to remove from all pools.
|
||||
It can still be assigned to individual enemies.
|
||||
This is overridden by [`DontRandomize`](#dontrandomize) directives.
|
||||
|
||||
```yaml
|
||||
# Remove the most annoying enemies from all pools
|
||||
RemoveSource: Bridge Darkeater Midir; Ancient Wyvern Mob; Curse-rotted Greatwood; High Lord Wolnir; Carthus Sandworm
|
||||
```
|
||||
|
||||
### OopsAll
|
||||
|
||||
Assigning an enemy or a pool to `OopsAll` sets all pools to that specific enemy or category of enemy. This can still be
|
||||
overridden using [individual enemy assginments](#individual-assignments), but otherwise every enemy is replaced by
|
||||
this setting.
|
||||
|
||||
```yaml
|
||||
# This run suddenly got very spooky
|
||||
OopsAll: Skeletons
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enemy Categories
|
||||
|
||||
The following enemy category pools are available:
|
||||
|
||||
- Any
|
||||
- Bosses
|
||||
- Minibosses
|
||||
- Bosses and Minibosses
|
||||
- Basic
|
||||
- Abyss Watchers
|
||||
- Aldrich, Devourer of Gods
|
||||
- Ancient Wyvern
|
||||
- Ancient Wyvern Mob
|
||||
- Angel Pilgrim
|
||||
- Basilisk
|
||||
- Black Knight
|
||||
- Blackflame Friede
|
||||
- Boreal Outrider Knight
|
||||
- Bridge Darkeater Midir
|
||||
- Cage Spider
|
||||
- Carthus Sandworm
|
||||
- Cathedral Evangelist
|
||||
- Cathedral Knight
|
||||
- Cemetery Hollow
|
||||
- Champion Gundyr
|
||||
- Champion's Gravetender and Gravetender Greatwolf
|
||||
- Consumed King Oceiros
|
||||
- Corpse-grub
|
||||
- Corvian
|
||||
- Corvian Knight
|
||||
- Corvian Settler
|
||||
- Crabs
|
||||
- Lesser Crab
|
||||
- Greater Crab
|
||||
- Ariandel Greater Crab
|
||||
- Crystal Lizard
|
||||
- Crystal Sage
|
||||
- Crystal Sage in Archives
|
||||
- Curse-rotted Greatwood
|
||||
- Dancer of the Boreal Valley
|
||||
- Darkeater Midir
|
||||
- Darkwraith
|
||||
- Deacon
|
||||
- Cathedral Deacon
|
||||
- Wide Deacon
|
||||
- Irirthyll Deacon
|
||||
- Irirthyll Tall Deacon
|
||||
- Deacons of the Deep
|
||||
- Deep Accursed
|
||||
- Demon
|
||||
- Demon Cleric
|
||||
- Demon Prince
|
||||
- Demonic Statue
|
||||
- Dragonslayer Armour
|
||||
- Dreg Heap Thrall
|
||||
- Elder Ghru
|
||||
- Father Ariandel
|
||||
- Farron Follower
|
||||
- Fire Witch
|
||||
- Gargoyles
|
||||
- Profaned Capital Gargoyle
|
||||
- Archives Gargoyle
|
||||
- Ghru Grunt
|
||||
- Giant Fly
|
||||
- Giant Slave
|
||||
- Grand Archives Scholar
|
||||
- Grave Warden
|
||||
- Halflight, Spear of the Church
|
||||
- Harald Legion Knight
|
||||
- High Lord Wolnir
|
||||
- Hobbled Cleric
|
||||
- Hollow Manservant
|
||||
- Hollow Soldiers
|
||||
- Hollow Soldier
|
||||
- Road of Sacrifices Hollow Soldier
|
||||
- Cathedral Hollow Soldier
|
||||
- Lothric Castle Hollow Soldier
|
||||
- Grand Archives Hollow Soldier
|
||||
- Hound Rat
|
||||
- Infested Corpse
|
||||
- Irirthyll Dungeon Peasant Hollow
|
||||
- Irithyll Giant Slave
|
||||
- Irithyll Starved Hound
|
||||
- Irithyllian Slave
|
||||
- Iudex Gundyr
|
||||
- Jailer
|
||||
- Judicator
|
||||
- King of the Storm
|
||||
- Large Hollow Soldiers
|
||||
- Large Hollow Soldier
|
||||
- Cathedral Large Hollow Soldier
|
||||
- Lothric Castle Large Hollow Soldier
|
||||
- Large Hound Rat
|
||||
- Large Serpent-Man
|
||||
- Large Starved Hound
|
||||
- Locust Preacher
|
||||
- Lords of Cinder (actually called "Soul of Cinder" ingame)
|
||||
- Lorian, Elder Prince
|
||||
- Lothric Knights
|
||||
- High Wall Lothric Knight
|
||||
- Lothric Castle Lothric Knight
|
||||
- Dreg Heap Lothric Knight
|
||||
- Red-Eyed Lothric Knight
|
||||
- Lothric Priest
|
||||
- Lothric, Younger Prince
|
||||
- Lycanthrope
|
||||
- Lycanthrope Hunter
|
||||
- Maggot Belly Starved Hound
|
||||
- Millwood Knight
|
||||
- Mimic Chest
|
||||
- Monstrosity of Sin
|
||||
- Murkman
|
||||
- Murkman Summoner
|
||||
- Nameless King
|
||||
- Old Demon King
|
||||
- Passive Locust Preacher
|
||||
- Peasant Hollow
|
||||
- Poisonhorn Bug
|
||||
- Pontiff Knight
|
||||
- Pontiff Sulyvahn
|
||||
- Pus of Man
|
||||
- Ravenous Crystal Lizard
|
||||
- Reanimated Corpse
|
||||
- Ringed City Cleric
|
||||
- Ringed Knight
|
||||
- Road of Sacrifices Sorcerer
|
||||
- Rock Lizard
|
||||
- Rotten Slug
|
||||
- Serpent-Man
|
||||
- Serpent-Man Summoner
|
||||
- Sewer Centipede
|
||||
- Silver Knight
|
||||
- Sister Friede
|
||||
- Skeletons
|
||||
- Skeleton
|
||||
- Bonewheel Skeleton
|
||||
- Carthus Curved Sword Skeleton
|
||||
- Carthus Shotel Skeleton
|
||||
- Ringed City Skeleton
|
||||
- Slave Knight Gael
|
||||
- Slave Knight Gael 1
|
||||
- Slave Knight Gael 2
|
||||
- Small Locust Preacher
|
||||
- Smouldering Ghru Grunt
|
||||
- Starved Hound
|
||||
- Stray Demon
|
||||
- Sulyvahn's Beast
|
||||
- Thrall
|
||||
- Tree Woman
|
||||
- Vordt of the Boreal Valley
|
||||
- Winged Knight
|
||||
- Wolves
|
||||
- Smaller Wolf
|
||||
- Larger Wolf
|
||||
- Greatwolf
|
||||
- Wretch
|
||||
- Writhing Flesh
|
||||
- Catacombs Writhing Flesh
|
||||
- Smouldering Writhing Flesh
|
||||
- Anor Londo Writhing Flesh
|
||||
- Yhorm the Giant
|
||||
@@ -1,9 +1,11 @@
|
||||
# Dark Souls III Items
|
||||
|
||||
[Game Page] | Items | [Locations]
|
||||
[Game Page] | [Setup] | Items | [Locations] | [Enemy Randomization]
|
||||
|
||||
[Game Page]: /games/Dark%20Souls%20III/info/en
|
||||
[Setup]: /tutorial/Dark%20Souls%20III/setup/en
|
||||
[Locations]: /tutorial/Dark%20Souls%20III/locations/en
|
||||
[Enemy Randomization]: /tutorial/Dark%20Souls%20III/enemy-randomization/en
|
||||
|
||||
## Item Groups
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Dark Souls III Locations
|
||||
|
||||
[Game Page] | [Items] | Locations
|
||||
[Game Page] | [Setup] | [Items] | Locations | [Enemy Randomization]
|
||||
|
||||
[Game Page]: /games/Dark%20Souls%20III/info/en
|
||||
[Setup]: /tutorial/Dark%20Souls%20III/setup/en
|
||||
[Items]: /tutorial/Dark%20Souls%20III/items/en
|
||||
[Enemy Randomization]: /tutorial/Dark%20Souls%20III/enemy-randomization/en
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Dark Souls III Randomizer Setup Guide
|
||||
|
||||
[Game Page] | Setup | [Items] | [Locations] | [Enemy Randomization]
|
||||
|
||||
[Game Page]: /games/Dark%20Souls%20III/info/en
|
||||
[Items]: /tutorial/Dark%20Souls%20III/items/en
|
||||
[Locations]: /tutorial/Dark%20Souls%20III/locations/en
|
||||
[Enemy Randomization]: /tutorial/Dark%20Souls%20III/enemy-randomization/en
|
||||
|
||||
## Required Software
|
||||
|
||||
- [Dark Souls III](https://store.steampowered.com/app/374320/DARK_SOULS_III/)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# Donkey Kong Country 3 - Changelog
|
||||
|
||||
|
||||
## v1.1
|
||||
|
||||
### Features:
|
||||
|
||||
- KONGsanity option (Collect all KONG letters in each level for a check)
|
||||
- Autosave option
|
||||
- Difficulty option
|
||||
- MERRY option
|
||||
- Handle collected/co-op locations
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
- Fixed Mekanos softlock
|
||||
- Prevent Brothers Bear giving extra Banana Birds
|
||||
- Fixed Banana Bird Mother check sending prematurely
|
||||
- Fix Logic bug with Krematoa level costs
|
||||
|
||||
|
||||
## v1.0
|
||||
|
||||
### Features:
|
||||
|
||||
- Goal
|
||||
- Knautilus
|
||||
- Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein to win
|
||||
- Banana Bird Hunt
|
||||
- Find the Banana Birds and rescue their mother to win
|
||||
- Locations included:
|
||||
- Level Flags
|
||||
- Bonuses
|
||||
- DK Coins
|
||||
- Banana Bird Caves
|
||||
- Items included:
|
||||
- Progressive Boat Upgrade
|
||||
- Three are placed into the item pool (Patch -> First Ski -> Second Ski)
|
||||
- Bonus Coins
|
||||
- DK Coins
|
||||
- Krematoa Cogs
|
||||
- Bear Coins
|
||||
- 1-Up Balloons
|
||||
- Level Shuffle is supported
|
||||
- Music Shuffle is supported
|
||||
- Kong Palette options are supported
|
||||
- Starting life count can be set
|
||||
@@ -1,229 +0,0 @@
|
||||
import logging
|
||||
|
||||
from NetUtils import ClientStatus, color
|
||||
from worlds.AutoSNIClient import SNIClient
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
||||
# FXPAK Pro protocol memory mapping used by SNI
|
||||
ROM_START = 0x000000
|
||||
WRAM_START = 0xF50000
|
||||
WRAM_SIZE = 0x20000
|
||||
SRAM_START = 0xE00000
|
||||
|
||||
DKC3_ROMNAME_START = 0x00FFC0
|
||||
DKC3_ROMHASH_START = 0x7FC0
|
||||
ROMNAME_SIZE = 0x15
|
||||
ROMHASH_SIZE = 0x15
|
||||
|
||||
DKC3_RECV_PROGRESS_ADDR = WRAM_START + 0x632
|
||||
DKC3_FILE_NAME_ADDR = WRAM_START + 0x5D9
|
||||
DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a permanent home for this
|
||||
|
||||
|
||||
class DKC3SNIClient(SNIClient):
|
||||
game = "Donkey Kong Country 3"
|
||||
patch_suffix = ".apdkc3"
|
||||
|
||||
async def deathlink_kill_player(self, ctx):
|
||||
pass
|
||||
# DKC3_TODO: Handle Receiving Deathlink
|
||||
|
||||
|
||||
async def validate_rom(self, ctx):
|
||||
from SNIClient import snes_read
|
||||
|
||||
rom_name = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
|
||||
if rom_name is None or rom_name == bytes([0] * ROMHASH_SIZE) or rom_name[:2] != b"D3":
|
||||
return False
|
||||
|
||||
ctx.game = self.game
|
||||
ctx.items_handling = 0b111 # remote items
|
||||
|
||||
ctx.rom = rom_name
|
||||
|
||||
#death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
|
||||
## DKC3_TODO: Handle Deathlink
|
||||
#if death_link:
|
||||
# ctx.allow_collect = bool(death_link[0] & 0b100)
|
||||
# await ctx.update_death_link(bool(death_link[0] & 0b1))
|
||||
return True
|
||||
|
||||
|
||||
async def game_watcher(self, ctx):
|
||||
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
|
||||
# DKC3_TODO: Handle Deathlink
|
||||
save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
|
||||
if save_file_name is None or save_file_name[0] == 0x00 or save_file_name == bytes([0x55] * 0x05):
|
||||
# We haven't loaded a save file
|
||||
return
|
||||
|
||||
new_checks = []
|
||||
from .Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map
|
||||
location_ram_data = await snes_read(ctx, WRAM_START + 0x5FE, 0x81)
|
||||
for loc_id, loc_data in location_rom_data.items():
|
||||
if loc_id not in ctx.locations_checked:
|
||||
data = location_ram_data[loc_data[0] - 0x5FE]
|
||||
masked_data = data & (1 << loc_data[1])
|
||||
bit_set = (masked_data != 0)
|
||||
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
|
||||
if bit_set != invert_bit:
|
||||
# DKC3_TODO: Handle non-included checks
|
||||
new_checks.append(loc_id)
|
||||
|
||||
verify_save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
|
||||
if verify_save_file_name is None or verify_save_file_name[0] == 0x00 or verify_save_file_name == bytes([0x55] * 0x05) or verify_save_file_name != save_file_name:
|
||||
# We have somehow exited the save file (or worse)
|
||||
ctx.rom = None
|
||||
return
|
||||
|
||||
rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
|
||||
if rom != ctx.rom:
|
||||
ctx.rom = None
|
||||
# We have somehow loaded a different ROM
|
||||
return
|
||||
|
||||
for new_check_id in new_checks:
|
||||
ctx.locations_checked.add(new_check_id)
|
||||
location = ctx.location_names.lookup_in_game(new_check_id)
|
||||
snes_logger.info(
|
||||
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
|
||||
|
||||
# DKC3_TODO: Make this actually visually display new things received (ASM Hook required)
|
||||
recv_count = await snes_read(ctx, DKC3_RECV_PROGRESS_ADDR, 1)
|
||||
recv_index = recv_count[0]
|
||||
|
||||
if recv_index < len(ctx.items_received):
|
||||
item = ctx.items_received[recv_index]
|
||||
recv_index += 1
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
|
||||
color(ctx.player_names[item.player], 'yellow'),
|
||||
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
|
||||
|
||||
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
|
||||
if item.item in item_rom_data:
|
||||
item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
for address in item_rom_data[item.item]:
|
||||
snes_buffered_write(ctx, WRAM_START + address, bytes([new_item_count]))
|
||||
|
||||
# Handle Coin Displays
|
||||
current_level = await snes_read(ctx, WRAM_START + 0x5E3, 0x5)
|
||||
overworld_locked = ((await snes_read(ctx, WRAM_START + 0x5FC, 0x1))[0] == 0x01)
|
||||
if item.item == 0xDC3002 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x00 and current_level[4] == 0x03):
|
||||
# Bazaar and Barter
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
|
||||
elif item.item == 0xDC3002 and not overworld_locked and current_level[0] == 0x04:
|
||||
# Swanky
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xA26, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xA26, bytes([new_item_count]))
|
||||
elif item.item == 0xDC3003 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x08 and current_level[4] == 0x01):
|
||||
# Boomer
|
||||
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
|
||||
new_item_count = item_count[0] + 1
|
||||
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
|
||||
else:
|
||||
# Handle Patch and Skis
|
||||
if item.item == 0xDC3007:
|
||||
num_upgrades = 1
|
||||
inventory = await snes_read(ctx, WRAM_START + 0x605, 0xF)
|
||||
|
||||
if (inventory[0] & 0x02):
|
||||
num_upgrades = 3
|
||||
elif (inventory[13] & 0x08) or (inventory[0] & 0x01):
|
||||
num_upgrades = 2
|
||||
|
||||
if num_upgrades == 1:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x01]))
|
||||
if inventory[4] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x01]))
|
||||
elif inventory[6] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x01]))
|
||||
elif inventory[8] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x01]))
|
||||
elif inventory[10] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x01]))
|
||||
|
||||
cove_mekanos_progress = await snes_read(ctx, WRAM_START + 0x691, 0x2)
|
||||
snes_buffered_write(ctx, WRAM_START + 0x691, bytes([cove_mekanos_progress[0] | 0x01]))
|
||||
snes_buffered_write(ctx, WRAM_START + 0x692, bytes([cove_mekanos_progress[1] | 0x01]))
|
||||
elif num_upgrades == 2:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x02]))
|
||||
if inventory[4] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x02]))
|
||||
elif inventory[6] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x02]))
|
||||
elif inventory[8] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x02]))
|
||||
elif inventory[10] == 0:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x02]))
|
||||
elif num_upgrades == 3:
|
||||
snes_buffered_write(ctx, WRAM_START + 0x606, bytes([inventory[1] | 0x20]))
|
||||
|
||||
k3_ridge_progress = await snes_read(ctx, WRAM_START + 0x693, 0x2)
|
||||
snes_buffered_write(ctx, WRAM_START + 0x693, bytes([k3_ridge_progress[0] | 0x01]))
|
||||
snes_buffered_write(ctx, WRAM_START + 0x694, bytes([k3_ridge_progress[1] | 0x01]))
|
||||
elif item.item == 0xDC3000:
|
||||
# Handle Victory
|
||||
if not ctx.finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.finished_game = True
|
||||
else:
|
||||
print("Item Not Recognized: ", item.item)
|
||||
pass
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
# Handle Collected Locations
|
||||
levels_to_tiles = await snes_read(ctx, ROM_START + 0x3FF800, 0x60)
|
||||
tiles_to_levels = await snes_read(ctx, ROM_START + 0x3FF860, 0x60)
|
||||
for loc_id in ctx.checked_locations:
|
||||
if loc_id not in ctx.locations_checked and loc_id not in boss_location_ids:
|
||||
loc_data = location_rom_data[loc_id]
|
||||
data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
|
||||
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
|
||||
if not invert_bit:
|
||||
masked_data = data[0] | (1 << loc_data[1])
|
||||
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
|
||||
|
||||
if (loc_data[1] == 1):
|
||||
# Make the next levels accessible
|
||||
level_id = loc_data[0] - 0x632
|
||||
tile_id = levels_to_tiles[level_id] if levels_to_tiles[level_id] != 0xFF else level_id
|
||||
tile_id = tile_id + 0x632
|
||||
if tile_id in level_unlock_map:
|
||||
for next_level_address in level_unlock_map[tile_id]:
|
||||
next_level_id = next_level_address - 0x632
|
||||
next_tile_id = tiles_to_levels[next_level_id] if tiles_to_levels[next_level_id] != 0xFF else next_level_id
|
||||
next_tile_id = next_tile_id + 0x632
|
||||
next_data = await snes_read(ctx, WRAM_START + next_tile_id, 1)
|
||||
snes_buffered_write(ctx, WRAM_START + next_tile_id, bytes([next_data[0] | 0x01]))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
else:
|
||||
masked_data = data[0] & ~(1 << loc_data[1])
|
||||
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
|
||||
await snes_flush_writes(ctx)
|
||||
ctx.locations_checked.add(loc_id)
|
||||
|
||||
# Calculate Boomer Cost Text
|
||||
boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2)
|
||||
if boomer_cost_text[0] == 0x31 and boomer_cost_text[1] == 0x35:
|
||||
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
|
||||
boomer_cost_tens = int(boomer_cost[0]) // 10
|
||||
boomer_cost_ones = int(boomer_cost[0]) % 10
|
||||
snes_buffered_write(ctx, WRAM_START + 0xAAFD, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
boomer_final_cost_text = await snes_read(ctx, WRAM_START + 0xAB9B, 2)
|
||||
if boomer_final_cost_text[0] == 0x32 and boomer_final_cost_text[1] == 0x35:
|
||||
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
|
||||
boomer_cost_tens = boomer_cost[0] // 10
|
||||
boomer_cost_ones = boomer_cost[0] % 10
|
||||
snes_buffered_write(ctx, WRAM_START + 0xAB9B, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
|
||||
await snes_flush_writes(ctx)
|
||||
@@ -1,52 +0,0 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import Item
|
||||
from .Names import ItemName
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: typing.Optional[int]
|
||||
progression: bool
|
||||
quantity: int = 1
|
||||
event: bool = False
|
||||
|
||||
|
||||
class DKC3Item(Item):
|
||||
game: str = "Donkey Kong Country 3"
|
||||
|
||||
|
||||
# Separate tables for each type of item.
|
||||
junk_table = {
|
||||
ItemName.one_up_balloon: ItemData(0xDC3001, False),
|
||||
ItemName.bear_coin: ItemData(0xDC3002, False),
|
||||
}
|
||||
|
||||
collectable_table = {
|
||||
ItemName.bonus_coin: ItemData(0xDC3003, True),
|
||||
ItemName.dk_coin: ItemData(0xDC3004, True),
|
||||
ItemName.banana_bird: ItemData(0xDC3005, True),
|
||||
ItemName.krematoa_cog: ItemData(0xDC3006, True),
|
||||
ItemName.progressive_boat: ItemData(0xDC3007, True),
|
||||
}
|
||||
|
||||
inventory_table = {
|
||||
ItemName.present: ItemData(0xDC3008, True),
|
||||
ItemName.bowling_ball: ItemData(0xDC3009, True),
|
||||
ItemName.shell: ItemData(0xDC300A, True),
|
||||
ItemName.mirror: ItemData(0xDC300B, True),
|
||||
ItemName.flower: ItemData(0xDC300C, True),
|
||||
ItemName.wrench: ItemData(0xDC300D, True),
|
||||
}
|
||||
|
||||
event_table = {
|
||||
ItemName.victory: ItemData(0xDC3000, True),
|
||||
}
|
||||
|
||||
# Complete item table.
|
||||
item_table = {
|
||||
**junk_table,
|
||||
**collectable_table,
|
||||
**event_table,
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
||||
@@ -1,27 +0,0 @@
|
||||
Modified MIT License
|
||||
|
||||
Copyright (c) 2025 PoryGone
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, and/or distribute copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
No copy or substantial portion of the Software shall be sublicensed or relicensed
|
||||
without the express written permission of the copyright holder(s)
|
||||
|
||||
No copy or substantial portion of the Software shall be sold without the express
|
||||
written permission of the copyright holder(s)
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,115 +0,0 @@
|
||||
|
||||
from .Names import LocationName
|
||||
|
||||
class DKC3Level():
|
||||
nameIDAddress: int
|
||||
levelIDAddress: int
|
||||
nameID: int
|
||||
levelID: int
|
||||
|
||||
def __init__(self, nameIDAddress: int, levelIDAddress: int, nameID: int, levelID: int):
|
||||
self.nameIDAddress = nameIDAddress
|
||||
self.levelIDAddress = levelIDAddress
|
||||
self.nameID = nameID
|
||||
self.levelID = levelID
|
||||
|
||||
|
||||
level_dict = {
|
||||
LocationName.lakeside_limbo_region: DKC3Level(0x34D19C, 0x34D19D, 0x01, 0x25),
|
||||
LocationName.doorstop_dash_region: DKC3Level(0x34D1A7, 0x34D1A8, 0x02, 0x28),
|
||||
LocationName.tidal_trouble_region: DKC3Level(0x34D1BD, 0x34D1BE, 0x04, 0x27),
|
||||
LocationName.skiddas_row_region: DKC3Level(0x34D1C8, 0x34D1C9, 0x05, 0x2B),
|
||||
LocationName.murky_mill_region: DKC3Level(0x34D1D3, 0x34D1D4, 0x0D, 0x2A),
|
||||
|
||||
LocationName.barrel_shield_bust_up_region: DKC3Level(0x34D217, 0x34D218, 0x0B, 0x30),
|
||||
LocationName.riverside_race_region: DKC3Level(0x34D22D, 0x34D22E, 0x0C, 0x32),
|
||||
LocationName.squeals_on_wheels_region: DKC3Level(0x34D238, 0x34D239, 0x06, 0x29),
|
||||
LocationName.springin_spiders_region: DKC3Level(0x34D24E, 0x34D24F, 0x0E, 0x2F),
|
||||
LocationName.bobbing_barrel_brawl_region: DKC3Level(0x34D264, 0x34D265, 0x37, 0x34),
|
||||
|
||||
LocationName.bazzas_blockade_region: DKC3Level(0x34D29D, 0x34D29E, 0x14, 0x35),
|
||||
LocationName.rocket_barrel_ride_region: DKC3Level(0x34D2A8, 0x34D2A9, 0x15, 0x38),
|
||||
LocationName.kreeping_klasps_region: DKC3Level(0x34D2BE, 0x34D2BF, 0x16, 0x26),
|
||||
LocationName.tracker_barrel_trek_region: DKC3Level(0x34D2D4, 0x34D2D5, 0x17, 0x39),
|
||||
LocationName.fish_food_frenzy_region: DKC3Level(0x34D2DF, 0x34D2E0, 0x18, 0x36),
|
||||
|
||||
LocationName.fire_ball_frenzy_region: DKC3Level(0x34D30D, 0x34D30E, 0x1B, 0x3B),
|
||||
LocationName.demolition_drain_pipe_region: DKC3Level(0x34D323, 0x34D324, 0x1D, 0x40),
|
||||
LocationName.ripsaw_rage_region: DKC3Level(0x34D339, 0x34D33A, 0x1E, 0x2E),
|
||||
LocationName.blazing_bazookas_region: DKC3Level(0x34D34F, 0x34D350, 0x1F, 0x3C),
|
||||
LocationName.low_g_labyrinth_region: DKC3Level(0x34D35A, 0x34D35B, 0x20, 0x3E),
|
||||
|
||||
LocationName.krevice_kreepers_region: DKC3Level(0x34D388, 0x34D389, 0x23, 0x41),
|
||||
LocationName.tearaway_toboggan_region: DKC3Level(0x34D393, 0x34D394, 0x24, 0x2D),
|
||||
LocationName.barrel_drop_bounce_region: DKC3Level(0x34D39E, 0x34D39F, 0x25, 0x3A),
|
||||
LocationName.krack_shot_kroc_region: DKC3Level(0x34D3A9, 0x34D3AA, 0x26, 0x3D),
|
||||
LocationName.lemguin_lunge_region: DKC3Level(0x34D3B4, 0x34D3B5, 0x27, 0x2C),
|
||||
|
||||
LocationName.buzzer_barrage_region: DKC3Level(0x34D40E, 0x34D40F, 0x2B, 0x44),
|
||||
LocationName.kong_fused_cliffs_region: DKC3Level(0x34D424, 0x34D425, 0x2D, 0x42),
|
||||
LocationName.floodlit_fish_region: DKC3Level(0x34D42F, 0x34D430, 0x2E, 0x37),
|
||||
LocationName.pothole_panic_region: DKC3Level(0x34D43A, 0x34D43B, 0x2F, 0x45),
|
||||
LocationName.ropey_rumpus_region: DKC3Level(0x34D450, 0x34D451, 0x30, 0x43),
|
||||
|
||||
LocationName.konveyor_rope_clash_region: DKC3Level(0x34D489, 0x34D48A, 0x38, 0x48),
|
||||
LocationName.creepy_caverns_region: DKC3Level(0x34D49F, 0x34D4A0, 0x36, 0x46),
|
||||
LocationName.lightning_lookout_region: DKC3Level(0x34D4AA, 0x34D4AB, 0x10, 0x33),
|
||||
LocationName.koindozer_klamber_region: DKC3Level(0x34D4C0, 0x34D4C1, 0x34, 0x47),
|
||||
LocationName.poisonous_pipeline_region: DKC3Level(0x34D4D6, 0x34D4D7, 0x39, 0x3F),
|
||||
|
||||
LocationName.stampede_sprint_region: DKC3Level(0x34D51A, 0x34D51B, 0x3D, 0x49),
|
||||
LocationName.criss_cross_cliffs_region: DKC3Level(0x34D525, 0x34D526, 0x3E, 0x4A),
|
||||
LocationName.tyrant_twin_tussle_region: DKC3Level(0x34D530, 0x34D531, 0x3F, 0x4B),
|
||||
LocationName.swoopy_salvo_region: DKC3Level(0x34D53B, 0x34D53C, 0x40, 0x31),
|
||||
#LocationName.rocket_rush_region: DKC3Level(0x34D546, 0x34D547, 0x05, 0x4C), # Rocket Rush is not getting shuffled
|
||||
}
|
||||
|
||||
level_list = [
|
||||
LocationName.lakeside_limbo_region,
|
||||
LocationName.doorstop_dash_region,
|
||||
LocationName.tidal_trouble_region,
|
||||
LocationName.skiddas_row_region,
|
||||
LocationName.murky_mill_region,
|
||||
|
||||
LocationName.barrel_shield_bust_up_region,
|
||||
LocationName.riverside_race_region,
|
||||
LocationName.squeals_on_wheels_region,
|
||||
LocationName.springin_spiders_region,
|
||||
LocationName.bobbing_barrel_brawl_region,
|
||||
|
||||
LocationName.bazzas_blockade_region,
|
||||
LocationName.rocket_barrel_ride_region,
|
||||
LocationName.kreeping_klasps_region,
|
||||
LocationName.tracker_barrel_trek_region,
|
||||
LocationName.fish_food_frenzy_region,
|
||||
|
||||
LocationName.fire_ball_frenzy_region,
|
||||
LocationName.demolition_drain_pipe_region,
|
||||
LocationName.ripsaw_rage_region,
|
||||
LocationName.blazing_bazookas_region,
|
||||
LocationName.low_g_labyrinth_region,
|
||||
|
||||
LocationName.krevice_kreepers_region,
|
||||
LocationName.tearaway_toboggan_region,
|
||||
LocationName.barrel_drop_bounce_region,
|
||||
LocationName.krack_shot_kroc_region,
|
||||
LocationName.lemguin_lunge_region,
|
||||
|
||||
LocationName.buzzer_barrage_region,
|
||||
LocationName.kong_fused_cliffs_region,
|
||||
LocationName.floodlit_fish_region,
|
||||
LocationName.pothole_panic_region,
|
||||
LocationName.ropey_rumpus_region,
|
||||
|
||||
LocationName.konveyor_rope_clash_region,
|
||||
LocationName.creepy_caverns_region,
|
||||
LocationName.lightning_lookout_region,
|
||||
LocationName.koindozer_klamber_region,
|
||||
LocationName.poisonous_pipeline_region,
|
||||
|
||||
LocationName.stampede_sprint_region,
|
||||
LocationName.criss_cross_cliffs_region,
|
||||
LocationName.tyrant_twin_tussle_region,
|
||||
LocationName.swoopy_salvo_region,
|
||||
#LocationName.rocket_rush_region,
|
||||
]
|
||||
@@ -1,337 +0,0 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import Location
|
||||
from .Names import LocationName
|
||||
from worlds.AutoWorld import World
|
||||
|
||||
|
||||
class DKC3Location(Location):
|
||||
game: str = "Donkey Kong Country 3"
|
||||
|
||||
progress_byte: int = 0x000000
|
||||
progress_bit: int = 0
|
||||
inverted_bit: bool = False
|
||||
|
||||
def __init__(self, player: int, name: str = '', address: int = None, parent=None, prog_byte: int = None, prog_bit: int = None, invert: bool = False):
|
||||
super().__init__(player, name, address, parent)
|
||||
self.progress_byte = prog_byte
|
||||
self.progress_bit = prog_bit
|
||||
self.inverted_bit = invert
|
||||
|
||||
|
||||
level_location_table = {
|
||||
LocationName.lakeside_limbo_flag: 0xDC3000,
|
||||
LocationName.lakeside_limbo_bonus_1: 0xDC3001,
|
||||
LocationName.lakeside_limbo_bonus_2: 0xDC3002,
|
||||
LocationName.lakeside_limbo_dk: 0xDC3003,
|
||||
|
||||
LocationName.doorstop_dash_flag: 0xDC3004,
|
||||
LocationName.doorstop_dash_bonus_1: 0xDC3005,
|
||||
LocationName.doorstop_dash_bonus_2: 0xDC3006,
|
||||
LocationName.doorstop_dash_dk: 0xDC3007,
|
||||
|
||||
LocationName.tidal_trouble_flag: 0xDC3008,
|
||||
LocationName.tidal_trouble_bonus_1: 0xDC3009,
|
||||
LocationName.tidal_trouble_bonus_2: 0xDC300A,
|
||||
LocationName.tidal_trouble_dk: 0xDC300B,
|
||||
|
||||
LocationName.skiddas_row_flag: 0xDC300C,
|
||||
LocationName.skiddas_row_bonus_1: 0xDC300D,
|
||||
LocationName.skiddas_row_bonus_2: 0xDC300E,
|
||||
LocationName.skiddas_row_dk: 0xDC300F,
|
||||
|
||||
LocationName.murky_mill_flag: 0xDC3010,
|
||||
LocationName.murky_mill_bonus_1: 0xDC3011,
|
||||
LocationName.murky_mill_bonus_2: 0xDC3012,
|
||||
LocationName.murky_mill_dk: 0xDC3013,
|
||||
|
||||
LocationName.barrel_shield_bust_up_flag: 0xDC3014,
|
||||
LocationName.barrel_shield_bust_up_bonus_1: 0xDC3015,
|
||||
LocationName.barrel_shield_bust_up_bonus_2: 0xDC3016,
|
||||
LocationName.barrel_shield_bust_up_dk: 0xDC3017,
|
||||
|
||||
LocationName.riverside_race_flag: 0xDC3018,
|
||||
LocationName.riverside_race_bonus_1: 0xDC3019,
|
||||
LocationName.riverside_race_bonus_2: 0xDC301A,
|
||||
LocationName.riverside_race_dk: 0xDC301B,
|
||||
|
||||
LocationName.squeals_on_wheels_flag: 0xDC301C,
|
||||
LocationName.squeals_on_wheels_bonus_1: 0xDC301D,
|
||||
LocationName.squeals_on_wheels_bonus_2: 0xDC301E,
|
||||
LocationName.squeals_on_wheels_dk: 0xDC301F,
|
||||
|
||||
LocationName.springin_spiders_flag: 0xDC3020,
|
||||
LocationName.springin_spiders_bonus_1: 0xDC3021,
|
||||
LocationName.springin_spiders_bonus_2: 0xDC3022,
|
||||
LocationName.springin_spiders_dk: 0xDC3023,
|
||||
|
||||
LocationName.bobbing_barrel_brawl_flag: 0xDC3024,
|
||||
LocationName.bobbing_barrel_brawl_bonus_1: 0xDC3025,
|
||||
LocationName.bobbing_barrel_brawl_bonus_2: 0xDC3026,
|
||||
LocationName.bobbing_barrel_brawl_dk: 0xDC3027,
|
||||
|
||||
LocationName.bazzas_blockade_flag: 0xDC3028,
|
||||
LocationName.bazzas_blockade_bonus_1: 0xDC3029,
|
||||
LocationName.bazzas_blockade_bonus_2: 0xDC302A,
|
||||
LocationName.bazzas_blockade_dk: 0xDC302B,
|
||||
|
||||
LocationName.rocket_barrel_ride_flag: 0xDC302C,
|
||||
LocationName.rocket_barrel_ride_bonus_1: 0xDC302D,
|
||||
LocationName.rocket_barrel_ride_bonus_2: 0xDC302E,
|
||||
LocationName.rocket_barrel_ride_dk: 0xDC302F,
|
||||
|
||||
LocationName.kreeping_klasps_flag: 0xDC3030,
|
||||
LocationName.kreeping_klasps_bonus_1: 0xDC3031,
|
||||
LocationName.kreeping_klasps_bonus_2: 0xDC3032,
|
||||
LocationName.kreeping_klasps_dk: 0xDC3033,
|
||||
|
||||
LocationName.tracker_barrel_trek_flag: 0xDC3034,
|
||||
LocationName.tracker_barrel_trek_bonus_1: 0xDC3035,
|
||||
LocationName.tracker_barrel_trek_bonus_2: 0xDC3036,
|
||||
LocationName.tracker_barrel_trek_dk: 0xDC3037,
|
||||
|
||||
LocationName.fish_food_frenzy_flag: 0xDC3038,
|
||||
LocationName.fish_food_frenzy_bonus_1: 0xDC3039,
|
||||
LocationName.fish_food_frenzy_bonus_2: 0xDC303A,
|
||||
LocationName.fish_food_frenzy_dk: 0xDC303B,
|
||||
|
||||
LocationName.fire_ball_frenzy_flag: 0xDC303C,
|
||||
LocationName.fire_ball_frenzy_bonus_1: 0xDC303D,
|
||||
LocationName.fire_ball_frenzy_bonus_2: 0xDC303E,
|
||||
LocationName.fire_ball_frenzy_dk: 0xDC303F,
|
||||
|
||||
LocationName.demolition_drain_pipe_flag: 0xDC3040,
|
||||
LocationName.demolition_drain_pipe_bonus_1: 0xDC3041,
|
||||
LocationName.demolition_drain_pipe_bonus_2: 0xDC3042,
|
||||
LocationName.demolition_drain_pipe_dk: 0xDC3043,
|
||||
|
||||
LocationName.ripsaw_rage_flag: 0xDC3044,
|
||||
LocationName.ripsaw_rage_bonus_1: 0xDC3045,
|
||||
LocationName.ripsaw_rage_bonus_2: 0xDC3046,
|
||||
LocationName.ripsaw_rage_dk: 0xDC3047,
|
||||
|
||||
LocationName.blazing_bazookas_flag: 0xDC3048,
|
||||
LocationName.blazing_bazookas_bonus_1: 0xDC3049,
|
||||
LocationName.blazing_bazookas_bonus_2: 0xDC304A,
|
||||
LocationName.blazing_bazookas_dk: 0xDC304B,
|
||||
|
||||
LocationName.low_g_labyrinth_flag: 0xDC304C,
|
||||
LocationName.low_g_labyrinth_bonus_1: 0xDC304D,
|
||||
LocationName.low_g_labyrinth_bonus_2: 0xDC304E,
|
||||
LocationName.low_g_labyrinth_dk: 0xDC304F,
|
||||
|
||||
LocationName.krevice_kreepers_flag: 0xDC3050,
|
||||
LocationName.krevice_kreepers_bonus_1: 0xDC3051,
|
||||
LocationName.krevice_kreepers_bonus_2: 0xDC3052,
|
||||
LocationName.krevice_kreepers_dk: 0xDC3053,
|
||||
|
||||
LocationName.tearaway_toboggan_flag: 0xDC3054,
|
||||
LocationName.tearaway_toboggan_bonus_1: 0xDC3055,
|
||||
LocationName.tearaway_toboggan_bonus_2: 0xDC3056,
|
||||
LocationName.tearaway_toboggan_dk: 0xDC3057,
|
||||
|
||||
LocationName.barrel_drop_bounce_flag: 0xDC3058,
|
||||
LocationName.barrel_drop_bounce_bonus_1: 0xDC3059,
|
||||
LocationName.barrel_drop_bounce_bonus_2: 0xDC305A,
|
||||
LocationName.barrel_drop_bounce_dk: 0xDC305B,
|
||||
|
||||
LocationName.krack_shot_kroc_flag: 0xDC305C,
|
||||
LocationName.krack_shot_kroc_bonus_1: 0xDC305D,
|
||||
LocationName.krack_shot_kroc_bonus_2: 0xDC305E,
|
||||
LocationName.krack_shot_kroc_dk: 0xDC305F,
|
||||
|
||||
LocationName.lemguin_lunge_flag: 0xDC3060,
|
||||
LocationName.lemguin_lunge_bonus_1: 0xDC3061,
|
||||
LocationName.lemguin_lunge_bonus_2: 0xDC3062,
|
||||
LocationName.lemguin_lunge_dk: 0xDC3063,
|
||||
|
||||
LocationName.buzzer_barrage_flag: 0xDC3064,
|
||||
LocationName.buzzer_barrage_bonus_1: 0xDC3065,
|
||||
LocationName.buzzer_barrage_bonus_2: 0xDC3066,
|
||||
LocationName.buzzer_barrage_dk: 0xDC3067,
|
||||
|
||||
LocationName.kong_fused_cliffs_flag: 0xDC3068,
|
||||
LocationName.kong_fused_cliffs_bonus_1: 0xDC3069,
|
||||
LocationName.kong_fused_cliffs_bonus_2: 0xDC306A,
|
||||
LocationName.kong_fused_cliffs_dk: 0xDC306B,
|
||||
|
||||
LocationName.floodlit_fish_flag: 0xDC306C,
|
||||
LocationName.floodlit_fish_bonus_1: 0xDC306D,
|
||||
LocationName.floodlit_fish_bonus_2: 0xDC306E,
|
||||
LocationName.floodlit_fish_dk: 0xDC306F,
|
||||
|
||||
LocationName.pothole_panic_flag: 0xDC3070,
|
||||
LocationName.pothole_panic_bonus_1: 0xDC3071,
|
||||
LocationName.pothole_panic_bonus_2: 0xDC3072,
|
||||
LocationName.pothole_panic_dk: 0xDC3073,
|
||||
|
||||
LocationName.ropey_rumpus_flag: 0xDC3074,
|
||||
LocationName.ropey_rumpus_bonus_1: 0xDC3075,
|
||||
LocationName.ropey_rumpus_bonus_2: 0xDC3076,
|
||||
LocationName.ropey_rumpus_dk: 0xDC3077,
|
||||
|
||||
LocationName.konveyor_rope_clash_flag: 0xDC3078,
|
||||
LocationName.konveyor_rope_clash_bonus_1: 0xDC3079,
|
||||
LocationName.konveyor_rope_clash_bonus_2: 0xDC307A,
|
||||
LocationName.konveyor_rope_clash_dk: 0xDC307B,
|
||||
|
||||
LocationName.creepy_caverns_flag: 0xDC307C,
|
||||
LocationName.creepy_caverns_bonus_1: 0xDC307D,
|
||||
LocationName.creepy_caverns_bonus_2: 0xDC307E,
|
||||
LocationName.creepy_caverns_dk: 0xDC307F,
|
||||
|
||||
LocationName.lightning_lookout_flag: 0xDC3080,
|
||||
LocationName.lightning_lookout_bonus_1: 0xDC3081,
|
||||
LocationName.lightning_lookout_bonus_2: 0xDC3082,
|
||||
LocationName.lightning_lookout_dk: 0xDC3083,
|
||||
|
||||
LocationName.koindozer_klamber_flag: 0xDC3084,
|
||||
LocationName.koindozer_klamber_bonus_1: 0xDC3085,
|
||||
LocationName.koindozer_klamber_bonus_2: 0xDC3086,
|
||||
LocationName.koindozer_klamber_dk: 0xDC3087,
|
||||
|
||||
LocationName.poisonous_pipeline_flag: 0xDC3088,
|
||||
LocationName.poisonous_pipeline_bonus_1: 0xDC3089,
|
||||
LocationName.poisonous_pipeline_bonus_2: 0xDC308A,
|
||||
LocationName.poisonous_pipeline_dk: 0xDC308B,
|
||||
|
||||
LocationName.stampede_sprint_flag: 0xDC308C,
|
||||
LocationName.stampede_sprint_bonus_1: 0xDC308D,
|
||||
LocationName.stampede_sprint_bonus_2: 0xDC308E,
|
||||
LocationName.stampede_sprint_bonus_3: 0xDC308F,
|
||||
LocationName.stampede_sprint_dk: 0xDC3090,
|
||||
|
||||
LocationName.criss_cross_cliffs_flag: 0xDC3091,
|
||||
LocationName.criss_cross_cliffs_bonus_1: 0xDC3092,
|
||||
LocationName.criss_cross_cliffs_bonus_2: 0xDC3093,
|
||||
LocationName.criss_cross_cliffs_dk: 0xDC3094,
|
||||
|
||||
LocationName.tyrant_twin_tussle_flag: 0xDC3095,
|
||||
LocationName.tyrant_twin_tussle_bonus_1: 0xDC3096,
|
||||
LocationName.tyrant_twin_tussle_bonus_2: 0xDC3097,
|
||||
LocationName.tyrant_twin_tussle_bonus_3: 0xDC3098,
|
||||
LocationName.tyrant_twin_tussle_dk: 0xDC3099,
|
||||
|
||||
LocationName.swoopy_salvo_flag: 0xDC309A,
|
||||
LocationName.swoopy_salvo_bonus_1: 0xDC309B,
|
||||
LocationName.swoopy_salvo_bonus_2: 0xDC309C,
|
||||
LocationName.swoopy_salvo_bonus_3: 0xDC309D,
|
||||
LocationName.swoopy_salvo_dk: 0xDC309E,
|
||||
|
||||
LocationName.rocket_rush_flag: 0xDC309F,
|
||||
LocationName.rocket_rush_dk: 0xDC30A0,
|
||||
}
|
||||
|
||||
kong_location_table = {
|
||||
LocationName.lakeside_limbo_kong: 0xDC3100,
|
||||
LocationName.doorstop_dash_kong: 0xDC3104,
|
||||
LocationName.tidal_trouble_kong: 0xDC3108,
|
||||
LocationName.skiddas_row_kong: 0xDC310C,
|
||||
LocationName.murky_mill_kong: 0xDC3110,
|
||||
|
||||
LocationName.barrel_shield_bust_up_kong: 0xDC3114,
|
||||
LocationName.riverside_race_kong: 0xDC3118,
|
||||
LocationName.squeals_on_wheels_kong: 0xDC311C,
|
||||
LocationName.springin_spiders_kong: 0xDC3120,
|
||||
LocationName.bobbing_barrel_brawl_kong: 0xDC3124,
|
||||
|
||||
LocationName.bazzas_blockade_kong: 0xDC3128,
|
||||
LocationName.rocket_barrel_ride_kong: 0xDC312C,
|
||||
LocationName.kreeping_klasps_kong: 0xDC3130,
|
||||
LocationName.tracker_barrel_trek_kong: 0xDC3134,
|
||||
LocationName.fish_food_frenzy_kong: 0xDC3138,
|
||||
|
||||
LocationName.fire_ball_frenzy_kong: 0xDC313C,
|
||||
LocationName.demolition_drain_pipe_kong: 0xDC3140,
|
||||
LocationName.ripsaw_rage_kong: 0xDC3144,
|
||||
LocationName.blazing_bazookas_kong: 0xDC3148,
|
||||
LocationName.low_g_labyrinth_kong: 0xDC314C,
|
||||
|
||||
LocationName.krevice_kreepers_kong: 0xDC3150,
|
||||
LocationName.tearaway_toboggan_kong: 0xDC3154,
|
||||
LocationName.barrel_drop_bounce_kong: 0xDC3158,
|
||||
LocationName.krack_shot_kroc_kong: 0xDC315C,
|
||||
LocationName.lemguin_lunge_kong: 0xDC3160,
|
||||
|
||||
LocationName.buzzer_barrage_kong: 0xDC3164,
|
||||
LocationName.kong_fused_cliffs_kong: 0xDC3168,
|
||||
LocationName.floodlit_fish_kong: 0xDC316C,
|
||||
LocationName.pothole_panic_kong: 0xDC3170,
|
||||
LocationName.ropey_rumpus_kong: 0xDC3174,
|
||||
|
||||
LocationName.konveyor_rope_clash_kong: 0xDC3178,
|
||||
LocationName.creepy_caverns_kong: 0xDC317C,
|
||||
LocationName.lightning_lookout_kong: 0xDC3180,
|
||||
LocationName.koindozer_klamber_kong: 0xDC3184,
|
||||
LocationName.poisonous_pipeline_kong: 0xDC3188,
|
||||
|
||||
LocationName.stampede_sprint_kong: 0xDC318C,
|
||||
LocationName.criss_cross_cliffs_kong: 0xDC3191,
|
||||
LocationName.tyrant_twin_tussle_kong: 0xDC3195,
|
||||
LocationName.swoopy_salvo_kong: 0xDC319A,
|
||||
}
|
||||
|
||||
|
||||
boss_location_table = {
|
||||
LocationName.belchas_barn: 0xDC30A1,
|
||||
LocationName.arichs_ambush: 0xDC30A2,
|
||||
LocationName.squirts_showdown: 0xDC30A3,
|
||||
LocationName.kaos_karnage: 0xDC30A4,
|
||||
LocationName.bleaks_house: 0xDC30A5,
|
||||
LocationName.barboss_barrier: 0xDC30A6,
|
||||
LocationName.kastle_kaos: 0xDC30A7,
|
||||
LocationName.knautilus: 0xDC30A8,
|
||||
}
|
||||
|
||||
secret_cave_location_table = {
|
||||
LocationName.belchas_burrow: 0xDC30A9,
|
||||
LocationName.kong_cave: 0xDC30AA,
|
||||
LocationName.undercover_cove: 0xDC30AB,
|
||||
LocationName.ks_cache: 0xDC30AC,
|
||||
LocationName.hill_top_hoard: 0xDC30AD,
|
||||
LocationName.bounty_beach: 0xDC30AE,
|
||||
LocationName.smugglers_cove: 0xDC30AF,
|
||||
LocationName.arichs_hoard: 0xDC30B0,
|
||||
LocationName.bounty_bay: 0xDC30B1,
|
||||
LocationName.sky_high_secret: 0xDC30B2,
|
||||
LocationName.glacial_grotto: 0xDC30B3,
|
||||
LocationName.cifftop_cache: 0xDC30B4,
|
||||
LocationName.sewer_stockpile: 0xDC30B5,
|
||||
LocationName.banana_bird_mother: 0xDC30B6,
|
||||
}
|
||||
|
||||
brothers_bear_location_table = {
|
||||
LocationName.bazaars_general_store_1: 0xDC30B7,
|
||||
LocationName.bazaars_general_store_2: 0xDC30B8,
|
||||
LocationName.brambles_bungalow: 0xDC30B9,
|
||||
LocationName.flower_spot: 0xDC30BA,
|
||||
LocationName.barters_swap_shop: 0xDC30BB,
|
||||
LocationName.barnacles_island: 0xDC30BC,
|
||||
LocationName.blues_beach_hut: 0xDC30BD,
|
||||
LocationName.blizzards_basecamp: 0xDC30BE,
|
||||
}
|
||||
|
||||
all_locations = {
|
||||
**level_location_table,
|
||||
**boss_location_table,
|
||||
**secret_cave_location_table,
|
||||
**brothers_bear_location_table,
|
||||
**kong_location_table,
|
||||
}
|
||||
|
||||
location_table = {}
|
||||
|
||||
|
||||
def setup_locations(world: World):
|
||||
location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table}
|
||||
|
||||
if False:#world.options.include_trade_sequence:
|
||||
location_table.update({**brothers_bear_location_table})
|
||||
|
||||
if world.options.kongsanity:
|
||||
location_table.update({**kong_location_table})
|
||||
|
||||
return location_table
|
||||
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Junk Definitions
|
||||
one_up_balloon = "1-Up Balloon"
|
||||
bear_coin = "Bear Coin"
|
||||
|
||||
# Collectable Definitions
|
||||
bonus_coin = "Bonus Coin"
|
||||
dk_coin = "DK Coin"
|
||||
banana_bird = "Banana Bird"
|
||||
krematoa_cog = "Krematoa Cog"
|
||||
|
||||
# Inventory Definitions
|
||||
progressive_boat = "Progressive Boat Upgrade"
|
||||
present = "Present"
|
||||
bowling_ball = "Bowling Ball"
|
||||
shell = "Shell"
|
||||
mirror = "Mirror"
|
||||
flower = "Flupperius Petallus Pongus"
|
||||
wrench = "No. 6 Wrench"
|
||||
|
||||
# Other Definitions
|
||||
victory = "Donkey Kong"
|
||||
@@ -1,375 +0,0 @@
|
||||
# Level Definitions
|
||||
lakeside_limbo_flag = "Lakeside Limbo - Flag"
|
||||
lakeside_limbo_kong = "Lakeside Limbo - KONG"
|
||||
lakeside_limbo_bonus_1 = "Lakeside Limbo - Bonus 1"
|
||||
lakeside_limbo_bonus_2 = "Lakeside Limbo - Bonus 2"
|
||||
lakeside_limbo_dk = "Lakeside Limbo - DK Coin"
|
||||
|
||||
doorstop_dash_flag = "Doorstop Dash - Flag"
|
||||
doorstop_dash_kong = "Doorstop Dash - KONG"
|
||||
doorstop_dash_bonus_1 = "Doorstop Dash - Bonus 1"
|
||||
doorstop_dash_bonus_2 = "Doorstop Dash - Bonus 2"
|
||||
doorstop_dash_dk = "Doorstop Dash - DK Coin"
|
||||
|
||||
tidal_trouble_flag = "Tidal Trouble - Flag"
|
||||
tidal_trouble_kong = "Tidal Trouble - KONG"
|
||||
tidal_trouble_bonus_1 = "Tidal Trouble - Bonus 1"
|
||||
tidal_trouble_bonus_2 = "Tidal Trouble - Bonus 2"
|
||||
tidal_trouble_dk = "Tidal Trouble - DK Coin"
|
||||
|
||||
skiddas_row_flag = "Skidda's Row - Flag"
|
||||
skiddas_row_kong = "Skidda's Row - KONG"
|
||||
skiddas_row_bonus_1 = "Skidda's Row - Bonus 1"
|
||||
skiddas_row_bonus_2 = "Skidda's Row - Bonus 2"
|
||||
skiddas_row_dk = "Skidda's Row - DK Coin"
|
||||
|
||||
murky_mill_flag = "Murky Mill - Flag"
|
||||
murky_mill_kong = "Murky Mill - KONG"
|
||||
murky_mill_bonus_1 = "Murky Mill - Bonus 1"
|
||||
murky_mill_bonus_2 = "Murky Mill - Bonus 2"
|
||||
murky_mill_dk = "Murky Mill - DK Coin"
|
||||
|
||||
barrel_shield_bust_up_flag = "Barrel Shield Bust-Up - Flag"
|
||||
barrel_shield_bust_up_kong = "Barrel Shield Bust-Up - KONG"
|
||||
barrel_shield_bust_up_bonus_1 = "Barrel Shield Bust-Up - Bonus 1"
|
||||
barrel_shield_bust_up_bonus_2 = "Barrel Shield Bust-Up - Bonus 2"
|
||||
barrel_shield_bust_up_dk = "Barrel Shield Bust-Up - DK Coin"
|
||||
|
||||
riverside_race_flag = "Riverside Race - Flag"
|
||||
riverside_race_kong = "Riverside Race - KONG"
|
||||
riverside_race_bonus_1 = "Riverside Race - Bonus 1"
|
||||
riverside_race_bonus_2 = "Riverside Race - Bonus 2"
|
||||
riverside_race_dk = "Riverside Race - DK Coin"
|
||||
|
||||
squeals_on_wheels_flag = "Squeals On Wheels - Flag"
|
||||
squeals_on_wheels_kong = "Squeals On Wheels - KONG"
|
||||
squeals_on_wheels_bonus_1 = "Squeals On Wheels - Bonus 1"
|
||||
squeals_on_wheels_bonus_2 = "Squeals On Wheels - Bonus 2"
|
||||
squeals_on_wheels_dk = "Squeals On Wheels - DK Coin"
|
||||
|
||||
springin_spiders_flag = "Springin' Spiders - Flag"
|
||||
springin_spiders_kong = "Springin' Spiders - KONG"
|
||||
springin_spiders_bonus_1 = "Springin' Spiders - Bonus 1"
|
||||
springin_spiders_bonus_2 = "Springin' Spiders - Bonus 2"
|
||||
springin_spiders_dk = "Springin' Spiders - DK Coin"
|
||||
|
||||
bobbing_barrel_brawl_flag = "Bobbing Barrel Brawl - Flag"
|
||||
bobbing_barrel_brawl_kong = "Bobbing Barrel Brawl - KONG"
|
||||
bobbing_barrel_brawl_bonus_1 = "Bobbing Barrel Brawl - Bonus 1"
|
||||
bobbing_barrel_brawl_bonus_2 = "Bobbing Barrel Brawl - Bonus 2"
|
||||
bobbing_barrel_brawl_dk = "Bobbing Barrel Brawl - DK Coin"
|
||||
|
||||
bazzas_blockade_flag = "Bazza's Blockade - Flag"
|
||||
bazzas_blockade_kong = "Bazza's Blockade - KONG"
|
||||
bazzas_blockade_bonus_1 = "Bazza's Blockade - Bonus 1"
|
||||
bazzas_blockade_bonus_2 = "Bazza's Blockade - Bonus 2"
|
||||
bazzas_blockade_dk = "Bazza's Blockade - DK Coin"
|
||||
|
||||
rocket_barrel_ride_flag = "Rocket Barrel Ride - Flag"
|
||||
rocket_barrel_ride_kong = "Rocket Barrel Ride - KONG"
|
||||
rocket_barrel_ride_bonus_1 = "Rocket Barrel Ride - Bonus 1"
|
||||
rocket_barrel_ride_bonus_2 = "Rocket Barrel Ride - Bonus 2"
|
||||
rocket_barrel_ride_dk = "Rocket Barrel Ride - DK Coin"
|
||||
|
||||
kreeping_klasps_flag = "Kreeping Klasps - Flag"
|
||||
kreeping_klasps_kong = "Kreeping Klasps - KONG"
|
||||
kreeping_klasps_bonus_1 = "Kreeping Klasps - Bonus 1"
|
||||
kreeping_klasps_bonus_2 = "Kreeping Klasps - Bonus 2"
|
||||
kreeping_klasps_dk = "Kreeping Klasps - DK Coin"
|
||||
|
||||
tracker_barrel_trek_flag = "Tracker Barrel Trek - Flag"
|
||||
tracker_barrel_trek_kong = "Tracker Barrel Trek - KONG"
|
||||
tracker_barrel_trek_bonus_1 = "Tracker Barrel Trek - Bonus 1"
|
||||
tracker_barrel_trek_bonus_2 = "Tracker Barrel Trek - Bonus 2"
|
||||
tracker_barrel_trek_dk = "Tracker Barrel Trek - DK Coin"
|
||||
|
||||
fish_food_frenzy_flag = "Fish Food Frenzy - Flag"
|
||||
fish_food_frenzy_kong = "Fish Food Frenzy - KONG"
|
||||
fish_food_frenzy_bonus_1 = "Fish Food Frenzy - Bonus 1"
|
||||
fish_food_frenzy_bonus_2 = "Fish Food Frenzy - Bonus 2"
|
||||
fish_food_frenzy_dk = "Fish Food Frenzy - DK Coin"
|
||||
|
||||
fire_ball_frenzy_flag = "Fire-Ball Frenzy - Flag"
|
||||
fire_ball_frenzy_kong = "Fire-Ball Frenzy - KONG"
|
||||
fire_ball_frenzy_bonus_1 = "Fire-Ball Frenzy - Bonus 1"
|
||||
fire_ball_frenzy_bonus_2 = "Fire-Ball Frenzy - Bonus 2"
|
||||
fire_ball_frenzy_dk = "Fire-Ball Frenzy - DK Coin"
|
||||
|
||||
demolition_drain_pipe_flag = "Demolition Drain-Pipe - Flag"
|
||||
demolition_drain_pipe_kong = "Demolition Drain-Pipe - KONG"
|
||||
demolition_drain_pipe_bonus_1 = "Demolition Drain-Pipe - Bonus 1"
|
||||
demolition_drain_pipe_bonus_2 = "Demolition Drain-Pipe - Bonus 2"
|
||||
demolition_drain_pipe_dk = "Demolition Drain-Pipe - DK Coin"
|
||||
|
||||
ripsaw_rage_flag = "Ripsaw Rage - Flag"
|
||||
ripsaw_rage_kong = "Ripsaw Rage - KONG"
|
||||
ripsaw_rage_bonus_1 = "Ripsaw Rage - Bonus 1"
|
||||
ripsaw_rage_bonus_2 = "Ripsaw Rage - Bonus 2"
|
||||
ripsaw_rage_dk = "Ripsaw Rage - DK Coin"
|
||||
|
||||
blazing_bazookas_flag = "Blazing Bazukas - Flag"
|
||||
blazing_bazookas_kong = "Blazing Bazukas - KONG"
|
||||
blazing_bazookas_bonus_1 = "Blazing Bazukas - Bonus 1"
|
||||
blazing_bazookas_bonus_2 = "Blazing Bazukas - Bonus 2"
|
||||
blazing_bazookas_dk = "Blazing Bazukas - DK Coin"
|
||||
|
||||
low_g_labyrinth_flag = "Low-G Labyrinth - Flag"
|
||||
low_g_labyrinth_kong = "Low-G Labyrinth - KONG"
|
||||
low_g_labyrinth_bonus_1 = "Low-G Labyrinth - Bonus 1"
|
||||
low_g_labyrinth_bonus_2 = "Low-G Labyrinth - Bonus 2"
|
||||
low_g_labyrinth_dk = "Low-G Labyrinth - DK Coin"
|
||||
|
||||
krevice_kreepers_flag = "Krevice Kreepers - Flag"
|
||||
krevice_kreepers_kong = "Krevice Kreepers - KONG"
|
||||
krevice_kreepers_bonus_1 = "Krevice Kreepers - Bonus 1"
|
||||
krevice_kreepers_bonus_2 = "Krevice Kreepers - Bonus 2"
|
||||
krevice_kreepers_dk = "Krevice Kreepers - DK Coin"
|
||||
|
||||
tearaway_toboggan_flag = "Tearaway Toboggan - Flag"
|
||||
tearaway_toboggan_kong = "Tearaway Toboggan - KONG"
|
||||
tearaway_toboggan_bonus_1 = "Tearaway Toboggan - Bonus 1"
|
||||
tearaway_toboggan_bonus_2 = "Tearaway Toboggan - Bonus 2"
|
||||
tearaway_toboggan_dk = "Tearaway Toboggan - DK Coin"
|
||||
|
||||
barrel_drop_bounce_flag = "Barrel Drop Bounce - Flag"
|
||||
barrel_drop_bounce_kong = "Barrel Drop Bounce - KONG"
|
||||
barrel_drop_bounce_bonus_1 = "Barrel Drop Bounce - Bonus 1"
|
||||
barrel_drop_bounce_bonus_2 = "Barrel Drop Bounce - Bonus 2"
|
||||
barrel_drop_bounce_dk = "Barrel Drop Bounce - DK Coin"
|
||||
|
||||
krack_shot_kroc_flag = "Krack-Shot Kroc - Flag"
|
||||
krack_shot_kroc_kong = "Krack-Shot Kroc - KONG"
|
||||
krack_shot_kroc_bonus_1 = "Krack-Shot Kroc - Bonus 1"
|
||||
krack_shot_kroc_bonus_2 = "Krack-Shot Kroc - Bonus 2"
|
||||
krack_shot_kroc_dk = "Krack-Shot Kroc - DK Coin"
|
||||
|
||||
lemguin_lunge_flag = "Lemguin Lunge - Flag"
|
||||
lemguin_lunge_kong = "Lemguin Lunge - KONG"
|
||||
lemguin_lunge_bonus_1 = "Lemguin Lunge - Bonus 1"
|
||||
lemguin_lunge_bonus_2 = "Lemguin Lunge - Bonus 2"
|
||||
lemguin_lunge_dk = "Lemguin Lunge - DK Coin"
|
||||
|
||||
buzzer_barrage_flag = "Buzzer Barrage - Flag"
|
||||
buzzer_barrage_kong = "Buzzer Barrage - KONG"
|
||||
buzzer_barrage_bonus_1 = "Buzzer Barrage - Bonus 1"
|
||||
buzzer_barrage_bonus_2 = "Buzzer Barrage - Bonus 2"
|
||||
buzzer_barrage_dk = "Buzzer Barrage - DK Coin"
|
||||
|
||||
kong_fused_cliffs_flag = "Kong-Fused Cliffs - Flag"
|
||||
kong_fused_cliffs_kong = "Kong-Fused Cliffs - KONG"
|
||||
kong_fused_cliffs_bonus_1 = "Kong-Fused Cliffs - Bonus 1"
|
||||
kong_fused_cliffs_bonus_2 = "Kong-Fused Cliffs - Bonus 2"
|
||||
kong_fused_cliffs_dk = "Kong-Fused Cliffs - DK Coin"
|
||||
|
||||
floodlit_fish_flag = "Floodlit Fish - Flag"
|
||||
floodlit_fish_kong = "Floodlit Fish - KONG"
|
||||
floodlit_fish_bonus_1 = "Floodlit Fish - Bonus 1"
|
||||
floodlit_fish_bonus_2 = "Floodlit Fish - Bonus 2"
|
||||
floodlit_fish_dk = "Floodlit Fish - DK Coin"
|
||||
|
||||
pothole_panic_flag = "Pothole Panic - Flag"
|
||||
pothole_panic_kong = "Pothole Panic - KONG"
|
||||
pothole_panic_bonus_1 = "Pothole Panic - Bonus 1"
|
||||
pothole_panic_bonus_2 = "Pothole Panic - Bonus 2"
|
||||
pothole_panic_dk = "Pothole Panic - DK Coin"
|
||||
|
||||
ropey_rumpus_flag = "Ropey Rumpus - Flag"
|
||||
ropey_rumpus_kong = "Ropey Rumpus - KONG"
|
||||
ropey_rumpus_bonus_1 = "Ropey Rumpus - Bonus 1"
|
||||
ropey_rumpus_bonus_2 = "Ropey Rumpus - Bonus 2"
|
||||
ropey_rumpus_dk = "Ropey Rumpus - DK Coin"
|
||||
|
||||
konveyor_rope_clash_flag = "Konveyor Rope Klash - Flag"
|
||||
konveyor_rope_clash_kong = "Konveyor Rope Klash - KONG"
|
||||
konveyor_rope_clash_bonus_1 = "Konveyor Rope Klash - Bonus 1"
|
||||
konveyor_rope_clash_bonus_2 = "Konveyor Rope Klash - Bonus 2"
|
||||
konveyor_rope_clash_dk = "Konveyor Rope Klash - DK Coin"
|
||||
|
||||
creepy_caverns_flag = "Creepy Caverns - Flag"
|
||||
creepy_caverns_kong = "Creepy Caverns - KONG"
|
||||
creepy_caverns_bonus_1 = "Creepy Caverns - Bonus 1"
|
||||
creepy_caverns_bonus_2 = "Creepy Caverns - Bonus 2"
|
||||
creepy_caverns_dk = "Creepy Caverns - DK Coin"
|
||||
|
||||
lightning_lookout_flag = "Lightning Lookout - Flag"
|
||||
lightning_lookout_kong = "Lightning Lookout - KONG"
|
||||
lightning_lookout_bonus_1 = "Lightning Lookout - Bonus 1"
|
||||
lightning_lookout_bonus_2 = "Lightning Lookout - Bonus 2"
|
||||
lightning_lookout_dk = "Lightning Lookout - DK Coin"
|
||||
|
||||
koindozer_klamber_flag = "Koindozer Klamber - Flag"
|
||||
koindozer_klamber_kong = "Koindozer Klamber - KONG"
|
||||
koindozer_klamber_bonus_1 = "Koindozer Klamber - Bonus 1"
|
||||
koindozer_klamber_bonus_2 = "Koindozer Klamber - Bonus 2"
|
||||
koindozer_klamber_dk = "Koindozer Klamber - DK Coin"
|
||||
|
||||
poisonous_pipeline_flag = "Poisonous Pipeline - Flag"
|
||||
poisonous_pipeline_kong = "Poisonous Pipeline - KONG"
|
||||
poisonous_pipeline_bonus_1 = "Poisonous Pipeline - Bonus 1"
|
||||
poisonous_pipeline_bonus_2 = "Poisonous Pipeline - Bonus 2"
|
||||
poisonous_pipeline_dk = "Poisonous Pipeline - DK Coin"
|
||||
|
||||
stampede_sprint_flag = "Stampede Sprint - Flag"
|
||||
stampede_sprint_kong = "Stampede Sprint - KONG"
|
||||
stampede_sprint_bonus_1 = "Stampede Sprint - Bonus 1"
|
||||
stampede_sprint_bonus_2 = "Stampede Sprint - Bonus 2"
|
||||
stampede_sprint_bonus_3 = "Stampede Sprint - Bonus 3"
|
||||
stampede_sprint_dk = "Stampede Sprint - DK Coin"
|
||||
|
||||
criss_cross_cliffs_flag = "Criss Kross Cliffs - Flag"
|
||||
criss_cross_cliffs_kong = "Criss Kross Cliffs - KONG"
|
||||
criss_cross_cliffs_bonus_1 = "Criss Kross Cliffs - Bonus 1"
|
||||
criss_cross_cliffs_bonus_2 = "Criss Kross Cliffs - Bonus 2"
|
||||
criss_cross_cliffs_dk = "Criss Kross Cliffs - DK Coin"
|
||||
|
||||
tyrant_twin_tussle_flag = "Tyrant Twin Tussle - Flag"
|
||||
tyrant_twin_tussle_kong = "Tyrant Twin Tussle - KONG"
|
||||
tyrant_twin_tussle_bonus_1 = "Tyrant Twin Tussle - Bonus 1"
|
||||
tyrant_twin_tussle_bonus_2 = "Tyrant Twin Tussle - Bonus 2"
|
||||
tyrant_twin_tussle_bonus_3 = "Tyrant Twin Tussle - Bonus 3"
|
||||
tyrant_twin_tussle_dk = "Tyrant Twin Tussle - DK Coin"
|
||||
|
||||
swoopy_salvo_flag = "Swoopy Salvo - Flag"
|
||||
swoopy_salvo_kong = "Swoopy Salvo - KONG"
|
||||
swoopy_salvo_bonus_1 = "Swoopy Salvo - Bonus 1"
|
||||
swoopy_salvo_bonus_2 = "Swoopy Salvo - Bonus 2"
|
||||
swoopy_salvo_bonus_3 = "Swoopy Salvo - Bonus 3"
|
||||
swoopy_salvo_dk = "Swoopy Salvo - DK Coin"
|
||||
|
||||
rocket_rush_flag = "Rocket Rush - Flag"
|
||||
rocket_rush_dk = "Rocket Rush - DK Coin"
|
||||
|
||||
# Boss Definitions
|
||||
belchas_barn = "Belcha's Barn"
|
||||
arichs_ambush = "Arich's Ambush"
|
||||
squirts_showdown = "Squirt's Showdown"
|
||||
kaos_karnage = "KAOS Karnage"
|
||||
bleaks_house = "Bleak's House"
|
||||
barboss_barrier = "Barbos's Barrier"
|
||||
kastle_kaos = "Kastle KAOS"
|
||||
knautilus = "Knautilus"
|
||||
|
||||
|
||||
# Banana Bird Cave Definitions
|
||||
belchas_burrow = "Belcha's Burrow"
|
||||
kong_cave = "Kong Cave"
|
||||
undercover_cove = "Undercover Cove"
|
||||
ks_cache = "K's Cache"
|
||||
hill_top_hoard = "Hill-Top Hoard"
|
||||
bounty_beach = "Bounty Beach"
|
||||
smugglers_cove = "Smuggler's Cove"
|
||||
arichs_hoard = "Arich's Hoard"
|
||||
bounty_bay = "Bounty Bay"
|
||||
sky_high_secret = "Sky-High Secret"
|
||||
glacial_grotto = "Glacial Grotto"
|
||||
cifftop_cache = "Clifftop Cache"
|
||||
sewer_stockpile = "Sewer Stockpile"
|
||||
|
||||
banana_bird_mother = "Banana Bird Mother"
|
||||
|
||||
|
||||
# Brothers Bear Definitions
|
||||
bazaars_general_store_1 = "Bazaar's General Store - 1"
|
||||
bazaars_general_store_2 = "Bazaar's General Store - 2"
|
||||
brambles_bungalow = "Bramble's Bungalow"
|
||||
flower_spot = "Flower Spot"
|
||||
barters_swap_shop = "Barter's Swap Shop"
|
||||
barnacles_island = "Barnacle's Island"
|
||||
blues_beach_hut = "Blue's Beach Hut"
|
||||
blizzards_basecamp = "Bizzard's Basecamp"
|
||||
|
||||
|
||||
# Region Definitions
|
||||
menu_region = "Menu"
|
||||
overworld_1_region = "Overworld 1"
|
||||
overworld_2_region = "Overworld 2"
|
||||
overworld_3_region = "Overworld 3"
|
||||
overworld_4_region = "Overworld 4"
|
||||
|
||||
bazaar_region = "Bazaar's General Store Region"
|
||||
bramble_region = "Bramble's Bungalow Region"
|
||||
flower_spot_region = "Flower Spot Region"
|
||||
barter_region = "Barter's Swap Shop Region"
|
||||
barnacle_region = "Barnacle's Island Region"
|
||||
blue_region = "Blue's Beach Hut Region"
|
||||
blizzard_region = "Bizzard's Basecamp Region"
|
||||
|
||||
lake_orangatanga_region = "Lake Orangatanga"
|
||||
kremwood_forest_region = "Kremwood Forest"
|
||||
cotton_top_cove_region = "Cotton-Top Cove"
|
||||
mekanos_region = "Mekanos"
|
||||
k3_region = "K3"
|
||||
razor_ridge_region = "Razor Ridge"
|
||||
kaos_kore_region = "KAOS Kore"
|
||||
krematoa_region = "Krematoa"
|
||||
|
||||
belchas_barn_region = "Belcha's Barn Region"
|
||||
arichs_ambush_region = "Arich's Ambush Region"
|
||||
squirts_showdown_region = "Squirt's Showdown Region"
|
||||
kaos_karnage_region = "KAOS Karnage Region"
|
||||
bleaks_house_region = "Bleak's House Region"
|
||||
barboss_barrier_region = "Barbos's Barrier Region"
|
||||
kastle_kaos_region = "Kastle KAOS Region"
|
||||
knautilus_region = "Knautilus Region"
|
||||
|
||||
belchas_burrow_region = "Belcha's Burrow Region"
|
||||
kong_cave_region = "Kong Cave Region"
|
||||
undercover_cove_region = "Undercover Cove Region"
|
||||
ks_cache_region = "K's Cache Region"
|
||||
hill_top_hoard_region = "Hill-Top Hoard Region"
|
||||
bounty_beach_region = "Bounty Beach Region"
|
||||
smugglers_cove_region = "Smuggler's Cove Region"
|
||||
arichs_hoard_region = "Arich's Hoard Region"
|
||||
bounty_bay_region = "Bounty Bay Region"
|
||||
sky_high_secret_region = "Sky-High Secret Region"
|
||||
glacial_grotto_region = "Glacial Grotto Region"
|
||||
cifftop_cache_region = "Clifftop Cache Region"
|
||||
sewer_stockpile_region = "Sewer Stockpile Region"
|
||||
|
||||
lakeside_limbo_region = "Lakeside Limbo"
|
||||
doorstop_dash_region = "Doorstop Dash"
|
||||
tidal_trouble_region = "Tidal Trouble"
|
||||
skiddas_row_region = "Skidda's Row"
|
||||
murky_mill_region = "Murky Mill"
|
||||
|
||||
barrel_shield_bust_up_region = "Barrel Shield Bust-Up"
|
||||
riverside_race_region = "Riverside Race"
|
||||
squeals_on_wheels_region = "Squeals On Wheels"
|
||||
springin_spiders_region = "Springin' Spiders"
|
||||
bobbing_barrel_brawl_region = "Bobbing Barrel Brawl"
|
||||
|
||||
bazzas_blockade_region = "Bazza's Blockade"
|
||||
rocket_barrel_ride_region = "Rocket Barrel Ride"
|
||||
kreeping_klasps_region = "Kreeping Klasps"
|
||||
tracker_barrel_trek_region = "Tracker Barrel Trek"
|
||||
fish_food_frenzy_region = "Fish Food Frenzy"
|
||||
|
||||
fire_ball_frenzy_region = "Fire-Ball Frenzy"
|
||||
demolition_drain_pipe_region = "Demolition Drain-Pipe"
|
||||
ripsaw_rage_region = "Ripsaw Rage"
|
||||
blazing_bazookas_region = "Blazing Bazukas"
|
||||
low_g_labyrinth_region = "Low-G Labyrinth"
|
||||
|
||||
krevice_kreepers_region = "Krevice Kreepers"
|
||||
tearaway_toboggan_region = "Tearaway Toboggan"
|
||||
barrel_drop_bounce_region = "Barrel Drop Bounce"
|
||||
krack_shot_kroc_region = "Krack-Shot Kroc"
|
||||
lemguin_lunge_region = "Lemguin Lunge"
|
||||
|
||||
buzzer_barrage_region = "Buzzer Barrage"
|
||||
kong_fused_cliffs_region = "Kong-Fused Cliffs"
|
||||
floodlit_fish_region = "Floodlit Fish"
|
||||
pothole_panic_region = "Pothole Panic"
|
||||
ropey_rumpus_region = "Ropey Rumpus"
|
||||
|
||||
konveyor_rope_clash_region = "Konveyor Rope Klash"
|
||||
creepy_caverns_region = "Creepy Caverns"
|
||||
lightning_lookout_region = "Lightning Lookout"
|
||||
koindozer_klamber_region = "Koindozer Klamber"
|
||||
poisonous_pipeline_region = "Poisonous Pipeline"
|
||||
|
||||
stampede_sprint_region = "Stampede Sprint"
|
||||
criss_cross_cliffs_region = "Criss Kross Cliffs"
|
||||
tyrant_twin_tussle_region = "Tyrant Twin Tussle"
|
||||
swoopy_salvo_region = "Swoopy Salvo"
|
||||
rocket_rush_region = "Rocket Rush"
|
||||
@@ -1,203 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from Options import Choice, Range, Toggle, DefaultOnToggle, OptionGroup, PerGameCommonOptions
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""
|
||||
Determines the goal of the seed
|
||||
|
||||
Knautilus: Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein
|
||||
|
||||
Banana Bird Hunt: Find a certain number of Banana Birds and rescue their mother
|
||||
"""
|
||||
display_name = "Goal"
|
||||
option_knautilus = 0
|
||||
option_banana_bird_hunt = 1
|
||||
default = 0
|
||||
|
||||
|
||||
class IncludeTradeSequence(Toggle):
|
||||
"""
|
||||
Allows logic to place items at the various steps of the trade sequence
|
||||
"""
|
||||
display_name = "Include Trade Sequence"
|
||||
|
||||
|
||||
class DKCoinsForGyrocopter(Range):
|
||||
"""
|
||||
How many DK Coins are needed to unlock the Gyrocopter
|
||||
|
||||
Note: Achieving this number before unlocking the Turbo Ski will cause the game to grant you a
|
||||
one-time upgrade to the next non-unlocked boat, until you return to Funky. Logic does not assume
|
||||
that you will use this.
|
||||
"""
|
||||
display_name = "DK Coins for Gyrocopter"
|
||||
range_start = 10
|
||||
range_end = 41
|
||||
default = 30
|
||||
|
||||
|
||||
class KrematoaBonusCoinCost(Range):
|
||||
"""
|
||||
How many Bonus Coins are needed to unlock each level in Krematoa
|
||||
"""
|
||||
display_name = "Krematoa Bonus Coins Cost"
|
||||
range_start = 1
|
||||
range_end = 17
|
||||
default = 15
|
||||
|
||||
|
||||
class PercentageOfExtraBonusCoins(Range):
|
||||
"""
|
||||
What Percentage of unneeded Bonus Coins are included in the item pool
|
||||
"""
|
||||
display_name = "Percentage of Extra Bonus Coins"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 100
|
||||
|
||||
|
||||
class NumberOfBananaBirds(Range):
|
||||
"""
|
||||
How many Banana Birds are put into the item pool
|
||||
"""
|
||||
display_name = "Number of Banana Birds"
|
||||
range_start = 5
|
||||
range_end = 15
|
||||
default = 15
|
||||
|
||||
|
||||
class PercentageOfBananaBirds(Range):
|
||||
"""
|
||||
What Percentage of Banana Birds in the item pool are required for Banana Bird Hunt
|
||||
"""
|
||||
display_name = "Percentage of Banana Birds"
|
||||
range_start = 20
|
||||
range_end = 100
|
||||
default = 100
|
||||
|
||||
|
||||
class KONGsanity(Toggle):
|
||||
"""
|
||||
Whether collecting all four KONG letters in each level grants a check
|
||||
"""
|
||||
display_name = "KONGsanity"
|
||||
|
||||
|
||||
class LevelShuffle(Toggle):
|
||||
"""
|
||||
Whether levels are shuffled
|
||||
"""
|
||||
display_name = "Level Shuffle"
|
||||
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""
|
||||
Which Difficulty Level to use
|
||||
|
||||
NORML: The Normal Difficulty
|
||||
HARDR: Many DK Barrels are removed
|
||||
TUFST: Most DK Barrels and all Midway Barrels are removed
|
||||
"""
|
||||
display_name = "Difficulty"
|
||||
option_norml = 0
|
||||
option_hardr = 1
|
||||
option_tufst = 2
|
||||
default = 0
|
||||
|
||||
@classmethod
|
||||
def get_option_name(cls, value) -> str:
|
||||
if cls.auto_display_name:
|
||||
return cls.name_lookup[value].upper()
|
||||
else:
|
||||
return cls.name_lookup[value]
|
||||
|
||||
|
||||
class Autosave(DefaultOnToggle):
|
||||
"""
|
||||
Whether the game should autosave after each level
|
||||
"""
|
||||
display_name = "Autosave"
|
||||
|
||||
|
||||
class MERRY(Toggle):
|
||||
"""
|
||||
Whether the Bonus Barrels will be Christmas-themed
|
||||
"""
|
||||
display_name = "MERRY"
|
||||
|
||||
|
||||
class MusicShuffle(Toggle):
|
||||
"""
|
||||
Whether music is shuffled
|
||||
"""
|
||||
display_name = "Music Shuffle"
|
||||
|
||||
|
||||
class KongPaletteSwap(Choice):
|
||||
"""
|
||||
Which Palette to use for the Kongs
|
||||
"""
|
||||
display_name = "Kong Palette Swap"
|
||||
option_default = 0
|
||||
option_purple = 1
|
||||
option_spooky = 2
|
||||
option_dark = 3
|
||||
option_chocolate = 4
|
||||
option_shadow = 5
|
||||
option_red_gold = 6
|
||||
option_gbc = 7
|
||||
option_halloween = 8
|
||||
default = 0
|
||||
|
||||
|
||||
class StartingLifeCount(Range):
|
||||
"""
|
||||
How many extra lives to start the game with
|
||||
"""
|
||||
display_name = "Starting Life Count"
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
default = 5
|
||||
|
||||
|
||||
dkc3_option_groups = [
|
||||
OptionGroup("Goal Options", [
|
||||
Goal,
|
||||
KrematoaBonusCoinCost,
|
||||
PercentageOfExtraBonusCoins,
|
||||
NumberOfBananaBirds,
|
||||
PercentageOfBananaBirds,
|
||||
]),
|
||||
OptionGroup("Aesthetics", [
|
||||
Autosave,
|
||||
MERRY,
|
||||
MusicShuffle,
|
||||
KongPaletteSwap,
|
||||
StartingLifeCount,
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class DKC3Options(PerGameCommonOptions):
|
||||
#death_link: DeathLink # Disabled
|
||||
#include_trade_sequence: IncludeTradeSequence # Disabled
|
||||
|
||||
goal: Goal
|
||||
krematoa_bonus_coin_cost: KrematoaBonusCoinCost
|
||||
percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins
|
||||
number_of_banana_birds: NumberOfBananaBirds
|
||||
percentage_of_banana_birds: PercentageOfBananaBirds
|
||||
|
||||
dk_coins_for_gyrocopter: DKCoinsForGyrocopter
|
||||
kongsanity: KONGsanity
|
||||
level_shuffle: LevelShuffle
|
||||
difficulty: Difficulty
|
||||
|
||||
autosave: Autosave
|
||||
merry: MERRY
|
||||
music_shuffle: MusicShuffle
|
||||
kong_palette_swap: KongPaletteSwap
|
||||
starting_life_count: StartingLifeCount
|
||||
@@ -1,954 +0,0 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import Region, Entrance
|
||||
from worlds.AutoWorld import World
|
||||
from .Locations import DKC3Location
|
||||
from .Names import LocationName, ItemName
|
||||
|
||||
|
||||
def create_regions(world: World, active_locations):
|
||||
menu_region = create_region(world, active_locations, 'Menu', None)
|
||||
|
||||
overworld_1_region_locations = {}
|
||||
if world.options.goal != "knautilus":
|
||||
overworld_1_region_locations.update({LocationName.banana_bird_mother: []})
|
||||
overworld_1_region = create_region(world, active_locations, LocationName.overworld_1_region,
|
||||
overworld_1_region_locations)
|
||||
|
||||
overworld_2_region_locations = {}
|
||||
overworld_2_region = create_region(world, active_locations, LocationName.overworld_2_region,
|
||||
overworld_2_region_locations)
|
||||
|
||||
overworld_3_region_locations = {}
|
||||
overworld_3_region = create_region(world, active_locations, LocationName.overworld_3_region,
|
||||
overworld_3_region_locations)
|
||||
|
||||
overworld_4_region_locations = {}
|
||||
overworld_4_region = create_region(world, active_locations, LocationName.overworld_4_region,
|
||||
overworld_4_region_locations)
|
||||
|
||||
|
||||
lake_orangatanga_region = create_region(world, active_locations, LocationName.lake_orangatanga_region, None)
|
||||
kremwood_forest_region = create_region(world, active_locations, LocationName.kremwood_forest_region, None)
|
||||
cotton_top_cove_region = create_region(world, active_locations, LocationName.cotton_top_cove_region, None)
|
||||
mekanos_region = create_region(world, active_locations, LocationName.mekanos_region, None)
|
||||
k3_region = create_region(world, active_locations, LocationName.k3_region, None)
|
||||
razor_ridge_region = create_region(world, active_locations, LocationName.razor_ridge_region, None)
|
||||
kaos_kore_region = create_region(world, active_locations, LocationName.kaos_kore_region, None)
|
||||
krematoa_region = create_region(world, active_locations, LocationName.krematoa_region, None)
|
||||
|
||||
|
||||
lakeside_limbo_region_locations = {
|
||||
LocationName.lakeside_limbo_flag : [0x657, 1],
|
||||
LocationName.lakeside_limbo_bonus_1 : [0x657, 2],
|
||||
LocationName.lakeside_limbo_bonus_2 : [0x657, 3],
|
||||
LocationName.lakeside_limbo_dk : [0x657, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = []
|
||||
lakeside_limbo_region = create_region(world, active_locations, LocationName.lakeside_limbo_region,
|
||||
lakeside_limbo_region_locations)
|
||||
|
||||
doorstop_dash_region_locations = {
|
||||
LocationName.doorstop_dash_flag : [0x65A, 1],
|
||||
LocationName.doorstop_dash_bonus_1 : [0x65A, 2],
|
||||
LocationName.doorstop_dash_bonus_2 : [0x65A, 3],
|
||||
LocationName.doorstop_dash_dk : [0x65A, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = []
|
||||
doorstop_dash_region = create_region(world, active_locations, LocationName.doorstop_dash_region,
|
||||
doorstop_dash_region_locations)
|
||||
|
||||
tidal_trouble_region_locations = {
|
||||
LocationName.tidal_trouble_flag : [0x659, 1],
|
||||
LocationName.tidal_trouble_bonus_1 : [0x659, 2],
|
||||
LocationName.tidal_trouble_bonus_2 : [0x659, 3],
|
||||
LocationName.tidal_trouble_dk : [0x659, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = []
|
||||
tidal_trouble_region = create_region(world, active_locations, LocationName.tidal_trouble_region,
|
||||
tidal_trouble_region_locations)
|
||||
|
||||
skiddas_row_region_locations = {
|
||||
LocationName.skiddas_row_flag : [0x65D, 1],
|
||||
LocationName.skiddas_row_bonus_1 : [0x65D, 2],
|
||||
LocationName.skiddas_row_bonus_2 : [0x65D, 3],
|
||||
LocationName.skiddas_row_dk : [0x65D, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
skiddas_row_region_locations[LocationName.skiddas_row_kong] = []
|
||||
skiddas_row_region = create_region(world, active_locations, LocationName.skiddas_row_region,
|
||||
skiddas_row_region_locations)
|
||||
|
||||
murky_mill_region_locations = {
|
||||
LocationName.murky_mill_flag : [0x65C, 1],
|
||||
LocationName.murky_mill_bonus_1 : [0x65C, 2],
|
||||
LocationName.murky_mill_bonus_2 : [0x65C, 3],
|
||||
LocationName.murky_mill_dk : [0x65C, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
murky_mill_region_locations[LocationName.murky_mill_kong] = []
|
||||
murky_mill_region = create_region(world, active_locations, LocationName.murky_mill_region,
|
||||
murky_mill_region_locations)
|
||||
|
||||
barrel_shield_bust_up_region_locations = {
|
||||
LocationName.barrel_shield_bust_up_flag : [0x662, 1],
|
||||
LocationName.barrel_shield_bust_up_bonus_1 : [0x662, 2],
|
||||
LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3],
|
||||
LocationName.barrel_shield_bust_up_dk : [0x662, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = []
|
||||
barrel_shield_bust_up_region = create_region(world, active_locations,
|
||||
LocationName.barrel_shield_bust_up_region,
|
||||
barrel_shield_bust_up_region_locations)
|
||||
|
||||
riverside_race_region_locations = {
|
||||
LocationName.riverside_race_flag : [0x664, 1],
|
||||
LocationName.riverside_race_bonus_1 : [0x664, 2],
|
||||
LocationName.riverside_race_bonus_2 : [0x664, 3],
|
||||
LocationName.riverside_race_dk : [0x664, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
riverside_race_region_locations[LocationName.riverside_race_kong] = []
|
||||
riverside_race_region = create_region(world, active_locations, LocationName.riverside_race_region,
|
||||
riverside_race_region_locations)
|
||||
|
||||
squeals_on_wheels_region_locations = {
|
||||
LocationName.squeals_on_wheels_flag : [0x65B, 1],
|
||||
LocationName.squeals_on_wheels_bonus_1 : [0x65B, 2],
|
||||
LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3],
|
||||
LocationName.squeals_on_wheels_dk : [0x65B, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = []
|
||||
squeals_on_wheels_region = create_region(world, active_locations, LocationName.squeals_on_wheels_region,
|
||||
squeals_on_wheels_region_locations)
|
||||
|
||||
springin_spiders_region_locations = {
|
||||
LocationName.springin_spiders_flag : [0x661, 1],
|
||||
LocationName.springin_spiders_bonus_1 : [0x661, 2],
|
||||
LocationName.springin_spiders_bonus_2 : [0x661, 3],
|
||||
LocationName.springin_spiders_dk : [0x661, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
springin_spiders_region_locations[LocationName.springin_spiders_kong] = []
|
||||
springin_spiders_region = create_region(world, active_locations, LocationName.springin_spiders_region,
|
||||
springin_spiders_region_locations)
|
||||
|
||||
bobbing_barrel_brawl_region_locations = {
|
||||
LocationName.bobbing_barrel_brawl_flag : [0x666, 1],
|
||||
LocationName.bobbing_barrel_brawl_bonus_1 : [0x666, 2],
|
||||
LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3],
|
||||
LocationName.bobbing_barrel_brawl_dk : [0x666, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = []
|
||||
bobbing_barrel_brawl_region = create_region(world, active_locations,
|
||||
LocationName.bobbing_barrel_brawl_region,
|
||||
bobbing_barrel_brawl_region_locations)
|
||||
|
||||
bazzas_blockade_region_locations = {
|
||||
LocationName.bazzas_blockade_flag : [0x667, 1],
|
||||
LocationName.bazzas_blockade_bonus_1 : [0x667, 2],
|
||||
LocationName.bazzas_blockade_bonus_2 : [0x667, 3],
|
||||
LocationName.bazzas_blockade_dk : [0x667, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = []
|
||||
bazzas_blockade_region = create_region(world, active_locations, LocationName.bazzas_blockade_region,
|
||||
bazzas_blockade_region_locations)
|
||||
|
||||
rocket_barrel_ride_region_locations = {
|
||||
LocationName.rocket_barrel_ride_flag : [0x66A, 1],
|
||||
LocationName.rocket_barrel_ride_bonus_1 : [0x66A, 2],
|
||||
LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3],
|
||||
LocationName.rocket_barrel_ride_dk : [0x66A, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = []
|
||||
rocket_barrel_ride_region = create_region(world, active_locations, LocationName.rocket_barrel_ride_region,
|
||||
rocket_barrel_ride_region_locations)
|
||||
|
||||
kreeping_klasps_region_locations = {
|
||||
LocationName.kreeping_klasps_flag : [0x658, 1],
|
||||
LocationName.kreeping_klasps_bonus_1 : [0x658, 2],
|
||||
LocationName.kreeping_klasps_bonus_2 : [0x658, 3],
|
||||
LocationName.kreeping_klasps_dk : [0x658, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = []
|
||||
kreeping_klasps_region = create_region(world, active_locations, LocationName.kreeping_klasps_region,
|
||||
kreeping_klasps_region_locations)
|
||||
|
||||
tracker_barrel_trek_region_locations = {
|
||||
LocationName.tracker_barrel_trek_flag : [0x66B, 1],
|
||||
LocationName.tracker_barrel_trek_bonus_1 : [0x66B, 2],
|
||||
LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3],
|
||||
LocationName.tracker_barrel_trek_dk : [0x66B, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = []
|
||||
tracker_barrel_trek_region = create_region(world, active_locations, LocationName.tracker_barrel_trek_region,
|
||||
tracker_barrel_trek_region_locations)
|
||||
|
||||
fish_food_frenzy_region_locations = {
|
||||
LocationName.fish_food_frenzy_flag : [0x668, 1],
|
||||
LocationName.fish_food_frenzy_bonus_1 : [0x668, 2],
|
||||
LocationName.fish_food_frenzy_bonus_2 : [0x668, 3],
|
||||
LocationName.fish_food_frenzy_dk : [0x668, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = []
|
||||
fish_food_frenzy_region = create_region(world, active_locations, LocationName.fish_food_frenzy_region,
|
||||
fish_food_frenzy_region_locations)
|
||||
|
||||
fire_ball_frenzy_region_locations = {
|
||||
LocationName.fire_ball_frenzy_flag : [0x66D, 1],
|
||||
LocationName.fire_ball_frenzy_bonus_1 : [0x66D, 2],
|
||||
LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3],
|
||||
LocationName.fire_ball_frenzy_dk : [0x66D, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = []
|
||||
fire_ball_frenzy_region = create_region(world, active_locations, LocationName.fire_ball_frenzy_region,
|
||||
fire_ball_frenzy_region_locations)
|
||||
|
||||
demolition_drain_pipe_region_locations = {
|
||||
LocationName.demolition_drain_pipe_flag : [0x672, 1],
|
||||
LocationName.demolition_drain_pipe_bonus_1 : [0x672, 2],
|
||||
LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3],
|
||||
LocationName.demolition_drain_pipe_dk : [0x672, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = []
|
||||
demolition_drain_pipe_region = create_region(world, active_locations,
|
||||
LocationName.demolition_drain_pipe_region,
|
||||
demolition_drain_pipe_region_locations)
|
||||
|
||||
ripsaw_rage_region_locations = {
|
||||
LocationName.ripsaw_rage_flag : [0x660, 1],
|
||||
LocationName.ripsaw_rage_bonus_1 : [0x660, 2],
|
||||
LocationName.ripsaw_rage_bonus_2 : [0x660, 3],
|
||||
LocationName.ripsaw_rage_dk : [0x660, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = []
|
||||
ripsaw_rage_region = create_region(world, active_locations, LocationName.ripsaw_rage_region,
|
||||
ripsaw_rage_region_locations)
|
||||
|
||||
blazing_bazookas_region_locations = {
|
||||
LocationName.blazing_bazookas_flag : [0x66E, 1],
|
||||
LocationName.blazing_bazookas_bonus_1 : [0x66E, 2],
|
||||
LocationName.blazing_bazookas_bonus_2 : [0x66E, 3],
|
||||
LocationName.blazing_bazookas_dk : [0x66E, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = []
|
||||
blazing_bazookas_region = create_region(world, active_locations, LocationName.blazing_bazookas_region,
|
||||
blazing_bazookas_region_locations)
|
||||
|
||||
low_g_labyrinth_region_locations = {
|
||||
LocationName.low_g_labyrinth_flag : [0x670, 1],
|
||||
LocationName.low_g_labyrinth_bonus_1 : [0x670, 2],
|
||||
LocationName.low_g_labyrinth_bonus_2 : [0x670, 3],
|
||||
LocationName.low_g_labyrinth_dk : [0x670, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = []
|
||||
low_g_labyrinth_region = create_region(world, active_locations, LocationName.low_g_labyrinth_region,
|
||||
low_g_labyrinth_region_locations)
|
||||
|
||||
krevice_kreepers_region_locations = {
|
||||
LocationName.krevice_kreepers_flag : [0x673, 1],
|
||||
LocationName.krevice_kreepers_bonus_1 : [0x673, 2],
|
||||
LocationName.krevice_kreepers_bonus_2 : [0x673, 3],
|
||||
LocationName.krevice_kreepers_dk : [0x673, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = []
|
||||
krevice_kreepers_region = create_region(world, active_locations, LocationName.krevice_kreepers_region,
|
||||
krevice_kreepers_region_locations)
|
||||
|
||||
tearaway_toboggan_region_locations = {
|
||||
LocationName.tearaway_toboggan_flag : [0x65F, 1],
|
||||
LocationName.tearaway_toboggan_bonus_1 : [0x65F, 2],
|
||||
LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3],
|
||||
LocationName.tearaway_toboggan_dk : [0x65F, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = []
|
||||
tearaway_toboggan_region = create_region(world, active_locations, LocationName.tearaway_toboggan_region,
|
||||
tearaway_toboggan_region_locations)
|
||||
|
||||
barrel_drop_bounce_region_locations = {
|
||||
LocationName.barrel_drop_bounce_flag : [0x66C, 1],
|
||||
LocationName.barrel_drop_bounce_bonus_1 : [0x66C, 2],
|
||||
LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3],
|
||||
LocationName.barrel_drop_bounce_dk : [0x66C, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = []
|
||||
barrel_drop_bounce_region = create_region(world, active_locations, LocationName.barrel_drop_bounce_region,
|
||||
barrel_drop_bounce_region_locations)
|
||||
|
||||
krack_shot_kroc_region_locations = {
|
||||
LocationName.krack_shot_kroc_flag : [0x66F, 1],
|
||||
LocationName.krack_shot_kroc_bonus_1 : [0x66F, 2],
|
||||
LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3],
|
||||
LocationName.krack_shot_kroc_dk : [0x66F, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = []
|
||||
krack_shot_kroc_region = create_region(world, active_locations, LocationName.krack_shot_kroc_region,
|
||||
krack_shot_kroc_region_locations)
|
||||
|
||||
lemguin_lunge_region_locations = {
|
||||
LocationName.lemguin_lunge_flag : [0x65E, 1],
|
||||
LocationName.lemguin_lunge_bonus_1 : [0x65E, 2],
|
||||
LocationName.lemguin_lunge_bonus_2 : [0x65E, 3],
|
||||
LocationName.lemguin_lunge_dk : [0x65E, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = []
|
||||
lemguin_lunge_region = create_region(world, active_locations, LocationName.lemguin_lunge_region,
|
||||
lemguin_lunge_region_locations)
|
||||
|
||||
buzzer_barrage_region_locations = {
|
||||
LocationName.buzzer_barrage_flag : [0x676, 1],
|
||||
LocationName.buzzer_barrage_bonus_1 : [0x676, 2],
|
||||
LocationName.buzzer_barrage_bonus_2 : [0x676, 3],
|
||||
LocationName.buzzer_barrage_dk : [0x676, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = []
|
||||
buzzer_barrage_region = create_region(world, active_locations, LocationName.buzzer_barrage_region,
|
||||
buzzer_barrage_region_locations)
|
||||
|
||||
kong_fused_cliffs_region_locations = {
|
||||
LocationName.kong_fused_cliffs_flag : [0x674, 1],
|
||||
LocationName.kong_fused_cliffs_bonus_1 : [0x674, 2],
|
||||
LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3],
|
||||
LocationName.kong_fused_cliffs_dk : [0x674, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = []
|
||||
kong_fused_cliffs_region = create_region(world, active_locations, LocationName.kong_fused_cliffs_region,
|
||||
kong_fused_cliffs_region_locations)
|
||||
|
||||
floodlit_fish_region_locations = {
|
||||
LocationName.floodlit_fish_flag : [0x669, 1],
|
||||
LocationName.floodlit_fish_bonus_1 : [0x669, 2],
|
||||
LocationName.floodlit_fish_bonus_2 : [0x669, 3],
|
||||
LocationName.floodlit_fish_dk : [0x669, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = []
|
||||
floodlit_fish_region = create_region(world, active_locations, LocationName.floodlit_fish_region,
|
||||
floodlit_fish_region_locations)
|
||||
|
||||
pothole_panic_region_locations = {
|
||||
LocationName.pothole_panic_flag : [0x677, 1],
|
||||
LocationName.pothole_panic_bonus_1 : [0x677, 2],
|
||||
LocationName.pothole_panic_bonus_2 : [0x677, 3],
|
||||
LocationName.pothole_panic_dk : [0x677, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
pothole_panic_region_locations[LocationName.pothole_panic_kong] = []
|
||||
pothole_panic_region = create_region(world, active_locations, LocationName.pothole_panic_region,
|
||||
pothole_panic_region_locations)
|
||||
|
||||
ropey_rumpus_region_locations = {
|
||||
LocationName.ropey_rumpus_flag : [0x675, 1],
|
||||
LocationName.ropey_rumpus_bonus_1 : [0x675, 2],
|
||||
LocationName.ropey_rumpus_bonus_2 : [0x675, 3],
|
||||
LocationName.ropey_rumpus_dk : [0x675, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = []
|
||||
ropey_rumpus_region = create_region(world, active_locations, LocationName.ropey_rumpus_region,
|
||||
ropey_rumpus_region_locations)
|
||||
|
||||
konveyor_rope_clash_region_locations = {
|
||||
LocationName.konveyor_rope_clash_flag : [0x657, 1],
|
||||
LocationName.konveyor_rope_clash_bonus_1 : [0x657, 2],
|
||||
LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3],
|
||||
LocationName.konveyor_rope_clash_dk : [0x657, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = []
|
||||
konveyor_rope_clash_region = create_region(world, active_locations, LocationName.konveyor_rope_clash_region,
|
||||
konveyor_rope_clash_region_locations)
|
||||
|
||||
creepy_caverns_region_locations = {
|
||||
LocationName.creepy_caverns_flag : [0x678, 1],
|
||||
LocationName.creepy_caverns_bonus_1 : [0x678, 2],
|
||||
LocationName.creepy_caverns_bonus_2 : [0x678, 3],
|
||||
LocationName.creepy_caverns_dk : [0x678, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = []
|
||||
creepy_caverns_region = create_region(world, active_locations, LocationName.creepy_caverns_region,
|
||||
creepy_caverns_region_locations)
|
||||
|
||||
lightning_lookout_region_locations = {
|
||||
LocationName.lightning_lookout_flag : [0x665, 1],
|
||||
LocationName.lightning_lookout_bonus_1 : [0x665, 2],
|
||||
LocationName.lightning_lookout_bonus_2 : [0x665, 3],
|
||||
LocationName.lightning_lookout_dk : [0x665, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = []
|
||||
lightning_lookout_region = create_region(world, active_locations, LocationName.lightning_lookout_region,
|
||||
lightning_lookout_region_locations)
|
||||
|
||||
koindozer_klamber_region_locations = {
|
||||
LocationName.koindozer_klamber_flag : [0x679, 1],
|
||||
LocationName.koindozer_klamber_bonus_1 : [0x679, 2],
|
||||
LocationName.koindozer_klamber_bonus_2 : [0x679, 3],
|
||||
LocationName.koindozer_klamber_dk : [0x679, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = []
|
||||
koindozer_klamber_region = create_region(world, active_locations, LocationName.koindozer_klamber_region,
|
||||
koindozer_klamber_region_locations)
|
||||
|
||||
poisonous_pipeline_region_locations = {
|
||||
LocationName.poisonous_pipeline_flag : [0x671, 1],
|
||||
LocationName.poisonous_pipeline_bonus_1 : [0x671, 2],
|
||||
LocationName.poisonous_pipeline_bonus_2 : [0x671, 3],
|
||||
LocationName.poisonous_pipeline_dk : [0x671, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = []
|
||||
poisonous_pipeline_region = create_region(world, active_locations, LocationName.poisonous_pipeline_region,
|
||||
poisonous_pipeline_region_locations)
|
||||
|
||||
stampede_sprint_region_locations = {
|
||||
LocationName.stampede_sprint_flag : [0x67B, 1],
|
||||
LocationName.stampede_sprint_bonus_1 : [0x67B, 2],
|
||||
LocationName.stampede_sprint_bonus_2 : [0x67B, 3],
|
||||
LocationName.stampede_sprint_bonus_3 : [0x67B, 4],
|
||||
LocationName.stampede_sprint_dk : [0x67B, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = []
|
||||
stampede_sprint_region = create_region(world, active_locations, LocationName.stampede_sprint_region,
|
||||
stampede_sprint_region_locations)
|
||||
|
||||
criss_cross_cliffs_region_locations = {
|
||||
LocationName.criss_cross_cliffs_flag : [0x67C, 1],
|
||||
LocationName.criss_cross_cliffs_bonus_1 : [0x67C, 2],
|
||||
LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3],
|
||||
LocationName.criss_cross_cliffs_dk : [0x67C, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = []
|
||||
criss_cross_cliffs_region = create_region(world, active_locations, LocationName.criss_cross_cliffs_region,
|
||||
criss_cross_cliffs_region_locations)
|
||||
|
||||
tyrant_twin_tussle_region_locations = {
|
||||
LocationName.tyrant_twin_tussle_flag : [0x67D, 1],
|
||||
LocationName.tyrant_twin_tussle_bonus_1 : [0x67D, 2],
|
||||
LocationName.tyrant_twin_tussle_bonus_2 : [0x67D, 3],
|
||||
LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4],
|
||||
LocationName.tyrant_twin_tussle_dk : [0x67D, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = []
|
||||
tyrant_twin_tussle_region = create_region(world, active_locations, LocationName.tyrant_twin_tussle_region,
|
||||
tyrant_twin_tussle_region_locations)
|
||||
|
||||
swoopy_salvo_region_locations = {
|
||||
LocationName.swoopy_salvo_flag : [0x663, 1],
|
||||
LocationName.swoopy_salvo_bonus_1 : [0x663, 2],
|
||||
LocationName.swoopy_salvo_bonus_2 : [0x663, 3],
|
||||
LocationName.swoopy_salvo_bonus_3 : [0x663, 4],
|
||||
LocationName.swoopy_salvo_dk : [0x663, 5],
|
||||
}
|
||||
if world.options.kongsanity:
|
||||
swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = []
|
||||
swoopy_salvo_region = create_region(world, active_locations, LocationName.swoopy_salvo_region,
|
||||
swoopy_salvo_region_locations)
|
||||
|
||||
rocket_rush_region_locations = {
|
||||
LocationName.rocket_rush_flag : [0x67E, 1],
|
||||
LocationName.rocket_rush_dk : [0x67E, 5],
|
||||
}
|
||||
rocket_rush_region = create_region(world, active_locations, LocationName.rocket_rush_region,
|
||||
rocket_rush_region_locations)
|
||||
|
||||
belchas_barn_region_locations = {
|
||||
LocationName.belchas_barn: [0x64F, 1],
|
||||
}
|
||||
belchas_barn_region = create_region(world, active_locations, LocationName.belchas_barn_region,
|
||||
belchas_barn_region_locations)
|
||||
|
||||
arichs_ambush_region_locations = {
|
||||
LocationName.arichs_ambush: [0x650, 1],
|
||||
}
|
||||
arichs_ambush_region = create_region(world, active_locations, LocationName.arichs_ambush_region,
|
||||
arichs_ambush_region_locations)
|
||||
|
||||
squirts_showdown_region_locations = {
|
||||
LocationName.squirts_showdown: [0x651, 1],
|
||||
}
|
||||
squirts_showdown_region = create_region(world, active_locations, LocationName.squirts_showdown_region,
|
||||
squirts_showdown_region_locations)
|
||||
|
||||
kaos_karnage_region_locations = {
|
||||
LocationName.kaos_karnage: [0x652, 1],
|
||||
}
|
||||
kaos_karnage_region = create_region(world, active_locations, LocationName.kaos_karnage_region,
|
||||
kaos_karnage_region_locations)
|
||||
|
||||
bleaks_house_region_locations = {
|
||||
LocationName.bleaks_house: [0x653, 1],
|
||||
}
|
||||
bleaks_house_region = create_region(world, active_locations, LocationName.bleaks_house_region,
|
||||
bleaks_house_region_locations)
|
||||
|
||||
barboss_barrier_region_locations = {
|
||||
LocationName.barboss_barrier: [0x654, 1],
|
||||
}
|
||||
barboss_barrier_region = create_region(world, active_locations, LocationName.barboss_barrier_region,
|
||||
barboss_barrier_region_locations)
|
||||
|
||||
kastle_kaos_region_locations = {
|
||||
LocationName.kastle_kaos: [0x655, 1],
|
||||
}
|
||||
kastle_kaos_region = create_region(world, active_locations, LocationName.kastle_kaos_region,
|
||||
kastle_kaos_region_locations)
|
||||
|
||||
knautilus_region_locations = {
|
||||
LocationName.knautilus: [0x656, 1],
|
||||
}
|
||||
knautilus_region = create_region(world, active_locations, LocationName.knautilus_region,
|
||||
knautilus_region_locations)
|
||||
|
||||
belchas_burrow_region_locations = {
|
||||
LocationName.belchas_burrow: [0x647, 1],
|
||||
}
|
||||
belchas_burrow_region = create_region(world, active_locations, LocationName.belchas_burrow_region,
|
||||
belchas_burrow_region_locations)
|
||||
|
||||
kong_cave_region_locations = {
|
||||
LocationName.kong_cave: [0x645, 1],
|
||||
}
|
||||
kong_cave_region = create_region(world, active_locations, LocationName.kong_cave_region,
|
||||
kong_cave_region_locations)
|
||||
|
||||
undercover_cove_region_locations = {
|
||||
LocationName.undercover_cove: [0x644, 1],
|
||||
}
|
||||
undercover_cove_region = create_region(world, active_locations, LocationName.undercover_cove_region,
|
||||
undercover_cove_region_locations)
|
||||
|
||||
ks_cache_region_locations = {
|
||||
LocationName.ks_cache: [0x642, 1],
|
||||
}
|
||||
ks_cache_region = create_region(world, active_locations, LocationName.ks_cache_region,
|
||||
ks_cache_region_locations)
|
||||
|
||||
hill_top_hoard_region_locations = {
|
||||
LocationName.hill_top_hoard: [0x643, 1],
|
||||
}
|
||||
hill_top_hoard_region = create_region(world, active_locations, LocationName.hill_top_hoard_region,
|
||||
hill_top_hoard_region_locations)
|
||||
|
||||
bounty_beach_region_locations = {
|
||||
LocationName.bounty_beach: [0x646, 1],
|
||||
}
|
||||
bounty_beach_region = create_region(world, active_locations, LocationName.bounty_beach_region,
|
||||
bounty_beach_region_locations)
|
||||
|
||||
smugglers_cove_region_locations = {
|
||||
LocationName.smugglers_cove: [0x648, 1],
|
||||
}
|
||||
smugglers_cove_region = create_region(world, active_locations, LocationName.smugglers_cove_region,
|
||||
smugglers_cove_region_locations)
|
||||
|
||||
arichs_hoard_region_locations = {
|
||||
LocationName.arichs_hoard: [0x649, 1],
|
||||
}
|
||||
arichs_hoard_region = create_region(world, active_locations, LocationName.arichs_hoard_region,
|
||||
arichs_hoard_region_locations)
|
||||
|
||||
bounty_bay_region_locations = {
|
||||
LocationName.bounty_bay: [0x64A, 1],
|
||||
}
|
||||
bounty_bay_region = create_region(world, active_locations, LocationName.bounty_bay_region,
|
||||
bounty_bay_region_locations)
|
||||
|
||||
sky_high_secret_region_locations = {}
|
||||
if False:#world.options.include_trade_sequence:
|
||||
sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1]
|
||||
sky_high_secret_region = create_region(world, active_locations, LocationName.sky_high_secret_region,
|
||||
sky_high_secret_region_locations)
|
||||
|
||||
glacial_grotto_region_locations = {
|
||||
LocationName.glacial_grotto: [0x64C, 1],
|
||||
}
|
||||
glacial_grotto_region = create_region(world, active_locations, LocationName.glacial_grotto_region,
|
||||
glacial_grotto_region_locations)
|
||||
|
||||
cifftop_cache_region_locations = {}
|
||||
if False:#world.options.include_trade_sequence:
|
||||
cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1]
|
||||
cifftop_cache_region = create_region(world, active_locations, LocationName.cifftop_cache_region,
|
||||
cifftop_cache_region_locations)
|
||||
|
||||
sewer_stockpile_region_locations = {
|
||||
LocationName.sewer_stockpile: [0x64E, 1],
|
||||
}
|
||||
sewer_stockpile_region = create_region(world, active_locations, LocationName.sewer_stockpile_region,
|
||||
sewer_stockpile_region_locations)
|
||||
|
||||
|
||||
# Set up the regions correctly.
|
||||
world.multiworld.regions += [
|
||||
menu_region,
|
||||
overworld_1_region,
|
||||
overworld_2_region,
|
||||
overworld_3_region,
|
||||
overworld_4_region,
|
||||
lake_orangatanga_region,
|
||||
kremwood_forest_region,
|
||||
cotton_top_cove_region,
|
||||
mekanos_region,
|
||||
k3_region,
|
||||
razor_ridge_region,
|
||||
kaos_kore_region,
|
||||
krematoa_region,
|
||||
lakeside_limbo_region,
|
||||
doorstop_dash_region,
|
||||
tidal_trouble_region,
|
||||
skiddas_row_region,
|
||||
murky_mill_region,
|
||||
barrel_shield_bust_up_region,
|
||||
riverside_race_region,
|
||||
squeals_on_wheels_region,
|
||||
springin_spiders_region,
|
||||
bobbing_barrel_brawl_region,
|
||||
bazzas_blockade_region,
|
||||
rocket_barrel_ride_region,
|
||||
kreeping_klasps_region,
|
||||
tracker_barrel_trek_region,
|
||||
fish_food_frenzy_region,
|
||||
fire_ball_frenzy_region,
|
||||
demolition_drain_pipe_region,
|
||||
ripsaw_rage_region,
|
||||
blazing_bazookas_region,
|
||||
low_g_labyrinth_region,
|
||||
krevice_kreepers_region,
|
||||
tearaway_toboggan_region,
|
||||
barrel_drop_bounce_region,
|
||||
krack_shot_kroc_region,
|
||||
lemguin_lunge_region,
|
||||
buzzer_barrage_region,
|
||||
kong_fused_cliffs_region,
|
||||
floodlit_fish_region,
|
||||
pothole_panic_region,
|
||||
ropey_rumpus_region,
|
||||
konveyor_rope_clash_region,
|
||||
creepy_caverns_region,
|
||||
lightning_lookout_region,
|
||||
koindozer_klamber_region,
|
||||
poisonous_pipeline_region,
|
||||
stampede_sprint_region,
|
||||
criss_cross_cliffs_region,
|
||||
tyrant_twin_tussle_region,
|
||||
swoopy_salvo_region,
|
||||
rocket_rush_region,
|
||||
belchas_barn_region,
|
||||
arichs_ambush_region,
|
||||
squirts_showdown_region,
|
||||
kaos_karnage_region,
|
||||
bleaks_house_region,
|
||||
barboss_barrier_region,
|
||||
kastle_kaos_region,
|
||||
knautilus_region,
|
||||
belchas_burrow_region,
|
||||
kong_cave_region,
|
||||
undercover_cove_region,
|
||||
ks_cache_region,
|
||||
hill_top_hoard_region,
|
||||
bounty_beach_region,
|
||||
smugglers_cove_region,
|
||||
arichs_hoard_region,
|
||||
bounty_bay_region,
|
||||
sky_high_secret_region,
|
||||
glacial_grotto_region,
|
||||
cifftop_cache_region,
|
||||
sewer_stockpile_region,
|
||||
]
|
||||
|
||||
bazaar_region_locations = {}
|
||||
bramble_region_locations = {}
|
||||
flower_spot_region_locations = {}
|
||||
barter_region_locations = {}
|
||||
barnacle_region_locations = {}
|
||||
blue_region_locations = {}
|
||||
blizzard_region_locations = {}
|
||||
|
||||
if False:#world.options.include_trade_sequence:
|
||||
bazaar_region_locations.update({
|
||||
LocationName.bazaars_general_store_1: [0x615, 2, True],
|
||||
LocationName.bazaars_general_store_2: [0x615, 3, True],
|
||||
})
|
||||
|
||||
bramble_region_locations[LocationName.brambles_bungalow] = [0x619, 2]
|
||||
|
||||
#flower_spot_region_locations.update({
|
||||
# LocationName.flower_spot: [0x615, 3, True],
|
||||
#})
|
||||
|
||||
barter_region_locations[LocationName.barters_swap_shop] = [0x61B, 3]
|
||||
|
||||
barnacle_region_locations[LocationName.barnacles_island] = [0x61D, 2]
|
||||
|
||||
blue_region_locations[LocationName.blues_beach_hut] = [0x621, 4]
|
||||
|
||||
blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True]
|
||||
|
||||
bazaar_region = create_region(world, active_locations, LocationName.bazaar_region, bazaar_region_locations)
|
||||
bramble_region = create_region(world, active_locations, LocationName.bramble_region,
|
||||
bramble_region_locations)
|
||||
flower_spot_region = create_region(world, active_locations, LocationName.flower_spot_region,
|
||||
flower_spot_region_locations)
|
||||
barter_region = create_region(world, active_locations, LocationName.barter_region, barter_region_locations)
|
||||
barnacle_region = create_region(world, active_locations, LocationName.barnacle_region,
|
||||
barnacle_region_locations)
|
||||
blue_region = create_region(world, active_locations, LocationName.blue_region, blue_region_locations)
|
||||
blizzard_region = create_region(world, active_locations, LocationName.blizzard_region,
|
||||
blizzard_region_locations)
|
||||
|
||||
world.multiworld.regions += [
|
||||
bazaar_region,
|
||||
bramble_region,
|
||||
flower_spot_region,
|
||||
barter_region,
|
||||
barnacle_region,
|
||||
blue_region,
|
||||
blizzard_region,
|
||||
]
|
||||
|
||||
|
||||
def connect_regions(world: World, level_list):
|
||||
names: typing.Dict[str, int] = {}
|
||||
|
||||
# Overworld
|
||||
connect(world, world.player, names, 'Menu', LocationName.overworld_1_region)
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_2_region,
|
||||
lambda state: (state.has(ItemName.progressive_boat, world.player, 1)))
|
||||
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.overworld_3_region,
|
||||
lambda state: (state.has(ItemName.progressive_boat, world.player, 3)))
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_4_region,
|
||||
lambda state: (state.has(ItemName.dk_coin, world.player, world.options.dk_coins_for_gyrocopter.value) and
|
||||
state.has(ItemName.progressive_boat, world.player, 3)))
|
||||
|
||||
# World Connections
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region)
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region)
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region)
|
||||
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bazaar_region)
|
||||
|
||||
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region)
|
||||
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.mekanos_region)
|
||||
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.kong_cave_region)
|
||||
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.bramble_region)
|
||||
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.k3_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.krematoa_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.flower_spot_region)
|
||||
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.barter_region)
|
||||
|
||||
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region)
|
||||
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.ks_cache_region)
|
||||
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region)
|
||||
|
||||
|
||||
# Lake Orangatanga Connections
|
||||
lake_orangatanga_levels = [
|
||||
level_list[0],
|
||||
level_list[1],
|
||||
level_list[2],
|
||||
level_list[3],
|
||||
level_list[4],
|
||||
LocationName.belchas_barn_region,
|
||||
LocationName.barnacle_region,
|
||||
LocationName.smugglers_cove_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(lake_orangatanga_levels)):
|
||||
connect(world, world.player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i])
|
||||
|
||||
# Kremwood Forest Connections
|
||||
kremwood_forest_levels = [
|
||||
level_list[5],
|
||||
level_list[6],
|
||||
level_list[7],
|
||||
level_list[8],
|
||||
level_list[9],
|
||||
LocationName.arichs_ambush_region,
|
||||
LocationName.arichs_hoard_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(kremwood_forest_levels) - 1):
|
||||
connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i])
|
||||
|
||||
connection = connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1],
|
||||
lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", world.player)))
|
||||
world.multiworld.register_indirect_condition(world.get_location(LocationName.riverside_race_flag).parent_region,
|
||||
connection)
|
||||
|
||||
# Cotton-Top Cove Connections
|
||||
cotton_top_cove_levels = [
|
||||
LocationName.blue_region,
|
||||
level_list[10],
|
||||
level_list[11],
|
||||
level_list[12],
|
||||
level_list[13],
|
||||
level_list[14],
|
||||
LocationName.squirts_showdown_region,
|
||||
LocationName.bounty_bay_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(cotton_top_cove_levels)):
|
||||
connect(world, world.player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i])
|
||||
|
||||
# Mekanos Connections
|
||||
mekanos_levels = [
|
||||
level_list[15],
|
||||
level_list[16],
|
||||
level_list[17],
|
||||
level_list[18],
|
||||
level_list[19],
|
||||
LocationName.kaos_karnage_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(mekanos_levels)):
|
||||
connect(world, world.player, names, LocationName.mekanos_region, mekanos_levels[i])
|
||||
|
||||
if False:#world.options.include_trade_sequence:
|
||||
connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
|
||||
lambda state: (state.has(ItemName.bowling_ball, world.player, 1)))
|
||||
else:
|
||||
connection = connect(world, world.player, names, LocationName.mekanos_region,
|
||||
LocationName.sky_high_secret_region,
|
||||
lambda state: (state.can_reach(LocationName.bleaks_house, "Location", world.player)))
|
||||
world.multiworld.register_indirect_condition(world.get_location(LocationName.bleaks_house).parent_region,
|
||||
connection)
|
||||
|
||||
# K3 Connections
|
||||
k3_levels = [
|
||||
level_list[20],
|
||||
level_list[21],
|
||||
level_list[22],
|
||||
level_list[23],
|
||||
level_list[24],
|
||||
LocationName.bleaks_house_region,
|
||||
LocationName.blizzard_region,
|
||||
LocationName.glacial_grotto_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(k3_levels)):
|
||||
connect(world, world.player, names, LocationName.k3_region, k3_levels[i])
|
||||
|
||||
# Razor Ridge Connections
|
||||
razor_ridge_levels = [
|
||||
level_list[25],
|
||||
level_list[26],
|
||||
level_list[27],
|
||||
level_list[28],
|
||||
level_list[29],
|
||||
LocationName.barboss_barrier_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(razor_ridge_levels)):
|
||||
connect(world, world.player, names, LocationName.razor_ridge_region, razor_ridge_levels[i])
|
||||
|
||||
if False:#world.options.include_trade_sequence:
|
||||
connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region,
|
||||
lambda state: (state.has(ItemName.wrench, world.player, 1)))
|
||||
else:
|
||||
connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region)
|
||||
|
||||
# KAOS Kore Connections
|
||||
kaos_kore_levels = [
|
||||
level_list[30],
|
||||
level_list[31],
|
||||
level_list[32],
|
||||
level_list[33],
|
||||
level_list[34],
|
||||
LocationName.sewer_stockpile_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(kaos_kore_levels)):
|
||||
connect(world, world.player, names, LocationName.kaos_kore_region, kaos_kore_levels[i])
|
||||
|
||||
# Krematoa Connections
|
||||
krematoa_levels = [
|
||||
level_list[35],
|
||||
level_list[36],
|
||||
level_list[37],
|
||||
level_list[38],
|
||||
LocationName.rocket_rush_region,
|
||||
]
|
||||
|
||||
for i in range(0, len(krematoa_levels)):
|
||||
connect(world, world.player, names, LocationName.krematoa_region, krematoa_levels[i],
|
||||
lambda state, i=i: (state.has(ItemName.bonus_coin, world.player, world.options.krematoa_bonus_coin_cost.value * (i+1))))
|
||||
|
||||
if world.options.goal == "knautilus":
|
||||
connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.knautilus_region)
|
||||
connect(world, world.player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region,
|
||||
lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
|
||||
else:
|
||||
connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region)
|
||||
connect(world, world.player, names, LocationName.krematoa_region, LocationName.knautilus_region,
|
||||
lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
|
||||
|
||||
|
||||
def create_region(world: World, active_locations, name: str, locations=None):
|
||||
# Shamelessly stolen from the ROR2 definition
|
||||
ret = Region(name, world.player, world.multiworld)
|
||||
if locations:
|
||||
for locationName, locationData in locations.items():
|
||||
loc_id = active_locations.get(locationName, 0)
|
||||
if loc_id:
|
||||
loc_byte = locationData[0] if (len(locationData) > 0) else 0
|
||||
loc_bit = locationData[1] if (len(locationData) > 1) else 0
|
||||
loc_invert = locationData[2] if (len(locationData) > 2) else False
|
||||
|
||||
location = DKC3Location(world.player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert)
|
||||
ret.locations.append(location)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def connect(world: World, player: int, used_names: typing.Dict[str, int], source: str, target: str,
|
||||
rule: typing.Optional[typing.Callable] = None):
|
||||
source_region = world.multiworld.get_region(source, player)
|
||||
target_region = world.multiworld.get_region(target, player)
|
||||
|
||||
if target not in used_names:
|
||||
used_names[target] = 1
|
||||
name = target
|
||||
else:
|
||||
used_names[target] += 1
|
||||
name = target + (' ' * used_names[target])
|
||||
|
||||
connection = Entrance(player, name, source_region)
|
||||
|
||||
if rule:
|
||||
connection.access_rule = rule
|
||||
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
return connection
|
||||
@@ -1,744 +0,0 @@
|
||||
|
||||
import Utils
|
||||
from Utils import read_snes_rom
|
||||
from worlds.AutoWorld import World
|
||||
from worlds.Files import APDeltaPatch
|
||||
from .Levels import level_list, level_dict
|
||||
|
||||
USHASH = '120abf304f0c40fe059f6a192ed4f947'
|
||||
ROM_PLAYER_LIMIT = 65535
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import math
|
||||
|
||||
|
||||
level_unlock_map = {
|
||||
0x657: [0x65A],
|
||||
0x65A: [0x680, 0x639, 0x659],
|
||||
0x659: [0x65D],
|
||||
0x65D: [0x65C],
|
||||
0x65C: [0x688, 0x64F],
|
||||
|
||||
0x662: [0x681, 0x664],
|
||||
0x664: [0x65B],
|
||||
0x65B: [0x689, 0x661],
|
||||
0x661: [0x63A, 0x666],
|
||||
0x666: [0x650, 0x649],
|
||||
|
||||
0x667: [0x66A],
|
||||
0x66A: [0x682, 0x658],
|
||||
0x658: [0x68A, 0x66B],
|
||||
0x66B: [0x668],
|
||||
0x668: [0x651],
|
||||
|
||||
0x66D: [0x63C, 0x672],
|
||||
0x672: [0x68B, 0x660],
|
||||
0x660: [0x683, 0x66E],
|
||||
0x66E: [0x670],
|
||||
0x670: [0x652],
|
||||
|
||||
0x673: [0x684, 0x65F],
|
||||
0x65F: [0x66C],
|
||||
0x66C: [0x66F],
|
||||
0x66F: [0x65E],
|
||||
0x65E: [0x63D, 0x653, 0x68C, 0x64C],
|
||||
|
||||
0x676: [0x63E, 0x674, 0x685],
|
||||
0x674: [0x63F, 0x669],
|
||||
0x669: [0x677],
|
||||
0x677: [0x68D, 0x675],
|
||||
0x675: [0x654],
|
||||
|
||||
0x67A: [0x640, 0x678],
|
||||
0x678: [0x665],
|
||||
0x665: [0x686, 0x679],
|
||||
0x679: [0x68E, 0x671],
|
||||
|
||||
0x67B: [0x67C],
|
||||
0x67C: [0x67D],
|
||||
0x67D: [0x663],
|
||||
0x663: [0x67E],
|
||||
}
|
||||
|
||||
location_rom_data = {
|
||||
0xDC3000: [0x657, 1], # Lakeside Limbo
|
||||
0xDC3001: [0x657, 2],
|
||||
0xDC3002: [0x657, 3],
|
||||
0xDC3003: [0x657, 5],
|
||||
0xDC3100: [0x657, 7],
|
||||
|
||||
0xDC3004: [0x65A, 1], # Doorstop Dash
|
||||
0xDC3005: [0x65A, 2],
|
||||
0xDC3006: [0x65A, 3],
|
||||
0xDC3007: [0x65A, 5],
|
||||
0xDC3104: [0x65A, 7],
|
||||
|
||||
0xDC3008: [0x659, 1], # Tidal Trouble
|
||||
0xDC3009: [0x659, 2],
|
||||
0xDC300A: [0x659, 3],
|
||||
0xDC300B: [0x659, 5],
|
||||
0xDC3108: [0x659, 7],
|
||||
|
||||
0xDC300C: [0x65D, 1], # Skidda's Row
|
||||
0xDC300D: [0x65D, 2],
|
||||
0xDC300E: [0x65D, 3],
|
||||
0xDC300F: [0x65D, 5],
|
||||
0xDC310C: [0x65D, 7],
|
||||
|
||||
0xDC3010: [0x65C, 1], # Murky Mill
|
||||
0xDC3011: [0x65C, 2],
|
||||
0xDC3012: [0x65C, 3],
|
||||
0xDC3013: [0x65C, 5],
|
||||
0xDC3110: [0x65C, 7],
|
||||
|
||||
|
||||
0xDC3014: [0x662, 1], # Barrel Shield Bust-Up
|
||||
0xDC3015: [0x662, 2],
|
||||
0xDC3016: [0x662, 3],
|
||||
0xDC3017: [0x662, 5],
|
||||
0xDC3114: [0x662, 7],
|
||||
|
||||
0xDC3018: [0x664, 1], # Riverside Race
|
||||
0xDC3019: [0x664, 2],
|
||||
0xDC301A: [0x664, 3],
|
||||
0xDC301B: [0x664, 5],
|
||||
0xDC3118: [0x664, 7],
|
||||
|
||||
0xDC301C: [0x65B, 1], # Squeals on Wheels
|
||||
0xDC301D: [0x65B, 2],
|
||||
0xDC301E: [0x65B, 3],
|
||||
0xDC301F: [0x65B, 5],
|
||||
0xDC311C: [0x65B, 7],
|
||||
|
||||
0xDC3020: [0x661, 1], # Springin' Spiders
|
||||
0xDC3021: [0x661, 2],
|
||||
0xDC3022: [0x661, 3],
|
||||
0xDC3023: [0x661, 5],
|
||||
0xDC3120: [0x661, 7],
|
||||
|
||||
0xDC3024: [0x666, 1], # Bobbing Barrel Brawl
|
||||
0xDC3025: [0x666, 2],
|
||||
0xDC3026: [0x666, 3],
|
||||
0xDC3027: [0x666, 5],
|
||||
0xDC3124: [0x666, 7],
|
||||
|
||||
|
||||
0xDC3028: [0x667, 1], # Bazza's Blockade
|
||||
0xDC3029: [0x667, 2],
|
||||
0xDC302A: [0x667, 3],
|
||||
0xDC302B: [0x667, 5],
|
||||
0xDC3128: [0x667, 7],
|
||||
|
||||
0xDC302C: [0x66A, 1], # Rocket Barrel Ride
|
||||
0xDC302D: [0x66A, 2],
|
||||
0xDC302E: [0x66A, 3],
|
||||
0xDC302F: [0x66A, 5],
|
||||
0xDC312C: [0x66A, 7],
|
||||
|
||||
0xDC3030: [0x658, 1], # Kreeping Klasps
|
||||
0xDC3031: [0x658, 2],
|
||||
0xDC3032: [0x658, 3],
|
||||
0xDC3033: [0x658, 5],
|
||||
0xDC3130: [0x658, 7],
|
||||
|
||||
0xDC3034: [0x66B, 1], # Tracker Barrel Trek
|
||||
0xDC3035: [0x66B, 2],
|
||||
0xDC3036: [0x66B, 3],
|
||||
0xDC3037: [0x66B, 5],
|
||||
0xDC3134: [0x66B, 7],
|
||||
|
||||
0xDC3038: [0x668, 1], # Fish Food Frenzy
|
||||
0xDC3039: [0x668, 2],
|
||||
0xDC303A: [0x668, 3],
|
||||
0xDC303B: [0x668, 5],
|
||||
0xDC3138: [0x668, 7],
|
||||
|
||||
|
||||
0xDC303C: [0x66D, 1], # Fire-ball Frenzy
|
||||
0xDC303D: [0x66D, 2],
|
||||
0xDC303E: [0x66D, 3],
|
||||
0xDC303F: [0x66D, 5],
|
||||
0xDC313C: [0x66D, 7],
|
||||
|
||||
0xDC3040: [0x672, 1], # Demolition Drainpipe
|
||||
0xDC3041: [0x672, 2],
|
||||
0xDC3042: [0x672, 3],
|
||||
0xDC3043: [0x672, 5],
|
||||
0xDC3140: [0x672, 7],
|
||||
|
||||
0xDC3044: [0x660, 1], # Ripsaw Rage
|
||||
0xDC3045: [0x660, 2],
|
||||
0xDC3046: [0x660, 3],
|
||||
0xDC3047: [0x660, 5],
|
||||
0xDC3144: [0x660, 7],
|
||||
|
||||
0xDC3048: [0x66E, 1], # Blazing Bazukas
|
||||
0xDC3049: [0x66E, 2],
|
||||
0xDC304A: [0x66E, 3],
|
||||
0xDC304B: [0x66E, 5],
|
||||
0xDC3148: [0x66E, 7],
|
||||
|
||||
0xDC304C: [0x670, 1], # Low-G Labyrinth
|
||||
0xDC304D: [0x670, 2],
|
||||
0xDC304E: [0x670, 3],
|
||||
0xDC304F: [0x670, 5],
|
||||
0xDC314C: [0x670, 7],
|
||||
|
||||
|
||||
0xDC3050: [0x673, 1], # Krevice Kreepers
|
||||
0xDC3051: [0x673, 2],
|
||||
0xDC3052: [0x673, 3],
|
||||
0xDC3053: [0x673, 5],
|
||||
0xDC3150: [0x673, 7],
|
||||
|
||||
0xDC3054: [0x65F, 1], # Tearaway Toboggan
|
||||
0xDC3055: [0x65F, 2],
|
||||
0xDC3056: [0x65F, 3],
|
||||
0xDC3057: [0x65F, 5],
|
||||
0xDC3154: [0x65F, 7],
|
||||
|
||||
0xDC3058: [0x66C, 1], # Barrel Drop Bounce
|
||||
0xDC3059: [0x66C, 2],
|
||||
0xDC305A: [0x66C, 3],
|
||||
0xDC305B: [0x66C, 5],
|
||||
0xDC3158: [0x66C, 7],
|
||||
|
||||
0xDC305C: [0x66F, 1], # Krack-Shot Kroc
|
||||
0xDC305D: [0x66F, 2],
|
||||
0xDC305E: [0x66F, 3],
|
||||
0xDC305F: [0x66F, 5],
|
||||
0xDC315C: [0x66F, 7],
|
||||
|
||||
0xDC3060: [0x65E, 1], # Lemguin Lunge
|
||||
0xDC3061: [0x65E, 2],
|
||||
0xDC3062: [0x65E, 3],
|
||||
0xDC3063: [0x65E, 5],
|
||||
0xDC3160: [0x65E, 7],
|
||||
|
||||
|
||||
0xDC3064: [0x676, 1], # Buzzer Barrage
|
||||
0xDC3065: [0x676, 2],
|
||||
0xDC3066: [0x676, 3],
|
||||
0xDC3067: [0x676, 5],
|
||||
0xDC3164: [0x676, 7],
|
||||
|
||||
0xDC3068: [0x674, 1], # Kong-Fused Cliffs
|
||||
0xDC3069: [0x674, 2],
|
||||
0xDC306A: [0x674, 3],
|
||||
0xDC306B: [0x674, 5],
|
||||
0xDC3168: [0x674, 7],
|
||||
|
||||
0xDC306C: [0x669, 1], # Floodlit Fish
|
||||
0xDC306D: [0x669, 2],
|
||||
0xDC306E: [0x669, 3],
|
||||
0xDC306F: [0x669, 5],
|
||||
0xDC316C: [0x669, 7],
|
||||
|
||||
0xDC3070: [0x677, 1], # Pothole Panic
|
||||
0xDC3071: [0x677, 2],
|
||||
0xDC3072: [0x677, 3],
|
||||
0xDC3073: [0x677, 5],
|
||||
0xDC3170: [0x677, 7],
|
||||
|
||||
0xDC3074: [0x675, 1], # Ropey Rumpus
|
||||
0xDC3075: [0x675, 2],
|
||||
0xDC3076: [0x675, 3],
|
||||
0xDC3077: [0x675, 5],
|
||||
0xDC3174: [0x675, 7],
|
||||
|
||||
|
||||
0xDC3078: [0x67A, 1], # Konveyor Rope Klash
|
||||
0xDC3079: [0x67A, 2],
|
||||
0xDC307A: [0x67A, 3],
|
||||
0xDC307B: [0x67A, 5],
|
||||
0xDC3178: [0x67A, 7],
|
||||
|
||||
0xDC307C: [0x678, 1], # Creepy Caverns
|
||||
0xDC307D: [0x678, 2],
|
||||
0xDC307E: [0x678, 3],
|
||||
0xDC307F: [0x678, 5],
|
||||
0xDC317C: [0x678, 7],
|
||||
|
||||
0xDC3080: [0x665, 1], # Lightning Lookout
|
||||
0xDC3081: [0x665, 2],
|
||||
0xDC3082: [0x665, 3],
|
||||
0xDC3083: [0x665, 5],
|
||||
0xDC3180: [0x665, 7],
|
||||
|
||||
0xDC3084: [0x679, 1], # Koindozer Klamber
|
||||
0xDC3085: [0x679, 2],
|
||||
0xDC3086: [0x679, 3],
|
||||
0xDC3087: [0x679, 5],
|
||||
0xDC3184: [0x679, 7],
|
||||
|
||||
0xDC3088: [0x671, 1], # Poisonous Pipeline
|
||||
0xDC3089: [0x671, 2],
|
||||
0xDC308A: [0x671, 3],
|
||||
0xDC308B: [0x671, 5],
|
||||
0xDC3188: [0x671, 7],
|
||||
|
||||
|
||||
0xDC308C: [0x67B, 1], # Stampede Sprint
|
||||
0xDC308D: [0x67B, 2],
|
||||
0xDC308E: [0x67B, 3],
|
||||
0xDC308F: [0x67B, 4],
|
||||
0xDC3090: [0x67B, 5],
|
||||
0xDC318C: [0x67B, 7],
|
||||
|
||||
0xDC3091: [0x67C, 1], # Criss Kross Cliffs
|
||||
0xDC3092: [0x67C, 2],
|
||||
0xDC3093: [0x67C, 3],
|
||||
0xDC3094: [0x67C, 5],
|
||||
0xDC3191: [0x67C, 7],
|
||||
|
||||
0xDC3095: [0x67D, 1], # Tyrant Twin Tussle
|
||||
0xDC3096: [0x67D, 2],
|
||||
0xDC3097: [0x67D, 3],
|
||||
0xDC3098: [0x67D, 4],
|
||||
0xDC3099: [0x67D, 5],
|
||||
0xDC3195: [0x67D, 7],
|
||||
|
||||
0xDC309A: [0x663, 1], # Swoopy Salvo
|
||||
0xDC309B: [0x663, 2],
|
||||
0xDC309C: [0x663, 3],
|
||||
0xDC309D: [0x663, 4],
|
||||
0xDC309E: [0x663, 5],
|
||||
0xDC319A: [0x663, 7],
|
||||
|
||||
0xDC309F: [0x67E, 1], # Rocket Rush
|
||||
0xDC30A0: [0x67E, 5],
|
||||
|
||||
0xDC30A1: [0x64F, 1], # Bosses
|
||||
0xDC30A2: [0x650, 1],
|
||||
0xDC30A3: [0x651, 1],
|
||||
0xDC30A4: [0x652, 1],
|
||||
0xDC30A5: [0x653, 1],
|
||||
0xDC30A6: [0x654, 1],
|
||||
0xDC30A7: [0x655, 1],
|
||||
0xDC30A8: [0x656, 1],
|
||||
|
||||
0xDC30A9: [0x647, 1], # Banana Bird Caves
|
||||
0xDC30AA: [0x645, 1],
|
||||
0xDC30AB: [0x644, 1],
|
||||
0xDC30AC: [0x642, 1],
|
||||
0xDC30AD: [0x643, 1],
|
||||
0xDC30AE: [0x646, 1],
|
||||
0xDC30AF: [0x648, 1],
|
||||
0xDC30B0: [0x649, 1],
|
||||
0xDC30B1: [0x64A, 1],
|
||||
#0xDC30B2: [0x64B, 1], # Disabled until Trade Sequence
|
||||
0xDC30B3: [0x64C, 1],
|
||||
#0xDC30B4: [0x64D, 1], # Disabled until Trade Sequence
|
||||
0xDC30B5: [0x64E, 1],
|
||||
|
||||
0xDC30B6: [0x5FE, 4], # Banana Bird Mother
|
||||
|
||||
# DKC3_TODO: Disabled until Trade Sequence
|
||||
#0xDC30B7: [0x615, 2, True],
|
||||
#0xDC30B8: [0x615, 3, True],
|
||||
#0xDC30B9: [0x619, 2],
|
||||
##0xDC30BA:
|
||||
#0xDC30BB: [0x61B, 3],
|
||||
#0xDC30BC: [0x61D, 2],
|
||||
#0xDC30BD: [0x621, 4],
|
||||
#0xDC30BE: [0x625, 4, True],
|
||||
}
|
||||
|
||||
boss_location_ids = [
|
||||
0xDC30A1,
|
||||
0xDC30A2,
|
||||
0xDC30A3,
|
||||
0xDC30A4,
|
||||
0xDC30A5,
|
||||
0xDC30A6,
|
||||
0xDC30A7,
|
||||
0xDC30A8,
|
||||
0xDC30B6,
|
||||
]
|
||||
|
||||
|
||||
item_rom_data = {
|
||||
0xDC3001: [0x5D5], # 1-Up Balloon
|
||||
0xDC3002: [0x5C9], # Bear Coin
|
||||
0xDC3003: [0x5CB], # Bonus Coin
|
||||
0xDC3004: [0x5CF], # DK Coin
|
||||
0xDC3005: [0x5CD], # Banana Bird
|
||||
0xDC3006: [0x5D1, 0x603], # Cog
|
||||
}
|
||||
|
||||
music_rom_data = [
|
||||
0x3D06B1,
|
||||
0x3D0753,
|
||||
0x3D071D,
|
||||
0x3D07FA,
|
||||
0x3D07C4,
|
||||
|
||||
0x3D08FE,
|
||||
0x3D096C,
|
||||
0x3D078E,
|
||||
0x3D08CD,
|
||||
0x3D09DD,
|
||||
|
||||
0x3D0A0E,
|
||||
0x3D0AB3,
|
||||
0x3D06E7,
|
||||
0x3D0AE4,
|
||||
0x3D0A45,
|
||||
|
||||
0x3D0B46,
|
||||
0x3D0C40,
|
||||
0x3D0897,
|
||||
0x3D0B77,
|
||||
0x3D0BD9,
|
||||
|
||||
0x3D0C71,
|
||||
0x3D0866,
|
||||
0x3D0B15,
|
||||
0x3D0BA8,
|
||||
0x3D0830,
|
||||
|
||||
0x3D0D04,
|
||||
0x3D0CA2,
|
||||
0x3D0A7C,
|
||||
0x3D0D35,
|
||||
0x3D0CD3,
|
||||
|
||||
0x3D0DC8,
|
||||
0x3D0D66,
|
||||
0x3D09AC,
|
||||
0x3D0D97,
|
||||
0x3D0C0F,
|
||||
|
||||
0x3D0DF9,
|
||||
0x3D0E31,
|
||||
0x3D0E62,
|
||||
0x3D0934,
|
||||
0x3D0E9A,
|
||||
]
|
||||
|
||||
level_music_ids = [
|
||||
0x06,
|
||||
0x07,
|
||||
0x08,
|
||||
0x0A,
|
||||
0x0B,
|
||||
0x0E,
|
||||
0x0F,
|
||||
0x10,
|
||||
0x17,
|
||||
0x19,
|
||||
0x1C,
|
||||
0x1D,
|
||||
0x1E,
|
||||
0x21,
|
||||
]
|
||||
|
||||
class LocalRom:
|
||||
|
||||
def __init__(self, file, name=None, hash=None):
|
||||
self.name = name
|
||||
self.hash = hash
|
||||
self.orig_buffer = None
|
||||
|
||||
with open(file, 'rb') as stream:
|
||||
self.buffer = read_snes_rom(stream)
|
||||
#if patch:
|
||||
# self.patch_rom()
|
||||
# self.orig_buffer = self.buffer.copy()
|
||||
#if vanillaRom:
|
||||
# with open(vanillaRom, 'rb') as vanillaStream:
|
||||
# self.orig_buffer = read_snes_rom(vanillaStream)
|
||||
|
||||
def read_bit(self, address: int, bit_number: int) -> bool:
|
||||
bitflag = (1 << bit_number)
|
||||
return ((self.buffer[address] & bitflag) != 0)
|
||||
|
||||
def read_byte(self, address: int) -> int:
|
||||
return self.buffer[address]
|
||||
|
||||
def read_bytes(self, startaddress: int, length: int) -> bytearray:
|
||||
return self.buffer[startaddress:startaddress + length]
|
||||
|
||||
def write_byte(self, address: int, value: int):
|
||||
self.buffer[address] = value
|
||||
|
||||
def write_bytes(self, startaddress: int, values):
|
||||
self.buffer[startaddress:startaddress + len(values)] = values
|
||||
|
||||
def write_to_file(self, file):
|
||||
with open(file, 'wb') as outfile:
|
||||
outfile.write(self.buffer)
|
||||
|
||||
def read_from_file(self, file):
|
||||
with open(file, 'rb') as stream:
|
||||
self.buffer = bytearray(stream.read())
|
||||
|
||||
|
||||
|
||||
def patch_rom(world: World, rom: LocalRom, active_level_list):
|
||||
|
||||
# Boomer Costs
|
||||
bonus_coin_cost = world.options.krematoa_bonus_coin_cost
|
||||
inverted_bonus_coin_cost = 0x100 - bonus_coin_cost
|
||||
rom.write_byte(0x3498B9, inverted_bonus_coin_cost)
|
||||
rom.write_byte(0x3498BA, inverted_bonus_coin_cost)
|
||||
rom.write_byte(0x3498BB, inverted_bonus_coin_cost)
|
||||
rom.write_byte(0x3498BC, inverted_bonus_coin_cost)
|
||||
rom.write_byte(0x3498BD, inverted_bonus_coin_cost)
|
||||
|
||||
rom.write_byte(0x349857, bonus_coin_cost)
|
||||
rom.write_byte(0x349862, bonus_coin_cost)
|
||||
|
||||
# Gyrocopter Costs
|
||||
dk_coin_cost = world.options.dk_coins_for_gyrocopter
|
||||
rom.write_byte(0x3484A6, dk_coin_cost)
|
||||
rom.write_byte(0x3484D5, dk_coin_cost)
|
||||
rom.write_byte(0x3484D7, 0x90)
|
||||
rom.write_byte(0x3484DC, 0xEA)
|
||||
rom.write_byte(0x3484DD, 0xEA)
|
||||
rom.write_byte(0x3484DE, 0xEA)
|
||||
rom.write_byte(0x348528, 0x80) # Prevent Single-Ski Lock
|
||||
|
||||
# Make Swanky free
|
||||
rom.write_byte(0x348C48, 0x00)
|
||||
|
||||
rom.write_bytes(0x34AB70, bytearray([0xEA, 0xEA]))
|
||||
rom.write_bytes(0x34ABF7, bytearray([0xEA, 0xEA]))
|
||||
rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA]))
|
||||
|
||||
# Banana Bird Costs
|
||||
if world.options.goal == "banana_bird_hunt":
|
||||
banana_bird_cost = math.floor(world.options.number_of_banana_birds * world.options.percentage_of_banana_birds / 100.0)
|
||||
rom.write_byte(0x34AB85, banana_bird_cost)
|
||||
rom.write_byte(0x329FD8, banana_bird_cost)
|
||||
rom.write_byte(0x32A025, banana_bird_cost)
|
||||
rom.write_byte(0x329FDA, 0xB0)
|
||||
else:
|
||||
# rom.write_byte(0x34AB84, 0x20) # These cause hangs at Wrinkly's
|
||||
# rom.write_byte(0x329FD8, 0x20)
|
||||
# rom.write_byte(0x32A025, 0x20)
|
||||
rom.write_byte(0x329FDA, 0xB0)
|
||||
|
||||
# Baffle Mirror Fix
|
||||
rom.write_byte(0x9133, 0x08)
|
||||
rom.write_byte(0x9135, 0x0C)
|
||||
rom.write_byte(0x9136, 0x2B)
|
||||
rom.write_byte(0x9137, 0x06)
|
||||
|
||||
# Palette Swap
|
||||
rom.write_byte(0x3B96A5, 0xD0)
|
||||
if world.options.kong_palette_swap == "default":
|
||||
rom.write_byte(0x3B96A9, 0x00)
|
||||
rom.write_byte(0x3B96A8, 0x00)
|
||||
elif world.options.kong_palette_swap == "purple":
|
||||
rom.write_byte(0x3B96A9, 0x00)
|
||||
rom.write_byte(0x3B96A8, 0x3C)
|
||||
elif world.options.kong_palette_swap == "spooky":
|
||||
rom.write_byte(0x3B96A9, 0x00)
|
||||
rom.write_byte(0x3B96A8, 0xA0)
|
||||
elif world.options.kong_palette_swap == "dark":
|
||||
rom.write_byte(0x3B96A9, 0x05)
|
||||
rom.write_byte(0x3B96A8, 0xA0)
|
||||
elif world.options.kong_palette_swap == "chocolate":
|
||||
rom.write_byte(0x3B96A9, 0x1D)
|
||||
rom.write_byte(0x3B96A8, 0xA0)
|
||||
elif world.options.kong_palette_swap == "shadow":
|
||||
rom.write_byte(0x3B96A9, 0x45)
|
||||
rom.write_byte(0x3B96A8, 0xA0)
|
||||
elif world.options.kong_palette_swap == "red_gold":
|
||||
rom.write_byte(0x3B96A9, 0x5D)
|
||||
rom.write_byte(0x3B96A8, 0xA0)
|
||||
elif world.options.kong_palette_swap == "gbc":
|
||||
rom.write_byte(0x3B96A9, 0x20)
|
||||
rom.write_byte(0x3B96A8, 0x3C)
|
||||
elif world.options.kong_palette_swap == "halloween":
|
||||
rom.write_byte(0x3B96A9, 0x70)
|
||||
rom.write_byte(0x3B96A8, 0x3C)
|
||||
|
||||
if world.options.music_shuffle:
|
||||
for address in music_rom_data:
|
||||
rand_song = world.random.choice(level_music_ids)
|
||||
rom.write_byte(address, rand_song)
|
||||
|
||||
# Starting Lives
|
||||
rom.write_byte(0x9130, world.options.starting_life_count.value)
|
||||
rom.write_byte(0x913B, world.options.starting_life_count.value)
|
||||
|
||||
# Cheat options
|
||||
cheat_bytes = [0x00, 0x00]
|
||||
|
||||
if world.options.merry:
|
||||
cheat_bytes[0] |= 0x01
|
||||
|
||||
if world.options.autosave:
|
||||
cheat_bytes[0] |= 0x02
|
||||
|
||||
if world.options.difficulty == "tufst":
|
||||
cheat_bytes[0] |= 0x80
|
||||
cheat_bytes[1] |= 0x80
|
||||
elif world.options.difficulty == "hardr":
|
||||
cheat_bytes[0] |= 0x00
|
||||
cheat_bytes[1] |= 0x00
|
||||
elif world.options.difficulty == "norml":
|
||||
cheat_bytes[1] |= 0x40
|
||||
|
||||
rom.write_bytes(0x8303, bytearray(cheat_bytes))
|
||||
|
||||
# Handle Level Shuffle Here
|
||||
if world.options.level_shuffle:
|
||||
for i in range(len(active_level_list)):
|
||||
rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID)
|
||||
rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID)
|
||||
|
||||
rom.write_byte(0x3FF800 + level_dict[active_level_list[i]].levelID, level_dict[level_list[i]].levelID)
|
||||
rom.write_byte(0x3FF860 + level_dict[level_list[i]].levelID, level_dict[active_level_list[i]].levelID)
|
||||
|
||||
# First levels of each world
|
||||
rom.write_byte(0x34BC3E, (0x32 + level_dict[active_level_list[0]].levelID))
|
||||
rom.write_byte(0x34BC47, (0x32 + level_dict[active_level_list[5]].levelID))
|
||||
rom.write_byte(0x34BC4A, (0x32 + level_dict[active_level_list[10]].levelID))
|
||||
rom.write_byte(0x34BC53, (0x32 + level_dict[active_level_list[15]].levelID))
|
||||
rom.write_byte(0x34BC59, (0x32 + level_dict[active_level_list[20]].levelID))
|
||||
rom.write_byte(0x34BC5C, (0x32 + level_dict[active_level_list[25]].levelID))
|
||||
rom.write_byte(0x34BC65, (0x32 + level_dict[active_level_list[30]].levelID))
|
||||
rom.write_byte(0x34BC6E, (0x32 + level_dict[active_level_list[35]].levelID))
|
||||
|
||||
# Cotton-Top Cove Boss Unlock
|
||||
rom.write_byte(0x34C02A, (0x32 + level_dict[active_level_list[14]].levelID))
|
||||
|
||||
# Kong-Fused Cliffs Unlock
|
||||
rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID))
|
||||
rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID))
|
||||
|
||||
if world.options.goal == "knautilus":
|
||||
# Swap Kastle KAOS and Knautilus
|
||||
rom.write_byte(0x34D4E1, 0xC2)
|
||||
rom.write_byte(0x34D4E2, 0x24)
|
||||
rom.write_byte(0x34D551, 0xBA)
|
||||
rom.write_byte(0x34D552, 0x23)
|
||||
|
||||
rom.write_byte(0x32F339, 0x55)
|
||||
|
||||
# Handle KONGsanity Here
|
||||
if world.options.kongsanity:
|
||||
# Arich's Hoard KONGsanity fix
|
||||
rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA]))
|
||||
|
||||
# Don't hide the level flag if the 0x80 bit is set
|
||||
rom.write_bytes(0x34CE92, bytearray([0x80]))
|
||||
|
||||
# Use the `!` next to level name for indicating KONG letters
|
||||
rom.write_bytes(0x34B8F0, bytearray([0x80]))
|
||||
rom.write_bytes(0x34B8F3, bytearray([0x80]))
|
||||
|
||||
# Hijack to code to set the 0x80 flag for the level when you complete KONG
|
||||
rom.write_bytes(0x3BCD4B, bytearray([0x22, 0x80, 0xFA, 0XB8])) # JSL $B8FA80
|
||||
|
||||
rom.write_bytes(0x38FA80, bytearray([0xDA])) # PHX
|
||||
rom.write_bytes(0x38FA81, bytearray([0x48])) # PHA
|
||||
rom.write_bytes(0x38FA82, bytearray([0x08])) # PHP
|
||||
rom.write_bytes(0x38FA83, bytearray([0xE2, 0x20])) # SEP #20
|
||||
rom.write_bytes(0x38FA85, bytearray([0x48])) # PHA
|
||||
rom.write_bytes(0x38FA86, bytearray([0x18])) # CLC
|
||||
rom.write_bytes(0x38FA87, bytearray([0x6D, 0xD3, 0x18])) # ADC $18D3
|
||||
rom.write_bytes(0x38FA8A, bytearray([0x8D, 0xD3, 0x18])) # STA $18D3
|
||||
rom.write_bytes(0x38FA8D, bytearray([0x68])) # PLA
|
||||
rom.write_bytes(0x38FA8E, bytearray([0xC2, 0x20])) # REP 20
|
||||
rom.write_bytes(0x38FA90, bytearray([0X18])) # CLC
|
||||
rom.write_bytes(0x38FA91, bytearray([0x6D, 0xD5, 0x05])) # ADC $05D5
|
||||
rom.write_bytes(0x38FA94, bytearray([0x8D, 0xD5, 0x05])) # STA $05D5
|
||||
rom.write_bytes(0x38FA97, bytearray([0xAE, 0xB9, 0x05])) # LDX $05B9
|
||||
rom.write_bytes(0x38FA9A, bytearray([0xBD, 0x32, 0x06])) # LDA $0632, X
|
||||
rom.write_bytes(0x38FA9D, bytearray([0x09, 0x80, 0x00])) # ORA #8000
|
||||
rom.write_bytes(0x38FAA0, bytearray([0x9D, 0x32, 0x06])) # STA $0632, X
|
||||
rom.write_bytes(0x38FAA3, bytearray([0xAD, 0xD5, 0x18])) # LDA $18D5
|
||||
rom.write_bytes(0x38FAA6, bytearray([0xD0, 0x03])) # BNE $80EA
|
||||
rom.write_bytes(0x38FAA8, bytearray([0x9C, 0xD9, 0x18])) # STZ $18D9
|
||||
rom.write_bytes(0x38FAAB, bytearray([0xA9, 0x78, 0x00])) # LDA #0078
|
||||
rom.write_bytes(0x38FAAE, bytearray([0x8D, 0xD5, 0x18])) # STA $18D5
|
||||
rom.write_bytes(0x38FAB1, bytearray([0x28])) # PLP
|
||||
rom.write_bytes(0x38FAB2, bytearray([0x68])) # PLA
|
||||
rom.write_bytes(0x38FAB3, bytearray([0xFA])) # PLX
|
||||
rom.write_bytes(0x38FAB4, bytearray([0x6B])) # RTL
|
||||
# End Handle KONGsanity
|
||||
|
||||
# Handle Credits
|
||||
rom.write_bytes(0x32A5DF, bytearray([0x41, 0x52, 0x43, 0x48, 0x49, 0x50, 0x45, 0x4C, 0x41, 0x47, 0x4F, 0x20, 0x4D, 0x4F, 0xC4])) # "ARCHIPELAGO MOD"
|
||||
rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE"
|
||||
|
||||
from Utils import __version__
|
||||
rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
|
||||
rom.name.extend([0] * (21 - len(rom.name)))
|
||||
rom.write_bytes(0x7FC0, rom.name)
|
||||
|
||||
# DKC3_TODO: This is a hack, reconsider
|
||||
# Don't grant (DK, Bonus, Bear) Coins
|
||||
rom.write_byte(0x3BD454, 0xEA)
|
||||
rom.write_byte(0x3BD455, 0xEA)
|
||||
|
||||
# Don't grant Cogs
|
||||
rom.write_byte(0x3BD574, 0xEA)
|
||||
rom.write_byte(0x3BD575, 0xEA)
|
||||
rom.write_byte(0x3BD576, 0xEA)
|
||||
|
||||
# Don't grant Banana Birds at their caves
|
||||
rom.write_byte(0x32DD62, 0xEA)
|
||||
rom.write_byte(0x32DD63, 0xEA)
|
||||
rom.write_byte(0x32DD64, 0xEA)
|
||||
|
||||
# Don't grant Banana Birds at Bears
|
||||
rom.write_byte(0x3492DB, 0xEA)
|
||||
rom.write_byte(0x3492DC, 0xEA)
|
||||
rom.write_byte(0x3492DD, 0xEA)
|
||||
rom.write_byte(0x3493F4, 0xEA)
|
||||
rom.write_byte(0x3493F5, 0xEA)
|
||||
rom.write_byte(0x3493F6, 0xEA)
|
||||
|
||||
# Don't grant present at Blizzard
|
||||
rom.write_byte(0x8454, 0x00)
|
||||
|
||||
# Don't grant Patch and Skis from their bosses
|
||||
rom.write_byte(0x3F3762, 0x00)
|
||||
rom.write_byte(0x3F377B, 0x00)
|
||||
rom.write_byte(0x3F3797, 0x00)
|
||||
|
||||
# Always allow Start+Select
|
||||
rom.write_byte(0x8BAB, 0x01)
|
||||
|
||||
# Handle Alt Palettes in Krematoa
|
||||
rom.write_byte(0x3B97E9, 0x80)
|
||||
rom.write_byte(0x3B97EA, 0xEA)
|
||||
|
||||
|
||||
class DKC3DeltaPatch(APDeltaPatch):
|
||||
hash = USHASH
|
||||
game = "Donkey Kong Country 3"
|
||||
patch_file_ending = ".apdkc3"
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
return get_base_rom_bytes()
|
||||
|
||||
|
||||
def get_base_rom_bytes(file_name: str = "") -> 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(read_snes_rom(open(file_name, "rb")))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(base_rom_bytes)
|
||||
if USHASH != basemd5.hexdigest():
|
||||
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
|
||||
'Get the correct game and version, then dump it')
|
||||
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:
|
||||
from settings import get_settings
|
||||
file_name = get_settings()["dkc3_options"]["rom_file"]
|
||||
if not os.path.exists(file_name):
|
||||
file_name = Utils.user_path(file_name)
|
||||
return file_name
|
||||
@@ -1,31 +0,0 @@
|
||||
import math
|
||||
|
||||
from worlds.AutoWorld import World
|
||||
from worlds.generic.Rules import add_rule
|
||||
from .Names import LocationName, ItemName
|
||||
|
||||
|
||||
def set_rules(world: World):
|
||||
|
||||
if False:#world.options.include_trade_sequence:
|
||||
add_rule(world.multiworld.get_location(LocationName.barnacles_island, world.player),
|
||||
lambda state: state.has(ItemName.shell, world.player))
|
||||
|
||||
add_rule(world.multiworld.get_location(LocationName.blues_beach_hut, world.player),
|
||||
lambda state: state.has(ItemName.present, world.player))
|
||||
|
||||
add_rule(world.multiworld.get_location(LocationName.brambles_bungalow, world.player),
|
||||
lambda state: state.has(ItemName.flower, world.player))
|
||||
|
||||
add_rule(world.multiworld.get_location(LocationName.barters_swap_shop, world.player),
|
||||
lambda state: state.has(ItemName.mirror, world.player))
|
||||
|
||||
|
||||
if world.options.goal != "knautilus":
|
||||
required_banana_birds = math.floor(
|
||||
world.options.number_of_banana_birds.value * (world.options.percentage_of_banana_birds.value / 100.0))
|
||||
|
||||
add_rule(world.multiworld.get_location(LocationName.banana_bird_mother, world.player),
|
||||
lambda state: state.has(ItemName.banana_bird, world.player, required_banana_birds))
|
||||
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user