mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-20 13:13:33 -07:00
Merge branch 'main' into NewSoupVi-patch-20
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
32
worlds/alttp/docs/fr_A Link to the Past.md
Normal file
32
worlds/alttp/docs/fr_A Link to the Past.md
Normal 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 !
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
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.
|
||||
|
||||
BIN
worlds/alttp/docs/retroarch-network-commands-fr.png
Normal file
BIN
worlds/alttp/docs/retroarch-network-commands-fr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -1,2 +1,2 @@
|
||||
maseya-z3pr>=1.0.0rc1
|
||||
xxtea>=3.0.0
|
||||
xxtea>=3.0.0
|
||||
|
||||
@@ -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']],
|
||||
])
|
||||
@@ -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']],
|
||||
])
|
||||
@@ -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']],
|
||||
|
||||
@@ -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']],
|
||||
|
||||
@@ -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, []],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from worlds.alttp.Shops import shop_table
|
||||
from test.TestBase import TestBase
|
||||
from test.bases import TestBase
|
||||
|
||||
|
||||
class TestSram(TestBase):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 + "\"")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class BumpStikTestBase(WorldTestBase):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
13
worlds/celeste64/Names/RegionName.py
Normal file
13
worlds/celeste64/Names/RegionName.py
Normal 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"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -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
342
worlds/civ_6/Civ6Client.py
Normal 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)
|
||||
119
worlds/civ_6/CivVIInterface.py
Normal file
119
worlds/civ_6/CivVIInterface.py
Normal 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
228
worlds/civ_6/Container.py
Normal 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
Reference in New Issue
Block a user