Merge branch 'main' into NewSoupVi-patch-20

This commit is contained in:
NewSoupVi
2025-04-05 03:42:23 +02:00
committed by GitHub
1001 changed files with 69934 additions and 17130 deletions

View File

@@ -1,9 +1,8 @@
from __future__ import annotations
import abc
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union
from typing_extensions import TypeGuard
import logging
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union, TypeGuard
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components
@@ -60,8 +59,12 @@ class AutoSNIClientRegister(abc.ABCMeta):
@staticmethod
async def get_handler(ctx: SNIContext) -> Optional[SNIClient]:
for _game, handler in AutoSNIClientRegister.game_handlers.items():
if await handler.validate_rom(ctx):
return handler
try:
if await handler.validate_rom(ctx):
return handler
except Exception as e:
text_file_logger = logging.getLogger()
text_file_logger.exception(e)
return None
@@ -83,3 +86,7 @@ class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
async def deathlink_kill_player(self, ctx: SNIContext) -> None:
""" override this with implementation to kill player """
pass
def on_package(self, ctx: SNIContext, cmd: str, args: Dict[str, Any]) -> None:
""" override this with code to handle packages from the server """
pass

View File

@@ -7,10 +7,10 @@ import sys
import time
from random import Random
from dataclasses import make_dataclass
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple,
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, Iterable, List, Mapping, Optional, Set, TextIO, Tuple,
TYPE_CHECKING, Type, Union)
from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions
from BaseClasses import CollectionState
if TYPE_CHECKING:
@@ -33,7 +33,10 @@ class AutoWorldRegister(type):
# lazy loading + caching to minimize runtime cost
if cls.__settings is None:
from settings import get_settings
cls.__settings = get_settings()[cls.settings_key]
try:
cls.__settings = get_settings()[cls.settings_key]
except AttributeError:
return None
return cls.__settings
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
@@ -352,7 +355,7 @@ class World(metaclass=AutoWorldRegister):
# overridable methods that get called by Main.py, sorted by execution order
# can also be implemented as a classmethod and called "stage_<original_name>",
# in that case the MultiWorld object is passed as an argument, and it gets called once for the entire multiworld.
# in that case the MultiWorld object is passed as the first argument, and it gets called once for the entire multiworld.
# An example of this can be found in alttp as stage_pre_fill
@classmethod
@@ -385,6 +388,10 @@ class World(metaclass=AutoWorldRegister):
"""Method for setting the rules on the World's regions and locations."""
pass
def connect_entrances(self) -> None:
"""Method to finalize the source and target regions of the World's entrances"""
pass
def generate_basic(self) -> None:
"""
Useful for randomizing things that don't affect logic but are better to be determined before the output stage.
@@ -490,6 +497,7 @@ class World(metaclass=AutoWorldRegister):
group = cls(multiworld, new_player_id)
group.options = cls.options_dataclass(**{option_key: option.from_any(option.default)
for option_key, option in cls.options_dataclass.type_hints.items()})
group.options.accessibility = ItemsAccessibility(ItemsAccessibility.option_items)
return group
@@ -540,12 +548,24 @@ class World(metaclass=AutoWorldRegister):
def get_location(self, location_name: str) -> "Location":
return self.multiworld.get_location(location_name, self.player)
def get_locations(self) -> "Iterable[Location]":
return self.multiworld.get_locations(self.player)
def get_entrance(self, entrance_name: str) -> "Entrance":
return self.multiworld.get_entrance(entrance_name, self.player)
def get_entrances(self) -> "Iterable[Entrance]":
return self.multiworld.get_entrances(self.player)
def get_region(self, region_name: str) -> "Region":
return self.multiworld.get_region(region_name, self.player)
def get_regions(self) -> "Iterable[Region]":
return self.multiworld.get_regions(self.player)
def push_precollected(self, item: Item) -> None:
self.multiworld.push_precollected(item)
@property
def player_name(self) -> str:
return self.multiworld.get_player_name(self.player)

View File

@@ -18,16 +18,42 @@ class Type(Enum):
class Component:
"""
A Component represents a process launchable by Archipelago Launcher, either by a User action in the GUI,
by resolving an archipelago://user:pass@host:port link from the WebHost, by resolving a patch file's metadata,
or by using a component name arg while running the Launcher in CLI i.e. `ArchipelagoLauncher.exe "Text Client"`
Expected to be appended to LauncherComponents.component list to be used.
"""
display_name: str
"""Used as the GUI button label and the component name in the CLI args"""
type: Type
"""
Enum "Type" classification of component intent, for filtering in the Launcher GUI
If not set in the constructor, it will be inferred by display_name
"""
script_name: Optional[str]
"""Recommended to use func instead; Name of file to run when the component is called"""
frozen_name: Optional[str]
"""Recommended to use func instead; Name of the frozen executable file for this component"""
icon: str # just the name, no suffix
"""Lookup ID for the icon path in LauncherComponents.icon_paths"""
cli: bool
"""Bool to control if the component gets launched in an appropriate Terminal for the OS"""
func: Optional[Callable]
"""
Function that gets called when the component gets launched
Any arg besides the component name arg is passed into the func as well, so handling *args is suggested
"""
file_identifier: Optional[Callable[[str], bool]]
"""
Function that is run against patch file arg to identify which component is appropriate to launch
If the function is an Instance of SuffixIdentifier the suffixes will also be valid for the Open Patch component
"""
game_name: Optional[str]
"""Game name to identify component when handling launch links from WebHost"""
supports_uri: Optional[bool]
"""Bool to identify if a component supports being launched by launch links from WebHost"""
def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None,
@@ -61,14 +87,21 @@ class Component:
processes = weakref.WeakSet()
def launch_subprocess(func: Callable, name: str = None, args: Tuple[str, ...] = ()) -> None:
global processes
def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
import multiprocessing
process = multiprocessing.Process(target=func, name=name, args=args)
process.start()
processes.add(process)
def launch(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
from Utils import is_kivy_running
if is_kivy_running():
launch_subprocess(func, name, args)
else:
func(*args)
class SuffixIdentifier:
suffixes: Iterable[str]
@@ -85,7 +118,7 @@ class SuffixIdentifier:
def launch_textclient(*args):
import CommonClient
launch_subprocess(CommonClient.run_as_textclient, name="TextClient", args=args)
launch(CommonClient.run_as_textclient, name="TextClient", args=args)
def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
@@ -100,10 +133,16 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
apworld_path = pathlib.Path(apworld_src)
module_name = pathlib.Path(apworld_path.name).stem
try:
import zipfile
zipfile.ZipFile(apworld_path).open(module_name + "/__init__.py")
zip = zipfile.ZipFile(apworld_path)
directories = [f.name for f in zipfile.Path(zip).iterdir() if f.is_dir()]
if len(directories) == 1 and directories[0] in apworld_path.stem:
module_name = directories[0]
apworld_name = module_name + ".apworld"
else:
raise Exception("APWorld appears to be invalid or damaged. (expected a single directory)")
zip.open(module_name + "/__init__.py")
except ValueError as e:
raise Exception("Archive appears invalid or damaged.") from e
except KeyError as e:
@@ -122,7 +161,7 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
# TODO: run generic test suite over the apworld.
# TODO: have some kind of version system to tell from metadata if the apworld should be compatible.
target = pathlib.Path(worlds.user_folder) / apworld_path.name
target = pathlib.Path(worlds.user_folder) / apworld_name
import shutil
shutil.copyfile(apworld_path, target)
@@ -201,6 +240,7 @@ components: List[Component] = [
]
# if registering an icon from within an apworld, the format "ap:module.name/path/to/file.png" can be used
icon_paths = {
'icon': local_path('data', 'icon.png'),
'mcicon': local_path('data', 'mcicon.png'),

View File

@@ -66,19 +66,12 @@ class WorldSource:
start = time.perf_counter()
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
assert spec, f"{self.path} is not a loadable module"
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
assert spec, f"{self.path} is not a loadable module"
mod = importlib.util.module_from_spec(spec)
mod.__package__ = f"worlds.{mod.__package__}"
if mod.__package__ is not None:
mod.__package__ = f"worlds.{mod.__package__}"
else:
# load_module does not populate package, we'll have to assume mod.__name__ is correct here
# probably safe to remove with 3.8 support
mod.__package__ = f"worlds.{mod.__name__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():

View File

@@ -55,6 +55,7 @@ async def lock(ctx) -> None
async def unlock(ctx) -> None
async def get_hash(ctx) -> str
async def get_memory_size(ctx, domain: str) -> int
async def get_system(ctx) -> str
async def get_cores(ctx) -> dict[str, str]
async def ping(ctx) -> None
@@ -168,9 +169,10 @@ select dialog and they will be associated with BizHawkClient. This does not affe
associate the file extension with Archipelago.
`validate_rom` is called to figure out whether a given ROM belongs to your client. It will only be called when a ROM is
running on a system you specified in your `system` class variable. In most cases, that will be a single system and you
can be sure that you're not about to try to read from nonexistent domains or out of bounds. If you decide to claim this
ROM as yours, this is where you should do setup for things like `items_handling`.
running on a system you specified in your `system` class variable. Take extra care here, because your code will run
against ROMs that you have no control over. If you're reading an address deep in ROM, you might want to check the size
of ROM before you attempt to read it using `get_memory_size`. If you decide to claim this ROM as yours, this is where
you should do setup for things like `items_handling`.
`game_watcher` is the "main loop" of your client where you should be checking memory and sending new items to the ROM.
`BizHawkClient` will make sure that your `game_watcher` only runs when your client has validated the ROM, and will do
@@ -268,6 +270,8 @@ server connection before trying to interact with it.
- By default, the player will be asked to provide their slot name after connecting to the server and validating, and
that input will be used to authenticate with the `Connect` command. You can override `set_auth` in your own client to
set it automatically based on data in the ROM or on your client instance.
- Use `get_memory_size` inside `validate_rom` if you need to read at large addresses, in case some other game has a
smaller ROM size.
- You can override `on_package` in your client to watch raw packages, but don't forget you also have access to a
subclass of `CommonContext` and its API.
- You can import `BizHawkClientContext` for type hints using `typing.TYPE_CHECKING`. Importing it without conditions at

View File

@@ -10,7 +10,7 @@ import base64
import enum
import json
import sys
import typing
from typing import Any, Sequence
BIZHAWK_SOCKET_PORT_RANGE_START = 43055
@@ -44,10 +44,10 @@ class SyncError(Exception):
class BizHawkContext:
streams: typing.Optional[typing.Tuple[asyncio.StreamReader, asyncio.StreamWriter]]
streams: tuple[asyncio.StreamReader, asyncio.StreamWriter] | None
connection_status: ConnectionStatus
_lock: asyncio.Lock
_port: typing.Optional[int]
_port: int | None
def __init__(self) -> None:
self.streams = None
@@ -122,12 +122,12 @@ async def get_script_version(ctx: BizHawkContext) -> int:
return int(await ctx._send_message("VERSION"))
async def send_requests(ctx: BizHawkContext, req_list: typing.List[typing.Dict[str, typing.Any]]) -> typing.List[typing.Dict[str, typing.Any]]:
async def send_requests(ctx: BizHawkContext, req_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Sends a list of requests to the BizHawk connector and returns their responses.
It's likely you want to use the wrapper functions instead of this."""
responses = json.loads(await ctx._send_message(json.dumps(req_list)))
errors: typing.List[ConnectorError] = []
errors: list[ConnectorError] = []
for response in responses:
if response["type"] == "ERROR":
@@ -151,7 +151,7 @@ async def ping(ctx: BizHawkContext) -> None:
async def get_hash(ctx: BizHawkContext) -> str:
"""Gets the system name for the currently loaded ROM"""
"""Gets the hash value of the currently loaded ROM"""
res = (await send_requests(ctx, [{"type": "HASH"}]))[0]
if res["type"] != "HASH_RESPONSE":
@@ -160,6 +160,16 @@ async def get_hash(ctx: BizHawkContext) -> str:
return res["value"]
async def get_memory_size(ctx: BizHawkContext, domain: str) -> int:
"""Gets the size in bytes of the specified memory domain"""
res = (await send_requests(ctx, [{"type": "MEMORY_SIZE", "domain": domain}]))[0]
if res["type"] != "MEMORY_SIZE_RESPONSE":
raise SyncError(f"Expected response of type MEMORY_SIZE_RESPONSE but got {res['type']}")
return res["value"]
async def get_system(ctx: BizHawkContext) -> str:
"""Gets the system name for the currently loaded ROM"""
res = (await send_requests(ctx, [{"type": "SYSTEM"}]))[0]
@@ -170,7 +180,7 @@ async def get_system(ctx: BizHawkContext) -> str:
return res["value"]
async def get_cores(ctx: BizHawkContext) -> typing.Dict[str, str]:
async def get_cores(ctx: BizHawkContext) -> dict[str, str]:
"""Gets the preferred cores for systems with multiple cores. Only systems with multiple available cores have
entries."""
res = (await send_requests(ctx, [{"type": "PREFERRED_CORES"}]))[0]
@@ -223,8 +233,8 @@ async def set_message_interval(ctx: BizHawkContext, value: float) -> None:
raise SyncError(f"Expected response of type SET_MESSAGE_INTERVAL_RESPONSE but got {res['type']}")
async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]],
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> typing.Optional[typing.List[bytes]]:
async def guarded_read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]],
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> list[bytes] | None:
"""Reads an array of bytes at 1 or more addresses if and only if every byte in guard_list matches its expected
value.
@@ -252,7 +262,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[
"domain": domain
} for address, size, domain in read_list])
ret: typing.List[bytes] = []
ret: list[bytes] = []
for item in res:
if item["type"] == "GUARD_RESPONSE":
if not item["value"]:
@@ -266,7 +276,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[
return ret
async def read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
async def read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]]) -> list[bytes]:
"""Reads data at 1 or more addresses.
Items in `read_list` should be organized `(address, size, domain)` where
@@ -278,8 +288,8 @@ async def read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int
return await guarded_read(ctx, read_list, [])
async def guarded_write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]],
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> bool:
async def guarded_write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]],
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> bool:
"""Writes data to 1 or more addresses if and only if every byte in guard_list matches its expected value.
Items in `write_list` should be organized `(address, value, domain)` where
@@ -316,7 +326,7 @@ async def guarded_write(ctx: BizHawkContext, write_list: typing.List[typing.Tupl
return True
async def write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> None:
async def write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]]) -> None:
"""Writes data to 1 or more addresses.
Items in write_list should be organized `(address, value, domain)` where

View File

@@ -5,9 +5,9 @@ A module containing the BizHawkClient base class and metaclass
from __future__ import annotations
import abc
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, ClassVar
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch as launch_component
if TYPE_CHECKING:
from .context import BizHawkClientContext
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
def launch_client(*args) -> None:
from .context import launch
launch_subprocess(launch, name="BizHawkClient", args=args)
launch_component(launch, name="BizHawkClient", args=args)
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
@@ -24,9 +24,9 @@ components.append(component)
class AutoBizHawkClientRegister(abc.ABCMeta):
game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
game_handlers: ClassVar[dict[tuple[str, ...], dict[str, BizHawkClient]]] = {}
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> AutoBizHawkClientRegister:
new_class = super().__new__(cls, name, bases, namespace)
# Register handler
@@ -54,7 +54,7 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
return new_class
@staticmethod
async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]:
async def get_handler(ctx: "BizHawkClientContext", system: str) -> BizHawkClient | None:
for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
if system in systems:
for handler in handlers.values():
@@ -65,13 +65,13 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
system: ClassVar[Union[str, Tuple[str, ...]]]
system: ClassVar[str | tuple[str, ...]]
"""The system(s) that the game this client is for runs on"""
game: ClassVar[str]
"""The game this client is for"""
patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
patch_suffix: ClassVar[str | tuple[str, ...] | None]
"""The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
@abc.abstractmethod

View File

@@ -6,7 +6,7 @@ checking or launching the client, otherwise it will probably cause circular impo
import asyncio
import enum
import subprocess
from typing import Any, Dict, Optional
from typing import Any
from CommonClient import CommonContext, ClientCommandProcessor, get_base_parser, server_loop, logger, gui_enabled
import Patch
@@ -41,17 +41,18 @@ class BizHawkClientCommandProcessor(ClientCommandProcessor):
class BizHawkClientContext(CommonContext):
command_processor = BizHawkClientCommandProcessor
server_seed_name: str | None = None
auth_status: AuthStatus
password_requested: bool
client_handler: Optional[BizHawkClient]
slot_data: Optional[Dict[str, Any]] = None
rom_hash: Optional[str] = None
client_handler: BizHawkClient | None
slot_data: dict[str, Any] | None = None
rom_hash: str | None = None
bizhawk_ctx: BizHawkContext
watcher_timeout: float
"""The maximum amount of time the game watcher loop will wait for an update from the server before executing"""
def __init__(self, server_address: Optional[str], password: Optional[str]):
def __init__(self, server_address: str | None, password: str | None):
super().__init__(server_address, password)
self.auth_status = AuthStatus.NOT_AUTHENTICATED
self.password_requested = False
@@ -68,6 +69,8 @@ class BizHawkClientContext(CommonContext):
if cmd == "Connected":
self.slot_data = args.get("slot_data", None)
self.auth_status = AuthStatus.AUTHENTICATED
elif cmd == "RoomInfo":
self.server_seed_name = args.get("seed_name", None)
if self.client_handler is not None:
self.client_handler.on_package(self, cmd, args)
@@ -100,6 +103,7 @@ class BizHawkClientContext(CommonContext):
async def disconnect(self, allow_autoreconnect: bool=False):
self.auth_status = AuthStatus.NOT_AUTHENTICATED
self.server_seed_name = None
await super().disconnect(allow_autoreconnect)
@@ -231,20 +235,28 @@ async def _run_game(rom: str):
)
async def _patch_and_run_game(patch_file: str):
def _patch_and_run_game(patch_file: str):
try:
metadata, output_file = Patch.create_rom_file(patch_file)
Utils.async_start(_run_game(output_file))
return metadata
except Exception as exc:
logger.exception(exc)
Utils.messagebox("Error Patching Game", str(exc), True)
return {}
def launch(*launch_args) -> None:
def launch(*launch_args: str) -> None:
async def main():
parser = get_base_parser()
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
args = parser.parse_args(launch_args)
if args.patch_file != "":
metadata = _patch_and_run_game(args.patch_file)
if "server" in metadata:
args.connect = metadata["server"]
ctx = BizHawkClientContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
@@ -252,9 +264,6 @@ def launch(*launch_args) -> None:
ctx.run_gui()
ctx.run_cli()
if args.patch_file != "":
Utils.async_start(_patch_and_run_game(args.patch_file))
watcher_task = asyncio.create_task(_game_watcher(ctx), name="GameWatcher")
try:
@@ -267,6 +276,6 @@ def launch(*launch_args) -> None:
Utils.init_logging("BizHawkClient", exception_logger="Client")
import colorama
colorama.init()
colorama.just_fix_windows_console()
asyncio.run(main())
colorama.deinit()

View File

@@ -3,4 +3,4 @@ mpyq>=0.2.5
portpicker>=1.5.2
aiohttp>=3.8.4
loguru>=0.7.0
protobuf==3.20.3
protobuf==3.20.3

View File

@@ -47,8 +47,6 @@ class LocationData:
self.local_item: int = None
def get_random_position(self, random):
x: int = None
y: int = None
if self.world_positions is None or len(self.world_positions) == 0:
if self.room_id is None:
return None

View File

@@ -1,9 +1,8 @@
from __future__ import annotations
from typing import Dict
from dataclasses import dataclass
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
from Options import Choice, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
class FreeincarnateMax(Range):

View File

@@ -1,6 +1,6 @@
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
from Options import PerGameCommonOptions
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
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,
@@ -76,10 +76,9 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
multiworld.regions.append(credits_room_far_side)
dragon_slay_check = options.dragon_slay_check.value
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
priority_locations = determine_priority_locations()
for name, location_data in location_table.items():
require_sword = False
if location_data.region == "Varies":
if location_data.name == "Slay Yorgle":
if not dragon_slay_check:
@@ -154,6 +153,7 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
# Placeholder for adding sets of priority locations at generation, possibly as an option in the future
def determine_priority_locations(world: MultiWorld, dragon_slay_check: bool) -> {}:
# def determine_priority_locations(multiworld: MultiWorld, dragon_slay_check: bool) -> {}:
def determine_priority_locations() -> {}:
priority_locations = {}
return priority_locations

View File

@@ -2,15 +2,15 @@ import hashlib
import json
import os
import zipfile
from typing import Optional, Any
import Utils
from .Locations import AdventureLocation, LocationData
from settings import get_settings
from worlds.Files import APPatch, AutoPatchRegister
from typing import Any
import bsdiff4
import Utils
from settings import get_settings
from worlds.Files import APPatch, AutoPatchRegister
from .Locations import LocationData
ADVENTUREHASH: str = "157bddb7192754a45372be196797f284"
@@ -86,9 +86,7 @@ class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):
# locations: [], autocollect: [], seed_name: bytes,
def __init__(self, *args: Any, **kwargs: Any) -> None:
patch_only = True
if "autocollect" in kwargs:
patch_only = False
self.foreign_items: [AdventureForeignItemInfo] = [AdventureForeignItemInfo(loc.short_location_id, loc.room_id, loc.room_x, loc.room_y)
for loc in kwargs["locations"]]

View File

@@ -1,35 +1,24 @@
import base64
import copy
import itertools
import math
import os
import settings
import typing
from enum import IntFlag
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple
from typing import ClassVar, Dict, Optional, Tuple
from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, Tutorial, \
LocationProgressType
import settings
from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial, LocationProgressType
from Utils import __version__
from Options import AssembleOptions
from worlds.AutoWorld import WebWorld, World
from Fill import fill_restrictive
from worlds.generic.Rules import add_rule, set_rule
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \
AdventureOptions
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
AdventureAutoCollectLocation
from worlds.LauncherComponents import Component, components, SuffixIdentifier
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
from .Locations import location_table, base_location_id, LocationData, get_random_room_in_regions
from .Offsets import static_item_data_location, items_ram_start, static_item_element_size, item_position_table, \
static_first_dragon_index, connector_port_offset, yorgle_speed_data_location, grundle_speed_data_location, \
rhindle_speed_data_location, item_ram_addresses, start_castle_values, start_castle_offset
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, AdventureOptions
from .Regions import create_regions
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, AdventureAutoCollectLocation
from .Rules import set_rules
from worlds.LauncherComponents import Component, components, SuffixIdentifier
# Adventure
components.append(Component('Adventure Client', 'AdventureClient', file_identifier=SuffixIdentifier('.apadvn')))
@@ -446,7 +435,7 @@ class AdventureWorld(World):
# end of ordered Main.py calls
def create_item(self, name: str) -> Item:
item_data: ItemData = item_table.get(name)
item_data: ItemData = item_table[name]
return AdventureItem(name, item_data.classification, item_data.id, self.player)
def create_event(self, name: str, classification: ItemClassification) -> Item:

View File

@@ -4,7 +4,7 @@ import websockets
import functools
from copy import deepcopy
from typing import List, Any, Iterable
from NetUtils import decode, encode, JSONtoTextParser, JSONMessagePart, NetworkItem
from NetUtils import decode, encode, JSONtoTextParser, JSONMessagePart, NetworkItem, NetworkPlayer
from MultiServer import Endpoint
from CommonClient import CommonContext, gui_enabled, ClientCommandProcessor, logger, get_base_parser
@@ -101,12 +101,35 @@ class AHITContext(CommonContext):
def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.connected_msg = encode([args])
json = args
# This data is not needed and causes the game to freeze for long periods of time in large asyncs.
if "slot_info" in json.keys():
json["slot_info"] = {}
if "players" in json.keys():
me: NetworkPlayer
for n in json["players"]:
if n.slot == json["slot"] and n.team == json["team"]:
me = n
break
# Only put our player info in there as we actually need it
json["players"] = [me]
if DEBUG:
print(json)
self.connected_msg = encode([json])
if self.awaiting_info:
self.server_msgs.append(self.room_info)
self.update_items()
self.awaiting_info = False
elif cmd == "RoomUpdate":
# Same story as above
json = args
if "players" in json.keys():
json["players"] = []
self.server_msgs.append(encode(json))
elif cmd == "ReceivedItems":
if args["index"] == 0:
self.full_inventory.clear()
@@ -166,6 +189,17 @@ async def proxy(websocket, path: str = "/", ctx: AHITContext = None):
await ctx.disconnect_proxy()
break
if ctx.auth:
name = msg.get("name", "")
if name != "" and name != ctx.auth:
logger.info("Aborting proxy connection: player name mismatch from save file")
logger.info(f"Expected: {ctx.auth}, got: {name}")
text = encode([{"cmd": "PrintJSON",
"data": [{"text": "Connection aborted - player name mismatch"}]}])
await ctx.send_msgs_proxy(text)
await ctx.disconnect_proxy()
break
if ctx.connected_msg and ctx.is_connected():
await ctx.send_msgs_proxy(ctx.connected_msg)
ctx.update_items()
@@ -227,6 +261,6 @@ def launch():
# options = Utils.get_options()
import colorama
colorama.init()
colorama.just_fix_windows_console()
asyncio.run(main())
colorama.deinit()

View File

@@ -152,10 +152,10 @@ def create_dw_regions(world: "HatInTimeWorld"):
for name in annoying_dws:
world.excluded_dws.append(name)
if not world.options.DWEnableBonus or world.options.DWAutoCompleteBonuses:
if not world.options.DWEnableBonus and world.options.DWAutoCompleteBonuses:
for name in death_wishes:
world.excluded_bonuses.append(name)
elif world.options.DWExcludeAnnoyingBonuses:
if world.options.DWExcludeAnnoyingBonuses and not world.options.DWAutoCompleteBonuses:
for name in annoying_bonuses:
world.excluded_bonuses.append(name)

View File

@@ -141,9 +141,12 @@ def set_dw_rules(world: "HatInTimeWorld"):
add_dw_rules(world, all_clear)
add_rule(main_stamp, main_objective.access_rule)
add_rule(all_clear, main_objective.access_rule)
# Only set bonus stamp rules if we don't auto complete bonuses
# Only set bonus stamp rules to require All Clear if we don't auto complete bonuses
if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name):
add_rule(bonus_stamps, all_clear.access_rule)
else:
# As soon as the Main Objective is completed, the bonuses auto-complete.
add_rule(bonus_stamps, main_objective.access_rule)
if world.options.DWShuffle:
for i in range(len(world.dw_shuffle)-1):
@@ -343,6 +346,7 @@ def create_enemy_events(world: "HatInTimeWorld"):
def set_enemy_rules(world: "HatInTimeWorld"):
no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.excluded_bonuses
difficulty = get_difficulty(world)
for enemy, regions in hit_list.items():
if no_tourist and enemy in bosses:
@@ -372,6 +376,14 @@ def set_enemy_rules(world: "HatInTimeWorld"):
or state.has("Zipline Unlock - The Lava Cake Path", world.player)
or state.has("Zipline Unlock - The Windmill Path", world.player))
elif enemy == "Toilet":
if area == "Toilet of Doom":
# The boss firewall is in the way and can only be skipped on Expert logic using a cherry hover.
add_rule(event, lambda state: has_paintings(state, world, 1, allow_skip=difficulty == Difficulty.EXPERT))
if difficulty < Difficulty.HARD:
# Hard logic and above can cross the boss arena gap with a cherry bridge.
add_rule(event, lambda state: can_use_hookshot(state, world))
elif enemy == "Director":
if area == "Dead Bird Studio Basement":
add_rule(event, lambda state: can_use_hookshot(state, world))
@@ -430,7 +442,7 @@ hit_list = {
# Bosses
"Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"],
"Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
"Director": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
"Toilet": ["Toilet of Doom", "Boss Rush"],
"Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush",
@@ -454,7 +466,7 @@ triple_enemy_locations = [
bosses = [
"Mafia Boss",
"Conductor",
"Director",
"Toilet",
"Snatcher",
"Toxic Flower",

View File

@@ -206,7 +206,7 @@ ahit_locations = {
"Subcon Village - Graveyard Ice Cube": LocData(2000325077, "Subcon Forest Area"),
"Subcon Village - House Top": LocData(2000325471, "Subcon Forest Area"),
"Subcon Village - Ice Cube House": LocData(2000325469, "Subcon Forest Area"),
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Area", paintings=1),
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Behind Boss Firewall"),
"Subcon Village - Stump Platform Chest": LocData(2000323729, "Subcon Forest Area"),
"Subcon Forest - Giant Tree Climb": LocData(2000325470, "Subcon Forest Area"),
@@ -233,7 +233,7 @@ ahit_locations = {
"Subcon Forest - Long Tree Climb Chest": LocData(2000323734, "Subcon Forest Area",
required_hats=[HatType.DWELLER], paintings=2),
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Area"),
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Boss Arena"),
"Subcon Forest - Manor Rooftop": LocData(2000325466, "Subcon Forest Area",
hit_type=HitType.dweller_bell, paintings=1),
@@ -264,7 +264,6 @@ ahit_locations = {
required_hats=[HatType.DWELLER], paintings=3),
"Subcon Forest - Tall Tree Hookshot Swing": LocData(2000324766, "Subcon Forest Area",
required_hats=[HatType.DWELLER],
hookshot=True,
paintings=3),
@@ -323,7 +322,7 @@ ahit_locations = {
"Alpine Skyline - The Twilight Path": LocData(2000334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]),
"Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(2000336478, "The Twilight Bell"),
"Alpine Skyline - The Twilight Bell: Ice Platform": LocData(2000335826, "The Twilight Bell"),
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area"),
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area (TIHS)", hookshot=True),
"Alpine Skyline - Windy Passage": LocData(2000334776, "Alpine Skyline Area (TIHS)", hookshot=True),
"Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(2000336395, "The Windmill"),
"Alpine Skyline - The Windmill: Entrance": LocData(2000335783, "The Windmill"),
@@ -407,12 +406,12 @@ act_completions = {
hit_type=HitType.umbrella_or_brewing, hookshot=True, paintings=1),
"Act Completion (Queen Vanessa's Manor)": LocData(2000312017, "Queen Vanessa's Manor",
hit_type=HitType.umbrella, paintings=1),
hit_type=HitType.dweller_bell, paintings=1),
"Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service",
required_hats=[HatType.SPRINT]),
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired",
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired - Post Fight",
hit_type=HitType.umbrella),
"Act Completion (Time Rift - Pipe)": LocData(2000313069, "Time Rift - Pipe", hookshot=True),
@@ -878,7 +877,7 @@ snatcher_coins = {
dlc_flags=HatDLC.death_wish),
"Snatcher Coin - Top of HQ (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of HQ",
dlc_flags=HatDLC.death_wish),
hit_type=HitType.umbrella, dlc_flags=HatDLC.death_wish),
"Snatcher Coin - Top of Tower": LocData(0, "Mafia Town Area (HUMT)", snatcher_coin="Snatcher Coin - Top of Tower",
dlc_flags=HatDLC.death_wish),
@@ -977,7 +976,6 @@ event_locs = {
**snatcher_coins,
"HUMT Access": LocData(0, "Heating Up Mafia Town"),
"TOD Access": LocData(0, "Toilet of Doom"),
"YCHE Access": LocData(0, "Your Contract has Expired"),
"AFR Access": LocData(0, "Alpine Free Roam"),
"TIHS Access": LocData(0, "The Illness has Spread"),

View File

@@ -338,7 +338,7 @@ class MinExtraYarn(Range):
There must be at least this much more yarn over the total number of yarn needed to craft all hats.
For example, if this option's value is 10, and the total yarn needed to craft all hats is 40,
there must be at least 50 yarn in the pool."""
display_name = "Max Extra Yarn"
display_name = "Min Extra Yarn"
range_start = 5
range_end = 15
default = 10

View File

@@ -347,7 +347,7 @@ def create_regions(world: "HatInTimeWorld"):
sf_act3 = create_region_and_connect(world, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest)
sf_act4 = create_region_and_connect(world, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest)
sf_act5 = create_region_and_connect(world, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest)
create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
sf_finale = create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
# ------------------------------------------- ALPINE SKYLINE ------------------------------------------ #
alpine_skyline = create_region_and_connect(world, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship)
@@ -386,11 +386,24 @@ def create_regions(world: "HatInTimeWorld"):
create_rift_connections(world, create_region(world, "Time Rift - Bazaar"))
sf_area: Region = create_region(world, "Subcon Forest Area")
sf_behind_boss_firewall: Region = create_region(world, "Subcon Forest Behind Boss Firewall")
sf_boss_arena: Region = create_region(world, "Subcon Forest Boss Arena")
sf_area.connect(sf_behind_boss_firewall, "SF Area -> SF Behind Boss Firewall")
sf_behind_boss_firewall.connect(sf_boss_arena, "SF Behind Boss Firewall -> SF Boss Arena")
sf_act1.connect(sf_area, "Subcon Forest Entrance CO")
sf_act2.connect(sf_area, "Subcon Forest Entrance SW")
sf_act3.connect(sf_area, "Subcon Forest Entrance TOD")
sf_act4.connect(sf_area, "Subcon Forest Entrance QVM")
sf_act5.connect(sf_area, "Subcon Forest Entrance MDS")
# YCHE puts the player directly in the boss arena, with no access to the rest of Subcon Forest by default.
sf_finale.connect(sf_boss_arena, "Subcon Forest Entrance YCHE")
# To support the Snatcher Hover expert logic for Act Completion (Your Contract has Expired), the act completion has
# to go in a separate region because the Snatcher Hover gives direct access to the Act Completion, but does not
# give access to the act itself.
sf_finale_post_fight: Region = create_region(world, "Your Contract has Expired - Post Fight")
# This connection must never have any rules placed on it because they will not be inherited when setting up act
# connections, only the rules for the entrances to the act and the rules for the Act Completion are inherited.
sf_finale.connect(sf_finale_post_fight, "YCHE -> YCHE - Post Fight")
create_rift_connections(world, create_region(world, "Time Rift - Sleepy Subcon"))
create_rift_connections(world, create_region(world, "Time Rift - Pipe"))
@@ -740,17 +753,20 @@ def is_valid_first_act(world: "HatInTimeWorld", act: Region) -> bool:
def connect_time_rift(world: "HatInTimeWorld", time_rift: Region, exit_region: Region):
i = 1
while i <= len(rift_access_regions[time_rift.name]):
for i, access_region in enumerate(rift_access_regions[time_rift.name], start=1):
# Matches the naming convention and iteration order in `create_rift_connections()`.
name = f"{time_rift.name} Portal - Entrance {i}"
entrance: Entrance
try:
entrance = world.multiworld.get_entrance(name, world.player)
entrance = world.get_entrance(name)
# Reconnect the rift access region to the new exit region.
reconnect_regions(entrance, entrance.parent_region, exit_region)
except KeyError:
time_rift.connect(exit_region, name)
i += 1
# The original entrance to the time rift has been deleted by already reconnecting a telescope act to the
# time rift, so create a new entrance from the original rift access region to the new exit region.
# Normally, acts and time rifts are sorted such that time rifts are reconnected to acts/rifts first, but
# starting acts/rifts and act-plando can reconnect acts to time rifts before this happens.
world.get_region(access_region).connect(exit_region, name)
def get_shuffleable_act_regions(world: "HatInTimeWorld") -> List[Region]:
@@ -944,6 +960,16 @@ def get_shuffled_region(world: "HatInTimeWorld", region: str) -> str:
return name
def get_region_shuffled_to(world: "HatInTimeWorld", region: str) -> str:
if world.options.ActRandomizer:
original_ci: str = chapter_act_info[region]
shuffled_ci = world.act_connections[original_ci]
return next(act_name for act_name, ci in chapter_act_info.items()
if ci == shuffled_ci)
else:
return region
def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int:
count = 0
region = world.multiworld.get_region(region_name, world.player)

View File

@@ -414,7 +414,7 @@ def set_moderate_rules(world: "HatInTimeWorld"):
# Moderate: Mystifying Time Mesa time trial without hats
set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
lambda state: can_use_hookshot(state, world))
lambda state: True)
# Moderate: Goat Refinery from TIHS with Sprint only
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
@@ -481,9 +481,8 @@ def set_hard_rules(world: "HatInTimeWorld"):
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
lambda state: has_paintings(state, world, 3))
# Cherry bridge over boss arena gap (painting still expected)
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
# Cherry bridge over boss arena gap
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"), lambda state: True)
set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player),
lambda state: has_paintings(state, world, 2, True))
@@ -493,9 +492,6 @@ def set_hard_rules(world: "HatInTimeWorld"):
lambda state: has_paintings(state, world, 3, True))
# SDJ
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or")
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
@@ -533,7 +529,10 @@ def set_expert_rules(world: "HatInTimeWorld"):
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True)
# There are not enough buckets/beach balls to bucket/ball hover in Heating Up Mafia Town, so any other Mafia Town
# act is required.
add_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player),
lambda state: state.can_reach_region("Mafia Town Area", world.player), "or")
# Expert: Clear Dead Bird Studio with nothing
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
@@ -566,31 +565,65 @@ def set_expert_rules(world: "HatInTimeWorld"):
lambda state: True)
# Expert: Cherry Hovering
subcon_area = world.multiworld.get_region("Subcon Forest Area", world.player)
yche = world.multiworld.get_region("Your Contract has Expired", world.player)
entrance = yche.connect(subcon_area, "Subcon Forest Entrance YCHE")
# Skipping the boss firewall is possible with a Cherry Hover.
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
lambda state: has_paintings(state, world, 1, True))
# The boss arena gap can be crossed in reverse with a Cherry Hover.
subcon_boss_arena = world.get_region("Subcon Forest Boss Arena")
subcon_behind_boss_firewall = world.get_region("Subcon Forest Behind Boss Firewall")
subcon_boss_arena.connect(subcon_behind_boss_firewall, "SF Boss Arena -> SF Behind Boss Firewall")
if world.options.NoPaintingSkips:
add_rule(entrance, lambda state: has_paintings(state, world, 1))
subcon_area = world.get_region("Subcon Forest Area")
# The boss firewall can be skipped in reverse with a Cherry Hover, but it is not possible to remove the boss
# firewall from reverse because the paintings to burn to remove the firewall are on the other side of the firewall.
# Therefore, a painting skip is required. The paintings could be burned by already having access to
# "Subcon Forest Area" through another entrance, but making a new connection to "Subcon Forest Area" in that case
# would be pointless.
if not world.options.NoPaintingSkips:
# The import cannot be done at the module-level because it would cause a circular import.
from .Regions import get_region_shuffled_to
subcon_behind_boss_firewall.connect(subcon_area, "SF Behind Boss Firewall -> SF Area")
# Because the Your Contract has Expired entrance can now reach "Subcon Forest Area", it needs to be connected to
# each of the Subcon Forest Time Rift entrances, like the other Subcon Forest Acts.
yche = world.get_region("Your Contract has Expired")
def connect_to_shuffled_act_at(original_act_name):
region_name = get_region_shuffled_to(world, original_act_name)
return yche.connect(world.get_region(region_name), f"{original_act_name} Portal - Entrance YCHE")
# Rules copied from `Rules.set_rift_rules()` with painting logic removed because painting skips must be
# available.
entrance = connect_to_shuffled_act_at("Time Rift - Pipe")
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2"))
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 2").connected_region, entrance)
entrance = connect_to_shuffled_act_at("Time Rift - Village")
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4"))
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 4").connected_region, entrance)
entrance = connect_to_shuffled_act_at("Time Rift - Sleepy Subcon")
add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO"))
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
and has_paintings(state, world, 1, True))
# Set painting rules only. Skipping paintings is determined in has_paintings
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
lambda state: has_paintings(state, world, 1, True))
set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player),
lambda state: has_paintings(state, world, 3, True))
# You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him
subcon_area.connect(yche, "Snatcher Hover")
set_rule(world.multiworld.get_location("Act Completion (Your Contract has Expired)", world.player),
lambda state: True)
yche_post_fight = world.get_region("Your Contract has Expired - Post Fight")
subcon_area.connect(yche_post_fight, "Snatcher Hover")
# Cherry Hover from YCHE also works, so there are no requirements for the Act Completion.
set_rule(world.get_location("Act Completion (Your Contract has Expired)"), lambda state: True)
if world.is_dlc2():
# Expert: clear Rush Hour with nothing
if not world.options.NoTicketSkips:
if world.options.NoTicketSkips != NoTicketSkips.option_true:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
else:
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
@@ -681,12 +714,18 @@ def set_subcon_rules(world: "HatInTimeWorld"):
lambda state: can_use_hat(state, world, HatType.BREWING) or state.has("Umbrella", world.player)
or can_use_hat(state, world, HatType.DWELLER))
# You can't skip over the boss arena wall without cherry hover, so these two need to be set this way
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
lambda state: state.has("TOD Access", world.player) and can_use_hookshot(state, world)
and has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
# You can't skip over the boss arena wall without cherry hover.
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
lambda state: has_paintings(state, world, 1, False))
# The painting wall can't be skipped without cherry hover, which is Expert
# The hookpoints to cross the boss arena gap are only present in Toilet of Doom.
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"),
lambda state: state.has("TOD Access", world.player)
and can_use_hookshot(state, world))
# The Act Completion is in the Toilet of Doom region, so the same rules as passing the boss firewall and crossing
# the boss arena gap are required. "TOD Access" is implied from the region so does not need to be included in the
# rule.
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
and has_paintings(state, world, 1, False))
@@ -739,7 +778,7 @@ def set_dlc1_rules(world: "HatInTimeWorld"):
# This particular item isn't present in Act 3 for some reason, yes in vanilla too
add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player),
lambda state: state.can_reach("Bon Voyage!", "Region", world.player)
lambda state: (state.can_reach("Bon Voyage!", "Region", world.player) and can_use_hookshot(state, world))
or state.can_reach("Ship Shape", "Region", world.player))

View File

@@ -12,13 +12,13 @@ from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses
from worlds.AutoWorld import World, WebWorld, CollectionState
from worlds.generic.Rules import add_rule
from typing import List, Dict, TextIO
from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type
from worlds.LauncherComponents import Component, components, icon_paths, launch as launch_component, Type
from Utils import local_path
def launch_client():
from .Client import launch
launch_subprocess(launch, name="AHITClient")
launch_component(launch, name="AHITClient")
components.append(Component("A Hat in Time Client", "AHITClient", func=launch_client,
@@ -253,7 +253,8 @@ class HatInTimeWorld(World):
else:
item_name = loc.item.name
shop_item_names.setdefault(str(loc.address), item_name)
shop_item_names.setdefault(str(loc.address),
f"{item_name} ({self.multiworld.get_player_name(loc.item.player)})")
slot_data["ShopItemNames"] = shop_item_names

View File

@@ -21,7 +21,7 @@
3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`.
While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601))
While it downloads, you can subscribe to the [Archipelago workshop mod](https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601).
4. Once the game finishes downloading, start it up.
@@ -62,4 +62,4 @@ 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.
if you have too many save files. Delete them and it should fix the problem.

View File

@@ -119,7 +119,9 @@ def KholdstareDefeatRule(state, player: int) -> bool:
def VitreousDefeatRule(state, player: int) -> bool:
return can_shoot_arrows(state, player) or has_melee_weapon(state, player)
return ((can_shoot_arrows(state, player) and can_use_bombs(state, player, 10))
or can_shoot_arrows(state, player, 35) or state.has("Silver Bow", player)
or has_melee_weapon(state, player))
def TrinexxDefeatRule(state, player: int) -> bool:

View File

@@ -464,7 +464,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
snes_logger.info(f"Discarding recent {len(new_locations)} checks as ROM Status has changed.")
return False
else:
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": new_locations}])
await ctx.check_locations(new_locations)
await snes_flush_writes(ctx)
return True

View File

@@ -3338,25 +3338,6 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
('Turtle Rock Exit (Front)', 'Dark Death Mountain'),
('Ice Palace Exit', 'Dark Lake Hylia')]
# Regions that can be required to access entrances through rules, not paths
indirect_connections = {
"Turtle Rock (Top)": "Turtle Rock",
"East Dark World": "Pyramid Fairy",
"Dark Desert": "Pyramid Fairy",
"West Dark World": "Pyramid Fairy",
"South Dark World": "Pyramid Fairy",
"Light World": "Pyramid Fairy",
"Old Man Cave": "Old Man S&Q"
}
indirect_connections_inverted = {
"Inverted Big Bomb Shop": "Pyramid Fairy",
}
indirect_connections_not_inverted = {
"Big Bomb Shop": "Pyramid Fairy",
}
# format:
# Key=Name
# addr = (door_index, exitdata) # multiexit

View File

@@ -484,8 +484,7 @@ def generate_itempool(world):
if multiworld.randomize_cost_types[player]:
# Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic
for item in items:
if (item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart")
or "Arrow Upgrade" in item.name):
if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"):
item.classification = ItemClassification.progression
else:
# Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
@@ -713,7 +712,7 @@ def get_pool_core(world, player: int):
pool.remove("Rupees (20)")
if retro_bow:
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (50)'}
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'}
pool = ['Rupees (5)' if item in replace else item for item in pool]
if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
pool.extend(diff.universal_keys)

View File

@@ -7,7 +7,7 @@ from worlds.AutoWorld import World
def GetBeemizerItem(world, player: int, item):
item_name = item if isinstance(item, str) else item.name
if item_name not in trap_replaceable:
if item_name not in trap_replaceable or player in world.groups:
return item
# first roll - replaceable item should be replaced, within beemizer_total_chance
@@ -110,9 +110,9 @@ item_table = {'Bow': ItemData(IC.progression, None, 0x0B, 'You have\nchosen the\
'Crystal 7': ItemData(IC.progression, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, "a blue crystal"),
'Single Arrow': ItemData(IC.filler, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': ItemData(IC.filler, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
'Arrow Upgrade (+10)': ItemData(IC.useful, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': ItemData(IC.useful, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (70)': ItemData(IC.useful, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+10)': ItemData(IC.progression_skip_balancing, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': ItemData(IC.progression_skip_balancing, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (70)': ItemData(IC.progression_skip_balancing, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': ItemData(IC.filler, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
'Bombs (3)': ItemData(IC.filler, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': ItemData(IC.filler, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),

View File

@@ -1,7 +1,7 @@
import typing
from dataclasses import dataclass
from BaseClasses import MultiWorld
from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, Option, \
from Options import Choice, Range, DeathLink, DefaultOnToggle, FreeText, ItemsAccessibility, PerGameCommonOptions, \
PlandoBosses, PlandoConnections, PlandoTexts, Removed, StartInventoryPool, Toggle
from .EntranceShuffle import default_connections, default_dungeon_connections, \
inverted_default_connections, inverted_default_dungeon_connections
@@ -742,86 +742,86 @@ class ALttPPlandoTexts(PlandoTexts):
valid_keys = TextTable.valid_keys
alttp_options: typing.Dict[str, type(Option)] = {
"accessibility": ItemsAccessibility,
"plando_connections": ALttPPlandoConnections,
"plando_texts": ALttPPlandoTexts,
"start_inventory_from_pool": StartInventoryPool,
"goal": Goal,
"mode": Mode,
"glitches_required": GlitchesRequired,
"dark_room_logic": DarkRoomLogic,
"open_pyramid": OpenPyramid,
"crystals_needed_for_gt": CrystalsTower,
"crystals_needed_for_ganon": CrystalsGanon,
"triforce_pieces_mode": TriforcePiecesMode,
"triforce_pieces_percentage": TriforcePiecesPercentage,
"triforce_pieces_required": TriforcePiecesRequired,
"triforce_pieces_available": TriforcePiecesAvailable,
"triforce_pieces_extra": TriforcePiecesExtra,
"entrance_shuffle": EntranceShuffle,
"entrance_shuffle_seed": EntranceShuffleSeed,
"big_key_shuffle": big_key_shuffle,
"small_key_shuffle": small_key_shuffle,
"key_drop_shuffle": key_drop_shuffle,
"compass_shuffle": compass_shuffle,
"map_shuffle": map_shuffle,
"restrict_dungeon_item_on_boss": RestrictBossItem,
"item_pool": ItemPool,
"item_functionality": ItemFunctionality,
"enemy_health": EnemyHealth,
"enemy_damage": EnemyDamage,
"progressive": Progressive,
"swordless": Swordless,
"dungeon_counters": DungeonCounters,
"retro_bow": RetroBow,
"retro_caves": RetroCaves,
"hints": Hints,
"scams": Scams,
"boss_shuffle": LTTPBosses,
"pot_shuffle": PotShuffle,
"enemy_shuffle": EnemyShuffle,
"killable_thieves": KillableThieves,
"bush_shuffle": BushShuffle,
"shop_item_slots": ShopItemSlots,
"randomize_shop_inventories": RandomizeShopInventories,
"shuffle_shop_inventories": ShuffleShopInventories,
"include_witch_hut": IncludeWitchHut,
"randomize_shop_prices": RandomizeShopPrices,
"randomize_cost_types": RandomizeCostTypes,
"shop_price_modifier": ShopPriceModifier,
"shuffle_capacity_upgrades": ShuffleCapacityUpgrades,
"bombless_start": BomblessStart,
"shuffle_prizes": ShufflePrizes,
"tile_shuffle": TileShuffle,
"misery_mire_medallion": MiseryMireMedallion,
"turtle_rock_medallion": TurtleRockMedallion,
"glitch_boots": GlitchBoots,
"beemizer_total_chance": BeemizerTotalChance,
"beemizer_trap_chance": BeemizerTrapChance,
"timer": Timer,
"countdown_start_time": CountdownStartTime,
"red_clock_time": RedClockTime,
"blue_clock_time": BlueClockTime,
"green_clock_time": GreenClockTime,
"death_link": DeathLink,
"allow_collect": AllowCollect,
"ow_palettes": OWPalette,
"uw_palettes": UWPalette,
"hud_palettes": HUDPalette,
"sword_palettes": SwordPalette,
"shield_palettes": ShieldPalette,
# "link_palettes": LinkPalette,
"heartbeep": HeartBeep,
"heartcolor": HeartColor,
"quickswap": QuickSwap,
"menuspeed": MenuSpeed,
"music": Music,
"reduceflashing": ReduceFlashing,
"triforcehud": TriforceHud,
@dataclass
class ALTTPOptions(PerGameCommonOptions):
accessibility: ItemsAccessibility
plando_connections: ALttPPlandoConnections
plando_texts: ALttPPlandoTexts
start_inventory_from_pool: StartInventoryPool
goal: Goal
mode: Mode
glitches_required: GlitchesRequired
dark_room_logic: DarkRoomLogic
open_pyramid: OpenPyramid
crystals_needed_for_gt: CrystalsTower
crystals_needed_for_ganon: CrystalsGanon
triforce_pieces_mode: TriforcePiecesMode
triforce_pieces_percentage: TriforcePiecesPercentage
triforce_pieces_required: TriforcePiecesRequired
triforce_pieces_available: TriforcePiecesAvailable
triforce_pieces_extra: TriforcePiecesExtra
entrance_shuffle: EntranceShuffle
entrance_shuffle_seed: EntranceShuffleSeed
big_key_shuffle: big_key_shuffle
small_key_shuffle: small_key_shuffle
key_drop_shuffle: key_drop_shuffle
compass_shuffle: compass_shuffle
map_shuffle: map_shuffle
restrict_dungeon_item_on_boss: RestrictBossItem
item_pool: ItemPool
item_functionality: ItemFunctionality
enemy_health: EnemyHealth
enemy_damage: EnemyDamage
progressive: Progressive
swordless: Swordless
dungeon_counters: DungeonCounters
retro_bow: RetroBow
retro_caves: RetroCaves
hints: Hints
scams: Scams
boss_shuffle: LTTPBosses
pot_shuffle: PotShuffle
enemy_shuffle: EnemyShuffle
killable_thieves: KillableThieves
bush_shuffle: BushShuffle
shop_item_slots: ShopItemSlots
randomize_shop_inventories: RandomizeShopInventories
shuffle_shop_inventories: ShuffleShopInventories
include_witch_hut: IncludeWitchHut
randomize_shop_prices: RandomizeShopPrices
randomize_cost_types: RandomizeCostTypes
shop_price_modifier: ShopPriceModifier
shuffle_capacity_upgrades: ShuffleCapacityUpgrades
bombless_start: BomblessStart
shuffle_prizes: ShufflePrizes
tile_shuffle: TileShuffle
misery_mire_medallion: MiseryMireMedallion
turtle_rock_medallion: TurtleRockMedallion
glitch_boots: GlitchBoots
beemizer_total_chance: BeemizerTotalChance
beemizer_trap_chance: BeemizerTrapChance
timer: Timer
countdown_start_time: CountdownStartTime
red_clock_time: RedClockTime
blue_clock_time: BlueClockTime
green_clock_time: GreenClockTime
death_link: DeathLink
allow_collect: AllowCollect
ow_palettes: OWPalette
uw_palettes: UWPalette
hud_palettes: HUDPalette
sword_palettes: SwordPalette
shield_palettes: ShieldPalette
# link_palettes: LinkPalette
heartbeep: HeartBeep
heartcolor: HeartColor
quickswap: QuickSwap
menuspeed: MenuSpeed
music: Music
reduceflashing: ReduceFlashing
triforcehud: TriforceHud
# removed:
"goals": Removed,
"smallkey_shuffle": Removed,
"bigkey_shuffle": Removed,
}
goals: Removed
smallkey_shuffle: Removed
bigkey_shuffle: Removed

View File

@@ -515,10 +515,15 @@ def _populate_sprite_table():
logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.")
with concurrent.futures.ThreadPoolExecutor() as pool:
for dir in [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]:
sprite_paths = [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]
for dir in [dir for dir in sprite_paths if os.path.isdir(dir)]:
for file in os.listdir(dir):
pool.submit(load_sprite_from_file, os.path.join(dir, file))
if "link" not in _sprite_table:
logging.info("Link sprite was not loaded. Loading link from base rom")
load_sprite_from_file(get_base_rom_path())
class Sprite():
sprite_size = 28672
@@ -554,6 +559,11 @@ class Sprite():
self.sprite = filedata[0x80000:0x87000]
self.palette = filedata[0xDD308:0xDD380]
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
h = hashlib.md5()
h.update(filedata)
if h.hexdigest() == LTTPJPN10HASH:
self.name = "Link"
self.author_name = "Nintendo"
elif filedata.startswith(b'ZSPR'):
self.from_zspr(filedata, filename)
else:
@@ -782,8 +792,8 @@ def get_nonnative_item_sprite(code: int) -> int:
def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
local_random = world.per_slot_randoms[player]
local_world = world.worlds[player]
local_random = local_world.random
# patch items
@@ -1547,9 +1557,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count
if local_world.clock_mode or not world.dungeon_counters[player]:
if local_world.clock_mode or world.dungeon_counters[player] == 'off':
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
elif world.dungeon_counters[player] is True:
elif world.dungeon_counters[player] == 'on':
rom.write_byte(0x18003C, 0x02) # always on
elif world.compass_shuffle[player] or world.dungeon_counters[player] == 'pickup':
rom.write_byte(0x18003C, 0x01) # show on pickup
@@ -1867,7 +1877,7 @@ def apply_oof_sfx(rom, oof: str):
def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, sprite: str, oof: str, palettes_options,
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
triforcehud: str = None, deathlink: bool = False, allowcollect: bool = False):
local_random = random if not world else world.per_slot_randoms[player]
local_random = random if not world else world.worlds[player].random
disable_music: bool = not music
# enable instant item menu
if menuspeed == 'instant':
@@ -2197,8 +2207,9 @@ def write_string_to_rom(rom, target, string):
def write_strings(rom, world, player):
from . import ALTTPWorld
local_random = world.per_slot_randoms[player]
w: ALTTPWorld = world.worlds[player]
local_random = w.random
tt = TextTable()
tt.removeUnwantedText()
@@ -2425,7 +2436,7 @@ def write_strings(rom, world, player):
if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or (
world.swordless[player] or world.glitches_required[player] == 'no_glitches')):
prog_bow_locs = world.find_item_locations('Progressive Bow', player, True)
world.per_slot_randoms[player].shuffle(prog_bow_locs)
local_random.shuffle(prog_bow_locs)
found_bow = False
found_bow_alt = False
while prog_bow_locs and not (found_bow and found_bow_alt):

View File

@@ -592,9 +592,9 @@ def global_rules(multiworld: MultiWorld, player: int):
lambda state: can_kill_most_things(state, player, 8) and has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
set_rule(multiworld.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1))
set_rule(multiworld.get_location('Ganons Tower - Pre-Moldorm Chest', player),
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7))
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Ganons Tower Moldorm Door', player),
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8))
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) and can_use_bombs(state, player))
set_rule(multiworld.get_entrance('Ganons Tower Moldorm Gap', player),
lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player))
@@ -1120,28 +1120,28 @@ def toss_junk_item(world, player):
raise Exception("Unable to find a junk item to toss to make room for a TR small key")
def set_trock_key_rules(world, player):
def set_trock_key_rules(multiworld, player):
# First set all relevant locked doors to impassible.
for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Entrance to Pokey Room', 'Turtle Rock (Pokey Room) (South)', 'Turtle Rock (Pokey Room) (North)', 'Turtle Rock Big Key Door']:
set_rule(world.get_entrance(entrance, player), lambda state: False)
set_rule(multiworld.get_entrance(entrance, player), lambda state: False)
all_state = world.get_all_state(use_cache=False)
all_state = multiworld.get_all_state(use_cache=False, allow_partial_entrances=True)
all_state.reachable_regions[player] = set() # wipe reachable regions so that the locked doors actually work
all_state.stale[player] = True
# Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon.
can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player))
can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player))
can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player))
can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player))
can_reach_back = all_state.can_reach(multiworld.get_region('Turtle Rock (Eye Bridge)', player))
can_reach_front = all_state.can_reach(multiworld.get_region('Turtle Rock (Entrance)', player))
can_reach_big_chest = all_state.can_reach(multiworld.get_region('Turtle Rock (Big Chest)', player))
can_reach_middle = all_state.can_reach(multiworld.get_region('Turtle Rock (Second Section)', player))
# If you can't enter from the back, the door to the front of TR requires only 2 small keys if the big key is in one of these chests since 2 key doors are locked behind the big key door.
# If you can only enter from the middle, this includes all locations that can only be reached by exiting the front. This can include Laser Bridge and Crystaroller if the front and back connect via Dark DM Ledge!
front_locked_locations = {('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)}
if can_reach_middle and not can_reach_back and not can_reach_front:
normal_regions = all_state.reachable_regions[player].copy()
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: True)
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: True)
set_rule(multiworld.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: True)
set_rule(multiworld.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: True)
all_state.update_reachable_regions(player)
front_locked_regions = all_state.reachable_regions[player].difference(normal_regions)
front_locked_locations = set((location.name, player) for region in front_locked_regions for location in region.locations)
@@ -1151,37 +1151,38 @@ def set_trock_key_rules(world, player):
# Big key door requires the big key, obviously. We removed this rule in the previous section to flag front_locked_locations correctly,
# otherwise crystaroller room might not be properly marked as reachable through the back.
set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player))
set_rule(multiworld.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10) and can_bomb_or_bonk(state, player))
# No matter what, the key requirement for going from the middle to the bottom should be five keys.
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
set_rule(multiworld.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
# Now we need to set rules based on which entrances we have access to. The most important point is whether we have back access. If we have back access, we
# might open all the locked doors in any order, so we need maximally restrictive rules.
if can_reach_back:
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state._lttp_has_key('Small Key (Turtle Rock)', player, 6) or location_item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state._lttp_has_key('Small Key (Turtle Rock)', player, 6) or location_item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
set_rule(multiworld.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
set_rule(multiworld.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(world.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
set_rule(multiworld.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(multiworld.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
set_rule(multiworld.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
else:
# Middle to front requires 3 keys if the back is locked by this door, otherwise 5
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3)
set_rule(multiworld.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3)
if item_name_in_location_names(state, 'Big Key (Turtle Rock)', player, front_locked_locations.union({('Turtle Rock - Pokey 1 Key Drop', player)}))
else state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
# Middle to front requires 4 keys if the back is locked by this door, otherwise 6
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 4)
set_rule(multiworld.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 4)
if item_name_in_location_names(state, 'Big Key (Turtle Rock)', player, front_locked_locations)
else state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
# Front to middle requires 3 keys (if the middle is accessible then these doors can be avoided, otherwise no keys can be wasted)
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3))
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2))
set_rule(world.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 1))
set_rule(multiworld.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3))
set_rule(multiworld.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2))
set_rule(multiworld.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 1))
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state)))
set_rule(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state)))
def tr_big_key_chest_keys_needed(state):
# This function handles the key requirements for the TR Big Chest in the situations it having the Big Key should logically require 2 keys, small key
@@ -1194,30 +1195,30 @@ def set_trock_key_rules(world, player):
return 6
# If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential
if not can_reach_front and not world.small_key_shuffle[player]:
if not can_reach_front and not multiworld.small_key_shuffle[player]:
# Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player)
forbid_item(multiworld.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player)
if not can_reach_big_chest:
# Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests
forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
forbid_item(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player)
if world.accessibility[player] == 'full':
if world.big_key_shuffle[player] and can_reach_big_chest:
forbid_item(multiworld.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
forbid_item(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player)
if multiworld.accessibility[player] == 'full':
if multiworld.big_key_shuffle[player] and can_reach_big_chest:
# Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first
for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest',
'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop',
'Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right']:
forbid_item(world.get_location(location, player), 'Big Key (Turtle Rock)', player)
forbid_item(multiworld.get_location(location, player), 'Big Key (Turtle Rock)', player)
else:
# A key is required in the Big Key Chest to prevent a possible softlock. Place an extra key to ensure 100% locations still works
item = item_factory('Small Key (Turtle Rock)', world.worlds[player])
location = world.get_location('Turtle Rock - Big Key Chest', player)
item = item_factory('Small Key (Turtle Rock)', multiworld.worlds[player])
location = multiworld.get_location('Turtle Rock - Big Key Chest', player)
location.place_locked_item(item)
toss_junk_item(world, player)
toss_junk_item(multiworld, player)
if world.accessibility[player] != 'full':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player)))
if multiworld.accessibility[player] != 'full':
set_always_allow(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player)))
def set_big_bomb_rules(world, player):

View File

@@ -170,7 +170,8 @@ def push_shop_inventories(multiworld):
# Retro Bow arrows will already have been pushed
if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player)
!= ("Single Arrow", location.player)):
location.shop.push_inventory(location.shop_slot, item_name, location.shop_price,
location.shop.push_inventory(location.shop_slot, item_name,
round(location.shop_price * get_price_modifier(location.item)),
1, location.item.player if location.item.player != location.player else 0,
location.shop_price_type)
location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price,

View File

@@ -15,18 +15,18 @@ def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bo
def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops)
shop in state.multiworld.shops)
def can_buy(state: CollectionState, item: str, player: int) -> bool:
return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(state) for
shop in state.multiworld.shops)
shop in state.multiworld.shops)
def can_shoot_arrows(state: CollectionState, player: int) -> bool:
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:
if state.multiworld.retro_bow[player]:
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player)
return state.has('Bow', player) or state.has('Silver Bow', player)
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count)
def has_triforce_pieces(state: CollectionState, player: int) -> bool:
@@ -61,13 +61,13 @@ def heart_count(state: CollectionState, player: int) -> int:
# Warning: This only considers items that are marked as advancement items
diff = state.multiworld.worlds[player].difficulty_requirements
return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \
+ state.count('Sanctuary Heart Container', player) \
+ state.count('Sanctuary Heart Container', player) \
+ min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
+ 3 # starting hearts
+ 3 # starting hearts
def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
basemagic = 8
if state.has('Magic Upgrade (1/4)', player):
basemagic = 32
@@ -84,11 +84,18 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
def can_hold_arrows(state: CollectionState, player: int, quantity: int):
arrows = 30 + ((state.count("Arrow Upgrade (+5)", player) * 5) + (state.count("Arrow Upgrade (+10)", player) * 10)
+ (state.count("Bomb Upgrade (50)", player) * 50))
# Arrow Upgrade (+5) beyond the 6th gives +10
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
return min(70, arrows) >= quantity
if state.multiworld.worlds[player].options.shuffle_capacity_upgrades:
if quantity == 0:
return True
if state.has("Arrow Upgrade (70)", player):
arrows = 70
else:
arrows = (30 + (state.count("Arrow Upgrade (+5)", player) * 5)
+ (state.count("Arrow Upgrade (+10)", player) * 10))
# Arrow Upgrade (+5) beyond the 6th gives +10
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
return min(70, arrows) >= quantity
return quantity <= 30 or state.has("Capacity Upgrade Shop", player)
def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool:
@@ -146,19 +153,19 @@ def can_get_good_bee(state: CollectionState, player: int) -> bool:
def can_retrieve_tablet(state: CollectionState, player: int) -> bool:
return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or
(state.multiworld.swordless[player] and
state.has("Hammer", player)))
state.has("Hammer", player)))
def has_sword(state: CollectionState, player: int) -> bool:
return state.has('Fighter Sword', player) \
or state.has('Master Sword', player) \
or state.has('Tempered Sword', player) \
or state.has('Golden Sword', player)
or state.has('Master Sword', player) \
or state.has('Tempered Sword', player) \
or state.has('Golden Sword', player)
def has_beam_sword(state: CollectionState, player: int) -> bool:
return state.has('Master Sword', player) or state.has('Tempered Sword', player) or state.has('Golden Sword',
player)
player)
def has_melee_weapon(state: CollectionState, player: int) -> bool:
@@ -171,9 +178,9 @@ def has_fire_source(state: CollectionState, player: int) -> bool:
def can_melt_things(state: CollectionState, player: int) -> bool:
return state.has('Fire Rod', player) or \
(state.has('Bombos', player) and
(state.multiworld.swordless[player] or
has_sword(state, player)))
(state.has('Bombos', player) and
(state.multiworld.swordless[player] or
has_sword(state, player)))
def has_misery_mire_medallion(state: CollectionState, player: int) -> bool:

View File

@@ -1,29 +1,27 @@
import logging
import os
import random
import settings
import threading
import typing
import Utils
import settings
from BaseClasses import Item, CollectionState, Tutorial, MultiWorld
from worlds.AutoWorld import World, WebWorld, LogicMixin
from .Client import ALTTPSNIClient
from .Dungeons import create_dungeons, Dungeon
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect, \
indirect_connections, indirect_connections_inverted, indirect_connections_not_inverted
from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
from .ItemPool import generate_itempool, difficulties
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
from .Options import alttp_options, small_key_shuffle
from .Options import ALTTPOptions, small_key_shuffle
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
is_main_entrance, key_drop_data
from .Client import ALTTPSNIClient
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
get_hash_string, get_base_rom_path, LttPDeltaPatch
from .Rules import set_rules
from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name
from .SubClasses import ALttPItem, LTTPRegionType
from worlds.AutoWorld import World, WebWorld, LogicMixin
from .StateHelpers import can_buy_unlimited
from .SubClasses import ALttPItem, LTTPRegionType
lttp_logger = logging.getLogger("A Link to the Past")
@@ -123,6 +121,7 @@ class ALTTPWeb(WebWorld):
)
tutorials = [setup_en, setup_de, setup_es, setup_fr, msu, msu_es, msu_fr, plando, oof_sound]
game_info_languages = ["en", "fr"]
class ALTTPWorld(World):
@@ -133,10 +132,12 @@ class ALTTPWorld(World):
Ganon!
"""
game = "A Link to the Past"
option_definitions = alttp_options
options_dataclass = ALTTPOptions
options: ALTTPOptions
settings_key = "lttp_options"
settings: typing.ClassVar[ALTTPSettings]
topology_present = True
explicit_indirect_conditions = False
item_name_groups = item_name_groups
location_name_groups = {
"Blind's Hideout": {"Blind's Hideout - Top", "Blind's Hideout - Left", "Blind's Hideout - Right",
@@ -286,13 +287,22 @@ class ALTTPWorld(World):
if not os.path.exists(rom_file):
raise FileNotFoundError(rom_file)
if multiworld.is_race:
import xxtea
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):
# write old options
import dataclasses
is_first = self.player == min(self.multiworld.get_game_players(self.game))
for field in dataclasses.fields(self.options_dataclass):
if is_first:
setattr(self.multiworld, field.name, {})
getattr(self.multiworld, field.name)[self.player] = getattr(self.options, field.name)
# end of old options re-establisher
player = self.player
multiworld = self.multiworld
@@ -394,23 +404,13 @@ class ALTTPWorld(World):
if multiworld.mode[player] != 'inverted':
link_entrances(multiworld, player)
mark_light_world_regions(multiworld, player)
for region_name, entrance_name in indirect_connections_not_inverted.items():
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
multiworld.get_entrance(entrance_name, player))
else:
link_inverted_entrances(multiworld, player)
mark_dark_world_regions(multiworld, player)
for region_name, entrance_name in indirect_connections_inverted.items():
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
multiworld.get_entrance(entrance_name, player))
multiworld.random = old_random
plando_connect(multiworld, player)
for region_name, entrance_name in indirect_connections.items():
multiworld.register_indirect_condition(multiworld.get_region(region_name, player),
multiworld.get_entrance(entrance_name, player))
def collect_item(self, state: CollectionState, item: Item, remove=False):
item_name = item.name
if item_name.startswith('Progressive '):
@@ -546,12 +546,10 @@ class ALTTPWorld(World):
@property
def use_enemizer(self) -> bool:
world = self.multiworld
player = self.player
return bool(world.boss_shuffle[player] or world.enemy_shuffle[player]
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
or world.pot_shuffle[player] or world.bush_shuffle[player]
or world.killable_thieves[player])
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

View File

@@ -0,0 +1,32 @@
# A Link to the Past
## Où se trouve la page des paramètres ?
La [page des paramètres du joueur pour ce jeu](../player-options) contient tous les paramètres dont vous avez besoin
pour configurer et exporter le fichier.
## Quel est l'effet de la randomisation sur ce jeu ?
Les objets que le joueur devrait normalement obtenir au cours du jeu ont été déplacés. Il y a tout de même une logique
pour que le jeu puisse être terminé, mais dû au mélange des objets, le joueur peut avoir besoin d'accéder à certaines
zones plus tôt que dans le jeu original.
## Quels sont les objets et endroits mélangés ?
Tous les objets principaux, les collectibles et munitions peuvent être mélangés, et tous les endroits qui
pourraient contenir un de ces objets peuvent avoir leur contenu modifié.
## Quels objets peuvent être dans le monde d'un autre joueur ?
Un objet pouvant être mélangé peut être aussi placé dans le monde d'un autre joueur. Il est possible de limiter certains
objets à votre propre monde.
## À quoi ressemble un objet d'un autre monde dans LttP ?
Les objets appartenant à d'autres mondes sont représentés par une Étoile de Super Mario World.
## Quand le joueur reçoit un objet, que ce passe-t-il ?
Quand le joueur reçoit un objet, Link montrera l'objet au monde en le mettant au-dessus de sa tête. C'est bon pour
les affaires !

View File

@@ -1,224 +1,123 @@
# Guía de instalación para A Link to the Past Randomizer Multiworld
<div id="tutorial-video-container">
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mJKEHaiyR_Y" frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
</iframe>
</div>
## Software requerido
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
- [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Incluido en Multiworld Utilities)
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES
- Un emulador capaz de ejecutar scripts Lua
([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- [SNI](https://github.com/alttpo/sni/releases). Esto está incluido automáticamente en la instalación de Archipelago.
- SNI no es compatible con (Q)Usb2Snes.
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES, por ejemplo:
- Un emulador capaz de conectarse a SNI
([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases),
[BSNES-plus](https://github.com/black-sliver/bsnes-plus),
[BizHawk](https://tasvideos.org/BizHawk), o
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo). O,
- Un flashcart SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), o otro hardware compatible
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo).
- Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), u otro hardware compatible. **nota:
Las SNES minis modificadas no tienen soporte de SNI. Algunos usuarios dicen haber tenido éxito con Qusb2Snes para esta consola,
pero no tiene soporte.**
- Tu archivo ROM japones v1.0, probablemente se llame `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Procedimiento de instalación
### Instalación en Windows
1. Descarga e instala MultiWorld Utilities desde el enlace anterior, asegurando que instalamos la versión más reciente.
**El archivo esta localizado en la sección "assets" en la parte inferior de la información de versión**. Si tu
intención es jugar la versión normal de multiworld, necesitarás el archivo `Setup.Archipelago.exe`
- Si estas interesado en jugar la variante que aleatoriza las puertas internas de las mazmorras, necesitaras bajar '
Setup.BerserkerMultiWorld.Doors.exe'
- Durante el proceso de instalación, se te pedirá donde esta situado tu archivo ROM japonés v1.0. Si ya habías
instalado este software con anterioridad y simplemente estas actualizando, no se te pedirá la localización del
archivo una segunda vez.
- Puede ser que el programa pida la instalación de Microsoft Visual C++. Si ya lo tienes en tu ordenador (
posiblemente por que un juego de Steam ya lo haya instalado), el instalador no te pedirá su instalación.
2. Si estas usando un emulador, deberías asignar la versión capaz de ejecutar scripts Lua como programa por defecto para
lanzar ficheros de ROM de SNES.
1. Extrae tu emulador al escritorio, o cualquier sitio que después recuerdes.
2. Haz click derecho en un fichero de ROM (ha de tener la extensión sfc) y selecciona **Abrir con...**
3. Marca la opción **Usar siempre esta aplicación para abrir los archivos .sfc**
4. Baja hasta el final de la lista y haz click en la opción **Buscar otra aplicación en el equipo** (Si usas Windows
10 es posible que debas hacer click en **Más aplicaciones**)
5. Busca el archivo .exe de tu emulador y haz click en **Abrir**. Este archivo debe estar en el directorio donde
extrajiste en el paso 1.
### Instalación en Macintosh
- ¡Necesitamos voluntarios para rellenar esta seccion! Contactad con **Farrak Kilhn** (en inglés) en Discord si queréis
ayudar.
## Configurar tu archivo YAML
### Que es un archivo YAML y por qué necesito uno?
Tu archivo YAML contiene un conjunto de opciones de configuración que proveen al generador con información sobre como
debe generar tu juego. Cada jugador en una partida de multiworld proveerá su propio fichero YAML. Esta configuración
permite que cada jugador disfrute de una experiencia personalizada a su gusto, y cada jugador dentro de la misma partida
de multiworld puede tener diferentes opciones.
### Donde puedo obtener un fichero YAML?
La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-options)" en el sitio web te permite configurar tu
configuración personal y descargar un fichero "YAML".
### Configuración YAML avanzada
Una version mas avanzada del fichero Yaml puede ser creada usando la pagina
["Weighted settings"](/games/A Link to the Past/weighted-options),
la cual te permite tener almacenadas hasta 3 preajustes. La pagina "Weighted Settings" tiene muchas opciones
representadas con controles deslizantes. Esto permite elegir cuan probable los valores de una categoría pueden ser
elegidos sobre otros de la misma.
Por ejemplo, imagina que el generador crea un cubo llamado "map_shuffle", y pone trozos de papel doblado en él por cada
sub-opción. Ademas imaginemos que tu valor elegido para "on" es 20 y el elegido para "off" es 40.
Por tanto, en este ejemplo, habrán 60 trozos de papel. 20 para "on" y 40 para "off". Cuando el generador esta decidiendo
si activar o no "map shuffle" para tu partida, meterá la mano en el cubo y sacara un trozo de papel al azar. En este
ejemplo, es mucho mas probable (2 de cada 3 veces (40/60)) que "map shuffle" esté desactivado.
Si quieres que una opción no pueda ser escogida, simplemente asigna el valor 0 a dicha opción. Recuerda que cada opción
debe tener al menos un valor mayor que cero, si no la generación fallará.
### Verificando tu archivo YAML
Si quieres validar que tu fichero YAML para asegurarte que funciona correctamente, puedes hacerlo en la pagina
[YAML Validator](/check).
## Generar una partida para un jugador
1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-options), configura tus opciones, haz
click en el boton "Generate game".
2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche.
3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no
es necesario para partidas de un jugador, puedes cerrarlo junto a la pagina web (que tiene como titulo "Multiworld
WebUI") que se ha abierto automáticamente.
## Unirse a una partida MultiWorld
1. Descarga e instala [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
**El archivo del instalador se encuentra en la sección de assets al final de la información de version**.
2. La primera vez que realices una generación local o parchees tu juego, se te pedirá que ubiques tu archivo ROM base.
Este es tu archivo ROM de Link to the Past japonés. Esto sólo debe hacerse una vez.
4. Si estás usando un emulador, deberías de asignar tu emulador con compatibilidad con Lua como el programa por defecto para abrir archivos
ROM.
1. Extrae la carpeta de tu emulador al Escritorio, o algún otro sitio que vayas a recordar.
2. Haz click derecho en un archivo ROM y selecciona **Abrir con...**
3. Marca la casilla junto a **Usar siempre este programa para abrir archivos .sfc**
4. Baja al final de la lista y haz click en el texto gris **Buscar otro programa en este PC**
5. Busca el archivo `.exe` de tu emulador y haz click en **Abrir**. Este archivo debería de encontrarse dentro de la carpeta que
extrajiste en el paso uno.
### Obtener el fichero de parche y crea tu ROM
Cuando te unes a una partida multiworld, debes proveer tu fichero YAML a quien sea el creador de la partida. Una vez
Cuando te unas a una partida multiworld, se te pedirá enviarle tu archivo de configuración a quien quiera que esté creando. Una vez eso
este hecho, el creador te devolverá un enlace para descargar el parche o un fichero zip conteniendo todos los ficheros
de parche de la partida Tu fichero de parche debe tener la extensión `.aplttp`.
de parche de la partida. Tu fichero de parche debe de tener la extensión `.aplttp`.
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y haz doble click. Esto debería ejecutar
automáticamente el cliente, y ademas creara la rom en el mismo directorio donde este el fichero de parche.
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y hazle doble click. Esto debería ejecutar
automáticamente el cliente, y además creará la rom en el mismo directorio donde este el fichero de parche.
### Conectar al cliente
#### Con emulador
Cuando el cliente se lance automáticamente, QUsb2Snes debería haberse ejecutado también. Si es la primera vez que lo
ejecutas, puedes ser que el firewall de Windows te pregunte si le permites la comunicación.
Cuando el cliente se lance automáticamente, SNI debería de ejecutarse en segundo plano. Si es la
primera vez que se ejecuta, tal vez se te pida permitir que se comunique a través del firewall de Windows
#### snes9x-nwa
1. Haz click en el menu Network y marca 'Enable Emu Network Control
2. Carga tu archivo ROM si no lo habías hecho antes
##### snes9x-rr
1. Carga tu fichero de ROM, si no lo has hecho ya
1. Carga tu fichero ROM, si no lo has hecho ya
2. Abre el menu "File" y situa el raton en **Lua Scripting**
3. Haz click en **New Lua Script Window...**
4. En la nueva ventana, haz click en **Browse...**
5. Navega hacia el directorio donde este situado snes9x-rr, entra en el directorio `lua`, y
escoge `multibridge.lua`
6. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
nombre en la esquina superior izquierda.
5. Selecciona el archivo lua conector incluido con tu cliente
- Busca en la carpeta de Archipelago `/SNI/lua/`.
6. Si ves un error mientras carga el script que dice `socket.dll missing` o algo similar, ve a la carpeta de
el lua que estas usando en tu gestor de archivos y copia el `socket.dll` a la raíz de tu instalación de snes9x.
##### BNES-Plus
1. Cargue su archivo ROM si aún no se ha cargado.
2. El emulador debería conectarse automáticamente mientras SNI se está ejecutando.
##### BizHawk
1. Asegurate que se ha cargado el nucleo BSNES. Debes hacer esto en el menu Tools y siguiento estas opciones:
`Config --> Cores --> SNES --> BSNES`
Una vez cambiado el nucleo cargado, BizHawk ha de ser reiniciado.
1. Asegurate que se ha cargado el núcleo BSNES. Se hace en la barra de menú principal, bajo:
- (≤ 2.8) `Config``Cores``SNES``BSNES`
- (≥ 2.9) `Config``Preferred Cores``SNES``BSNESv115+`
2. Carga tu fichero de ROM, si no lo has hecho ya.
3. Haz click en el menu Tools y en la opción **Lua Console**
4. Haz click en el botón para abrir un nuevo script Lua.
5. Navega al directorio de instalación de MultiWorld Utilities, y en los siguiente directorios:
`QUsb2Snes/Qusb2Snes/LuaBridge`
6. Selecciona `luabridge.lua` y haz click en Abrir.
7. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
nombre en la esquina superior izquierda.
Si has cambiado tu preferencia de núcleo tras haber cargado la ROM, no te olvides de volverlo a cargar (atajo por defecto: Ctrl+R).
3. Arrastra el archivo `Connector.lua` que has descargado a la ventana principal de EmuHawk.
- Busca en la carpeta de Archipelago `/SNI/lua/`.
- También podrías abrir la consola de Lua manualmente, hacer click en `Script``Open Script`, e ir a `Connector.lua`
con el selector de archivos.
##### RetroArch 1.10.1 o más nuevo
Sólo hay que segiur estos pasos una vez.
Sólo hay que seguir estos pasos una vez.
1. Comienza en la pantalla del menú principal de RetroArch.
2. Ve a Ajustes --> Interfaz de usario. Configura "Mostrar ajustes avanzados" en ON.
3. Ve a Ajustes --> Red. Configura "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 (el
default) el Puerto de comandos de red.
3. Ve a Ajustes --> Red. Pon "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 el valor por defecto,
el Puerto de comandos de red.
![Captura de pantalla del ajuste Comandos de red](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png)
4. Ve a Menú principal --> Actualizador en línea --> Descargador de núcleos. Desplázate y selecciona "Nintendo - SNES /
SFC (bsnes-mercury Performance)".
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los sólos núcleos que permiten
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los únicos núcleos que permiten
que herramientas externas lean datos del ROM.
#### Con Hardware
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, hazlo ahora. Los
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, por favor hazlo ahora. Los
usuarios de SD2SNES y FXPak Pro pueden descargar el firmware apropiado
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Los usuarios de otros dispositivos pueden encontrar información
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Puede que los usuarios de otros dispositivos encuentren informacion útil
[en esta página](http://usb2snes.com/#supported-platforms).
1. Cierra tu emulador, el cual debe haberse autoejecutado.
2. Cierra QUsb2Snes, el cual fue ejecutado junto al cliente.
3. Ejecuta la version correcta de QUsb2Snes (v0.7.16).
4. Enciende tu dispositivo y carga la ROM.
5. Observa en el cliente que ahora muestra "SNES Device: Connected", y aparece el nombre del dispositivo.
2. Enciende tu dispositivo y carga la ROM.
### Conecta al MultiServer
### Conecta al Servidor Archipelago
El fichero de parche que ha lanzado el cliente debe haberte conectado automaticamente al MultiServer. Hay algunas
razonas por las que esto puede que no pase, incluyendo que el juego este hospedado en el sitio web pero se genero en
algún otro sitio. Si el cliente muestra "Server Status: Not Connected", preguntale al creador de la partida la dirección
del servidor, copiala en el campo "Server" y presiona Enter.
El fichero de parche que ha lanzado el cliente debería de haberte conectado automaticamente al MultiServer. Sin embargo hay algunas
razones por las que puede que esto no suceda, como que la partida este hospedada en la página web pero generada en otra parte. Si la
ventana del cliente muestra "Server Status: Not Connected", simplemente preguntale al creador de la partida la dirección
del servidor, cópiala en el campo "Server" y presiona Enter.
El cliente intentara conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" en algún momento.
Si el cliente no se conecta al cabo de un rato, puede ser que necesites refrescar la pagina web.
El cliente intentará conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" momentáneamente.
### Jugando
### Jugar al juego
Cuando ambos SNES Device and Server aparezcan como "connected", estas listo para empezar a jugar. Felicidades por unirte
satisfactoriamente a una partida de multiworld!
## Hospedando una partida de multiworld
La manera recomendad para hospedar una partida es usar el servicio proveído en
[el sitio web](/generate). El proceso es relativamente sencillo:
1. Recolecta los ficheros YAML de todos los jugadores que participen.
2. Crea un fichero ZIP conteniendo esos ficheros.
3. Carga el fichero zip en el sitio web enlazado anteriormente.
4. Espera a que la seed sea generada.
5. Cuando esto acabe, se te redigirá a una pagina titulada "Seed Info".
6. Haz click en "Create New Room". Esto te llevara a la pagina del servidor. Pasa el enlace a esta pagina a los
jugadores para que puedan descargar los ficheros de parche de ahi.
**Nota:** Los ficheros de parche de esta pagina permiten a los jugadores conectarse al servidor automaticamente,
mientras que los de la pagina "Seed info" no.
7. Hay un enlace a un MultiWorld Tracker en la parte superior de la pagina de la sala. Deberías pasar también este
enlace a los jugadores para que puedan ver el progreso de la partida. A los observadores también se les puede pasar
este enlace.
8. Una vez todos los jugadores se han unido, podeis empezar a jugar.
## Auto-Tracking
Si deseas usar auto-tracking para tu partida, varios programas ofrecen esta funcionalidad.
El programa recomentdado actualmente es:
[OpenTracker](https://github.com/trippsc2/OpenTracker/releases).
### Instalación
1. Descarga el fichero de instalacion apropiado para tu ordenador (Usuarios de windows quieren el fichero ".msi").
2. Durante el proceso de insatalación, puede que se te pida instalar Microsoft Visual Studio Build Tools. Un enlace este
programa se muestra durante la proceso, y debe ser ejecutado manualmente.
### Activar auto-tracking
1. Con OpenTracker ejecutado, haz click en el menu Tracking en la parte superior de la ventana, y elige **
AutoTracker...**
2. Click the **Get Devices** button
3. Selecciona tu "SNES device" de la lista
4. Si quieres que las llaves y los objetos de mazmorra tambien sean marcados, activa la caja con nombre **Race Illegal
Tracking**
5. Haz click en el boton **Start Autotracking**
6. Cierra la ventana AutoTracker, ya que deja de ser necesaria
Cuando el cliente muestre tanto el dispositivo SNES como el servidor como conectados, estas listo para empezar a jugar. Felicidades por
haberte unido a una partida multiworld con exito! Puedes ejecutar varios comandos en tu cliente. Para mas informacion
acerca de estos comando puedes usar `/help` para comandos locales del cliente y `!help` para comandos de servidor.

View File

@@ -1,41 +1,28 @@
# Guide d'installation du MultiWorld de A Link to the Past Randomizer
<div id="tutorial-video-container">
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mJKEHaiyR_Y" frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
</iframe>
</div>
## Logiciels requis
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
- [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Inclus dans les utilitaires précédents)
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- [SNI](https://github.com/alttpo/sni/releases). Inclus avec l'installation d'Archipelago ci-dessus.
- SNI n'est pas compatible avec (Q)Usb2Snes.
- Une solution logicielle ou matérielle capable de charger et de lancer des fichiers ROM de SNES
- Un émulateur capable d'éxécuter des scripts Lua
([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
[BizHawk](https://tasvideos.org/BizHawk))
- Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle
compatible
- Le fichier ROM de la v1.0 japonaise, sûrement nommé `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
- Un émulateur capable de se connecter à SNI
[snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), ([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
[BSNES-plus](https://github.com/black-sliver/bsnes-plus),
[BizHawk](https://tasvideos.org/BizHawk), ou
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 ou plus récent). Ou,
- Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matérielle compatible. **À noter:
les SNES minis ne sont pas encore supportés par SNI. Certains utilisateurs rapportent avoir du succès avec QUsb2Snes pour ce système,
mais ce n'est pas supporté.**
- Le fichier ROM de la v1.0 japonaise, habituellement nommé `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
## Procédure d'installation
### Installation sur Windows
1. Téléchargez et installez [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). **L'installateur se situe dans la section "assets" en bas des informations de version**.
2. Si c'est la première fois que vous faites une génération locale ou un patch, il vous sera demandé votre fichier ROM de base. Il s'agit de votre fichier ROM Link to the Past japonais. Cet étape n'a besoin d'être faite qu'une seule fois.
1. Téléchargez et installez les utilitaires du MultiWorld à l'aide du lien au-dessus, faites attention à bien installer
la version la plus récente.
**Le fichier se situe dans la section "assets" en bas des informations de version**. Si vous voulez jouer des parties
classiques de multiworld, téléchargez `Setup.BerserkerMultiWorld.exe`
- Si vous voulez jouer à la version alternative avec le mélangeur de portes dans les donjons, vous téléchargez le
fichier
`Setup.BerserkerMultiWorld.Doors.exe`.
- Durant le processus d'installation, il vous sera demandé de localiser votre ROM v1.0 japonaise. Si vous avez déjà
installé le logiciel auparavant et qu'il s'agit simplement d'une mise à jour, la localisation de la ROM originale
ne sera pas requise.
- Il vous sera peut-être également demandé d'installer Microsoft Visual C++. Si vous le possédez déjà (possiblement
parce qu'un jeu Steam l'a déjà installé), l'installateur ne reproposera pas de l'installer.
2. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme
3. Si vous utilisez un émulateur, il est recommandé d'assigner votre émulateur capable d'éxécuter des scripts Lua comme
programme par défaut pour ouvrir vos ROMs.
1. Extrayez votre dossier d'émulateur sur votre Bureau, ou à un endroit dont vous vous souviendrez.
2. Faites un clic droit sur un fichier ROM et sélectionnez **Ouvrir avec...**
@@ -44,58 +31,6 @@
5. Naviguez dans les dossiers jusqu'au fichier `.exe` de votre émulateur et choisissez **Ouvrir**. Ce fichier
devrait se trouver dans le dossier que vous avez extrait à la première étape.
### Installation sur Mac
- Des volontaires sont recherchés pour remplir cette section ! Contactez **Farrak Kilhn** sur Discord si vous voulez
aider.
## Configurer son fichier YAML
### Qu'est-ce qu'un fichier YAML et pourquoi en ai-je besoin ?
Votre fichier YAML contient un ensemble d'options de configuration qui fournissent au générateur des informations sur
comment il devrait générer votre seed. Chaque joueur d'un multiwolrd devra fournir son propre fichier YAML. Cela permet
à chaque joueur d'apprécier une expérience customisée selon ses goûts, et les différents joueurs d'un même multiworld
peuvent avoir différentes options.
### Où est-ce que j'obtiens un fichier YAML ?
La page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options) vous permet de configurer vos
paramètres personnels et de les exporter vers un fichier YAML.
### Configuration avancée du fichier YAML
Une version plus avancée du fichier YAML peut être créée en utilisant la page
des [paramètres de pondération](/games/A Link to the Past/weighted-options), qui vous permet de configurer jusqu'à
trois préréglages. Cette page a de nombreuses options qui sont essentiellement représentées avec des curseurs
glissants. Cela vous permet de choisir quelles sont les chances qu'une certaine option apparaisse par rapport aux
autres disponibles dans une même catégorie.
Par exemple, imaginez que le générateur crée un seau étiqueté "Mélange des cartes", et qu'il place un morceau de papier
pour chaque sous-option. Imaginez également que la valeur pour "On" est 20 et la valeur pour "Off" est 40.
Dans cet exemple, il y a soixante morceaux de papier dans le seau : vingt pour "On" et quarante pour "Off". Quand le
générateur décide s'il doit oui ou non activer le mélange des cartes pour votre partie, , il tire aléatoirement un
papier dans le seau. Dans cet exemple, il y a de plus grandes chances d'avoir le mélange de cartes désactivé.
S'il y a une option dont vous ne voulez jamais, mettez simplement sa valeur à zéro. N'oubliez pas qu'il faut que pour
chaque paramètre il faut au moins une option qui soit paramétrée sur un nombre strictement positif.
### Vérifier son fichier YAML
Si vous voulez valider votre fichier YAML pour être sûr qu'il fonctionne, vous pouvez le vérifier sur la page du
[Validateur de YAML](/check).
## Générer une partie pour un joueur
1. Aller sur la page [Génération de partie](/games/A%20Link%20to%20the%20Past/player-options), configurez vos options,
et cliquez sur le bouton "Generate Game".
2. Il vous sera alors présenté une page d'informations sur la seed, où vous pourrez télécharger votre patch.
3. Double-cliquez sur le patch et l'émulateur devrait se lancer automatiquement avec la seed. Etant donné que le client
n'est pas requis pour les parties à un joueur, vous pouvez le fermer ainsi que l'interface Web (WebUI).
## Rejoindre un MultiWorld
### Obtenir son patch et créer sa ROM
Quand vous rejoignez un multiworld, il vous sera demandé de fournir votre fichier YAML à celui qui héberge la partie ou
@@ -109,35 +44,58 @@ automatiquement le client, et devrait créer la ROM dans le même dossier que vo
#### Avec un émulateur
Quand le client se lance automatiquement, QUsb2Snes devrait se lancer automatiquement également en arrière-plan. Si
Quand le client se lance automatiquement, SNI devrait se lancer automatiquement également en arrière-plan. Si
c'est la première fois qu'il démarre, il vous sera peut-être demandé de l'autoriser à communiquer à travers le pare-feu
Windows.
#### snes9x-nwa
1. Cliquez sur 'Network Menu' et cochez **Enable Emu Network Control**
2. Chargez votre ROM si ce n'est pas déjà fait.
##### snes9x-rr
1. Chargez votre ROM si ce n'est pas déjà fait.
2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting**
3. Cliquez alors sur **New Lua Script Window...**
4. Dans la nouvelle fenêtre, sélectionnez **Browse...**
5. Dirigez vous vers le dossier où vous avez extrait snes9x-rr, allez dans le dossier `lua`, puis
choisissez `multibridge.lua`
6. Remarquez qu'un nom vous a été assigné, et que l'interface Web affiche "SNES Device: Connected", avec ce même nom
dans le coin en haut à gauche.
5. Sélectionnez le fichier lua connecteur inclus avec votre client
- Recherchez `/SNI/lua/` dans votre fichier Archipelago.
6. Si vous avez une erreur en chargeant le script indiquant `socket.dll missing` ou similaire, naviguez vers le fichier du
lua que vous utilisez dans votre explorateur de fichiers et copiez le `socket.dll` à la base de votre installation snes9x.
#### BSNES-Plus
1. Chargez votre ROM si ce n'est pas déjà fait.
2. L'émulateur devrait automatiquement se connecter lorsque SNI se lancera.
##### BizHawk
1. Assurez vous d'avoir le coeur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant
1. Assurez vous d'avoir le cœur BSNES chargé. Cela est possible en cliquant sur le menu "Tools" de BizHawk et suivant
ces options de menu :
`Config --> Cores --> SNES --> BSNES`
Une fois le coeur changé, vous devez redémarrer BizHawk.
- (≤ 2.8) `Config``Cores``SNES``BSNES`
- (≥ 2.9) `Config``Preferred Cores``SNES``BSNESv115+`
Une fois le cœur changé, rechargez le avec Ctrl+R (par défaut).
2. Chargez votre ROM si ce n'est pas déjà fait.
3. Cliquez sur le menu "Tools" et cliquez sur **Lua Console**
4. Cliquez sur le bouton pour ouvrir un nouveau script Lua.
5. Dirigez vous vers le dossier d'installation des utilitaires du MultiWorld, puis dans les dossiers suivants :
`QUsb2Snes/Qusb2Snes/LuaBridge`
6. Sélectionnez `luabridge.lua` et cliquez sur "Open".
7. Remarquez qu'un nom vous a été assigné, et que l'interface Web affiche "SNES Device: Connected", avec ce même nom
dans le coin en haut à gauche.
3. Glissez et déposez le fichier `Connector.lua` que vous avez téléchargé ci-dessus sur la fenêtre principale EmuHawk.
- Recherchez `/SNI/lua/` dans votre fichier Archipelago.
- Vous pouvez aussi ouvrir la console Lua manuellement, cliquez sur `Script``Open Script`, et naviguez sur `Connecteur.lua`
avec le sélecteur de fichiers.
##### RetroArch 1.10.1 ou plus récent
Vous n'avez qu'à faire ces étapes qu'une fois.
1. Entrez dans le menu principal RetroArch
2. Allez dans Réglages --> Interface utilisateur. Mettez "Afficher les réglages avancés" sur ON.
3. Allez dans Réglages --> Réseau. Mettez "Commandes Réseau" sur ON. (trouvé sous Request Device 16.) Laissez le
Port des commandes réseau à 555355.
![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-fr.png)
4. Allez dans Menu Principal --> Mise à jour en ligne --> Téléchargement de cœurs. Descendez jusqu'a"Nintendo - SNES / SFC (bsnes-mercury Performance)" et
sélectionnez le.
Quand vous chargez une ROM, veillez a sélectionner un cœur **bsnes-mercury**. Ce sont les seuls cœurs qui autorisent les outils externs à lire les données d'une ROM.
#### Avec une solution matérielle
@@ -147,10 +105,7 @@ le maintenant. Les utilisateurs de SD2SNES et de FXPak Pro peuvent télécharger
[sur cette page](http://usb2snes.com/#supported-platforms).
1. Fermez votre émulateur, qui s'est potentiellement lancé automatiquement.
2. Fermez QUsb2Snes, qui s'est lancé automatiquement avec le client.
3. Lancez la version appropriée de QUsb2Snes (v0.7.16).
4. Lancer votre console et chargez la ROM.
5. Remarquez que l'interface Web affiche "SNES Device: Connected", avec le nom de votre appareil.
2. Lancez votre console et chargez la ROM.
### Se connecter au MultiServer
@@ -165,47 +120,6 @@ l'interface Web.
### Jouer au jeu
Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations
pour avoir rejoint un multiworld !
## Héberger un MultiWorld
La méthode recommandée pour héberger une partie est d'utiliser le service d'hébergement fourni par
[le site](https://berserkermulti.world/generate). Le processus est relativement simple :
1. Récupérez les fichiers YAML des joueurs.
2. Créez une archive zip contenant ces fichiers YAML.
3. Téléversez l'archive zip sur le lien ci-dessus.
4. Attendez un moment que les seed soient générées.
5. Lorsque les seeds sont générées, vous serez redirigé vers une page d'informations "Seed Info".
6. Cliquez sur "Create New Room". Cela vous amènera à la page du serveur. Fournissez le lien de cette page aux autres
joueurs afin qu'ils puissent récupérer leurs patchs.
**Note:** Les patchs fournis sur cette page permettront aux joueurs de se connecteur automatiquement au serveur,
tandis que ceux de la page "Seed Info" non.
7. Remarquez qu'un lien vers le traqueur du MultiWorld est en haut de la page de la salle. Vous devriez également
fournir ce lien aux joueurs pour qu'ils puissent suivre la progression de la partie. N'importe quel personne voulant
observer devrait avoir accès à ce lien.
8. Une fois que tous les joueurs ont rejoint, vous pouvez commencer à jouer.
## Auto-tracking
Si vous voulez utiliser l'auto-tracking, plusieurs logiciels offrent cette possibilité.
Le logiciel recommandé pour l'auto-tracking actuellement est
[OpenTracker](https://github.com/trippsc2/OpenTracker/releases).
### Installation
1. Téléchargez le fichier d'installation approprié pour votre ordinateur (Les utilisateurs Windows voudront le
fichier `.msi`).
2. Durant le processus d'installation, il vous sera peut-être demandé d'installer les outils "Microsoft Visual Studio
Build Tools". Un lien est fourni durant l'installation d'OpenTracker, et celle des outils doit se faire manuellement.
### Activer l'auto-tracking
1. Une fois OpenTracker démarré, cliquez sur le menu "Tracking" en haut de la fenêtre, puis choisissez **
AutoTracker...**
2. Appuyez sur le bouton **Get Devices**
3. Sélectionnez votre appareil SNES dans la liste déroulante.
4. Si vous voulez tracquer les petites clés ainsi que les objets des donjons, cochez la case **Race Illegal Tracking**
5. Cliquez sur le bouton **Start Autotracking**
6. Fermez la fenêtre "AutoTracker" maintenant, elle n'est plus nécessaire
Une fois que l'interface Web affiche que la SNES et le serveur sont connectés, vous êtes prêt à jouer. Félicitations,
vous venez de rejoindre un multiworld ! Vous pouvez exécuter différentes commandes dans votre client. Pour plus d'informations
sur ces commandes, vous pouvez utiliser `/help` pour les commandes locales et `!help` pour les commandes serveur.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,2 +1,2 @@
maseya-z3pr>=1.0.0rc1
xxtea>=3.0.0
xxtea>=3.0.0

View File

@@ -130,19 +130,21 @@ class TestGanonsTower(TestDungeon):
["Ganons Tower - Pre-Moldorm Chest", False, []],
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']],
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']],
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']],
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
["Ganons Tower - Validation Chest", False, []],
["Ganons Tower - Validation Chest", False, [], ['Hookshot']],
["Ganons Tower - Validation Chest", False, [], ['Progressive Bow']],
["Ganons Tower - Validation Chest", False, [], ['Bomb Upgrade (50)']],
["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']],
["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']],
["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']],
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
])

View File

@@ -77,5 +77,5 @@ class TestMiseryMire(TestDungeon):
["Misery Mire - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']],
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Arrow Upgrade (+5)', 'Pegasus Boots']],
])

View File

@@ -79,12 +79,12 @@ class TestInvertedTurtleRock(TestInverted):
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']],
@@ -97,9 +97,9 @@ class TestInvertedTurtleRock(TestInverted):
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
@@ -117,12 +117,12 @@ class TestInvertedTurtleRock(TestInverted):
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
[location, False, [], ['Magic Mirror', 'Lamp']],
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
# Mirroring into Eye Bridge does not require Cane of Somaria
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],

View File

@@ -80,12 +80,12 @@ class TestInvertedTurtleRock(TestInvertedMinor):
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']],
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']],
@@ -98,9 +98,9 @@ class TestInvertedTurtleRock(TestInvertedMinor):
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
])
@@ -116,12 +116,12 @@ class TestInvertedTurtleRock(TestInvertedMinor):
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
[location, False, [], ['Magic Mirror', 'Lamp']],
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
# Mirroring into Eye Bridge does not require Cane of Somaria
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],

View File

@@ -102,7 +102,7 @@ class TestDungeons(TestInvertedOWG):
["Turtle Rock - Chain Chomps", True, ['Progressive Sword', 'Progressive Sword', 'Pegasus Boots']],
["Turtle Rock - Crystaroller Room", False, []],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)', 'Bomb Upgrade (50)']],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Lamp', 'Cane of Somaria']],
["Ganons Tower - Hope Room - Left", False, []],

View File

@@ -1,5 +1,5 @@
from worlds.alttp.ItemPool import difficulties
from test.TestBase import TestBase
from test.bases import TestBase
base_items = 41
extra_counts = (15, 15, 10, 5, 25)

View File

@@ -1,7 +1,7 @@
from typing import List
from BaseClasses import Item, Location
from test.TestBase import WorldTestBase
from test.bases import WorldTestBase
class TestPrizes(WorldTestBase):

View File

@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.TestBase import TestBase
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase

View File

@@ -120,8 +120,8 @@ class TestDungeons(TestVanillaOWG):
#todo: does clip require sword?
#["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Progressive Sword']],
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Hookshot']],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Big Key (Turtle Rock)']],
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Hookshot', 'Bomb Upgrade (50)']],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Big Key (Turtle Rock)', 'Bomb Upgrade (50)']],
["Ganons Tower - Hope Room - Left", False, []],
["Ganons Tower - Hope Room - Left", False, ['Moon Pearl', 'Crystal 1']],

View File

@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.TestBase import TestBase
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase

View File

@@ -1,5 +1,5 @@
from worlds.alttp.Shops import shop_table
from test.TestBase import TestBase
from test.bases import TestBase
class TestSram(TestBase):

View File

@@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.TestBase import TestBase
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase

View File

@@ -59,156 +59,316 @@ class ItemData:
type: ItemType
group: ItemGroup
def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup):
def __init__(self, aId: int, count: int, aType: ItemType, group: ItemGroup):
"""
Initialisation of the item data
@param id: The item ID
@param aId: The item ID
@param count: the number of items in the pool
@param type: the importance type of the item
@param aType: the importance type of the item
@param group: the usage of the item in the game
"""
self.id = id
self.id = aId
self.count = count
self.type = type
self.type = aType
self.group = group
class ItemNames:
"""
Constants used to represent the mane of every items.
"""
# Normal items
ANEMONE = "Anemone"
ARNASSI_STATUE = "Arnassi Statue"
BIG_SEED = "Big Seed"
GLOWING_SEED = "Glowing Seed"
BLACK_PEARL = "Black Pearl"
BABY_BLASTER = "Baby Blaster"
CRAB_ARMOR = "Crab Armor"
BABY_DUMBO = "Baby Dumbo"
TOOTH = "Tooth"
ENERGY_STATUE = "Energy Statue"
KROTITE_ARMOR = "Krotite Armor"
GOLDEN_STARFISH = "Golden Starfish"
GOLDEN_GEAR = "Golden Gear"
JELLY_BEACON = "Jelly Beacon"
JELLY_COSTUME = "Jelly Costume"
JELLY_PLANT = "Jelly Plant"
MITHALAS_DOLL = "Mithalas Doll"
MITHALAN_DRESS = "Mithalan Dress"
MITHALAS_BANNER = "Mithalas Banner"
MITHALAS_POT = "Mithalas Pot"
MUTANT_COSTUME = "Mutant Costume"
BABY_NAUTILUS = "Baby Nautilus"
BABY_PIRANHA = "Baby Piranha"
ARNASSI_ARMOR = "Arnassi Armor"
SEED_BAG = "Seed Bag"
KING_S_SKULL = "King's Skull"
SONG_PLANT_SPORE = "Song Plant Spore"
STONE_HEAD = "Stone Head"
SUN_KEY = "Sun Key"
GIRL_COSTUME = "Girl Costume"
ODD_CONTAINER = "Odd Container"
TRIDENT = "Trident"
TURTLE_EGG = "Turtle Egg"
JELLY_EGG = "Jelly Egg"
URCHIN_COSTUME = "Urchin Costume"
BABY_WALKER = "Baby Walker"
VEDHA_S_CURE_ALL = "Vedha's Cure-All"
ZUUNA_S_PEROGI = "Zuuna's Perogi"
ARCANE_POULTICE = "Arcane Poultice"
BERRY_ICE_CREAM = "Berry Ice Cream"
BUTTERY_SEA_LOAF = "Buttery Sea Loaf"
COLD_BORSCHT = "Cold Borscht"
COLD_SOUP = "Cold Soup"
CRAB_CAKE = "Crab Cake"
DIVINE_SOUP = "Divine Soup"
DUMBO_ICE_CREAM = "Dumbo Ice Cream"
FISH_OIL = "Fish Oil"
GLOWING_EGG = "Glowing Egg"
HAND_ROLL = "Hand Roll"
HEALING_POULTICE = "Healing Poultice"
HEARTY_SOUP = "Hearty Soup"
HOT_BORSCHT = "Hot Borscht"
HOT_SOUP = "Hot Soup"
ICE_CREAM = "Ice Cream"
LEADERSHIP_ROLL = "Leadership Roll"
LEAF_POULTICE = "Leaf Poultice"
LEECHING_POULTICE = "Leeching Poultice"
LEGENDARY_CAKE = "Legendary Cake"
LOAF_OF_LIFE = "Loaf of Life"
LONG_LIFE_SOUP = "Long Life Soup"
MAGIC_SOUP = "Magic Soup"
MUSHROOM_X_2 = "Mushroom x 2"
PEROGI = "Perogi"
PLANT_LEAF = "Plant Leaf"
PLUMP_PEROGI = "Plump Perogi"
POISON_LOAF = "Poison Loaf"
POISON_SOUP = "Poison Soup"
RAINBOW_MUSHROOM = "Rainbow Mushroom"
RAINBOW_SOUP = "Rainbow Soup"
RED_BERRY = "Red Berry"
RED_BULB_X_2 = "Red Bulb x 2"
ROTTEN_CAKE = "Rotten Cake"
ROTTEN_LOAF_X_8 = "Rotten Loaf x 8"
ROTTEN_MEAT = "Rotten Meat"
ROYAL_SOUP = "Royal Soup"
SEA_CAKE = "Sea Cake"
SEA_LOAF = "Sea Loaf"
SHARK_FIN_SOUP = "Shark Fin Soup"
SIGHT_POULTICE = "Sight Poultice"
SMALL_BONE_X_2 = "Small Bone x 2"
SMALL_EGG = "Small Egg"
SMALL_TENTACLE_X_2 = "Small Tentacle x 2"
SPECIAL_BULB = "Special Bulb"
SPECIAL_CAKE = "Special Cake"
SPICY_MEAT_X_2 = "Spicy Meat x 2"
SPICY_ROLL = "Spicy Roll"
SPICY_SOUP = "Spicy Soup"
SPIDER_ROLL = "Spider Roll"
SWAMP_CAKE = "Swamp Cake"
TASTY_CAKE = "Tasty Cake"
TASTY_ROLL = "Tasty Roll"
TOUGH_CAKE = "Tough Cake"
TURTLE_SOUP = "Turtle Soup"
VEDHA_SEA_CRISP = "Vedha Sea Crisp"
VEGGIE_CAKE = "Veggie Cake"
VEGGIE_ICE_CREAM = "Veggie Ice Cream"
VEGGIE_SOUP = "Veggie Soup"
VOLCANO_ROLL = "Volcano Roll"
HEALTH_UPGRADE = "Health Upgrade"
WOK = "Wok"
EEL_OIL_X_2 = "Eel Oil x 2"
FISH_MEAT_X_2 = "Fish Meat x 2"
FISH_OIL_X_3 = "Fish Oil x 3"
GLOWING_EGG_X_2 = "Glowing Egg x 2"
HEALING_POULTICE_X_2 = "Healing Poultice x 2"
HOT_SOUP_X_2 = "Hot Soup x 2"
LEADERSHIP_ROLL_X_2 = "Leadership Roll x 2"
LEAF_POULTICE_X_3 = "Leaf Poultice x 3"
PLANT_LEAF_X_2 = "Plant Leaf x 2"
PLANT_LEAF_X_3 = "Plant Leaf x 3"
ROTTEN_MEAT_X_2 = "Rotten Meat x 2"
ROTTEN_MEAT_X_8 = "Rotten Meat x 8"
SEA_LOAF_X_2 = "Sea Loaf x 2"
SMALL_BONE_X_3 = "Small Bone x 3"
SMALL_EGG_X_2 = "Small Egg x 2"
LI_AND_LI_SONG = "Li and Li Song"
SHIELD_SONG = "Shield Song"
BEAST_FORM = "Beast Form"
SUN_FORM = "Sun Form"
NATURE_FORM = "Nature Form"
ENERGY_FORM = "Energy Form"
BIND_SONG = "Bind Song"
FISH_FORM = "Fish Form"
SPIRIT_FORM = "Spirit Form"
DUAL_FORM = "Dual Form"
TRANSTURTLE_VEIL_TOP_LEFT = "Transturtle Veil top left"
TRANSTURTLE_VEIL_TOP_RIGHT = "Transturtle Veil top right"
TRANSTURTLE_OPEN_WATERS = "Transturtle Open Waters top right"
TRANSTURTLE_KELP_FOREST = "Transturtle Kelp Forest bottom left"
TRANSTURTLE_HOME_WATERS = "Transturtle Home Waters"
TRANSTURTLE_ABYSS = "Transturtle Abyss right"
TRANSTURTLE_BODY = "Transturtle Final Boss"
TRANSTURTLE_SIMON_SAYS = "Transturtle Simon Says"
TRANSTURTLE_ARNASSI_RUINS = "Transturtle Arnassi Ruins"
# Events name
BODY_TONGUE_CLEARED = "Body Tongue cleared"
HAS_SUN_CRYSTAL = "Has Sun Crystal"
FALLEN_GOD_BEATED = "Fallen God beated"
MITHALAN_GOD_BEATED = "Mithalan God beated"
DRUNIAN_GOD_BEATED = "Drunian God beated"
LUMEREAN_GOD_BEATED = "Lumerean God beated"
THE_GOLEM_BEATED = "The Golem beated"
NAUTILUS_PRIME_BEATED = "Nautilus Prime beated"
BLASTER_PEG_PRIME_BEATED = "Blaster Peg Prime beated"
MERGOG_BEATED = "Mergog beated"
MITHALAN_PRIESTS_BEATED = "Mithalan priests beated"
OCTOPUS_PRIME_BEATED = "Octopus Prime beated"
CRABBIUS_MAXIMUS_BEATED = "Crabbius Maximus beated"
MANTIS_SHRIMP_PRIME_BEATED = "Mantis Shrimp Prime beated"
KING_JELLYFISH_GOD_PRIME_BEATED = "King Jellyfish God Prime beated"
VICTORY = "Victory"
FIRST_SECRET_OBTAINED = "First Secret obtained"
SECOND_SECRET_OBTAINED = "Second Secret obtained"
THIRD_SECRET_OBTAINED = "Third Secret obtained"
"""Information data for every (not event) item."""
item_table = {
# name: ID, Nb, Item Type, Item Group
"Anemone": ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone
"Arnassi Statue": ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue
"Big Seed": ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed
"Glowing Seed": ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed
"Black Pearl": ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl
"Baby Blaster": ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster
"Crab Armor": ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume
"Baby Dumbo": ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo
"Tooth": ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss
"Energy Statue": ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue
"Krotite Armor": ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple
"Golden Starfish": ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star
"Golden Gear": ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear
"Jelly Beacon": ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon
"Jelly Costume": ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume
"Jelly Plant": ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant
"Mithalas Doll": ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll
"Mithalan Dress": ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume
"Mithalas Banner": ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner
"Mithalas Pot": ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot
"Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
"Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
"Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
"Arnassi Armor": ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume
"Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
"King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
"Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
"Stone Head": ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head
"Sun Key": ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key
"Girl Costume": ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume
"Odd Container": ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest
"Trident": ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
"Turtle Egg": ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
"Jelly Egg": ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
"Urchin Costume": ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
"Baby Walker": ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
"Vedha's Cure-All-All": ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
"Zuuna's perogi": ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
"Arcane poultice": ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice
"Berry ice cream": ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream
"Buttery sea loaf": ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf
"Cold borscht": ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht
"Cold soup": ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup
"Crab cake": ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake
"Divine soup": ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup
"Dumbo ice cream": ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream
"Fish oil": ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
"Glowing egg": ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
"Hand roll": ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll
"Healing poultice": ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
"Hearty soup": ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup
"Hot borscht": ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht
"Hot soup": ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
"Ice cream": ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream
"Leadership roll": ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
"Leaf poultice": ItemData(698055, 5, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
"Leeching poultice": ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice
"Legendary cake": ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake
"Loaf of life": ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife
"Long life soup": ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup
"Magic soup": ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup
"Mushroom x 2": ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom
"Perogi": ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi
"Plant leaf": ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
"Plump perogi": ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi
"Poison loaf": ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf
"Poison soup": ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup
"Rainbow mushroom": ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom
"Rainbow soup": ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup
"Red berry": ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry
"Red bulb x 2": ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb
"Rotten cake": ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake
"Rotten loaf x 8": ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf
"Rotten meat": ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
"Royal soup": ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup
"Sea cake": ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake
"Sea loaf": ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
"Shark fin soup": ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup
"Sight poultice": ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice
"Small bone x 2": ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
"Small egg": ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
"Small tentacle x 2": ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle
"Special bulb": ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb
"Special cake": ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake
"Spicy meat x 2": ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat
"Spicy roll": ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll
"Spicy soup": ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup
"Spider roll": ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll
"Swamp cake": ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake
"Tasty cake": ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake
"Tasty roll": ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll
"Tough cake": ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake
"Turtle soup": ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup
"Vedha sea crisp": ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp
"Veggie cake": ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake
"Veggie ice cream": ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream
"Veggie soup": ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup
"Volcano roll": ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll
"Health upgrade": ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_?
"Wok": ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok
"Eel oil x 2": ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil
"Fish meat x 2": ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat
"Fish oil x 3": ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
"Glowing egg x 2": ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
"Healing poultice x 2": ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
"Hot soup x 2": ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
"Leadership roll x 2": ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
"Leaf poultice x 3": ItemData(698107, 2, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
"Plant leaf x 2": ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
"Plant leaf x 3": ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
"Rotten meat x 2": ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
"Rotten meat x 8": ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
"Sea loaf x 2": ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
"Small bone x 3": ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
"Small egg x 2": ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
"Li and Li song": ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li
"Shield song": ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield
"Beast form": ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast
"Sun form": ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun
"Nature form": ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature
"Energy form": ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy
"Bind song": ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind
"Fish form": ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish
"Spirit form": ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit
"Dual form": ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual
"Transturtle Veil top left": ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01
"Transturtle Veil top right": ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02
"Transturtle Open Water top right": ItemData(698127, 1, ItemType.PROGRESSION,
ItemGroup.TURTLE), # transport_openwater03
"Transturtle Forest bottom left": ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest04
"Transturtle Home Water": ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea
"Transturtle Abyss right": ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03
"Transturtle Final Boss": ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss
"Transturtle Simon Says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
"Transturtle Arnassi Ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
ItemNames.ANEMONE: ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone
ItemNames.ARNASSI_STATUE: ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue
ItemNames.BIG_SEED: ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed
ItemNames.GLOWING_SEED: ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed
ItemNames.BLACK_PEARL: ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl
ItemNames.BABY_BLASTER: ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster
ItemNames.CRAB_ARMOR: ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume
ItemNames.BABY_DUMBO: ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo
ItemNames.TOOTH: ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss
ItemNames.ENERGY_STATUE: ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue
ItemNames.KROTITE_ARMOR: ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple
ItemNames.GOLDEN_STARFISH: ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star
ItemNames.GOLDEN_GEAR: ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear
ItemNames.JELLY_BEACON: ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon
ItemNames.JELLY_COSTUME: ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume
ItemNames.JELLY_PLANT: ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant
ItemNames.MITHALAS_DOLL: ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll
ItemNames.MITHALAN_DRESS: ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume
ItemNames.MITHALAS_BANNER: ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner
ItemNames.MITHALAS_POT: ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot
ItemNames.MUTANT_COSTUME: ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
ItemNames.BABY_NAUTILUS: ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
ItemNames.BABY_PIRANHA: ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
ItemNames.ARNASSI_ARMOR: ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume
ItemNames.SEED_BAG: ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
ItemNames.KING_S_SKULL: ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
ItemNames.SONG_PLANT_SPORE: ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
ItemNames.STONE_HEAD: ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head
ItemNames.SUN_KEY: ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key
ItemNames.GIRL_COSTUME: ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume
ItemNames.ODD_CONTAINER: ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest
ItemNames.TRIDENT: ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
ItemNames.TURTLE_EGG: ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
ItemNames.JELLY_EGG: ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
ItemNames.BABY_WALKER: ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
ItemNames.VEDHA_S_CURE_ALL: ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
ItemNames.ZUUNA_S_PEROGI: ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
ItemNames.ARCANE_POULTICE: ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice
ItemNames.BERRY_ICE_CREAM: ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream
ItemNames.BUTTERY_SEA_LOAF: ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf
ItemNames.COLD_BORSCHT: ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht
ItemNames.COLD_SOUP: ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup
ItemNames.CRAB_CAKE: ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake
ItemNames.DIVINE_SOUP: ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup
ItemNames.DUMBO_ICE_CREAM: ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream
ItemNames.FISH_OIL: ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
ItemNames.GLOWING_EGG: ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
ItemNames.HAND_ROLL: ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll
ItemNames.HEALING_POULTICE: ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
ItemNames.HEARTY_SOUP: ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup
ItemNames.HOT_BORSCHT: ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht
ItemNames.HOT_SOUP: ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
ItemNames.ICE_CREAM: ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream
ItemNames.LEADERSHIP_ROLL: ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
ItemNames.LEAF_POULTICE: ItemData(698055, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leafpoultice
ItemNames.LEECHING_POULTICE: ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice
ItemNames.LEGENDARY_CAKE: ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake
ItemNames.LOAF_OF_LIFE: ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife
ItemNames.LONG_LIFE_SOUP: ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup
ItemNames.MAGIC_SOUP: ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup
ItemNames.MUSHROOM_X_2: ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom
ItemNames.PEROGI: ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi
ItemNames.PLANT_LEAF: ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
ItemNames.PLUMP_PEROGI: ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi
ItemNames.POISON_LOAF: ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf
ItemNames.POISON_SOUP: ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup
ItemNames.RAINBOW_MUSHROOM: ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom
ItemNames.RAINBOW_SOUP: ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup
ItemNames.RED_BERRY: ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry
ItemNames.RED_BULB_X_2: ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb
ItemNames.ROTTEN_CAKE: ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake
ItemNames.ROTTEN_LOAF_X_8: ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf
ItemNames.ROTTEN_MEAT: ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
ItemNames.ROYAL_SOUP: ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup
ItemNames.SEA_CAKE: ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake
ItemNames.SEA_LOAF: ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
ItemNames.SHARK_FIN_SOUP: ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup
ItemNames.SIGHT_POULTICE: ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice
ItemNames.SMALL_BONE_X_2: ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
ItemNames.SMALL_EGG: ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
ItemNames.SMALL_TENTACLE_X_2: ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle
ItemNames.SPECIAL_BULB: ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb
ItemNames.SPECIAL_CAKE: ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake
ItemNames.SPICY_MEAT_X_2: ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat
ItemNames.SPICY_ROLL: ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll
ItemNames.SPICY_SOUP: ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup
ItemNames.SPIDER_ROLL: ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll
ItemNames.SWAMP_CAKE: ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake
ItemNames.TASTY_CAKE: ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake
ItemNames.TASTY_ROLL: ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll
ItemNames.TOUGH_CAKE: ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake
ItemNames.TURTLE_SOUP: ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup
ItemNames.VEDHA_SEA_CRISP: ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp
ItemNames.VEGGIE_CAKE: ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake
ItemNames.VEGGIE_ICE_CREAM: ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream
ItemNames.VEGGIE_SOUP: ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup
ItemNames.VOLCANO_ROLL: ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll
ItemNames.HEALTH_UPGRADE: ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_?
ItemNames.WOK: ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok
ItemNames.EEL_OIL_X_2: ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil
ItemNames.FISH_MEAT_X_2: ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat
ItemNames.FISH_OIL_X_3: ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
ItemNames.GLOWING_EGG_X_2: ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
ItemNames.HEALING_POULTICE_X_2: ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
ItemNames.HOT_SOUP_X_2: ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
ItemNames.LEADERSHIP_ROLL_X_2: ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
ItemNames.LEAF_POULTICE_X_3: ItemData(698107, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leafpoultice
ItemNames.PLANT_LEAF_X_2: ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
ItemNames.PLANT_LEAF_X_3: ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
ItemNames.ROTTEN_MEAT_X_2: ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
ItemNames.ROTTEN_MEAT_X_8: ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
ItemNames.SEA_LOAF_X_2: ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
ItemNames.SMALL_BONE_X_3: ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
ItemNames.SMALL_EGG_X_2: ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
ItemNames.LI_AND_LI_SONG: ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li
ItemNames.SHIELD_SONG: ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield
ItemNames.BEAST_FORM: ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast
ItemNames.SUN_FORM: ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun
ItemNames.NATURE_FORM: ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature
ItemNames.ENERGY_FORM: ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy
ItemNames.BIND_SONG: ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind
ItemNames.FISH_FORM: ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish
ItemNames.SPIRIT_FORM: ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit
ItemNames.DUAL_FORM: ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual
ItemNames.TRANSTURTLE_VEIL_TOP_LEFT: ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01
ItemNames.TRANSTURTLE_VEIL_TOP_RIGHT: ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02
ItemNames.TRANSTURTLE_OPEN_WATERS: ItemData(698127, 1, ItemType.PROGRESSION,
ItemGroup.TURTLE), # transport_openwater03
ItemNames.TRANSTURTLE_KELP_FOREST: ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE),
# transport_forest04
ItemNames.TRANSTURTLE_HOME_WATERS: ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea
ItemNames.TRANSTURTLE_ABYSS: ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03
ItemNames.TRANSTURTLE_BODY: ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss
ItemNames.TRANSTURTLE_SIMON_SAYS: ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
ItemNames.TRANSTURTLE_ARNASSI_RUINS: ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,10 @@ class IngredientRandomizer(Choice):
"""
display_name = "Randomize Ingredients"
option_off = 0
alias_false = 0
option_common_ingredients = 1
alias_on = 1
alias_true = 1
option_all_ingredients = 2
default = 0
@@ -29,14 +32,43 @@ class TurtleRandomizer(Choice):
"""Randomize the transportation turtle."""
display_name = "Turtle Randomizer"
option_none = 0
alias_off = 0
alias_false = 0
option_all = 1
option_all_except_final = 2
alias_on = 2
alias_true = 2
default = 2
class EarlyEnergyForm(DefaultOnToggle):
""" Force the Energy Form to be in a location early in the game """
display_name = "Early Energy Form"
class EarlyBindSong(Choice):
"""
Force the Bind song to be in a location early in the multiworld (or directly in your world if Early and Local is
selected).
"""
display_name = "Early Bind song"
option_off = 0
alias_false = 0
option_early = 1
alias_on = 1
alias_true = 1
option_early_and_local = 2
default = 1
class EarlyEnergyForm(Choice):
"""
Force the Energy form to be in a location early in the multiworld (or directly in your world if Early and Local is
selected).
"""
display_name = "Early Energy form"
option_off = 0
alias_false = 0
option_early = 1
alias_on = 1
alias_true = 1
option_early_and_local = 2
default = 1
class AquarianTranslation(Toggle):
@@ -47,7 +79,7 @@ class AquarianTranslation(Toggle):
class BigBossesToBeat(Range):
"""
The number of big bosses to beat before having access to the creator (the final boss). The big bosses are
"Fallen God", "Mithalan God", "Drunian God", "Sun God" and "The Golem".
"Fallen God", "Mithalan God", "Drunian God", "Lumerean God" and "The Golem".
"""
display_name = "Big bosses to beat"
range_start = 0
@@ -104,7 +136,7 @@ class LightNeededToGetToDarkPlaces(DefaultOnToggle):
display_name = "Light needed to get to dark places"
class BindSongNeededToGetUnderRockBulb(Toggle):
class BindSongNeededToGetUnderRockBulb(DefaultOnToggle):
"""
Make sure that the bind song can be acquired before having to obtain sing bulbs under rocks.
"""
@@ -121,13 +153,18 @@ class BlindGoal(Toggle):
class UnconfineHomeWater(Choice):
"""
Open the way out of the Home Water area so that Naija can go to open water and beyond without the bind song.
Open the way out of the Home Waters area so that Naija can go to open water and beyond without the bind song.
Note that if you turn this option off, it is recommended to turn on the Early Energy form and Early Bind Song
options.
"""
display_name = "Unconfine Home Water Area"
display_name = "Unconfine Home Waters Area"
option_off = 0
alias_false = 0
option_via_energy_door = 1
option_via_transturtle = 2
option_via_both = 3
alias_on = 3
alias_true = 3
default = 0
@@ -142,6 +179,7 @@ class AquariaOptions(PerGameCommonOptions):
big_bosses_to_beat: BigBossesToBeat
turtle_randomizer: TurtleRandomizer
early_energy_form: EarlyEnergyForm
early_bind_song: EarlyBindSong
light_needed_to_get_to_dark_places: LightNeededToGetToDarkPlaces
bind_song_needed_to_get_under_rock_bulb: BindSongNeededToGetUnderRockBulb
unconfine_home_water: UnconfineHomeWater

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,10 @@ Description: Main module for Aquaria game multiworld randomizer
from typing import List, Dict, ClassVar, Any
from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial, MultiWorld, ItemClassification
from .Items import item_table, AquariaItem, ItemType, ItemGroup
from .Locations import location_table
from .Options import AquariaOptions
from .Items import item_table, AquariaItem, ItemType, ItemGroup, ItemNames
from .Locations import location_table, AquariaLocationNames
from .Options import (AquariaOptions, IngredientRandomizer, TurtleRandomizer, EarlyBindSong, EarlyEnergyForm,
UnconfineHomeWater, Objective)
from .Regions import AquariaRegions
@@ -40,6 +41,7 @@ class AquariaWeb(WebWorld):
)
tutorials = [setup, setup_fr]
game_info_languages = ["en", "fr"]
class AquariaWorld(World):
@@ -65,15 +67,15 @@ class AquariaWorld(World):
web: WebWorld = AquariaWeb()
"The web page generation informations"
item_name_to_id: ClassVar[Dict[str, int]] =\
item_name_to_id: ClassVar[Dict[str, int]] = \
{name: data.id for name, data in item_table.items()}
"The name and associated ID of each item of the world"
item_name_groups = {
"Damage": {"Energy form", "Nature form", "Beast form",
"Li and Li song", "Baby Nautilus", "Baby Piranha",
"Baby Blaster"},
"Light": {"Sun form", "Baby Dumbo"}
"Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
ItemNames.BABY_BLASTER},
"Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO}
}
"""Grouping item make it easier to find them"""
@@ -92,7 +94,7 @@ class AquariaWorld(World):
options: AquariaOptions
"Every options of the world"
regions: AquariaRegions
regions: AquariaRegions | None
"Used to manage Regions"
exclude: List[str]
@@ -100,10 +102,17 @@ class AquariaWorld(World):
def __init__(self, multiworld: MultiWorld, player: int):
"""Initialisation of the Aquaria World"""
super(AquariaWorld, self).__init__(multiworld, player)
self.regions = AquariaRegions(multiworld, player)
self.regions = None
self.ingredients_substitution = []
self.exclude = []
def generate_early(self) -> None:
"""
Run before any general steps of the MultiWorld other than options. Useful for getting and adjusting option
results and determining layouts for entrance rando etc. start inventory gets pushed after this step.
"""
self.regions = AquariaRegions(self.multiworld, self.player)
def create_regions(self) -> None:
"""
Create every Region in `regions`
@@ -117,25 +126,23 @@ class AquariaWorld(World):
Create an AquariaItem using 'name' as item name.
"""
result: AquariaItem
try:
data = item_table[name]
classification: ItemClassification = ItemClassification.useful
if data.type == ItemType.JUNK:
classification = ItemClassification.filler
elif data.type == ItemType.PROGRESSION:
classification = ItemClassification.progression
result = AquariaItem(name, classification, data.id, self.player)
except BaseException:
raise Exception('The item ' + name + ' is not valid.')
data = item_table[name]
classification: ItemClassification = ItemClassification.useful
if data.type == ItemType.JUNK:
classification = ItemClassification.filler
elif data.type == ItemType.PROGRESSION:
classification = ItemClassification.progression
result = AquariaItem(name, classification, data.id, self.player)
return result
def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None:
def __pre_fill_item(self, item_name: str, location_name: str, precollected,
itemClassification: ItemClassification = ItemClassification.useful) -> None:
"""Pre-assign an item to a location"""
if item_name not in precollected:
self.exclude.append(item_name)
data = item_table[item_name]
item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player)
item = AquariaItem(item_name, itemClassification, data.id, self.player)
self.multiworld.get_location(location_name, self.player).place_locked_item(item)
def get_filler_item_name(self):
@@ -150,22 +157,32 @@ class AquariaWorld(World):
def create_items(self) -> None:
"""Create every item in the world"""
precollected = [item.name for item in self.multiworld.precollected_items[self.player]]
if self.options.turtle_randomizer.value > 0:
if self.options.turtle_randomizer.value == 2:
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
if self.options.turtle_randomizer.value != TurtleRandomizer.option_none:
if self.options.turtle_randomizer.value == TurtleRandomizer.option_all_except_final:
self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
precollected)
else:
self.__pre_fill_item("Transturtle Veil top left", "The Veil top left area, Transturtle", precollected)
self.__pre_fill_item("Transturtle Veil top right", "The Veil top right area, Transturtle", precollected)
self.__pre_fill_item("Transturtle Open Water top right", "Open Water top right area, Transturtle",
self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_LEFT,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_TRANSTURTLE, precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_RIGHT,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_TRANSTURTLE, precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_OPEN_WATERS,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_TRANSTURTLE,
precollected)
self.__pre_fill_item("Transturtle Forest bottom left", "Kelp Forest bottom left area, Transturtle",
self.__pre_fill_item(ItemNames.TRANSTURTLE_KELP_FOREST,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_TRANSTURTLE,
precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_HOME_WATERS, AquariaLocationNames.HOME_WATERS_TRANSTURTLE,
precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_ABYSS, AquariaLocationNames.ABYSS_RIGHT_AREA_TRANSTURTLE,
precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
precollected)
self.__pre_fill_item("Transturtle Home Water", "Home Water, Transturtle", precollected)
self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected)
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
# The last two are inverted because in the original game, they are special turtle that communicate directly
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
self.__pre_fill_item(ItemNames.TRANSTURTLE_SIMON_SAYS, AquariaLocationNames.ARNASSI_RUINS_TRANSTURTLE,
precollected, ItemClassification.progression)
self.__pre_fill_item(ItemNames.TRANSTURTLE_ARNASSI_RUINS, AquariaLocationNames.SIMON_SAYS_AREA_TRANSTURTLE,
precollected)
for name, data in item_table.items():
if name not in self.exclude:
for i in range(data.count):
@@ -176,10 +193,17 @@ class AquariaWorld(World):
"""
Launched when the Multiworld generator is ready to generate rules
"""
if self.options.early_energy_form == EarlyEnergyForm.option_early:
self.multiworld.early_items[self.player][ItemNames.ENERGY_FORM] = 1
elif self.options.early_energy_form == EarlyEnergyForm.option_early_and_local:
self.multiworld.local_early_items[self.player][ItemNames.ENERGY_FORM] = 1
if self.options.early_bind_song == EarlyBindSong.option_early:
self.multiworld.early_items[self.player][ItemNames.BIND_SONG] = 1
elif self.options.early_bind_song == EarlyBindSong.option_early_and_local:
self.multiworld.local_early_items[self.player][ItemNames.BIND_SONG] = 1
self.regions.adjusting_rules(self.options)
self.multiworld.completion_condition[self.player] = lambda \
state: state.has("Victory", self.player)
state: state.has(ItemNames.VICTORY, self.player)
def generate_basic(self) -> None:
"""
@@ -187,13 +211,13 @@ class AquariaWorld(World):
Used to fill then `ingredients_substitution` list
"""
simple_ingredients_substitution = [i for i in range(27)]
if self.options.ingredient_randomizer.value > 0:
if self.options.ingredient_randomizer.value == 1:
if self.options.ingredient_randomizer.value > IngredientRandomizer.option_off:
if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
simple_ingredients_substitution.pop(-1)
simple_ingredients_substitution.pop(-1)
simple_ingredients_substitution.pop(-1)
self.random.shuffle(simple_ingredients_substitution)
if self.options.ingredient_randomizer.value == 1:
if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
simple_ingredients_substitution.extend([24, 25, 26])
dishes_substitution = [i for i in range(27, 76)]
if self.options.dish_randomizer:
@@ -206,10 +230,19 @@ class AquariaWorld(World):
return {"ingredientReplacement": self.ingredients_substitution,
"aquarian_translate": bool(self.options.aquarian_translation.value),
"blind_goal": bool(self.options.blind_goal.value),
"secret_needed": self.options.objective.value > 0,
"secret_needed":
self.options.objective.value == Objective.option_obtain_secrets_and_kill_the_creator,
"minibosses_to_kill": self.options.mini_bosses_to_beat.value,
"bigbosses_to_kill": self.options.big_bosses_to_beat.value,
"skip_first_vision": bool(self.options.skip_first_vision.value),
"unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3],
"unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3],
"unconfine_home_water_energy_door":
self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_energy_door
or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
"unconfine_home_water_transturtle":
self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_transturtle
or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
"bind_song_needed_to_get_under_rock_bulb": bool(self.options.bind_song_needed_to_get_under_rock_bulb),
"no_progression_hard_or_hidden_locations": bool(self.options.no_progression_hard_or_hidden_locations),
"light_needed_to_get_to_dark_places": bool(self.options.light_needed_to_get_to_dark_places),
"turtle_randomizer": self.options.turtle_randomizer.value
}

View File

@@ -24,7 +24,7 @@ The locations in the randomizer are:
* Beating Mithalan God boss
* Fish Cave puzzle
* Beating Drunian God boss
* Beating Sun God boss
* Beating Lumerean God boss
* Breaking Li cage in the body
Note that, unlike the vanilla game, when opening sing bulbs, Mithalas urns and Sunken City crates,

View File

@@ -3,11 +3,13 @@
## Required Software
- The original Aquaria Game (purchasable from most online game stores)
- The [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
- The [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases/latest)
## Optional Software
- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), for use with
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
## Installation and execution Procedures
@@ -113,3 +115,16 @@ sure that your executable has executable permission:
```bash
chmod +x aquaria_randomizer
```
## Auto-Tracking
Aquaria has a fully functional map tracker that supports auto-tracking.
1. Download [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) and
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
2. Put the tracker pack into /packs/ in your PopTracker install.
3. Open PopTracker, and load the Aquaria pack.
4. For autotracking, click on the "AP" symbol at the top.
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.
This pack will automatically prompt you to update if one is available.

View File

@@ -2,9 +2,13 @@
## Logiciels nécessaires
- Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne)
- Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
- Une copie du jeu Aquaria non-modifiée (disponible sur la majorité des sites de ventes de jeux vidéos en ligne)
- Le client du Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases/latest)
## Logiciels optionnels
- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest)
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), pour utiliser avec [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
## Procédures d'installation et d'exécution
@@ -116,3 +120,15 @@ pour vous assurer que votre fichier est exécutable:
```bash
chmod +x aquaria_randomizer
```
## Tracking automatique
Aquaria a un tracker complet qui supporte le tracking automatique.
1. Téléchargez [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) et [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
2. Mettre le fichier compressé du tracker dans le sous-répertoire /packs/ du répertoire d'installation de PopTracker.
3. Lancez PopTracker, et ouvrez le pack d'Aquaria.
4. Pour activer le tracking automatique, cliquez sur le symbole "AP" dans le haut de la fenêtre.
5. Entrez l'adresse du serveur Archipelago (le serveur auquel vous avez connecté le client), le nom de votre slot, et le mot de passe (si un mot de passe est nécessaire).
Le logiciel vous indiquera si une mise à jour du pack est disponible.

View File

@@ -6,211 +6,212 @@ Description: Base class for the Aquaria randomizer unit tests
from test.bases import WorldTestBase
from ..Locations import AquariaLocationNames
# Every location accessible after the home water.
after_home_water_locations = [
"Sun Crystal",
"Home Water, Transturtle",
"Open Water top left area, bulb under the rock in the right path",
"Open Water top left area, bulb under the rock in the left path",
"Open Water top left area, bulb to the right of the save crystal",
"Open Water top right area, bulb in the small path before Mithalas",
"Open Water top right area, bulb in the path from the left entrance",
"Open Water top right area, bulb in the clearing close to the bottom exit",
"Open Water top right area, bulb in the big clearing close to the save crystal",
"Open Water top right area, bulb in the big clearing to the top exit",
"Open Water top right area, first urn in the Mithalas exit",
"Open Water top right area, second urn in the Mithalas exit",
"Open Water top right area, third urn in the Mithalas exit",
"Open Water top right area, bulb in the turtle room",
"Open Water top right area, Transturtle",
"Open Water bottom left area, bulb behind the chomper fish",
"Open Water bottom left area, bulb inside the lowest fish pass",
"Open Water skeleton path, bulb close to the right exit",
"Open Water skeleton path, bulb behind the chomper fish",
"Open Water skeleton path, King Skull",
"Arnassi Ruins, bulb in the right part",
"Arnassi Ruins, bulb in the left part",
"Arnassi Ruins, bulb in the center part",
"Arnassi Ruins, Song Plant Spore",
"Arnassi Ruins, Arnassi Armor",
"Arnassi Ruins, Arnassi Statue",
"Arnassi Ruins, Transturtle",
"Arnassi Ruins, Crab Armor",
"Simon Says area, Transturtle",
"Mithalas City, first bulb in the left city part",
"Mithalas City, second bulb in the left city part",
"Mithalas City, bulb in the right part",
"Mithalas City, bulb at the top of the city",
"Mithalas City, first bulb in a broken home",
"Mithalas City, second bulb in a broken home",
"Mithalas City, bulb in the bottom left part",
"Mithalas City, first bulb in one of the homes",
"Mithalas City, second bulb in one of the homes",
"Mithalas City, first urn in one of the homes",
"Mithalas City, second urn in one of the homes",
"Mithalas City, first urn in the city reserve",
"Mithalas City, second urn in the city reserve",
"Mithalas City, third urn in the city reserve",
"Mithalas City, first bulb at the end of the top path",
"Mithalas City, second bulb at the end of the top path",
"Mithalas City, bulb in the top path",
"Mithalas City, Mithalas Pot",
"Mithalas City, urn in the Castle flower tube entrance",
"Mithalas City, Doll",
"Mithalas City, urn inside a home fish pass",
"Mithalas City Castle, bulb in the flesh hole",
"Mithalas City Castle, Blue Banner",
"Mithalas City Castle, urn in the bedroom",
"Mithalas City Castle, first urn of the single lamp path",
"Mithalas City Castle, second urn of the single lamp path",
"Mithalas City Castle, urn in the bottom room",
"Mithalas City Castle, first urn on the entrance path",
"Mithalas City Castle, second urn on the entrance path",
"Mithalas City Castle, beating the Priests",
"Mithalas City Castle, Trident Head",
"Mithalas Cathedral, first urn in the top right room",
"Mithalas Cathedral, second urn in the top right room",
"Mithalas Cathedral, third urn in the top right room",
"Mithalas Cathedral, urn in the flesh room with fleas",
"Mithalas Cathedral, first urn in the bottom right path",
"Mithalas Cathedral, second urn in the bottom right path",
"Mithalas Cathedral, urn behind the flesh vein",
"Mithalas Cathedral, urn in the top left eyes boss room",
"Mithalas Cathedral, first urn in the path behind the flesh vein",
"Mithalas Cathedral, second urn in the path behind the flesh vein",
"Mithalas Cathedral, third urn in the path behind the flesh vein",
"Mithalas Cathedral, fourth urn in the top right room",
"Mithalas Cathedral, Mithalan Dress",
"Mithalas Cathedral, urn below the left entrance",
"Cathedral Underground, bulb in the center part",
"Cathedral Underground, first bulb in the top left part",
"Cathedral Underground, second bulb in the top left part",
"Cathedral Underground, third bulb in the top left part",
"Cathedral Underground, bulb close to the save crystal",
"Cathedral Underground, bulb in the bottom right path",
"Mithalas boss area, beating Mithalan God",
"Kelp Forest top left area, bulb in the bottom left clearing",
"Kelp Forest top left area, bulb in the path down from the top left clearing",
"Kelp Forest top left area, bulb in the top left clearing",
"Kelp Forest top left area, Jelly Egg",
"Kelp Forest top left area, bulb close to the Verse Egg",
"Kelp Forest top left area, Verse Egg",
"Kelp Forest top right area, bulb under the rock in the right path",
"Kelp Forest top right area, bulb at the left of the center clearing",
"Kelp Forest top right area, bulb in the left path's big room",
"Kelp Forest top right area, bulb in the left path's small room",
"Kelp Forest top right area, bulb at the top of the center clearing",
"Kelp Forest top right area, Black Pearl",
"Kelp Forest top right area, bulb in the top fish pass",
"Kelp Forest bottom left area, bulb close to the spirit crystals",
"Kelp Forest bottom left area, Walker Baby",
"Kelp Forest bottom left area, Transturtle",
"Kelp Forest bottom right area, Odd Container",
"Kelp Forest boss area, beating Drunian God",
"Kelp Forest boss room, bulb at the bottom of the area",
"Kelp Forest bottom left area, Fish Cave puzzle",
"Kelp Forest sprite cave, bulb inside the fish pass",
"Kelp Forest sprite cave, bulb in the second room",
"Kelp Forest sprite cave, Seed Bag",
"Mermog cave, bulb in the left part of the cave",
"Mermog cave, Piranha Egg",
"The Veil top left area, In Li's cave",
"The Veil top left area, bulb under the rock in the top right path",
"The Veil top left area, bulb hidden behind the blocking rock",
"The Veil top left area, Transturtle",
"The Veil top left area, bulb inside the fish pass",
"Turtle cave, Turtle Egg",
"Turtle cave, bulb in Bubble Cliff",
"Turtle cave, Urchin Costume",
"The Veil top right area, bulb in the middle of the wall jump cliff",
"The Veil top right area, Golden Starfish",
"The Veil top right area, bulb at the top of the waterfall",
"The Veil top right area, Transturtle",
"The Veil bottom area, bulb in the left path",
"The Veil bottom area, bulb in the spirit path",
"The Veil bottom area, Verse Egg",
"The Veil bottom area, Stone Head",
"Octopus Cave, Dumbo Egg",
"Octopus Cave, bulb in the path below the Octopus Cave path",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Sun Temple, bulb in the top left part",
"Sun Temple, bulb in the top right part",
"Sun Temple, bulb at the top of the high dark room",
"Sun Temple, Golden Gear",
"Sun Temple, first bulb of the temple",
"Sun Temple, bulb on the right part",
"Sun Temple, bulb in the hidden room of the right part",
"Sun Temple, Sun Key",
"Sun Worm path, first path bulb",
"Sun Worm path, second path bulb",
"Sun Worm path, first cliff bulb",
"Sun Worm path, second cliff bulb",
"Sun Temple boss area, beating Sun God",
"Abyss left area, bulb in hidden path room",
"Abyss left area, bulb in the right part",
"Abyss left area, Glowing Seed",
"Abyss left area, Glowing Plant",
"Abyss left area, bulb in the bottom fish pass",
"Abyss right area, bulb behind the rock in the whale room",
"Abyss right area, bulb in the middle path",
"Abyss right area, bulb behind the rock in the middle path",
"Abyss right area, bulb in the left green room",
"Abyss right area, Transturtle",
"Ice Cave, bulb in the room to the right",
"Ice Cave, first bulb in the top exit room",
"Ice Cave, second bulb in the top exit room",
"Ice Cave, third bulb in the top exit room",
"Ice Cave, bulb in the left room",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"The Whale, Verse Egg",
"Sunken City right area, crate close to the save crystal",
"Sunken City right area, crate in the left bottom room",
"Sunken City left area, crate in the little pipe room",
"Sunken City left area, crate close to the save crystal",
"Sunken City left area, crate before the bedroom",
"Sunken City left area, Girl Costume",
"Sunken City, bulb on top of the boss area",
"The Body center area, breaking Li's cage",
"The Body center area, bulb on the main path blocking tube",
"The Body left area, first bulb in the top face room",
"The Body left area, second bulb in the top face room",
"The Body left area, bulb below the water stream",
"The Body left area, bulb in the top path to the top face room",
"The Body left area, bulb in the bottom face room",
"The Body right area, bulb in the top face room",
"The Body right area, bulb in the top path to the bottom face room",
"The Body right area, bulb in the bottom face room",
"The Body bottom area, bulb in the Jelly Zap room",
"The Body bottom area, bulb in the nautilus room",
"The Body bottom area, Mutant Costume",
"Final Boss area, first bulb in the turtle room",
"Final Boss area, second bulb in the turtle room",
"Final Boss area, third bulb in the turtle room",
"Final Boss area, Transturtle",
"Final Boss area, bulb in the boss third form room",
"Simon Says area, beating Simon Says",
"Beating Fallen God",
"Beating Mithalan God",
"Beating Drunian God",
"Beating Sun God",
"Beating the Golem",
"Beating Nautilus Prime",
"Beating Blaster Peg Prime",
"Beating Mergog",
"Beating Mithalan priests",
"Beating Octopus Prime",
"Beating Crabbius Maximus",
"Beating Mantis Shrimp Prime",
"Beating King Jellyfish God Prime",
"First secret",
"Second secret",
"Third secret",
"Sunken City cleared",
"Objective complete",
AquariaLocationNames.SUN_CRYSTAL,
AquariaLocationNames.HOME_WATERS_TRANSTURTLE,
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_RIGHT_PATH,
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_LEFT_PATH,
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_TO_THE_RIGHT_OF_THE_SAVE_CRYSTAL,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_SMALL_PATH_BEFORE_MITHALAS,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_PATH_FROM_THE_LEFT_ENTRANCE,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_CLEARING_CLOSE_TO_THE_BOTTOM_EXIT,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_BIG_CLEARING_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_BIG_CLEARING_TO_THE_TOP_EXIT,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_FIRST_URN_IN_THE_MITHALAS_EXIT,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_SECOND_URN_IN_THE_MITHALAS_EXIT,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_THIRD_URN_IN_THE_MITHALAS_EXIT,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_TURTLE_ROOM,
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_TRANSTURTLE,
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_BEHIND_THE_CHOMPER_FISH,
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_INSIDE_THE_LOWEST_FISH_PASS,
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_BULB_CLOSE_TO_THE_RIGHT_EXIT,
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_BULB_BEHIND_THE_CHOMPER_FISH,
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_KING_SKULL,
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_RIGHT_PART,
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_LEFT_PART,
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_CENTER_PART,
AquariaLocationNames.ARNASSI_RUINS_SONG_PLANT_SPORE,
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_STATUE,
AquariaLocationNames.ARNASSI_RUINS_TRANSTURTLE,
AquariaLocationNames.ARNASSI_RUINS_CRAB_ARMOR,
AquariaLocationNames.SIMON_SAYS_AREA_TRANSTURTLE,
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_THE_LEFT_CITY_PART,
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_THE_LEFT_CITY_PART,
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_RIGHT_PART,
AquariaLocationNames.MITHALAS_CITY_BULB_AT_THE_TOP_OF_THE_CITY,
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_A_BROKEN_HOME,
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_A_BROKEN_HOME,
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_BOTTOM_LEFT_PART,
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_ONE_OF_THE_HOMES,
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_ONE_OF_THE_HOMES,
AquariaLocationNames.MITHALAS_CITY_FIRST_URN_IN_ONE_OF_THE_HOMES,
AquariaLocationNames.MITHALAS_CITY_SECOND_URN_IN_ONE_OF_THE_HOMES,
AquariaLocationNames.MITHALAS_CITY_FIRST_URN_IN_THE_CITY_RESERVE,
AquariaLocationNames.MITHALAS_CITY_SECOND_URN_IN_THE_CITY_RESERVE,
AquariaLocationNames.MITHALAS_CITY_THIRD_URN_IN_THE_CITY_RESERVE,
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_AT_THE_END_OF_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_AT_THE_END_OF_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_MITHALAS_POT,
AquariaLocationNames.MITHALAS_CITY_URN_IN_THE_CASTLE_FLOWER_TUBE_ENTRANCE,
AquariaLocationNames.MITHALAS_CITY_DOLL,
AquariaLocationNames.MITHALAS_CITY_URN_INSIDE_A_HOME_FISH_PASS,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BULB_IN_THE_FLESH_HOLE,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BLUE_BANNER,
AquariaLocationNames.MITHALAS_CITY_CASTLE_URN_IN_THE_BEDROOM,
AquariaLocationNames.MITHALAS_CITY_CASTLE_FIRST_URN_OF_THE_SINGLE_LAMP_PATH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_SECOND_URN_OF_THE_SINGLE_LAMP_PATH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_URN_IN_THE_BOTTOM_ROOM,
AquariaLocationNames.MITHALAS_CITY_CASTLE_FIRST_URN_ON_THE_ENTRANCE_PATH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_SECOND_URN_ON_THE_ENTRANCE_PATH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.MITHALAS_CITY_CASTLE_TRIDENT_HEAD,
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_TOP_RIGHT_ROOM,
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_TOP_RIGHT_ROOM,
AquariaLocationNames.MITHALAS_CATHEDRAL_THIRD_URN_IN_THE_TOP_RIGHT_ROOM,
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_BOTTOM_RIGHT_PATH,
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_BOTTOM_RIGHT_PATH,
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_BEHIND_THE_FLESH_VEIN,
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_IN_THE_TOP_LEFT_EYES_BOSS_ROOM,
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
AquariaLocationNames.MITHALAS_CATHEDRAL_THIRD_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
AquariaLocationNames.MITHALAS_CATHEDRAL_FOURTH_URN_IN_THE_TOP_RIGHT_ROOM,
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_BELOW_THE_LEFT_ENTRANCE,
AquariaLocationNames.MITHALAS_CATHEDRAL_BULB_IN_THE_FLESH_ROOM_WITH_FLEAS,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_IN_THE_CENTER_PART,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_FIRST_BULB_IN_THE_TOP_LEFT_PART,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_SECOND_BULB_IN_THE_TOP_LEFT_PART,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_THIRD_BULB_IN_THE_TOP_LEFT_PART,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_IN_THE_BOTTOM_RIGHT_PATH,
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_BOTTOM_LEFT_CLEARING,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_PATH_DOWN_FROM_THE_TOP_LEFT_CLEARING,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_TOP_LEFT_CLEARING,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_UNDER_THE_ROCK_IN_THE_RIGHT_PATH,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_AT_THE_LEFT_OF_THE_CENTER_CLEARING,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_LEFT_PATH_S_BIG_ROOM,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_LEFT_PATH_S_SMALL_ROOM,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_CENTER_CLEARING,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BLACK_PEARL,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_TOP_FISH_PASS,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_TRANSTURTLE,
AquariaLocationNames.KELP_FOREST_BOTTOM_RIGHT_AREA_ODD_CONTAINER,
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
AquariaLocationNames.KELP_FOREST_BOSS_ROOM_BULB_AT_THE_BOTTOM_OF_THE_AREA,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_FISH_CAVE_PUZZLE,
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_INSIDE_THE_FISH_PASS,
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_IN_THE_SECOND_ROOM,
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_SEED_BAG,
AquariaLocationNames.MERMOG_CAVE_BULB_IN_THE_LEFT_PART_OF_THE_CAVE,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_IN_LI_S_CAVE,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_TOP_RIGHT_PATH,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_HIDDEN_BEHIND_THE_BLOCKING_ROCK,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_TRANSTURTLE,
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_INSIDE_THE_FISH_PASS,
AquariaLocationNames.TURTLE_CAVE_TURTLE_EGG,
AquariaLocationNames.TURTLE_CAVE_BULB_IN_BUBBLE_CLIFF,
AquariaLocationNames.TURTLE_CAVE_URCHIN_COSTUME,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_IN_THE_MIDDLE_OF_THE_WALL_JUMP_CLIFF,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_GOLDEN_STARFISH,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_TRANSTURTLE,
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_LEFT_PATH,
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_SPIRIT_PATH,
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_VERSE_EGG,
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_STONE_HEAD,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.OCTOPUS_CAVE_BULB_IN_THE_PATH_BELOW_THE_OCTOPUS_CAVE_PATH,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_LEFT_PART,
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_RIGHT_PART,
AquariaLocationNames.SUN_TEMPLE_BULB_AT_THE_TOP_OF_THE_HIGH_DARK_ROOM,
AquariaLocationNames.SUN_TEMPLE_GOLDEN_GEAR,
AquariaLocationNames.SUN_TEMPLE_FIRST_BULB_OF_THE_TEMPLE,
AquariaLocationNames.SUN_TEMPLE_BULB_ON_THE_RIGHT_PART,
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_PATH_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_PATH_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_HIDDEN_PATH_ROOM,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_RIGHT_PART,
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_SEED,
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_PLANT,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_MIDDLE_PATH,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_MIDDLE_PATH,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_LEFT_GREEN_ROOM,
AquariaLocationNames.ABYSS_RIGHT_AREA_TRANSTURTLE,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
AquariaLocationNames.THE_WHALE_VERSE_EGG,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_FIRST_BULB_IN_THE_TURTLE_ROOM,
AquariaLocationNames.FINAL_BOSS_AREA_SECOND_BULB_IN_THE_TURTLE_ROOM,
AquariaLocationNames.FINAL_BOSS_AREA_THIRD_BULB_IN_THE_TURTLE_ROOM,
AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.SIMON_SAYS_AREA_BEATING_SIMON_SAYS,
AquariaLocationNames.BEATING_FALLEN_GOD,
AquariaLocationNames.BEATING_MITHALAN_GOD,
AquariaLocationNames.BEATING_DRUNIAN_GOD,
AquariaLocationNames.BEATING_LUMEREAN_GOD,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.BEATING_NAUTILUS_PRIME,
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
AquariaLocationNames.BEATING_MERGOG,
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.BEATING_CRABBIUS_MAXIMUS,
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
AquariaLocationNames.FIRST_SECRET,
AquariaLocationNames.SECOND_SECRET,
AquariaLocationNames.THIRD_SECRET,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.OBJECTIVE_COMPLETE,
]
class AquariaTestBase(WorldTestBase):

View File

@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
class BeastFormAccessTest(AquariaTestBase):
@@ -13,16 +15,16 @@ class BeastFormAccessTest(AquariaTestBase):
def test_beast_form_location(self) -> None:
"""Test locations that require beast form"""
locations = [
"Mermog cave, Piranha Egg",
"Kelp Forest top left area, Jelly Egg",
"Mithalas Cathedral, Mithalan Dress",
"The Veil top right area, bulb at the top of the waterfall",
"Sunken City, bulb on top of the boss area",
"Octopus Cave, Dumbo Egg",
"Beating the Golem",
"Beating Mergog",
"Beating Octopus Prime",
"Sunken City cleared",
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.BEATING_MERGOG,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
]
items = [["Beast form"]]
items = [[ItemNames.BEAST_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
class BeastForArnassiArmormAccessTest(AquariaTestBase):
@@ -13,27 +15,27 @@ class BeastForArnassiArmormAccessTest(AquariaTestBase):
def test_beast_form_arnassi_armor_location(self) -> None:
"""Test locations that require beast form or arnassi armor"""
locations = [
"Mithalas City Castle, beating the Priests",
"Arnassi Ruins, Crab Armor",
"Arnassi Ruins, Song Plant Spore",
"Mithalas City, first bulb at the end of the top path",
"Mithalas City, second bulb at the end of the top path",
"Mithalas City, bulb in the top path",
"Mithalas City, Mithalas Pot",
"Mithalas City, urn in the Castle flower tube entrance",
"Mermog cave, Piranha Egg",
"Mithalas Cathedral, Mithalan Dress",
"Kelp Forest top left area, Jelly Egg",
"The Veil top right area, bulb in the middle of the wall jump cliff",
"The Veil top right area, bulb at the top of the waterfall",
"Sunken City, bulb on top of the boss area",
"Octopus Cave, Dumbo Egg",
"Beating the Golem",
"Beating Mergog",
"Beating Crabbius Maximus",
"Beating Octopus Prime",
"Beating Mithalan priests",
"Sunken City cleared"
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.ARNASSI_RUINS_CRAB_ARMOR,
AquariaLocationNames.ARNASSI_RUINS_SONG_PLANT_SPORE,
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_AT_THE_END_OF_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_AT_THE_END_OF_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_TOP_PATH,
AquariaLocationNames.MITHALAS_CITY_MITHALAS_POT,
AquariaLocationNames.MITHALAS_CITY_URN_IN_THE_CASTLE_FLOWER_TUBE_ENTRANCE,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_IN_THE_MIDDLE_OF_THE_WALL_JUMP_CLIFF,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.BEATING_MERGOG,
AquariaLocationNames.BEATING_CRABBIUS_MAXIMUS,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
AquariaLocationNames.SUNKEN_CITY_CLEARED
]
items = [["Beast form", "Arnassi Armor"]]
items = [[ItemNames.BEAST_FORM, ItemNames.ARNASSI_ARMOR]]
self.assertAccessDependency(locations, items)

View File

@@ -6,31 +6,36 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase, after_home_water_locations
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import UnconfineHomeWater, EarlyBindSong
class BindSongAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the bind song"""
options = {
"bind_song_needed_to_get_under_rock_bulb": False,
"unconfine_home_water": UnconfineHomeWater.option_off,
"early_bind_song": EarlyBindSong.option_off
}
def test_bind_song_location(self) -> None:
"""Test locations that require Bind song"""
locations = [
"Verse Cave right area, Big Seed",
"Home Water, bulb in the path below Nautilus Prime",
"Home Water, bulb in the bottom left room",
"Home Water, Nautilus Egg",
"Song Cave, Verse Egg",
"Energy Temple first area, beating the Energy Statue",
"Energy Temple first area, bulb in the bottom room blocked by a rock",
"Energy Temple first area, Energy Idol",
"Energy Temple second area, bulb under the rock",
"Energy Temple bottom entrance, Krotite Armor",
"Energy Temple third area, bulb in the bottom path",
"Energy Temple boss area, Fallen God Tooth",
"Energy Temple blaster room, Blaster Egg",
AquariaLocationNames.VERSE_CAVE_RIGHT_AREA_BIG_SEED,
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_PATH_BELOW_NAUTILUS_PRIME,
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_BOTTOM_LEFT_ROOM,
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
AquariaLocationNames.SONG_CAVE_VERSE_EGG,
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BEATING_THE_ENERGY_STATUE,
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BULB_IN_THE_BOTTOM_ROOM_BLOCKED_BY_A_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
*after_home_water_locations
]
items = [["Bind song"]]
items = [[ItemNames.BIND_SONG]]
self.assertAccessDependency(locations, items)

View File

@@ -7,6 +7,8 @@ Description: Unit test used to test accessibility of locations with and without
from . import AquariaTestBase
from .test_bind_song_access import after_home_water_locations
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
class BindSongOptionAccessTest(AquariaTestBase):
@@ -18,25 +20,25 @@ class BindSongOptionAccessTest(AquariaTestBase):
def test_bind_song_location(self) -> None:
"""Test locations that require Bind song with the bind song needed option activated"""
locations = [
"Verse Cave right area, Big Seed",
"Verse Cave left area, bulb under the rock at the end of the path",
"Home Water, bulb under the rock in the left path from the Verse Cave",
"Song Cave, bulb under the rock close to the song door",
"Song Cave, bulb under the rock in the path to the singing statues",
"Naija's Home, bulb under the rock at the right of the main path",
"Home Water, bulb in the path below Nautilus Prime",
"Home Water, bulb in the bottom left room",
"Home Water, Nautilus Egg",
"Song Cave, Verse Egg",
"Energy Temple first area, beating the Energy Statue",
"Energy Temple first area, bulb in the bottom room blocked by a rock",
"Energy Temple first area, Energy Idol",
"Energy Temple second area, bulb under the rock",
"Energy Temple bottom entrance, Krotite Armor",
"Energy Temple third area, bulb in the bottom path",
"Energy Temple boss area, Fallen God Tooth",
"Energy Temple blaster room, Blaster Egg",
AquariaLocationNames.VERSE_CAVE_RIGHT_AREA_BIG_SEED,
AquariaLocationNames.VERSE_CAVE_LEFT_AREA_BULB_UNDER_THE_ROCK_AT_THE_END_OF_THE_PATH,
AquariaLocationNames.HOME_WATERS_BULB_UNDER_THE_ROCK_IN_THE_LEFT_PATH_FROM_THE_VERSE_CAVE,
AquariaLocationNames.SONG_CAVE_BULB_UNDER_THE_ROCK_CLOSE_TO_THE_SONG_DOOR,
AquariaLocationNames.SONG_CAVE_BULB_UNDER_THE_ROCK_IN_THE_PATH_TO_THE_SINGING_STATUES,
AquariaLocationNames.NAIJA_S_HOME_BULB_UNDER_THE_ROCK_AT_THE_RIGHT_OF_THE_MAIN_PATH,
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_PATH_BELOW_NAUTILUS_PRIME,
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_BOTTOM_LEFT_ROOM,
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
AquariaLocationNames.SONG_CAVE_VERSE_EGG,
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BEATING_THE_ENERGY_STATUE,
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BULB_IN_THE_BOTTOM_ROOM_BLOCKED_BY_A_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
*after_home_water_locations
]
items = [["Bind song"]]
items = [[ItemNames.BIND_SONG]]
self.assertAccessDependency(locations, items)

View File

@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the home water
"""
from . import AquariaTestBase
from ..Options import UnconfineHomeWater, EarlyEnergyForm
class ConfinedHomeWaterAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of region with the unconfine home water option disabled"""
options = {
"unconfine_home_water": 0,
"early_energy_form": False
"unconfine_home_water": UnconfineHomeWater.option_off,
"early_energy_form": EarlyEnergyForm.option_off
}
def test_confine_home_water_location(self) -> None:
"""Test region accessible with confined home water"""
self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area")
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
self.assertFalse(self.can_reach_region("Open Waters top left area"), "Can reach Open Waters top left area")
self.assertFalse(self.can_reach_region("Home Waters, turtle room"), "Can reach Home Waters, turtle room")

View File

@@ -5,22 +5,25 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import TurtleRandomizer
class LiAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the dual song"""
options = {
"turtle_randomizer": 1,
"turtle_randomizer": TurtleRandomizer.option_all,
}
def test_li_song_location(self) -> None:
"""Test locations that require the dual song"""
locations = [
"The Body bottom area, bulb in the Jelly Zap room",
"The Body bottom area, bulb in the nautilus room",
"The Body bottom area, Mutant Costume",
"Final Boss area, bulb in the boss third form room",
"Objective complete"
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.OBJECTIVE_COMPLETE
]
items = [["Dual form"]]
items = [[ItemNames.DUAL_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -6,28 +6,31 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import EarlyEnergyForm
class EnergyFormAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the energy form"""
options = {
"early_energy_form": False,
"early_energy_form": EarlyEnergyForm.option_off
}
def test_energy_form_location(self) -> None:
"""Test locations that require Energy form"""
locations = [
"Energy Temple second area, bulb under the rock",
"Energy Temple third area, bulb in the bottom path",
"The Body left area, first bulb in the top face room",
"The Body left area, second bulb in the top face room",
"The Body left area, bulb below the water stream",
"The Body left area, bulb in the top path to the top face room",
"The Body left area, bulb in the bottom face room",
"The Body right area, bulb in the top path to the bottom face room",
"The Body right area, bulb in the bottom face room",
"Final Boss area, bulb in the boss third form room",
"Objective complete",
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.OBJECTIVE_COMPLETE,
]
items = [["Energy form"]]
items = [[ItemNames.ENERGY_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -5,88 +5,74 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import EarlyEnergyForm, TurtleRandomizer
class EnergyFormDualFormAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)"""
options = {
"early_energy_form": False,
"early_energy_form": EarlyEnergyForm.option_off,
"turtle_randomizer": TurtleRandomizer.option_all
}
def test_energy_form_or_dual_form_location(self) -> None:
"""Test locations that require Energy form or dual form"""
locations = [
"Naija's Home, bulb after the energy door",
"Home Water, Nautilus Egg",
"Energy Temple second area, bulb under the rock",
"Energy Temple bottom entrance, Krotite Armor",
"Energy Temple third area, bulb in the bottom path",
"Energy Temple blaster room, Blaster Egg",
"Energy Temple boss area, Fallen God Tooth",
"Mithalas City Castle, beating the Priests",
"Mithalas boss area, beating Mithalan God",
"Mithalas Cathedral, first urn in the top right room",
"Mithalas Cathedral, second urn in the top right room",
"Mithalas Cathedral, third urn in the top right room",
"Mithalas Cathedral, urn in the flesh room with fleas",
"Mithalas Cathedral, first urn in the bottom right path",
"Mithalas Cathedral, second urn in the bottom right path",
"Mithalas Cathedral, urn behind the flesh vein",
"Mithalas Cathedral, urn in the top left eyes boss room",
"Mithalas Cathedral, first urn in the path behind the flesh vein",
"Mithalas Cathedral, second urn in the path behind the flesh vein",
"Mithalas Cathedral, third urn in the path behind the flesh vein",
"Mithalas Cathedral, fourth urn in the top right room",
"Mithalas Cathedral, Mithalan Dress",
"Mithalas Cathedral, urn below the left entrance",
"Kelp Forest top left area, bulb close to the Verse Egg",
"Kelp Forest top left area, Verse Egg",
"Kelp Forest boss area, beating Drunian God",
"Mermog cave, Piranha Egg",
"Octopus Cave, Dumbo Egg",
"Sun Temple boss area, beating Sun God",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"Sunken City right area, crate close to the save crystal",
"Sunken City right area, crate in the left bottom room",
"Sunken City left area, crate in the little pipe room",
"Sunken City left area, crate close to the save crystal",
"Sunken City left area, crate before the bedroom",
"Sunken City left area, Girl Costume",
"Sunken City, bulb on top of the boss area",
"The Body center area, breaking Li's cage",
"The Body center area, bulb on the main path blocking tube",
"The Body left area, first bulb in the top face room",
"The Body left area, second bulb in the top face room",
"The Body left area, bulb below the water stream",
"The Body left area, bulb in the top path to the top face room",
"The Body left area, bulb in the bottom face room",
"The Body right area, bulb in the top face room",
"The Body right area, bulb in the top path to the bottom face room",
"The Body right area, bulb in the bottom face room",
"The Body bottom area, bulb in the Jelly Zap room",
"The Body bottom area, bulb in the nautilus room",
"The Body bottom area, Mutant Costume",
"Final Boss area, bulb in the boss third form room",
"Final Boss area, first bulb in the turtle room",
"Final Boss area, second bulb in the turtle room",
"Final Boss area, third bulb in the turtle room",
"Final Boss area, Transturtle",
"Beating Fallen God",
"Beating Blaster Peg Prime",
"Beating Mithalan God",
"Beating Drunian God",
"Beating Sun God",
"Beating the Golem",
"Beating Nautilus Prime",
"Beating Mergog",
"Beating Mithalan priests",
"Beating Octopus Prime",
"Beating King Jellyfish God Prime",
"Beating the Golem",
"Sunken City cleared",
"First secret",
"Objective complete"
AquariaLocationNames.NAIJA_S_HOME_BULB_AFTER_THE_ENERGY_DOOR,
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.BEATING_FALLEN_GOD,
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
AquariaLocationNames.BEATING_MITHALAN_GOD,
AquariaLocationNames.BEATING_DRUNIAN_GOD,
AquariaLocationNames.BEATING_LUMEREAN_GOD,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.BEATING_NAUTILUS_PRIME,
AquariaLocationNames.BEATING_MERGOG,
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.FIRST_SECRET,
AquariaLocationNames.OBJECTIVE_COMPLETE
]
items = [["Energy form", "Dual form", "Li and Li song", "Body tongue cleared"]]
items = [[ItemNames.ENERGY_FORM, ItemNames.DUAL_FORM, ItemNames.LI_AND_LI_SONG, ItemNames.BODY_TONGUE_CLEARED]]
self.assertAccessDependency(locations, items)

View File

@@ -5,33 +5,36 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import TurtleRandomizer
class FishFormAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the fish form"""
options = {
"turtle_randomizer": 1,
"turtle_randomizer": TurtleRandomizer.option_all,
}
def test_fish_form_location(self) -> None:
"""Test locations that require fish form"""
locations = [
"The Veil top left area, bulb inside the fish pass",
"Energy Temple first area, Energy Idol",
"Mithalas City, Doll",
"Mithalas City, urn inside a home fish pass",
"Kelp Forest top right area, bulb in the top fish pass",
"The Veil bottom area, Verse Egg",
"Open Water bottom left area, bulb inside the lowest fish pass",
"Kelp Forest top left area, bulb close to the Verse Egg",
"Kelp Forest top left area, Verse Egg",
"Mermog cave, bulb in the left part of the cave",
"Mermog cave, Piranha Egg",
"Beating Mergog",
"Octopus Cave, Dumbo Egg",
"Octopus Cave, bulb in the path below the Octopus Cave path",
"Beating Octopus Prime",
"Abyss left area, bulb in the bottom fish pass"
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_INSIDE_THE_FISH_PASS,
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
AquariaLocationNames.MITHALAS_CITY_DOLL,
AquariaLocationNames.MITHALAS_CITY_URN_INSIDE_A_HOME_FISH_PASS,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_TOP_FISH_PASS,
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_VERSE_EGG,
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_INSIDE_THE_LOWEST_FISH_PASS,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
AquariaLocationNames.MERMOG_CAVE_BULB_IN_THE_LEFT_PART_OF_THE_CAVE,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.BEATING_MERGOG,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.OCTOPUS_CAVE_BULB_IN_THE_PATH_BELOW_THE_OCTOPUS_CAVE_PATH,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS
]
items = [["Fish form"]]
items = [[ItemNames.FISH_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -5,41 +5,44 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import TurtleRandomizer
class LiAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without Li"""
options = {
"turtle_randomizer": 1,
"turtle_randomizer": TurtleRandomizer.option_all,
}
def test_li_song_location(self) -> None:
"""Test locations that require Li"""
locations = [
"Sunken City right area, crate close to the save crystal",
"Sunken City right area, crate in the left bottom room",
"Sunken City left area, crate in the little pipe room",
"Sunken City left area, crate close to the save crystal",
"Sunken City left area, crate before the bedroom",
"Sunken City left area, Girl Costume",
"Sunken City, bulb on top of the boss area",
"The Body center area, breaking Li's cage",
"The Body center area, bulb on the main path blocking tube",
"The Body left area, first bulb in the top face room",
"The Body left area, second bulb in the top face room",
"The Body left area, bulb below the water stream",
"The Body left area, bulb in the top path to the top face room",
"The Body left area, bulb in the bottom face room",
"The Body right area, bulb in the top face room",
"The Body right area, bulb in the top path to the bottom face room",
"The Body right area, bulb in the bottom face room",
"The Body bottom area, bulb in the Jelly Zap room",
"The Body bottom area, bulb in the nautilus room",
"The Body bottom area, Mutant Costume",
"Final Boss area, bulb in the boss third form room",
"Beating the Golem",
"Sunken City cleared",
"Objective complete"
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.OBJECTIVE_COMPLETE
]
items = [["Li and Li song", "Body tongue cleared"]]
items = [[ItemNames.LI_AND_LI_SONG, ItemNames.BODY_TONGUE_CLEARED]]
self.assertAccessDependency(locations, items)

View File

@@ -5,12 +5,15 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import TurtleRandomizer
class LightAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without light"""
options = {
"turtle_randomizer": 1,
"turtle_randomizer": TurtleRandomizer.option_all,
"light_needed_to_get_to_dark_places": True,
}
@@ -19,52 +22,52 @@ class LightAccessTest(AquariaTestBase):
locations = [
# Since the `assertAccessDependency` sweep for events even if I tell it not to, those location cannot be
# tested.
# "Third secret",
# "Sun Temple, bulb in the top left part",
# "Sun Temple, bulb in the top right part",
# "Sun Temple, bulb at the top of the high dark room",
# "Sun Temple, Golden Gear",
# "Sun Worm path, first path bulb",
# "Sun Worm path, second path bulb",
# "Sun Worm path, first cliff bulb",
"Octopus Cave, Dumbo Egg",
"Kelp Forest bottom right area, Odd Container",
"Kelp Forest top right area, Black Pearl",
"Abyss left area, bulb in hidden path room",
"Abyss left area, bulb in the right part",
"Abyss left area, Glowing Seed",
"Abyss left area, Glowing Plant",
"Abyss left area, bulb in the bottom fish pass",
"Abyss right area, bulb behind the rock in the whale room",
"Abyss right area, bulb in the middle path",
"Abyss right area, bulb behind the rock in the middle path",
"Abyss right area, bulb in the left green room",
"Ice Cave, bulb in the room to the right",
"Ice Cave, first bulb in the top exit room",
"Ice Cave, second bulb in the top exit room",
"Ice Cave, third bulb in the top exit room",
"Ice Cave, bulb in the left room",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Beating Mantis Shrimp Prime",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"Beating King Jellyfish God Prime",
"The Whale, Verse Egg",
"First secret",
"Sunken City right area, crate close to the save crystal",
"Sunken City right area, crate in the left bottom room",
"Sunken City left area, crate in the little pipe room",
"Sunken City left area, crate close to the save crystal",
"Sunken City left area, crate before the bedroom",
"Sunken City left area, Girl Costume",
"Sunken City, bulb on top of the boss area",
"Sunken City cleared",
"Beating the Golem",
"Beating Octopus Prime",
"Final Boss area, bulb in the boss third form room",
"Objective complete",
# AquariaLocationNames.THIRD_SECRET,
# AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_LEFT_PART,
# AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_RIGHT_PART,
# AquariaLocationNames.SUN_TEMPLE_BULB_AT_THE_TOP_OF_THE_HIGH_DARK_ROOM,
# AquariaLocationNames.SUN_TEMPLE_GOLDEN_GEAR,
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_PATH_BULB,
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_PATH_BULB,
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.KELP_FOREST_BOTTOM_RIGHT_AREA_ODD_CONTAINER,
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BLACK_PEARL,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_HIDDEN_PATH_ROOM,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_RIGHT_PART,
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_SEED,
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_PLANT,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_MIDDLE_PATH,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_MIDDLE_PATH,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_LEFT_GREEN_ROOM,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
AquariaLocationNames.THE_WHALE_VERSE_EGG,
AquariaLocationNames.FIRST_SECRET,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.OBJECTIVE_COMPLETE,
]
items = [["Sun form", "Baby Dumbo", "Has sun crystal"]]
items = [[ItemNames.SUN_FORM, ItemNames.BABY_DUMBO, ItemNames.HAS_SUN_CRYSTAL]]
self.assertAccessDependency(locations, items)

View File

@@ -5,53 +5,56 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
from ..Options import TurtleRandomizer
class NatureFormAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of locations with and without the nature form"""
options = {
"turtle_randomizer": 1,
"turtle_randomizer": TurtleRandomizer.option_all,
}
def test_nature_form_location(self) -> None:
"""Test locations that require nature form"""
locations = [
"Song Cave, Anemone Seed",
"Energy Temple blaster room, Blaster Egg",
"Beating Blaster Peg Prime",
"Kelp Forest top left area, Verse Egg",
"Kelp Forest top left area, bulb close to the Verse Egg",
"Mithalas City Castle, beating the Priests",
"Kelp Forest sprite cave, bulb in the second room",
"Kelp Forest sprite cave, Seed Bag",
"Beating Mithalan priests",
"Abyss left area, bulb in the bottom fish pass",
"Bubble Cave, Verse Egg",
"Beating Mantis Shrimp Prime",
"Sunken City right area, crate close to the save crystal",
"Sunken City right area, crate in the left bottom room",
"Sunken City left area, crate in the little pipe room",
"Sunken City left area, crate close to the save crystal",
"Sunken City left area, crate before the bedroom",
"Sunken City left area, Girl Costume",
"Sunken City, bulb on top of the boss area",
"Beating the Golem",
"Sunken City cleared",
"The Body center area, breaking Li's cage",
"The Body center area, bulb on the main path blocking tube",
"The Body left area, first bulb in the top face room",
"The Body left area, second bulb in the top face room",
"The Body left area, bulb below the water stream",
"The Body left area, bulb in the top path to the top face room",
"The Body left area, bulb in the bottom face room",
"The Body right area, bulb in the top face room",
"The Body right area, bulb in the top path to the bottom face room",
"The Body right area, bulb in the bottom face room",
"The Body bottom area, bulb in the Jelly Zap room",
"The Body bottom area, bulb in the nautilus room",
"The Body bottom area, Mutant Costume",
"Final Boss area, bulb in the boss third form room",
"Objective complete"
AquariaLocationNames.SONG_CAVE_ANEMONE_SEED,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_IN_THE_SECOND_ROOM,
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_SEED_BAG,
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.OBJECTIVE_COMPLETE
]
items = [["Nature form"]]
items = [[ItemNames.NATURE_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -6,6 +6,7 @@ Description: Unit test used to test that no progression items can be put in hard
from . import AquariaTestBase
from BaseClasses import ItemClassification
from ..Locations import AquariaLocationNames
class UNoProgressionHardHiddenTest(AquariaTestBase):
@@ -15,31 +16,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
}
unfillable_locations = [
"Energy Temple boss area, Fallen God Tooth",
"Mithalas boss area, beating Mithalan God",
"Kelp Forest boss area, beating Drunian God",
"Sun Temple boss area, beating Sun God",
"Sunken City, bulb on top of the boss area",
"Home Water, Nautilus Egg",
"Energy Temple blaster room, Blaster Egg",
"Mithalas City Castle, beating the Priests",
"Mermog cave, Piranha Egg",
"Octopus Cave, Dumbo Egg",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"Final Boss area, bulb in the boss third form room",
"Sun Worm path, first cliff bulb",
"Sun Worm path, second cliff bulb",
"The Veil top right area, bulb at the top of the waterfall",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Kelp Forest bottom left area, bulb close to the spirit crystals",
"Kelp Forest bottom left area, Walker Baby",
"Sun Temple, Sun Key",
"The Body bottom area, Mutant Costume",
"Sun Temple, bulb in the hidden room of the right part",
"Arnassi Ruins, Arnassi Armor",
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
]
def test_unconfine_home_water_both_location_fillable(self) -> None:
@@ -49,7 +50,7 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
for location in self.unfillable_locations:
for item_name in self.world.item_names:
item = self.get_item_by_name(item_name)
if item.classification == ItemClassification.progression:
if item.advancement:
self.assertFalse(
self.world.get_location(location).can_fill(self.multiworld.state, item, False),
"The location \"" + location + "\" can be filled with \"" + item_name + "\"")

View File

@@ -5,6 +5,7 @@ Description: Unit test used to test that progression items can be put in hard or
"""
from . import AquariaTestBase
from ..Locations import AquariaLocationNames
class UNoProgressionHardHiddenTest(AquariaTestBase):
@@ -14,31 +15,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
}
unfillable_locations = [
"Energy Temple boss area, Fallen God Tooth",
"Mithalas boss area, beating Mithalan God",
"Kelp Forest boss area, beating Drunian God",
"Sun Temple boss area, beating Sun God",
"Sunken City, bulb on top of the boss area",
"Home Water, Nautilus Egg",
"Energy Temple blaster room, Blaster Egg",
"Mithalas City Castle, beating the Priests",
"Mermog cave, Piranha Egg",
"Octopus Cave, Dumbo Egg",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"Final Boss area, bulb in the boss third form room",
"Sun Worm path, first cliff bulb",
"Sun Worm path, second cliff bulb",
"The Veil top right area, bulb at the top of the waterfall",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Kelp Forest bottom left area, bulb close to the spirit crystals",
"Kelp Forest bottom left area, Walker Baby",
"Sun Temple, Sun Key",
"The Body bottom area, Mutant Costume",
"Sun Temple, bulb in the hidden room of the right part",
"Arnassi Ruins, Arnassi Armor",
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
]
def test_unconfine_home_water_both_location_fillable(self) -> None:

View File

@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
class SpiritFormAccessTest(AquariaTestBase):
@@ -13,23 +15,23 @@ class SpiritFormAccessTest(AquariaTestBase):
def test_spirit_form_location(self) -> None:
"""Test locations that require spirit form"""
locations = [
"The Veil bottom area, bulb in the spirit path",
"Mithalas City Castle, Trident Head",
"Open Water skeleton path, King Skull",
"Kelp Forest bottom left area, Walker Baby",
"Abyss right area, bulb behind the rock in the whale room",
"The Whale, Verse Egg",
"Ice Cave, bulb in the room to the right",
"Ice Cave, first bulb in the top exit room",
"Ice Cave, second bulb in the top exit room",
"Ice Cave, third bulb in the top exit room",
"Ice Cave, bulb in the left room",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Sunken City left area, Girl Costume",
"Beating Mantis Shrimp Prime",
"First secret",
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_SPIRIT_PATH,
AquariaLocationNames.MITHALAS_CITY_CASTLE_TRIDENT_HEAD,
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_KING_SKULL,
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
AquariaLocationNames.THE_WHALE_VERSE_EGG,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
AquariaLocationNames.FIRST_SECRET,
]
items = [["Spirit form"]]
items = [[ItemNames.SPIRIT_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
"""
from . import AquariaTestBase
from ..Items import ItemNames
from ..Locations import AquariaLocationNames
class SunFormAccessTest(AquariaTestBase):
@@ -13,16 +15,16 @@ class SunFormAccessTest(AquariaTestBase):
def test_sun_form_location(self) -> None:
"""Test locations that require sun form"""
locations = [
"First secret",
"The Whale, Verse Egg",
"Abyss right area, bulb behind the rock in the whale room",
"Octopus Cave, Dumbo Egg",
"Beating Octopus Prime",
"Sunken City, bulb on top of the boss area",
"Beating the Golem",
"Sunken City cleared",
"Final Boss area, bulb in the boss third form room",
"Objective complete"
AquariaLocationNames.FIRST_SECRET,
AquariaLocationNames.THE_WHALE_VERSE_EGG,
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
AquariaLocationNames.BEATING_THE_GOLEM,
AquariaLocationNames.SUNKEN_CITY_CLEARED,
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
AquariaLocationNames.OBJECTIVE_COMPLETE
]
items = [["Sun form"]]
items = [[ItemNames.SUN_FORM]]
self.assertAccessDependency(locations, items)

View File

@@ -6,16 +6,17 @@ Description: Unit test used to test accessibility of region with the unconfined
"""
from . import AquariaTestBase
from ..Options import UnconfineHomeWater, EarlyEnergyForm
class UnconfineHomeWaterBothAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
options = {
"unconfine_home_water": 3,
"early_energy_form": False
"unconfine_home_water": UnconfineHomeWater.option_via_both,
"early_energy_form": EarlyEnergyForm.option_off
}
def test_unconfine_home_water_both_location(self) -> None:
"""Test locations accessible with unconfined home water via energy door and transportation turtle"""
self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area")
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
self.assertTrue(self.can_reach_region("Open Waters top left area"), "Cannot reach Open Waters top left area")
self.assertTrue(self.can_reach_region("Home Waters, turtle room"), "Cannot reach Home Waters, turtle room")

View File

@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the unconfined
"""
from . import AquariaTestBase
from ..Options import UnconfineHomeWater, EarlyEnergyForm
class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
options = {
"unconfine_home_water": 1,
"early_energy_form": False
"unconfine_home_water": UnconfineHomeWater.option_via_energy_door,
"early_energy_form": EarlyEnergyForm.option_off
}
def test_unconfine_home_water_energy_door_location(self) -> None:
"""Test locations accessible with unconfined home water via energy door"""
self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area")
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
self.assertTrue(self.can_reach_region("Open Waters top left area"), "Cannot reach Open Waters top left area")
self.assertFalse(self.can_reach_region("Home Waters, turtle room"), "Can reach Home Waters, turtle room")

View File

@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the unconfined
"""
from . import AquariaTestBase
from ..Options import UnconfineHomeWater, EarlyEnergyForm
class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase):
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
options = {
"unconfine_home_water": 2,
"early_energy_form": False
"unconfine_home_water": UnconfineHomeWater.option_via_transturtle,
"early_energy_form": EarlyEnergyForm.option_off
}
def test_unconfine_home_water_transturtle_location(self) -> None:
"""Test locations accessible with unconfined home water via transportation turtle"""
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area")
self.assertTrue(self.can_reach_region("Home Waters, turtle room"), "Cannot reach Home Waters, turtle room")
self.assertFalse(self.can_reach_region("Open Waters top left area"), "Can reach Open Waters top left area")

View File

@@ -89,7 +89,7 @@ location_names: Dict[str, str] = {
"RESCUED_CHERUB_15": "DC: Top of elevator Child of Moonlight",
"Lady[D01Z05S22]": "DC: Lady of the Six Sorrows, from MD",
"QI75": "DC: Chalice room",
"Sword[D01Z05S24]": "DC: Mea culpa altar",
"Sword[D01Z05S24]": "DC: Mea Culpa altar",
"CO44": "DC: Elevator shaft ledge",
"RESCUED_CHERUB_22": "DC: Elevator shaft Child of Moonlight",
"Lady[D01Z05S26]": "DC: Lady of the Six Sorrows, elevator shaft",

View File

@@ -1,17 +1,20 @@
from dataclasses import dataclass
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, OptionGroup
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, StartInventoryPool, OptionGroup
import random
class ChoiceIsRandom(Choice):
randomized: bool = False
randomized: bool
def __init__(self, value: int, randomized: bool = False):
super().__init__(value)
self.randomized = randomized
@classmethod
def from_text(cls, text: str) -> Choice:
text = text.lower()
if text == "random":
cls.randomized = True
return cls(random.choice(list(cls.name_lookup)))
return cls(random.choice(list(cls.name_lookup)), True)
for option_name, value in cls.options.items():
if option_name == text:
return cls(value)
@@ -213,6 +216,7 @@ class BlasphemousDeathLink(DeathLink):
@dataclass
class BlasphemousOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
prie_dieu_warp: PrieDieuWarp
skip_cutscenes: SkipCutscenes
corpse_hints: CorpseHints

View File

@@ -67,7 +67,8 @@ class BlasphemousWorld(World):
def generate_early(self):
if not self.options.starting_location.randomized:
if self.options.starting_location == "mourning_havoc" and self.options.difficulty < 2:
if (self.options.starting_location == "knot_of_words" or self.options.starting_location == "rooftops" \
or self.options.starting_location == "mourning_havoc") and self.options.difficulty < 2:
raise OptionError(f"[Blasphemous - '{self.player_name}'] "
f"{self.options.starting_location} cannot be chosen if Difficulty is lower than Hard.")
@@ -83,6 +84,8 @@ class BlasphemousWorld(World):
locations: List[int] = [ 0, 1, 2, 3, 4, 5, 6 ]
if self.options.difficulty < 2:
locations.remove(4)
locations.remove(5)
locations.remove(6)
if self.options.dash_shuffle:
@@ -103,6 +106,9 @@ class BlasphemousWorld(World):
if not self.options.wall_climb_shuffle:
self.multiworld.push_precollected(self.create_item("Wall Climb Ability"))
if self.options.thorn_shuffle == "local_only":
self.options.local_items.value.add("Thorn Upgrade")
if not self.options.boots_of_pleading:
self.disabled_locations.append("RE401")
@@ -137,12 +143,6 @@ class BlasphemousWorld(World):
]
skipped_items = []
junk: int = 0
for item, count in self.options.start_inventory.value.items():
for _ in range(count):
skipped_items.append(item)
junk += 1
skipped_items.extend(unrandomized_dict.values())
@@ -194,9 +194,6 @@ class BlasphemousWorld(World):
for _ in range(count):
pool.append(self.create_item(item["name"]))
for _ in range(junk):
pool.append(self.create_item(self.get_filler_item_name()))
self.multiworld.itempool += pool
self.place_items_from_dict(unrandomized_dict)
@@ -209,9 +206,6 @@ class BlasphemousWorld(World):
if not self.options.skill_randomizer:
self.place_items_from_dict(skill_dict)
if self.options.thorn_shuffle == "local_only":
self.options.local_items.value.add("Thorn Upgrade")
def place_items_from_set(self, location_set: Set[str], name: str):

View File

@@ -85,20 +85,7 @@ class TestGrievanceHard(BlasphemousTestBase):
}
class TestKnotOfWordsEasy(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
"difficulty": "easy"
}
class TestKnotOfWordsNormal(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
"difficulty": "normal"
}
# knot of the three words, rooftops, and mourning and havoc can't be selected on easy or normal. hard only
class TestKnotOfWordsHard(BlasphemousTestBase):
options = {
"starting_location": "knot_of_words",
@@ -106,20 +93,6 @@ class TestKnotOfWordsHard(BlasphemousTestBase):
}
class TestRooftopsEasy(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
"difficulty": "easy"
}
class TestRooftopsNormal(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
"difficulty": "normal"
}
class TestRooftopsHard(BlasphemousTestBase):
options = {
"starting_location": "rooftops",
@@ -127,7 +100,6 @@ class TestRooftopsHard(BlasphemousTestBase):
}
# mourning and havoc can't be selected on easy or normal. hard only
class TestMourningHavocHard(BlasphemousTestBase):
options = {
"starting_location": "mourning_havoc",

View File

@@ -125,6 +125,6 @@ class BumpStikWorld(World):
lambda state: state.has("Hazard Bumper", self.player, 25)
self.multiworld.completion_condition[self.player] = \
lambda state: state.has("Booster Bumper", self.player, 5) and \
state.has("Treasure Bumper", self.player, 32)
lambda state: state.has_all_counts({"Booster Bumper": 5, "Treasure Bumper": 32, "Hazard Bumper": 25}, \
self.player)

View File

@@ -1,4 +1,4 @@
from test.TestBase import WorldTestBase
from test.bases import WorldTestBase
class BumpStikTestBase(WorldTestBase):

View File

@@ -1,6 +1,31 @@
# Celeste 64 - Changelog
## v1.3
### Features:
- New optional Location Checks
- Checkpointsanity
- Hair Color
- Allows for setting of Maddy's hair color in each of No Dash, One Dash, Two Dash, and Feather states
- Other Player Ghosts
- A game config option allows you to see ghosts of other Celeste 64 players in the multiworld
### Quality of Life:
- Checkpoint Warping
- Received Checkpoint items allow for warping to their respective checkpoint
- These items are on their respective checkpoint location if Checkpointsanity is disabled
- Logic accounts for being able to warp to otherwise inaccessible areas
- Checkpoints are a possible option for a starting item on Standard Logic + Move Shuffle + Checkpointsanity
- New Options toggle to enable/disable background input
### Bug Fixes:
- Traffic Blocks now correctly appear disabled within Cassettes
## v1.2
### Features:

View File

@@ -39,6 +39,22 @@ move_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression),
}
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table}
checkpoint_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.checkpoint_1: Celeste64ItemData(celeste_64_base_id + 0x20, ItemClassification.progression),
ItemName.checkpoint_2: Celeste64ItemData(celeste_64_base_id + 0x21, ItemClassification.progression),
ItemName.checkpoint_3: Celeste64ItemData(celeste_64_base_id + 0x22, ItemClassification.progression),
ItemName.checkpoint_4: Celeste64ItemData(celeste_64_base_id + 0x23, ItemClassification.progression),
ItemName.checkpoint_5: Celeste64ItemData(celeste_64_base_id + 0x24, ItemClassification.progression),
ItemName.checkpoint_6: Celeste64ItemData(celeste_64_base_id + 0x25, ItemClassification.progression),
ItemName.checkpoint_7: Celeste64ItemData(celeste_64_base_id + 0x26, ItemClassification.progression),
ItemName.checkpoint_8: Celeste64ItemData(celeste_64_base_id + 0x27, ItemClassification.progression),
ItemName.checkpoint_9: Celeste64ItemData(celeste_64_base_id + 0x28, ItemClassification.progression),
ItemName.checkpoint_10: Celeste64ItemData(celeste_64_base_id + 0x29, ItemClassification.progression),
}
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table,
**unlockable_item_data_table,
**move_item_data_table,
**checkpoint_item_data_table}
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}

View File

@@ -1,7 +1,7 @@
from typing import Dict, NamedTuple, Optional
from BaseClasses import Location
from .Names import LocationName
from .Names import LocationName, RegionName
celeste_64_base_id: int = 0xCA0000
@@ -17,66 +17,80 @@ class Celeste64LocationData(NamedTuple):
strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00),
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01),
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02),
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03),
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04),
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05),
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06),
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07),
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08),
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09),
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A),
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B),
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C),
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D),
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E),
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F),
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10),
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11),
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12),
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13),
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14),
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15),
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16),
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17),
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18),
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19),
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A),
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B),
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C),
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D),
LocationName.strawberry_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x00),
LocationName.strawberry_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x01),
LocationName.strawberry_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x02),
LocationName.strawberry_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x03),
LocationName.strawberry_5: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x04),
LocationName.strawberry_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x05),
LocationName.strawberry_7: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x06),
LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07),
LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08),
LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09),
LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A),
LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B),
LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C),
LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D),
LocationName.strawberry_15: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0E),
LocationName.strawberry_16: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0F),
LocationName.strawberry_17: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x10),
LocationName.strawberry_18: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x11),
LocationName.strawberry_19: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x12),
LocationName.strawberry_20: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x13),
LocationName.strawberry_21: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x14),
LocationName.strawberry_22: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x15),
LocationName.strawberry_23: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x16),
LocationName.strawberry_24: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x17),
LocationName.strawberry_25: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x18),
LocationName.strawberry_26: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x19),
LocationName.strawberry_27: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1A),
LocationName.strawberry_28: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1B),
LocationName.strawberry_29: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x1C),
LocationName.strawberry_30: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x1D),
}
friend_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00),
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01),
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02),
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03),
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04),
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05),
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06),
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07),
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08),
LocationName.granny_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x00),
LocationName.granny_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x01),
LocationName.granny_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x02),
LocationName.theo_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x03),
LocationName.theo_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x04),
LocationName.theo_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x05),
LocationName.badeline_1: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x06),
LocationName.badeline_2: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x07),
LocationName.badeline_3: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x08),
}
sign_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00),
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01),
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02),
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03),
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04),
LocationName.sign_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x00),
LocationName.sign_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x01),
LocationName.sign_3: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x200 + 0x02),
LocationName.sign_4: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x200 + 0x03),
LocationName.sign_5: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x200 + 0x04),
}
car_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00),
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01),
LocationName.car_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x300 + 0x00),
LocationName.car_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x300 + 0x01),
}
checkpoint_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.checkpoint_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x400 + 0x00),
LocationName.checkpoint_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x01),
LocationName.checkpoint_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x02),
LocationName.checkpoint_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x03),
LocationName.checkpoint_5: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x04),
LocationName.checkpoint_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x05),
LocationName.checkpoint_7: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x400 + 0x06),
LocationName.checkpoint_8: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x400 + 0x07),
LocationName.checkpoint_9: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x400 + 0x08),
LocationName.checkpoint_10: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x400 + 0x09),
}
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
**friend_location_data_table,
**sign_location_data_table,
**car_location_data_table}
**car_location_data_table,
**checkpoint_location_data_table}
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}

View File

@@ -15,3 +15,18 @@ ground_dash = "Ground Dash"
air_dash = "Air Dash"
skid_jump = "Skid Jump"
climb = "Climb"
# Checkpoint Items
checkpoint_1 = "Intro Checkpoint"
checkpoint_2 = "Granny Checkpoint"
checkpoint_3 = "South-East Tower Checkpoint"
checkpoint_4 = "Climb Sign Checkpoint"
checkpoint_5 = "Freeway Checkpoint"
checkpoint_6 = "Freeway Feather Checkpoint"
checkpoint_7 = "Feather Maze Checkpoint"
checkpoint_8 = "Double Dash House Checkpoint"
checkpoint_9 = "Badeline Tower Checkpoint"
checkpoint_10 = "Badeline Island Checkpoint"
# Item used for logic definitions that are not possible with the given options
cannot_access = "CANNOT ACCESS"

View File

@@ -10,7 +10,7 @@ strawberry_8 = "Traffic Block Strawberry"
strawberry_9 = "South-West Dash Refills Strawberry"
strawberry_10 = "South-East Tower Side Strawberry"
strawberry_11 = "Girders Strawberry"
strawberry_12 = "North-East Tower Bottom Strawberry"
strawberry_12 = "Badeline Tower Bottom Strawberry"
strawberry_13 = "Breakable Blocks Strawberry"
strawberry_14 = "Feather Maze Strawberry"
strawberry_15 = "Feather Chain Strawberry"
@@ -18,7 +18,7 @@ strawberry_16 = "Feather Hidden Strawberry"
strawberry_17 = "Double Dash Puzzle Strawberry"
strawberry_18 = "Double Dash Spike Climb Strawberry"
strawberry_19 = "Double Dash Spring Strawberry"
strawberry_20 = "North-East Tower Breakable Bottom Strawberry"
strawberry_20 = "Badeline Tower Breakable Bottom Strawberry"
strawberry_21 = "Theo Tower Lower Cassette Strawberry"
strawberry_22 = "Theo Tower Upper Cassette Strawberry"
strawberry_23 = "South End of Bridge Cassette Strawberry"
@@ -27,8 +27,8 @@ strawberry_25 = "Cassette Hidden in the House Strawberry"
strawberry_26 = "North End of Bridge Cassette Strawberry"
strawberry_27 = "Distant Feather Cassette Strawberry"
strawberry_28 = "Feather Arches Cassette Strawberry"
strawberry_29 = "North-East Tower Cassette Strawberry"
strawberry_30 = "Badeline Cassette Strawberry"
strawberry_29 = "Badeline Tower Cassette Strawberry"
strawberry_30 = "Badeline Island Cassette Strawberry"
# Friend Locations
granny_1 = "Granny Conversation 1"
@@ -51,3 +51,15 @@ sign_5 = "Credits Sign"
# Car Locations
car_1 = "Intro Car"
car_2 = "Secret Car"
# Checkpoint Locations
checkpoint_1 = "Intro Checkpoint"
checkpoint_2 = "Granny Checkpoint"
checkpoint_3 = "South-East Tower Checkpoint"
checkpoint_4 = "Climb Sign Checkpoint"
checkpoint_5 = "Freeway Checkpoint"
checkpoint_6 = "Freeway Feather Checkpoint"
checkpoint_7 = "Feather Maze Checkpoint"
checkpoint_8 = "Double Dash House Checkpoint"
checkpoint_9 = "Badeline Tower Checkpoint"
checkpoint_10 = "Badeline Island Checkpoint"

View File

@@ -0,0 +1,13 @@
# Level Base Regions
forsaken_city = "Forsaken City"
# Forsaken City Regions
intro_islands = "Intro Islands"
granny_island = "Granny Island"
highway_island = "Freeway Island"
nw_girders_island = "North-West Girders Island"
ne_feathers_island = "North-East Feathers Island"
se_house_island = "South-East House Island"
badeline_tower_lower = "Badeline Tower Lower"
badeline_tower_upper = "Badeline Tower Upper"
badeline_island = "Badeline Island"

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
import random
from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions
from Options import Choice, TextChoice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions, OptionError
from worlds.AutoWorld import World
class DeathLinkAmnesty(Range):
@@ -18,7 +20,7 @@ class TotalStrawberries(Range):
"""
display_name = "Total Strawberries"
range_start = 0
range_end = 46
range_end = 55
default = 20
class StrawberriesRequiredPercentage(Range):
@@ -73,6 +75,93 @@ class Carsanity(Toggle):
"""
display_name = "Carsanity"
class Checkpointsanity(Toggle):
"""
Whether activating Checkpoints grants location checks
Activating this will also shuffle items into the pool which allow usage and warping to each Checkpoint
"""
display_name = "Checkpointsanity"
class ColorChoice(TextChoice):
option_strawberry = 0xDB2C00
option_empty = 0x6EC0FF
option_double = 0xFA91FF
option_golden = 0xF2D450
option_baddy = 0x9B3FB5
option_fire_red = 0xFF0000
option_maroon = 0x800000
option_salmon = 0xFF3A65
option_orange = 0xD86E0A
option_lime_green = 0x8DF920
option_bright_green = 0x0DAF05
option_forest_green = 0x132818
option_royal_blue = 0x0036BF
option_brown = 0xB78726
option_black = 0x000000
option_white = 0xFFFFFF
option_grey = 0x808080
option_any_color = -1
@classmethod
def from_text(cls, text: str) -> Choice:
text = text.lower()
if text == "random":
choice_list = list(cls.name_lookup)
choice_list.remove(cls.option_any_color)
return cls(random.choice(choice_list))
return super().from_text(text)
class MadelineOneDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has one dash
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline One Dash Hair Color"
default = ColorChoice.option_strawberry
class MadelineTwoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has two dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Two Dash Hair Color"
default = ColorChoice.option_double
class MadelineNoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has no dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline No Dash Hair Color"
default = ColorChoice.option_empty
class MadelineFeatherHairColor(ColorChoice):
"""
What color Madeline's hair is when she has a feather
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Feather Hair Color"
default = ColorChoice.option_golden
class BadelineChaserSource(Choice):
"""
@@ -119,6 +208,13 @@ celeste_64_option_groups = [
Friendsanity,
Signsanity,
Carsanity,
Checkpointsanity,
]),
OptionGroup("Aesthetic Options", [
MadelineOneDashHairColor,
MadelineTwoDashHairColor,
MadelineNoDashHairColor,
MadelineFeatherHairColor,
]),
OptionGroup("Badeline Chasers", [
BadelineChaserSource,
@@ -142,7 +238,68 @@ class Celeste64Options(PerGameCommonOptions):
friendsanity: Friendsanity
signsanity: Signsanity
carsanity: Carsanity
checkpointsanity: Checkpointsanity
madeline_one_dash_hair_color: MadelineOneDashHairColor
madeline_two_dash_hair_color: MadelineTwoDashHairColor
madeline_no_dash_hair_color: MadelineNoDashHairColor
madeline_feather_hair_color: MadelineFeatherHairColor
badeline_chaser_source: BadelineChaserSource
badeline_chaser_frequency: BadelineChaserFrequency
badeline_chaser_speed: BadelineChaserSpeed
def resolve_options(world: World):
# One Dash Hair
if isinstance(world.options.madeline_one_dash_hair_color.value, str):
try:
world.madeline_one_dash_hair_color = int(world.options.madeline_one_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_one_dash_hair_color`:"
f"{world.options.madeline_one_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_one_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_one_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_one_dash_hair_color = world.options.madeline_one_dash_hair_color.value
# Two Dash Hair
if isinstance(world.options.madeline_two_dash_hair_color.value, str):
try:
world.madeline_two_dash_hair_color = int(world.options.madeline_two_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_two_dash_hair_color`:"
f"{world.options.madeline_two_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_two_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_two_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_two_dash_hair_color = world.options.madeline_two_dash_hair_color.value
# No Dash Hair
if isinstance(world.options.madeline_no_dash_hair_color.value, str):
try:
world.madeline_no_dash_hair_color = int(world.options.madeline_no_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_no_dash_hair_color`:"
f"{world.options.madeline_no_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_no_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_no_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_no_dash_hair_color = world.options.madeline_no_dash_hair_color.value
# Feather Hair
if isinstance(world.options.madeline_feather_hair_color.value, str):
try:
world.madeline_feather_hair_color = int(world.options.madeline_feather_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_feather_hair_color`:"
f"{world.options.madeline_feather_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_feather_hair_color.value == ColorChoice.option_any_color:
world.madeline_feather_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_feather_hair_color = world.options.madeline_feather_hair_color.value

View File

@@ -1,11 +1,23 @@
from typing import Dict, List, NamedTuple
from .Names import RegionName
class Celeste64RegionData(NamedTuple):
connecting_regions: List[str] = []
region_data_table: Dict[str, Celeste64RegionData] = {
"Menu": Celeste64RegionData(["Forsaken City"]),
"Forsaken City": Celeste64RegionData(),
"Menu": Celeste64RegionData([RegionName.forsaken_city]),
RegionName.forsaken_city: Celeste64RegionData([RegionName.intro_islands, RegionName.granny_island, RegionName.highway_island, RegionName.ne_feathers_island, RegionName.se_house_island, RegionName.badeline_tower_upper, RegionName.badeline_island]),
RegionName.intro_islands: Celeste64RegionData([RegionName.granny_island]),
RegionName.granny_island: Celeste64RegionData([RegionName.highway_island, RegionName.nw_girders_island, RegionName.badeline_tower_lower, RegionName.se_house_island]),
RegionName.highway_island: Celeste64RegionData([RegionName.granny_island, RegionName.ne_feathers_island, RegionName.nw_girders_island]),
RegionName.nw_girders_island: Celeste64RegionData([RegionName.highway_island]),
RegionName.ne_feathers_island: Celeste64RegionData([RegionName.se_house_island, RegionName.highway_island, RegionName.badeline_tower_lower, RegionName.badeline_tower_upper]),
RegionName.se_house_island: Celeste64RegionData([RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_lower]),
RegionName.badeline_tower_lower: Celeste64RegionData([RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_upper]),
RegionName.badeline_tower_upper: Celeste64RegionData([RegionName.badeline_island, RegionName.badeline_tower_lower, RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island]),
RegionName.badeline_island: Celeste64RegionData([RegionName.badeline_tower_upper, RegionName.granny_island, RegionName.highway_island]),
}

View File

@@ -1,265 +1,85 @@
from typing import Dict, List
from typing import Dict, List, Tuple, Callable
from BaseClasses import CollectionState
from BaseClasses import CollectionState, Region
from worlds.generic.Rules import set_rule
from . import Celeste64World
from .Names import ItemName, LocationName
from .Names import ItemName, LocationName, RegionName
def set_rules(world: Celeste64World):
if world.options.logic_difficulty == "standard":
if world.options.move_shuffle:
world.active_logic_mapping = location_standard_moves_logic
else:
world.active_logic_mapping = location_standard_logic
world.active_logic_mapping = location_standard_moves_logic
world.active_region_logic_mapping = region_standard_moves_logic
else:
if world.options.move_shuffle:
world.active_logic_mapping = location_hard_moves_logic
else:
world.active_logic_mapping = location_hard_logic
world.active_logic_mapping = location_hard_moves_logic
world.active_region_logic_mapping = region_hard_moves_logic
for location in world.multiworld.get_locations(world.player):
set_rule(location, lambda state, location=location: location_rule(state, world, location.name))
if world.options.logic_difficulty == "standard":
if world.options.move_shuffle:
world.goal_logic_mapping = goal_standard_moves_logic
else:
world.goal_logic_mapping = goal_standard_logic
else:
if world.options.move_shuffle:
world.goal_logic_mapping = goal_hard_moves_logic
else:
world.goal_logic_mapping = goal_hard_logic
# Completion condition.
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world)
goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]]
goal_hard_logic: List[List[str]] = [[]]
goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]]
goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]]
location_standard_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_6: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_7: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_8: [[ItemName.traffic_block]],
LocationName.strawberry_9: [[ItemName.dash_refill]],
LocationName.strawberry_11: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.traffic_block, ItemName.double_dash_refill]],
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables],
[ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]],
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables],
[ItemName.traffic_block, ItemName.feather, ItemName.breakables]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.coin]],
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill],
[ItemName.cassette, ItemName.traffic_block]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]],
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.sign_2: [[ItemName.breakables]],
LocationName.sign_3: [[ItemName.dash_refill],
[ItemName.traffic_block]],
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill],
[ItemName.dash_refill, ItemName.feather],
[ItemName.traffic_block, ItemName.feather]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
LocationName.car_2: [[ItemName.breakables]],
}
location_hard_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_13: [[ItemName.breakables]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
LocationName.strawberry_20: [[ItemName.breakables]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]],
LocationName.strawberry_22: [[ItemName.cassette]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]],
LocationName.strawberry_24: [[ItemName.cassette]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]],
LocationName.strawberry_26: [[ItemName.cassette]],
LocationName.strawberry_27: [[ItemName.cassette]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]],
LocationName.strawberry_29: [[ItemName.cassette]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]],
LocationName.sign_2: [[ItemName.breakables]],
LocationName.car_2: [[ItemName.breakables]],
}
location_standard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.strawberry_2: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.strawberry_2: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_5: [[ItemName.air_dash]],
LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.climb]],
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash],
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump],
[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]],
LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]],
LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_11: [[ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_13: [[ItemName.breakables, ItemName.air_dash],
[ItemName.breakables, ItemName.ground_dash]],
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.feather, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_16: [[ItemName.feather]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
LocationName.strawberry_18: [[ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_19: [[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash, ItemName.skid_jump]],
LocationName.strawberry_20: [[ItemName.feather, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.granny_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.granny_2: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.granny_3: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.sign_1: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.climb]],
LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash],
[ItemName.dash_refill, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.ground_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash],
[ItemName.traffic_block, ItemName.feather, ItemName.skid_jump],
[ItemName.traffic_block, ItemName.feather, ItemName.climb]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]],
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash, ItemName.climb],
[ItemName.breakables, ItemName.air_dash, ItemName.climb]],
}
location_hard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_5: [[ItemName.ground_dash],
[ItemName.air_dash]],
LocationName.strawberry_8: [[ItemName.traffic_block],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.air_dash],
[ItemName.climb]],
LocationName.strawberry_11: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_12: [[ItemName.feather],
[ItemName.ground_dash],
[ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
[ItemName.air_dash, ItemName.climb]],
[ItemName.air_dash, ItemName.climb],
[ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.feather],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
@@ -287,42 +107,94 @@ location_hard_moves_logic: Dict[str, List[List[str]]] = {
[ItemName.cassette, ItemName.feather, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]],
LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb, ItemName.skid_jump],
[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.air_dash, ItemName.climb]],
LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb],
[ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump],
[ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump],
[ItemName.feather, ItemName.traffic_block, ItemName.air_dash],
[ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]],
LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]],
}
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
region_standard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
(RegionName.intro_islands, RegionName.granny_island): [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
(RegionName.granny_island, RegionName.highway_island): [[ItemName.air_dash, ItemName.dash_refill]],
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.climb, ItemName.dash_refill]],
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill]],
(RegionName.highway_island, RegionName.granny_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.dash_refill]],
(RegionName.highway_island, RegionName.ne_feathers_island): [[ItemName.feather]],
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.cannot_access]],
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block]],
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.climb, ItemName.air_dash, ItemName.feather]],
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.air_dash, ItemName.traffic_block, ItemName.double_dash_refill]],
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.double_dash_refill]],
(RegionName.badeline_tower_lower, RegionName.se_house_island): [[ItemName.cannot_access]],
(RegionName.badeline_tower_lower, RegionName.ne_feathers_island): [[ItemName.air_dash, ItemName.breakables, ItemName.feather]],
(RegionName.badeline_tower_lower, RegionName.granny_island): [[ItemName.cannot_access]],
(RegionName.badeline_tower_lower, RegionName.badeline_tower_upper): [[ItemName.cannot_access]],
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
(RegionName.badeline_tower_upper, RegionName.se_house_island): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.ne_feathers_island): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.granny_island): [[ItemName.dash_refill]],
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
}
region_hard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.double_dash_refill], [ItemName.ground_dash]],
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.air_dash, ItemName.ground_dash]],
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.ground_dash]],
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash], [ItemName.skid_jump]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.feather]],
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.traffic_block]],
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.traffic_block],
[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.skid_jump],
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.traffic_block],
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.skid_jump]],
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
}
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
if loc not in world.active_logic_mapping:
return True
@@ -332,12 +204,28 @@ def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bo
return False
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
return False
def region_connection_rule(state: CollectionState, world: Celeste64World, region_connection: Tuple[str]) -> bool:
if region_connection not in world.active_region_logic_mapping:
return True
for possible_access in world.goal_logic_mapping:
for possible_access in world.active_region_logic_mapping[region_connection]:
if state.has_all(possible_access, world.player):
return True
return False
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
return False
goal_region: Region = world.multiworld.get_region(RegionName.badeline_island, world.player)
return state.can_reach(goal_region)
def connect_region(world: Celeste64World, region: Region, dest_regions: List[str]):
rules: Dict[str, Callable[[CollectionState], bool]] = {}
for dest_region in dest_regions:
region_connection: Tuple[str] = (region.name, dest_region)
rules[dest_region] = lambda state, region_connection=region_connection: region_connection_rule(state, world, region_connection)
region.add_exits(dest_regions, rules)

View File

@@ -1,13 +1,15 @@
from copy import deepcopy
from typing import Dict, List
from typing import Dict, List, Tuple
from BaseClasses import ItemClassification, Location, Region, Tutorial
from worlds.AutoWorld import WebWorld, World
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\
checkpoint_item_data_table, item_table
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
sign_location_data_table, car_location_data_table, location_table
sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\
location_table
from .Names import ItemName, LocationName
from .Options import Celeste64Options, celeste_64_option_groups
from .Options import Celeste64Options, celeste_64_option_groups, resolve_options
class Celeste64WebWorld(WebWorld):
@@ -42,8 +44,15 @@ class Celeste64World(World):
# Instance Data
strawberries_required: int
active_logic_mapping: Dict[str, List[List[str]]]
goal_logic_mapping: Dict[str, List[List[str]]]
active_region_logic_mapping: Dict[Tuple[str], List[List[str]]]
madeline_one_dash_hair_color: int
madeline_two_dash_hair_color: int
madeline_no_dash_hair_color: int
madeline_feather_hair_color: int
def generate_early(self) -> None:
resolve_options(self)
def create_item(self, name: str) -> Celeste64Item:
# Only make required amount of strawberries be Progression
@@ -76,25 +85,49 @@ class Celeste64World(World):
for name in unlockable_item_data_table.keys()
if name not in self.options.start_inventory]
if self.options.move_shuffle:
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
chosen_start_item: str = ""
if self.options.move_shuffle:
if self.options.logic_difficulty == "standard":
# If the start_inventory already includes a move, don't worry about giving it one
if not [move for move in move_items_for_itempool if move in self.options.start_inventory]:
chosen_start_move = self.random.choice(move_items_for_itempool)
move_items_for_itempool.remove(chosen_start_move)
possible_unwalls: List[str] = [name for name in move_item_data_table.keys()
if name != ItemName.skid_jump]
if self.options.checkpointsanity:
possible_unwalls.extend([name for name in checkpoint_item_data_table.keys()
if name != ItemName.checkpoint_1 and name != ItemName.checkpoint_10])
# If the start_inventory already includes a move or checkpoint, don't worry about giving it one
if not [item for item in possible_unwalls if item in self.multiworld.precollected_items[self.player]]:
chosen_start_item = self.random.choice(possible_unwalls)
if self.options.carsanity:
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
intro_car_loc.place_locked_item(self.create_item(chosen_start_move))
intro_car_loc.place_locked_item(self.create_item(chosen_start_item))
location_count -= 1
else:
self.multiworld.push_precollected(self.create_item(chosen_start_move))
self.multiworld.push_precollected(self.create_item(chosen_start_item))
item_pool += [self.create_item(name)
for name in move_items_for_itempool
if name not in self.options.start_inventory]
for name in move_item_data_table.keys()
if name not in self.multiworld.precollected_items[self.player]
and name != chosen_start_item]
else:
for start_move in move_item_data_table.keys():
self.multiworld.push_precollected(self.create_item(start_move))
if self.options.checkpointsanity:
location_count += 9
goal_checkpoint_loc: Location = self.multiworld.get_location(LocationName.checkpoint_10, self.player)
goal_checkpoint_loc.place_locked_item(self.create_item(ItemName.checkpoint_10))
item_pool += [self.create_item(name)
for name in checkpoint_item_data_table.keys()
if name not in self.multiworld.precollected_items[self.player]
and name != ItemName.checkpoint_10
and name != chosen_start_item]
else:
for item_name in checkpoint_item_data_table.keys():
checkpoint_loc: Location = self.multiworld.get_location(item_name, self.player)
checkpoint_loc.place_locked_item(self.create_item(item_name))
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool))
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
@@ -140,18 +173,23 @@ class Celeste64World(World):
if location_data.region == region_name
}, Celeste64Location)
region.add_exits(region_data_table[region_name].connecting_regions)
region.add_locations({
location_name: location_data.address for location_name, location_data in checkpoint_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
from .Rules import connect_region
connect_region(self, region, region_data_table[region_name].connecting_regions)
# Have to do this here because of other games using State in a way that's bad
from .Rules import set_rules
set_rules(self)
def get_filler_item_name(self) -> str:
return ItemName.raspberry
def set_rules(self) -> None:
from .Rules import set_rules
set_rules(self)
def fill_slot_data(self):
return {
"death_link": self.options.death_link.value,
@@ -161,6 +199,11 @@ class Celeste64World(World):
"friendsanity": self.options.friendsanity.value,
"signsanity": self.options.signsanity.value,
"carsanity": self.options.carsanity.value,
"checkpointsanity": self.options.checkpointsanity.value,
"madeline_one_dash_hair_color": self.madeline_one_dash_hair_color,
"madeline_two_dash_hair_color": self.madeline_two_dash_hair_color,
"madeline_no_dash_hair_color": self.madeline_no_dash_hair_color,
"madeline_feather_hair_color": self.madeline_feather_hair_color,
"badeline_chaser_source": self.options.badeline_chaser_source.value,
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
"badeline_chaser_speed": self.options.badeline_chaser_speed.value,

View File

@@ -12,6 +12,12 @@
1. Download the above release and extract it.
## Installation Procedures (Linux and Steam Deck)
1. Download the above release and extract it.
2. Add Celeste64.exe to Steam as a Non-Steam Game. In the properties for it on Steam, set it to use Proton as the compatibility tool. Launch the game through Steam in order to run it.
## Joining a MultiWorld Game
1. Before launching the game, edit the `AP.json` file in the root of the Celeste 64 install.
@@ -33,5 +39,3 @@ An Example `AP.json` file:
"Password": ""
}
```

View File

@@ -11,19 +11,18 @@ client_version = 7
class ChecksFinderWeb(WebWorld):
tutorials = [Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Archipelago ChecksFinder software on your computer. This guide covers "
"single-player, multiworld, and related software.",
"A guide to playing Archipelago ChecksFinder.",
"English",
"setup_en.md",
"setup/en",
["Mewlif"]
["SunCat"]
)]
class ChecksFinderWorld(World):
"""
ChecksFinder is a game where you avoid mines and find checks inside the board
with the mines! You win when you get all your items and beat the board!
ChecksFinder is a game where you avoid mines and collect checks by beating boards!
You win when you get all your items and beat the last board!
"""
game = "ChecksFinder"
options_dataclass = PerGameCommonOptions

342
worlds/civ_6/Civ6Client.py Normal file
View File

@@ -0,0 +1,342 @@
import asyncio
import logging
import os
import traceback
from typing import Any, Dict, List, Optional
import zipfile
from CommonClient import ClientCommandProcessor, CommonContext, get_base_parser, logger, server_loop, gui_enabled
from .Data import get_progressive_districts_data
from .DeathLink import handle_check_deathlink
from NetUtils import ClientStatus
import Utils
from .CivVIInterface import CivVIInterface, ConnectionState
from .Enum import CivVICheckType
from .Items import CivVIItemData, generate_item_table, get_item_by_civ_name
from .Locations import CivVILocationData, generate_era_location_table
from .TunerClient import TunerErrorException, TunerTimeoutException
class CivVICommandProcessor(ClientCommandProcessor):
def __init__(self, ctx: CommonContext):
super().__init__(ctx)
def _cmd_deathlink(self):
"""Toggle deathlink from client. Overrides default setting."""
if isinstance(self.ctx, CivVIContext):
self.ctx.death_link_enabled = not self.ctx.death_link_enabled
self.ctx.death_link_just_changed = True
Utils.async_start(self.ctx.update_death_link(
self.ctx.death_link_enabled), name="Update Deathlink")
self.ctx.logger.info(f"Deathlink is now {'enabled' if self.ctx.death_link_enabled else 'disabled'}")
def _cmd_resync(self):
"""Resends all items to client, and has client resend all locations to server. This can take up to a minute if the player has received a lot of items"""
if isinstance(self.ctx, CivVIContext):
logger.info("Resyncing...")
asyncio.create_task(self.ctx.resync())
def _cmd_toggle_progressive_eras(self):
"""If you get stuck for some reason and unable to continue your game, you can run this command to disable the defeat that comes from pushing past the max unlocked era """
if isinstance(self.ctx, CivVIContext):
print("Toggling progressive eras, stand by...")
self.ctx.is_pending_toggle_progressive_eras = True
class CivVIContext(CommonContext):
is_pending_death_link_reset = False
is_pending_toggle_progressive_eras = False
command_processor = CivVICommandProcessor
game = "Civilization VI"
items_handling = 0b111
tuner_sync_task: Optional[asyncio.Task[None]] = None
game_interface: CivVIInterface
location_name_to_civ_location: Dict[str, CivVILocationData] = {}
location_name_to_id: Dict[str, int] = {}
item_id_to_civ_item: Dict[int, CivVIItemData] = {}
item_table: Dict[str, CivVIItemData] = {}
processing_multiple_items = False
received_death_link = False
death_link_message = ""
death_link_enabled = False
slot_data: Dict[str, Any]
death_link_just_changed = False
# Used to prevent the deathlink from triggering when someone re enables it
logger = logger
progressive_items_by_type = get_progressive_districts_data()
item_name_to_id = {
item.name: item.code for item in generate_item_table().values()}
connection_state = ConnectionState.DISCONNECTED
def __init__(self, server_address: Optional[str], password: Optional[str], apcivvi_file: Optional[str] = None):
super().__init__(server_address, password)
self.slot_data: Dict[str, Any] = {}
self.game_interface = CivVIInterface(logger)
location_by_era = generate_era_location_table()
self.item_table = generate_item_table()
self.apcivvi_file = apcivvi_file
# Get tables formatted in a way that is easier to use here
for locations in location_by_era.values():
for location in locations.values():
self.location_name_to_id[location.name] = location.code
self.location_name_to_civ_location[location.name] = location
for item in self.item_table.values():
self.item_id_to_civ_item[item.code] = item
async def resync(self):
if self.processing_multiple_items:
logger.info(
"Waiting for items to finish processing, try again later")
return
await self.game_interface.resync()
await handle_receive_items(self, -1)
logger.info("Resynced")
def on_deathlink(self, data: Utils.Dict[str, Utils.Any]) -> None:
super().on_deathlink(data)
text = data.get("cause", "")
if text:
message = text
else:
message = f"Received from {data['source']}"
self.death_link_message = message
self.received_death_link = True
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(CivVIContext, self).server_auth(password_requested)
await self.get_username()
self.tags = set()
await self.send_connect()
def run_gui(self):
from kvui import GameManager
class CivVIManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago Civilization VI Client"
self.ui = CivVIManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def on_package(self, cmd: str, args: Dict[str, Any]):
if cmd == "Connected":
self.slot_data = args["slot_data"]
if "death_link" in args["slot_data"]:
self.death_link_enabled = bool(args["slot_data"]["death_link"])
Utils.async_start(self.update_death_link(
bool(args["slot_data"]["death_link"])))
def update_connection_status(ctx: CivVIContext, status: ConnectionState):
if ctx.connection_state == status:
return
elif status == ConnectionState.IN_GAME:
ctx.logger.info("Connected to Civ VI")
elif status == ConnectionState.IN_MENU:
ctx.logger.info("Connected to Civ VI, waiting for game to start")
elif status == ConnectionState.DISCONNECTED:
ctx.logger.info("Disconnected from Civ VI, attempting to reconnect...")
ctx.connection_state = status
async def tuner_sync_task(ctx: CivVIContext):
logger.info("Starting CivVI connector")
while not ctx.exit_event.is_set():
if not ctx.slot:
await asyncio.sleep(3)
continue
else:
try:
if ctx.processing_multiple_items:
await asyncio.sleep(3)
else:
state = await ctx.game_interface.is_in_game()
update_connection_status(ctx, state)
if state == ConnectionState.IN_GAME:
await _handle_game_ready(ctx)
else:
await asyncio.sleep(3)
except TunerTimeoutException:
logger.error(
"Timeout occurred while receiving data from Civ VI, this usually isn't a problem unless you see it repeatedly")
await asyncio.sleep(3)
except Exception as e:
if isinstance(e, TunerErrorException):
logger.debug(str(e))
else:
logger.debug(traceback.format_exc())
await asyncio.sleep(3)
continue
async def handle_toggle_progressive_eras(ctx: CivVIContext):
if ctx.is_pending_toggle_progressive_eras:
ctx.is_pending_toggle_progressive_eras = False
current = await ctx.game_interface.get_max_allowed_era()
if current > -1:
await ctx.game_interface.set_max_allowed_era(-1)
logger.info("Disabled progressive eras")
else:
count = 0
for _, network_item in enumerate(ctx.items_received):
item: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
if item.item_type == CivVICheckType.ERA:
count += 1
await ctx.game_interface.set_max_allowed_era(count)
logger.info(f"Enabled progressive eras, set to {count}")
async def handle_checked_location(ctx: CivVIContext):
checked_locations = await ctx.game_interface.get_checked_locations()
checked_location_ids = [location.code for location_name, location in ctx.location_name_to_civ_location.items(
) if location_name in checked_locations]
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": checked_location_ids}])
async def handle_receive_items(ctx: CivVIContext, last_received_index_override: Optional[int] = None):
try:
last_received_index = last_received_index_override or await ctx.game_interface.get_last_received_index()
if len(ctx.items_received) - last_received_index > 1:
ctx.processing_multiple_items = True
progressive_districts: List[CivVIItemData] = []
progressive_eras: List[CivVIItemData] = []
for index, network_item in enumerate(ctx.items_received):
# Track these separately so if we replace "PROGRESSIVE_DISTRICT" with a specific tech, we can still check if need to add it to the list of districts
item: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
item_to_send: CivVIItemData = ctx.item_id_to_civ_item[network_item.item]
if index > last_received_index:
if item.item_type == CivVICheckType.PROGRESSIVE_DISTRICT and item.civ_name:
# if the item is progressive, then check how far in that progression type we are and send the appropriate item
count = sum(
1 for count_item in progressive_districts if count_item.civ_name == item.civ_name)
if count >= len(ctx.progressive_items_by_type[item.civ_name]):
logger.error(
f"Received more progressive items than expected for {item.civ_name}")
continue
item_civ_name = ctx.progressive_items_by_type[item.civ_name][count]
actual_item_name = get_item_by_civ_name(item_civ_name, ctx.item_table).name
item_to_send = ctx.item_table[actual_item_name]
sender = ctx.player_names[network_item.player]
if item.item_type == CivVICheckType.ERA:
count = len(progressive_eras) + 1
await ctx.game_interface.give_item_to_player(item_to_send, sender, count)
elif item.item_type == CivVICheckType.GOODY and item_to_send.civ_name:
await ctx.game_interface.give_item_to_player(item_to_send, sender, game_id_override=item_to_send.civ_name)
else:
await ctx.game_interface.give_item_to_player(item_to_send, sender)
await asyncio.sleep(0.02)
if item.item_type == CivVICheckType.PROGRESSIVE_DISTRICT:
progressive_districts.append(item)
elif item.item_type == CivVICheckType.ERA:
progressive_eras.append(item)
ctx.processing_multiple_items = False
finally:
# If something errors out, then unblock item processing
ctx.processing_multiple_items = False
async def handle_check_goal_complete(ctx: CivVIContext):
if ctx.finished_game:
return
result = await ctx.game_interface.check_victory()
if result:
logger.info("Sending Victory to server!")
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
async def _handle_game_ready(ctx: CivVIContext):
if ctx.server:
if not ctx.slot:
await asyncio.sleep(3)
return
await handle_receive_items(ctx)
await handle_checked_location(ctx)
await handle_check_goal_complete(ctx)
if ctx.death_link_enabled:
await handle_check_deathlink(ctx)
# process pending commands
await handle_toggle_progressive_eras(ctx)
await asyncio.sleep(3)
else:
logger.info("Waiting for player to connect to server")
await asyncio.sleep(3)
def main(connect: Optional[str] = None, password: Optional[str] = None, name: Optional[str] = None):
Utils.init_logging("Civilization VI Client")
async def _main(connect: Optional[str], password: Optional[str], name: Optional[str]):
parser = get_base_parser()
parser.add_argument("apcivvi_file", default="", type=str, nargs="?", help="Path to apcivvi file")
args = parser.parse_args()
ctx = CivVIContext(connect, password, args.apcivvi_file)
if args.apcivvi_file:
parent_dir: str = os.path.dirname(args.apcivvi_file)
target_name: str = os.path.basename(args.apcivvi_file).replace(".apcivvi", "-MOD-FILES")
target_path: str = os.path.join(parent_dir, target_name)
if not os.path.exists(target_path):
os.makedirs(target_path, exist_ok=True)
logger.info("Extracting mod files to %s", target_path)
with zipfile.ZipFile(args.apcivvi_file, "r") as zip_ref:
for member in zip_ref.namelist():
zip_ref.extract(member, target_path)
ctx.auth = name
ctx.server_task = asyncio.create_task(
server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
await asyncio.sleep(1)
ctx.tuner_sync_task = asyncio.create_task(
tuner_sync_task(ctx), name="TunerSync")
await ctx.exit_event.wait()
ctx.server_address = None
await ctx.shutdown()
if ctx.tuner_sync_task:
await asyncio.sleep(3)
await ctx.tuner_sync_task
import colorama
colorama.init()
asyncio.run(_main(connect, password, name))
colorama.deinit()
def debug_main():
parser = get_base_parser()
parser.add_argument("apcivvi_file", default="", type=str, nargs="?", help="Path to apcivvi file")
parser.add_argument("--name", default=None,
help="Slot Name to connect as.")
parser.add_argument("--debug", default=None,
help="debug mode, additional logging")
args = parser.parse_args()
if args.debug:
logger.setLevel(logging.DEBUG)
main(args.connect, args.password, args.name)

View File

@@ -0,0 +1,119 @@
from enum import Enum
from logging import Logger
from typing import List, Optional
from .Items import CivVIItemData
from .TunerClient import TunerClient, TunerConnectionException, TunerTimeoutException
class ConnectionState(Enum):
DISCONNECTED = 0
IN_GAME = 1
IN_MENU = 2
class CivVIInterface:
logger: Logger
tuner: TunerClient
last_error: Optional[str] = None
def __init__(self, logger: Logger):
self.logger = logger
self.tuner = TunerClient(logger)
async def is_in_game(self) -> ConnectionState:
command = "IsInGame()"
try:
result = await self.tuner.send_game_command(command)
if result == "false":
return ConnectionState.IN_MENU
self.last_error = None
return ConnectionState.IN_GAME
except TunerTimeoutException:
self.print_connection_error(
"Not connected to game, waiting for connection to be available")
return ConnectionState.DISCONNECTED
except TunerConnectionException as e:
if "The remote computer refused the network connection" in str(e):
self.print_connection_error(
"Unable to connect to game. Verify that the tuner is enabled. Attempting to reconnect")
else:
self.print_connection_error(
"Not connected to game, waiting for connection to be available")
return ConnectionState.DISCONNECTED
except Exception as e:
if "attempt to index a nil valuestack traceback" in str(e) \
or ".. is not supported for string .. nilstack traceback" in str(e):
return ConnectionState.IN_MENU
return ConnectionState.DISCONNECTED
def print_connection_error(self, error: str) -> None:
if error != self.last_error:
self.last_error = error
self.logger.info(error)
async def give_item_to_player(self, item: CivVIItemData, sender: str = "", amount: int = 1, game_id_override: Optional[str] = None) -> None:
if game_id_override:
item_id = f'"{game_id_override}"'
else:
item_id = item.civ_vi_id
command = f"HandleReceiveItem({item_id}, \"{item.name}\", \"{item.item_type.value}\", \"{sender}\", {amount})"
await self.tuner.send_game_command(command)
async def resync(self) -> None:
"""Has the client resend all the checked locations"""
command = "Resync()"
await self.tuner.send_game_command(command)
async def check_victory(self) -> bool:
command = "ClientGetVictory()"
result = await self.tuner.send_game_command(command)
return result == "true"
async def get_checked_locations(self) -> List[str]:
command = "GetUnsentCheckedLocations()"
result = await self.tuner.send_game_command(command, 2048 * 4)
return result.split(",")
async def get_deathlink(self) -> str:
"""returns either "false" or the name of the unit that killed the player's unit"""
command = "ClientGetDeathLink()"
result = await self.tuner.send_game_command(command)
return result
async def kill_unit(self, message: str) -> None:
command = f"KillUnit(\"{message}\")"
await self.tuner.send_game_command(command)
async def get_last_received_index(self) -> int:
command = "ClientGetLastReceivedIndex()"
result = await self.tuner.send_game_command(command)
return int(result)
async def send_notification(self, item: CivVIItemData, sender: str = "someone") -> None:
command = f"GameCore.NotificationManager:SendNotification(GameCore.NotificationTypes.USER_DEFINED_2, \"{item.name} Received\", \"You have received {item.name} from \" .. \"{sender}\", 0, {item.civ_vi_id})"
await self.tuner.send_command(command)
async def decrease_gold_by_percent(self, percent: int, message: str) -> None:
command = f"DecreaseGoldByPercent({percent}, \"{message}\")"
await self.tuner.send_game_command(command)
async def decrease_faith_by_percent(self, percent: int, message: str) -> None:
command = f"DecreaseFaithByPercent({percent}, \"{message}\")"
await self.tuner.send_game_command(command)
async def decrease_era_score_by_amount(self, amount: int, message: str) -> None:
command = f"DecreaseEraScoreByAmount({amount}, \"{message}\")"
await self.tuner.send_game_command(command)
async def set_max_allowed_era(self, count: int) -> None:
command = f"SetMaxAllowedEra(\"{count}\")"
await self.tuner.send_game_command(command)
async def get_max_allowed_era(self) -> int:
command = "ClientGetMaxAllowedEra()"
result = await self.tuner.send_game_command(command)
if result == "":
return -1
return int(result)

228
worlds/civ_6/Container.py Normal file
View File

@@ -0,0 +1,228 @@
from dataclasses import dataclass
import os
import io
from typing import TYPE_CHECKING, Dict, List, Optional, cast
import zipfile
from BaseClasses import Location
from worlds.Files import APContainer, AutoPatchRegister
from .Enum import CivVICheckType
from .Locations import CivVILocation, CivVILocationData
if TYPE_CHECKING:
from . import CivVIWorld
# Python fstrings don't allow backslashes, so we use this workaround
nl = "\n"
tab = "\t"
apo = "\'"
@dataclass
class CivTreeItem:
name: str
cost: int
ui_tree_row: int
class CivVIContainer(APContainer, metaclass=AutoPatchRegister):
"""
Responsible for generating the dynamic mod files for the Civ VI multiworld
"""
game: Optional[str] = "Civilization VI"
patch_file_ending = ".apcivvi"
def __init__(self, patch_data: Dict[str, str] | io.BytesIO, base_path: str = "", output_directory: str = "",
player: Optional[int] = None, player_name: str = "", server: str = ""):
if isinstance(patch_data, io.BytesIO):
super().__init__(patch_data, player, player_name, server)
else:
self.patch_data = patch_data
self.file_path = base_path
container_path = os.path.join(output_directory, base_path + ".apcivvi")
super().__init__(container_path, player, player_name, server)
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
for filename, yml in self.patch_data.items():
opened_zipfile.writestr(filename, yml)
super().write_contents(opened_zipfile)
def sanitize_value(value: str) -> str:
"""Removes values that can cause issues in XML"""
return value.replace('"', "'").replace('&', 'and')
def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int:
"""
Returns the cost of the item based on the game options
"""
# Research cost is between 50 and 150 where 100 equals the default cost
multiplier = world.options.research_cost_multiplier / 100
return int(world.location_table[location.name].cost * multiplier)
def get_formatted_player_name(world: 'CivVIWorld', player: int) -> str:
"""
Returns the name of the player in the world
"""
if player != world.player:
return sanitize_value(f"{world.multiworld.player_name[player]}{apo}s")
return "Your"
def get_advisor_type(world: 'CivVIWorld', location: Location) -> str:
if world.options.advisor_show_progression_items and location.item and location.item.advancement:
return "ADVISOR_PROGRESSIVE"
return "ADVISOR_GENERIC"
def generate_new_items(world: 'CivVIWorld') -> str:
"""
Generates the XML for the new techs/civics as well as the blockers used to prevent players from researching their own items
"""
locations: List[CivVILocation] = cast(List[CivVILocation], world.multiworld.get_filled_locations(world.player))
techs = [location for location in locations if location.location_type ==
CivVICheckType.TECH]
civics = [location for location in locations if location.location_type ==
CivVICheckType.CIVIC]
boost_techs = []
boost_civics = []
if world.options.boostsanity:
boost_techs = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "TECH"]
boost_civics = [location for location in locations if location.location_type == CivVICheckType.BOOST and location.name.split("_")[1] == "CIVIC"]
techs += boost_techs
civics += boost_civics
return f"""<?xml version="1.0" encoding="utf-8"?>
<GameInfo>
<Types>
<Row Type="TECH_BLOCKER" Kind="KIND_TECH" />
<Row Type="CIVIC_BLOCKER" Kind="KIND_CIVIC" />
{"".join([f'{tab}<Row Type="{tech.name}" Kind="KIND_TECH" />{nl}' for
tech in techs])}
{"".join([f'{tab}<Row Type="{civic.name}" Kind="KIND_CIVIC" />{nl}' for
civic in civics])}
</Types>
<Technologies>
<Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row TechnologyType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" '
f'Description="{location.name}" '
f'AdvisorType="{get_advisor_type(world, location)}"'
f'/>{nl}'
for location in techs if location.item])}
</Technologies>
<TechnologyPrereqs>
{"".join([f'{tab}<Row Technology="{location.name}" PrereqTech="TECH_BLOCKER" />{nl}' for location in boost_techs])}
</TechnologyPrereqs>
<Civics>
<Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row CivicType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" '
f'Description="{location.name}" '
f'AdvisorType="{get_advisor_type(world, location)}"'
f'/>{nl}'
for location in civics if location.item])}
</Civics>
<CivicPrereqs>
{"".join([f'{tab}<Row Civic="{location.name}" PrereqCivic="CIVIC_BLOCKER" />{nl}' for location in boost_civics])}
</CivicPrereqs>
<Civics_XP2>
{"".join([f'{tab}<Row CivicType="{location.name}" HiddenUntilPrereqComplete="true" RandomPrereqs="false"/>{nl}' for location in civics if world.options.hide_item_names])}
</Civics_XP2>
<Technologies_XP2>
{"".join([f'{tab}<Row TechnologyType="{location.name}" HiddenUntilPrereqComplete="true" RandomPrereqs="false"/>{nl}' for location in techs if world.options.hide_item_names])}
</Technologies_XP2>
</GameInfo>
"""
def generate_setup_file(world: 'CivVIWorld') -> str:
"""
Generates the Lua for the setup file. This sets initial variables and state that affect gameplay around Progressive Eras
"""
setup = "-- Setup"
if world.options.progression_style == "eras_and_districts":
setup += f"""
-- Init Progressive Era Value if it hasn't been set already
if Game.GetProperty("MaxAllowedEra") == nil then
print("Setting MaxAllowedEra to 0")
Game.SetProperty("MaxAllowedEra", 0)
end
"""
if world.options.boostsanity:
setup += f"""
-- Init Boosts
if Game.GetProperty("BoostsAsChecks") == nil then
print("Setting Boosts As Checks to True")
Game.SetProperty("BoostsAsChecks", true)
end
"""
return setup
def generate_goody_hut_sql(world: 'CivVIWorld') -> str:
"""
Generates the SQL for the goody huts or an empty string if they are disabled since the mod expects the file to be there
"""
if world.options.shuffle_goody_hut_rewards:
return f"""
UPDATE GoodyHutSubTypes SET Description = NULL WHERE GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND Weight > 0;
INSERT INTO Modifiers
(ModifierId, ModifierType, RunOnce, Permanent, SubjectRequirementSetId)
SELECT ModifierID||'_AI', ModifierType, RunOnce, Permanent, 'PLAYER_IS_AI'
FROM Modifiers
WHERE EXISTS (
SELECT ModifierId
FROM GoodyHutSubTypes
WHERE Modifiers.ModifierId = GoodyHutSubTypes.ModifierId AND GoodyHutSubTypes.GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND GoodyHutSubTypes.Weight > 0);
INSERT INTO ModifierArguments
(ModifierId, Name, Type, Value)
SELECT ModifierID||'_AI', Name, Type, Value
FROM ModifierArguments
WHERE EXISTS (
SELECT ModifierId
FROM GoodyHutSubTypes
WHERE ModifierArguments.ModifierId = GoodyHutSubTypes.ModifierId AND GoodyHutSubTypes.GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND GoodyHutSubTypes.Weight > 0);
UPDATE GoodyHutSubTypes
SET ModifierID = ModifierID||'_AI'
WHERE GoodyHut NOT IN ('METEOR_GOODIES', 'GOODYHUT_SAILOR_WONDROUS', 'DUMMY_GOODY_BUILDIER') AND Weight > 0;
"""
return "-- Goody Huts are disabled, no changes needed"
def generate_update_boosts_sql(world: 'CivVIWorld') -> str:
"""
Generates the SQL for existing boosts in boostsanity or an empty string if they are disabled since the mod expects the file to be there
"""
if world.options.boostsanity:
return f"""
UPDATE Boosts
SET TechnologyType = 'BOOST_' || TechnologyType
WHERE TechnologyType IS NOT NULL;
UPDATE Boosts
SET CivicType = 'BOOST_' || CivicType
WHERE CivicType IS NOT NULL AND CivicType NOT IN ('CIVIC_CORPORATE_LIBERTARIANISM', 'CIVIC_DIGITAL_DEMOCRACY', 'CIVIC_SYNTHETIC_TECHNOCRACY', 'CIVIC_NEAR_FUTURE_GOVERNANCE');
"""
return "-- Boostsanity is disabled, no changes needed"

Some files were not shown because too many files have changed in this diff Show More