Compare commits

...

52 Commits

Author SHA1 Message Date
Fabian Dill
37b77398a7 Merge branch 'main' into multiserver_data_store_datapackage 2024-06-03 15:27:37 +02:00
Star Rauchenberger
fb2c194e37 Lingo: Fix Basement access with THE MASTER (#3231) 2024-06-03 03:51:27 -05:00
Zach Parks
cff7327558 Utils: Fix mistake made with KeyedDefaultDict from #1933 that broke tracker functionality. (#3433) 2024-06-03 03:45:01 -05:00
Scipio Wright
70e9ccb13c TUNIC: Fix plando connections, seed groups, and UT support (#3429) 2024-06-03 03:44:37 -05:00
Exempt-Medic
d9120f0bea WebHost: Allowing options that work on WebHost to be used in presets (#3441) 2024-06-03 03:42:27 -05:00
Remy Jette
424c8b0be9 Pokemon RB: Add an item group for each HM to improve hinting (#3311)
* Pokemon RB: Add an item group for each HM

HMs are suffixed with the name of the move, e.g. "HM02 Fly". If TM
move are randomized, they do not have the move name, e.g. "TM02".

If someone hints for an HM using the just the number, the fuzzy matching
sees "TM02" as closer than "HM02 Fly", and in fact sees it as close
enough to not ask the user to confirm, leading them to waste hint points
on non-progression item that they didn't intend.

Emerald already does this for this reason, adding the same for RB.

* Add the new groups for HMs in the item_table instead
2024-06-03 04:42:15 +02:00
qwint
6432560fe5 Fix Egg_Shop typo in costsanity (#3447) 2024-06-03 04:39:34 +02:00
Emily
dedabad290 APSudoku: take over maintaining hintgame sudoku from bk_sudoku (#3432) 2024-06-02 11:45:46 -05:00
NewSoupVi
e49b1f9fbb The Witness: Automatic Postgame & Disabled Panels Calculation (#2698)
* Refactor postgame code to be more readable

* Change all references to options to strings

* oops

* Fix some outdated code related to yaml-disabled EPs

* Small fixes to short/longbox stuff (thanks Medic)

* comment

* fix duplicate

* Removed triplicate lmfao

* Better comment

* added another 'unfun' postgame consideration

* comment

* more option strings

* oops

* Remove an unnecessary comparison

* another string missed

* New classification changes (Credit: Exempt-Medic)

* Don't need to pass world

* Comments

* Replace it with another magic system because why not at this point :DDDDDD

* oops

* Oops

* Another was missed

* Make events conditions. Disable_Non_Randomized will no longer just 'have all events'

* What the fuck? Has this just always been broken?

* Don't have boolean function with 'not' in the name

* Another useful classification

* slight code refactor

* Funny haha booleans

* This would create a really bad merge error

* I can't believe this actually kind of works

* And here's the punchline. + some bugfixes

* Comment dat code

* Comments galore

* LMAO OOPS

* so nice I did it twice

* debug x2

* Careful

* Add more comments

* That comment is a bit unnecessary now

* Fix overriding region connections

* Correct a comment

* Correct again

* Rename variable

* Idk I guess this is in this branch now

* More tweaking of postgame & comments

* This is commit just exists to fix that grammar error

* I think I can just fucking delete this now???

* Forgot to reset something here

* Delete dead codepath

* Obelisk Keys were getting yote erroneously

* More comments

* Fix duplicate connections

* Oopsington III

* performance improvements & cleanup

* More rules cleanup and performance improvements

* Oh cool I can do this huh

* Okay but this is even more swag tho

* Lazy eval

* remove some implicit checks

* Is this too magical yet

* more guard magic

* Maaaaaaaagiccccccccc

* Laaaaaaaaaaaaaaaazzzzzzyyyyyyyyyyy

* Make it docstring

* Newline bc I like that better

* this is a little spooky lol

* lol

* Wait

* spoO

* Better variable name and comment

* Improved comment again

* better API

* oops I deleted a deepcopy

* lol help

* Help???

* player_regionsns lmao

* Add some comments

* Make doors disabled properly again. I hope this works

* Don't disable lasers

* Omega oops

* Make Floor 2 Exit not exist

* Make a fix that's warps compatible

* I think this was an oversight, I tested a seed and it seems to have the same result

* This is definitely less Violet than before

* Does this feel more violet lol

* Exception if a laser gets disabled, cleanup

* Ruff

* >:(

* consistent utils import

* Make autopostgame more reviewable (hopefully)

* more reviewability

* WitnessRule

* replace another instance of it

* lint

* style

* comment

* found the bug

* Move comment

* Get rid of cache and ugly allow_victory

* comments and lint
2024-06-01 23:11:28 +02:00
Fabian Dill
da33d1576a WebHost: update trackers only if they're visible. (#3407) 2024-06-01 17:07:58 +02:00
Fabian Dill
13bc121c27 Webhost: Sphere Tracker (#3412) 2024-06-01 14:43:11 +02:00
Fabian Dill
bbc79a5b99 LttP: allow Triforce Piece as start inventory item (#3292) 2024-06-01 14:38:45 +02:00
Ishigh1
3cb5452455 Core: Fix auto-fill in the text client when clicking on a hint suggestion (#3267) 2024-06-01 07:32:41 -05:00
Nicholas Saylor
8dbc8d2d41 Installer: Prevent ALTTP Sprite Download from being Interrupted (#3293) 2024-06-01 06:42:02 -05:00
Dinopony
1e205f9d73 Landstalker: Fixed rare generation issues (#3353)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-06-01 06:39:57 -05:00
Exempt-Medic
97c9c5310b PKMN R/B: Fixing Key Items Only + Removed Exp. All (#3420) 2024-06-01 06:35:33 -05:00
Silvris
4e5b6bb3d2 Core: move PlandoConnections and PlandoTexts to the options system (#2904)
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
Co-authored-by: beauxq <beauxq@yahoo.com>
Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>
2024-06-01 06:34:41 -05:00
Bryce Wilson
f40b10dc97 Pokemon Emerald: Adjust options (#3278) 2024-06-01 06:14:40 -05:00
NewSoupVi
4cab3b6371 The Witness: Put Treehouse Both Orange Bridges EP on the normal EPs exclusion list (#3308) 2024-06-01 06:13:00 -05:00
Bryce Wilson
67cd32b37c Pokemon Emerald: Use self.player_name (#3384) 2024-06-01 06:12:37 -05:00
Rensen3
91c89604a5 YGO06: prevent multiple players affecting each others procedure patch (#3409) 2024-06-01 06:10:02 -05:00
Louis M
f2587d5d27 Aquatia: Locations name changed due to typo's, grammar, or inconsistencies (#3421) 2024-06-01 06:09:34 -05:00
Exempt-Medic
2a5de8567e Docs: Making option description more readable and accurate (#3426) 2024-06-01 06:07:43 -05:00
Zach Parks
5aa6ad63ca Core: Remove Universally Unique ID Requirements (Per-Game Data Packages) (#1933) 2024-06-01 06:07:13 -05:00
Chris Wilson
f3003ff147 Fix options pages sometimes displaying blank values in form fields (#3364) 2024-05-31 22:41:49 -04:00
Chris Wilson
15e06e1779 Fix TextChoice options sometimes creating a broken YAML (#3390)
* Fix TextChoice options with custom values improperly being included in YAML output

* Update WebHostLib/options.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-05-31 22:41:03 -04:00
Exempt-Medic
b055a39454 PKMN R/B: "J.r" -> "Jr." (#3423) 2024-05-31 14:48:21 -05:00
BadMagic100
7058575c95 Hollow Knight: Add missing comma (#3403) 2024-05-30 19:57:54 +02:00
Salzkorn
2fe8c43351 SC2: Fix Kerrigan Primal Form on Half Completion (#3419) 2024-05-30 18:52:01 +02:00
black-sliver
6f6bf3c62d CustomServer: properly 'inherit' Archipelago from static_server_data (#3366)
This fixes a potential exception during room spin-up.
2024-05-30 18:16:13 +02:00
Witchybun
378af4b07c Stardew Valley: Fix magic altar logic (#3417)
* Fix magic altar logic

* Force a tuple (really?)

* Fix received and force progression on all spells

* Reversing the tuple change (?yllaer)
2024-05-29 20:16:19 +02:00
Zach Parks
34f903e97a CODEOWNERS: Remove @jtoyoda as world maintainer for Final Fantasy (#3398) 2024-05-29 09:59:40 -05:00
Fabian Dill
e31a7093de WebHost: use settings defaults for /api/generate and options -> Single Player Generate (#3411) 2024-05-29 16:53:18 +02:00
neocerber
527559395c Docs, Starcraft 2: Add French documentation for setup and game page (#3031)
* Started to create the french doc

* First version of sc2 setup in french finish, created the file for the introduction of the game in french

* French-fy upgrade in setup, continue translation of game description

* Finish writing FR game page, added a link to it on the english game page. Re-read and corrected both the game page and setup page.

* Corrected a sentence in the SC2 English setup guide.

* Applied 120 carac limits for french part, applied modification for consistency.

* Added reference to website yaml checker, applied several wording correction/suggestions

* Modified link to AP page to be in relative (fr/en), uniformed SC2 and random writing (fr), applied some suggestons in writing quality(fr), added a mention to the datapackage (fr/en), enhanced prog balancing recommendation (fr)

* Correction of some grammar issues

* Removed name correction for english part since done in other PR; added mention to hotkey and language restriction

* Applied suggestions of peer review

* Applied mofications proposed by reviewer about the external website

---------

Co-authored-by: neocerber <neorcerber@gmail.com>
2024-05-29 03:48:52 +02:00
Aaron Wagener
649ee117da Docs: improve contributing sign posting (#2888)
* Docs: improve sign posting for contributing

* fix styling as per the style guide

* address review comments

* apply medic's feedback
2024-05-29 03:46:17 +02:00
qwint
5b34e06c8b adds godtuner to prog and requires it for godhome flower quest manually (#3402) 2024-05-29 03:37:44 +02:00
Seldom
04e9f5c47a Migrate Terraria to new options API (#3414) 2024-05-28 20:37:07 +02:00
Aaron Wagener
dfc347cd24 Core: add options to the list of valid names instead of deleting game weights (#3381) 2024-05-27 23:52:23 +02:00
Fabian Dill
74aa4eca9d MultiServer: make !hint prefer early sphere (#2862) 2024-05-27 18:43:25 +02:00
Justus Lind
df877a9254 Muse Dash: 4.4.0 (#3395) 2024-05-27 02:27:43 +02:00
Bryce Wilson
70d97a0eb4 BizHawkClient: Add suggestion when no handler is found (#3375) 2024-05-27 02:27:04 +02:00
black-sliver
f249c36f8b Setup: pin cx_freeze to 7.0.0 (#3406)
7.1.0 is broken on Linux when using pygobject, which we use as optional dependency for kivy.
2024-05-26 21:22:40 +02:00
NewSoupVi
61e88526cf Core: Rename "count_exclusive" methods to "count_unique" (#3386)
* rename exclusive to unique

* lint

* group as well
2024-05-25 13:14:13 +02:00
Exempt-Medic
18390ecc09 Witness: Fix option description (#3396)
* Fixing description

* Another mistake
2024-05-24 19:32:23 +02:00
Aaron Wagener
8045c8717c Webhost: Allow Option Groups to specify whether they start collapsed (#3370)
* allow option groups to specify whether they should be hidden or not

* allow worlds to override whether game options starts collapsed

* remove Game Options assert so the visibility of that group can be changed

* if "Game Options" or "Item & Location Options" groups are specified, fix casing

* don't allow item & location options to have duplicates of the auto added options

* use a generator instead of a comprehension

* use consistent naming
2024-05-24 01:18:21 -04:00
NewSoupVi
613e76689e CODEOWNERS: Actually link the correct person for Yu Gi Oh (#3389) 2024-05-23 20:25:41 -05:00
Exempt-Medic
2a47f03e72 Docs: Update trigger guide and advanced yaml guide (#3385)
* I guess these don't exist anymore

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2024-05-23 20:36:45 -04:00
Aaron Wagener
8b992cbf00 Webhost: Disallow empty option groups (#3369)
* move item_and_loc_options out of the meta class and into the Options module

* don't allow empty world specified option groups

* reuse option_group generation code instead of rewriting it

* delete the default group if it's empty

* indent
2024-05-23 18:50:40 -04:00
Fabian Dill
27ae7e081f Update MultiServer.py
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2024-04-09 00:26:07 +02:00
Fabian Dill
add68329e6 MultiServer: make name groups also public 2023-12-04 22:00:10 +01:00
Fabian Dill
0768bc066a MultiServer: alternative data store based DataPackage retrieval 2023-12-04 21:51:26 +01:00
Fabian Dill
001fdfbe9f MultiServer: introduce Get keys that don't need auth 2023-12-04 21:50:10 +01:00
202 changed files with 4024 additions and 2589 deletions

View File

@@ -112,7 +112,7 @@ class AdventureContext(CommonContext):
if ': !' not in msg:
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "ReceivedItems":
msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
msg = f"Received {', '.join([self.item_names.lookup_in_slot(item.item) for item in args['items']])}"
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "Retrieved":
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:

View File

@@ -728,7 +728,7 @@ class CollectionState():
return True
return False
def has_from_list_exclusive(self, items: Iterable[str], player: int, count: int) -> bool:
def has_from_list_unique(self, items: Iterable[str], player: int, count: int) -> bool:
"""Returns True if the state contains at least `count` items matching any of the item names from a list.
Ignores duplicates of the same item."""
found: int = 0
@@ -743,7 +743,7 @@ class CollectionState():
"""Returns the cumulative count of items from a list present in state."""
return sum(self.prog_items[player][item_name] for item_name in items)
def count_from_list_exclusive(self, items: Iterable[str], player: int) -> int:
def count_from_list_unique(self, items: Iterable[str], player: int) -> int:
"""Returns the cumulative count of items from a list present in state. Ignores duplicates of the same item."""
return sum(self.prog_items[player][item_name] > 0 for item_name in items)
@@ -758,7 +758,7 @@ class CollectionState():
return True
return False
def has_group_exclusive(self, item_name_group: str, player: int, count: int = 1) -> bool:
def has_group_unique(self, item_name_group: str, player: int, count: int = 1) -> bool:
"""Returns True if the state contains at least `count` items present in a specified item group.
Ignores duplicates of the same item.
"""
@@ -778,7 +778,7 @@ class CollectionState():
for item_name in self.multiworld.worlds[player].item_name_groups[item_name_group]
)
def count_group_exclusive(self, item_name_group: str, player: int) -> int:
def count_group_unique(self, item_name_group: str, player: int) -> int:
"""Returns the cumulative count of items from an item group present in state.
Ignores duplicates of the same item."""
player_prog_items = self.prog_items[player]

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import collections
import copy
import logging
import asyncio
@@ -8,6 +9,7 @@ import sys
import typing
import time
import functools
import warnings
import ModuleUpdate
ModuleUpdate.update()
@@ -173,10 +175,74 @@ class CommonContext:
items_handling: typing.Optional[int] = None
want_slot_data: bool = True # should slot_data be retrieved via Connect
# data package
# Contents in flux until connection to server is made, to download correct data for this multiworld.
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
class NameLookupDict:
"""A specialized dict, with helper methods, for id -> name item/location data package lookups by game."""
def __init__(self, ctx: CommonContext, lookup_type: typing.Literal["item", "location"]):
self.ctx: CommonContext = ctx
self.lookup_type: typing.Literal["item", "location"] = lookup_type
self._unknown_item: typing.Callable[[int], str] = lambda key: f"Unknown {lookup_type} (ID: {key})"
self._archipelago_lookup: typing.Dict[int, str] = {}
self._flat_store: typing.Dict[int, str] = Utils.KeyedDefaultDict(self._unknown_item)
self._game_store: typing.Dict[str, typing.ChainMap[int, str]] = collections.defaultdict(
lambda: collections.ChainMap(self._archipelago_lookup, Utils.KeyedDefaultDict(self._unknown_item)))
self.warned: bool = False
# noinspection PyTypeChecker
def __getitem__(self, key: str) -> typing.Mapping[int, str]:
# TODO: In a future version (0.6.0?) this should be simplified by removing implicit id lookups support.
if isinstance(key, int):
if not self.warned:
# Use warnings instead of logger to avoid deprecation message from appearing on user side.
self.warned = True
warnings.warn(f"Implicit name lookup by id only is deprecated and only supported to maintain "
f"backwards compatibility for now. If multiple games share the same id for a "
f"{self.lookup_type}, name could be incorrect. Please use "
f"`{self.lookup_type}_names.lookup_in_game()` or "
f"`{self.lookup_type}_names.lookup_in_slot()` instead.")
return self._flat_store[key] # type: ignore
return self._game_store[key]
def __len__(self) -> int:
return len(self._game_store)
def __iter__(self) -> typing.Iterator[str]:
return iter(self._game_store)
def __repr__(self) -> str:
return self._game_store.__repr__()
def lookup_in_game(self, code: int, game_name: typing.Optional[str] = None) -> str:
"""Returns the name for an item/location id in the context of a specific game or own game if `game` is
omitted.
"""
if game_name is None:
game_name = self.ctx.game
assert game_name is not None, f"Attempted to lookup {self.lookup_type} with no game name available."
return self._game_store[game_name][code]
def lookup_in_slot(self, code: int, slot: typing.Optional[int] = None) -> str:
"""Returns the name for an item/location id in the context of a specific slot or own slot if `slot` is
omitted.
"""
if slot is None:
slot = self.ctx.slot
assert slot is not None, f"Attempted to lookup {self.lookup_type} with no slot info available."
return self.lookup_in_game(code, self.ctx.slot_info[slot].game)
def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int]) -> None:
"""Overrides existing lookup tables for a particular game."""
id_to_name_lookup_table = Utils.KeyedDefaultDict(self._unknown_item)
id_to_name_lookup_table.update({code: name for name, code in name_to_id_lookup_table.items()})
self._game_store[game] = collections.ChainMap(self._archipelago_lookup, id_to_name_lookup_table)
self._flat_store.update(id_to_name_lookup_table) # Only needed for legacy lookup method.
if game == "Archipelago":
# Keep track of the Archipelago data package separately so if it gets updated in a custom datapackage,
# it updates in all chain maps automatically.
self._archipelago_lookup.clear()
self._archipelago_lookup.update(id_to_name_lookup_table)
# defaults
starting_reconnect_delay: int = 5
@@ -231,7 +297,7 @@ class CommonContext:
# message box reporting a loss of connection
_messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None:
def __init__(self, server_address: typing.Optional[str] = None, password: typing.Optional[str] = None) -> None:
# server state
self.server_address = server_address
self.username = None
@@ -271,6 +337,9 @@ class CommonContext:
self.exit_event = asyncio.Event()
self.watcher_event = asyncio.Event()
self.item_names = self.NameLookupDict(self, "item")
self.location_names = self.NameLookupDict(self, "location")
self.jsontotextparser = JSONtoTextParser(self)
self.rawjsontotextparser = RawJSONtoTextParser(self)
self.update_data_package(network_data_package)
@@ -486,19 +555,17 @@ class CommonContext:
or remote_checksum != cache_checksum:
needed_updates.add(game)
else:
self.update_game(cached_game)
self.update_game(cached_game, game)
if needed_updates:
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
def update_game(self, game_package: dict):
for item_name, item_id in game_package["item_name_to_id"].items():
self.item_names[item_id] = item_name
for location_name, location_id in game_package["location_name_to_id"].items():
self.location_names[location_id] = location_name
def update_game(self, game_package: dict, game: str):
self.item_names.update_game(game, game_package["item_name_to_id"])
self.location_names.update_game(game, game_package["location_name_to_id"])
def update_data_package(self, data_package: dict):
for game, game_data in data_package["games"].items():
self.update_game(game_data)
self.update_game(game_data, game)
def consume_network_data_package(self, data_package: dict):
self.update_data_package(data_package)

View File

@@ -23,9 +23,7 @@ from Main import main as ERmain
from settings import get_settings
from Utils import parse_yamls, version_tuple, __version__, tuplize_version
from worlds.alttp.EntranceRandomizer import parse_arguments
from worlds.alttp.Text import TextTable
from worlds.AutoWorld import AutoWorldRegister
from worlds.generic import PlandoConnection
from worlds import failed_world_loads
@@ -432,7 +430,6 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str,
player_option = option.from_any(game_weights[option_key])
else:
player_option = option.from_any(get_choice(option_key, game_weights))
del game_weights[option_key]
else:
player_option = option.from_any(option.default) # call the from_any here to support default "random"
setattr(ret, option_key, player_option)
@@ -446,9 +443,9 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
if "linked_options" in weights:
weights = roll_linked_options(weights)
valid_trigger_names = set()
valid_keys = set()
if "triggers" in weights:
weights = roll_triggers(weights, weights["triggers"], valid_trigger_names)
weights = roll_triggers(weights, weights["triggers"], valid_keys)
requirements = weights.get("requires", {})
if requirements:
@@ -490,7 +487,7 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
raise Exception(f"Remove tag cannot be used outside of trigger contexts. Found {weight}")
if "triggers" in game_weights:
weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names)
weights = roll_triggers(weights, game_weights["triggers"], valid_keys)
game_weights = weights[ret.game]
ret.name = get_choice('name', weights)
@@ -499,42 +496,20 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
for option_key, option in world_type.options_dataclass.type_hints.items():
handle_option(ret, game_weights, option_key, option, plando_options)
valid_keys.add(option_key)
for option_key in game_weights:
if option_key in {"triggers", *valid_trigger_names}:
if option_key in {"triggers", *valid_keys}:
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.")
if PlandoOptions.items in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights, plando_options)
if PlandoOptions.connections in plando_options:
ret.plando_connections = []
options = game_weights.get("plando_connections", [])
for placement in options:
if roll_percentage(get_choice("percentage", placement, 100)):
ret.plando_connections.append(PlandoConnection(
get_choice("entrance", placement),
get_choice("exit", placement),
get_choice("direction", placement, "both")
))
roll_alttp_settings(ret, game_weights)
return ret
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
ret.plando_texts = {}
if PlandoOptions.texts in plando_options:
tt = TextTable()
tt.removeUnwantedText()
options = weights.get("plando_texts", [])
for placement in options:
if roll_percentage(get_choice_legacy("percentage", placement, 100)):
at = str(get_choice_legacy("at", placement))
if at not in tt:
raise Exception(f"No text target \"{at}\" found.")
ret.plando_texts[at] = str(get_choice_legacy("text", placement))
def roll_alttp_settings(ret: argparse.Namespace, weights):
ret.sprite_pool = weights.get('sprite_pool', [])
ret.sprite = get_choice_legacy('sprite', weights, "Link")
if 'random_sprite_on_event' in weights:

12
Main.py
View File

@@ -372,6 +372,17 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {}
# get spheres -> filter address==None -> skip empty
spheres: List[Dict[int, Set[int]]] = []
for sphere in multiworld.get_spheres():
current_sphere: Dict[int, Set[int]] = collections.defaultdict(set)
for sphere_location in sphere:
if type(sphere_location.address) is int:
current_sphere[sphere_location.player].add(sphere_location.address)
if current_sphere:
spheres.append(dict(current_sphere))
multidata = {
"slot_data": slot_data,
"slot_info": slot_info,
@@ -386,6 +397,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
"tags": ["AP"],
"minimum_versions": minimum_versions,
"seed_name": multiworld.seed_name,
"spheres": spheres,
"datapackage": data_package,
}
AutoWorld.call_all(multiworld, "modify_multidata", multidata)

View File

@@ -37,7 +37,7 @@ except ImportError:
import NetUtils
import Utils
from Utils import version_tuple, restricted_loads, Version, async_start
from Utils import version_tuple, restricted_loads, Version, async_start, get_intended_text
from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \
SlotType, LocationStore
@@ -168,13 +168,18 @@ class Context:
slot_info: typing.Dict[int, NetworkSlot]
generator_version = Version(0, 0, 0)
checksums: typing.Dict[str, str]
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
item_names: typing.Dict[str, typing.Dict[int, str]] = (
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')))
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
location_names: typing.Dict[str, typing.Dict[int, str]] = (
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')))
location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
all_item_and_group_names: typing.Dict[str, typing.Set[str]]
all_location_and_group_names: typing.Dict[str, typing.Set[str]]
non_hintable_names: typing.Dict[str, typing.Set[str]]
public_stored_data_keys: typing.Set[str] # keys that can be retrieved by a client that has not reached "auth" yet
spheres: typing.List[typing.Dict[int, typing.Set[int]]]
""" each sphere is { player: { location_id, ... } } """
logger: logging.Logger
def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
@@ -238,6 +243,7 @@ class Context:
self.stored_data = {}
self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet)
self.read_data = {}
self.spheres = []
# init empty to satisfy linter, I suppose
self.gamespackage = {}
@@ -247,6 +253,7 @@ class Context:
self.all_item_and_group_names = {}
self.all_location_and_group_names = {}
self.non_hintable_names = collections.defaultdict(frozenset)
self.public_stored_data_keys = set()
self._load_game_data()
@@ -267,14 +274,21 @@ class Context:
if "checksum" in game_package:
self.checksums[game_name] = game_package["checksum"]
for item_name, item_id in game_package["item_name_to_id"].items():
self.item_names[item_id] = item_name
self.item_names[game_name][item_id] = item_name
for location_name, location_id in game_package["location_name_to_id"].items():
self.location_names[location_id] = location_name
self.location_names[game_name][location_id] = location_name
self.all_item_and_group_names[game_name] = \
set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
self.all_location_and_group_names[game_name] = \
set(game_package["location_name_to_id"]) | set(self.location_name_groups.get(game_name, []))
archipelago_item_names = self.item_names["Archipelago"]
archipelago_location_names = self.location_names["Archipelago"]
for game in [game_name for game_name in self.gamespackage if game_name != "Archipelago"]:
# Add Archipelago items and locations to each data package.
self.item_names[game].update(archipelago_item_names)
self.location_names[game].update(archipelago_location_names)
def item_names_for_game(self, game: str) -> typing.Optional[typing.Dict[str, int]]:
return self.gamespackage[game]["item_name_to_id"] if game in self.gamespackage else None
@@ -461,10 +475,29 @@ class Context:
del data["location_name_groups"]
del data["item_name_groups"] # remove from data package, but keep in self.item_name_groups
self._init_game_data()
def _add_public_data_store_key(key: str, retriever: typing.Callable[[], typing.Any]):
"""Add key to read_data and also public_stored_data_keys, to allow retrieval before auth."""
self.public_stored_data_keys.add(key)
self.read_data[key] = retriever
for game_name, game_package in self.gamespackage.items():
_add_public_data_store_key(f"datapackage_checksum_{game_name}",
lambda lgame=game_name: self.checksums.get(lgame, None))
_add_public_data_store_key(f"item_name_to_id_{game_name}",
lambda lgame=game_name: self.gamespackage[lgame]["item_name_to_id"])
_add_public_data_store_key(f"location_name_to_id_{game_name}",
lambda lgame=game_name: self.gamespackage[lgame]["location_name_to_id"])
for game_name, data in self.item_name_groups.items():
self.read_data[f"item_name_groups_{game_name}"] = lambda lgame=game_name: self.item_name_groups[lgame]
_add_public_data_store_key(f"item_name_groups_{game_name}",
lambda lgame=game_name: self.item_name_groups[lgame])
for game_name, data in self.location_name_groups.items():
self.read_data[f"location_name_groups_{game_name}"] = lambda lgame=game_name: self.location_name_groups[lgame]
_add_public_data_store_key(f"location_name_groups_{game_name}",
lambda lgame=game_name: self.location_name_groups[lgame])
# sorted access spheres
self.spheres = decoded_obj.get("spheres", [])
# saving
@@ -624,6 +657,16 @@ class Context:
self.recheck_hints(team, slot)
return self.hints[team, slot]
def get_sphere(self, player: int, location_id: int) -> int:
"""Get sphere of a location, -1 if spheres are not available."""
if self.spheres:
for i, sphere in enumerate(self.spheres):
if location_id in sphere.get(player, set()):
return i
raise KeyError(f"No Sphere found for location ID {location_id} belonging to player {player}. "
f"Location or player may not exist.")
return -1
def get_players_package(self):
return [NetworkPlayer(t, p, self.get_aliased_name(t, p), n) for (t, p), n in self.player_names.items()]
@@ -766,10 +809,7 @@ async def on_client_connected(ctx: Context, client: Client):
for slot, connected_clients in clients.items():
if connected_clients:
name = ctx.player_names[team, slot]
players.append(
NetworkPlayer(team, slot,
ctx.name_aliases.get((team, slot), name), name)
)
players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name))
games = {ctx.games[x] for x in range(1, len(ctx.games) + 1)}
games.add("Archipelago")
await ctx.send_msgs(client, [{
@@ -784,8 +824,6 @@ async def on_client_connected(ctx: Context, client: Client):
'permissions': get_permissions(ctx),
'hint_cost': ctx.hint_cost,
'location_check_points': ctx.location_check_points,
'datapackage_versions': {game: game_data["version"] for game, game_data
in ctx.gamespackage.items() if game in games},
'datapackage_checksums': {game: game_data["checksum"] for game, game_data
in ctx.gamespackage.items() if game in games and "checksum" in game_data},
'seed_name': ctx.seed_name,
@@ -989,8 +1027,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
send_items_to(ctx, team, target_player, new_item)
ctx.logger.info('(Team #%d) %s sent %s to %s (%s)' % (
team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
ctx.player_names[(team, target_player)], ctx.location_names[location]))
team + 1, ctx.player_names[(team, slot)], ctx.item_names[ctx.slot_info[target_player].game][item_id],
ctx.player_names[(team, target_player)], ctx.location_names[ctx.slot_info[slot].game][location]))
info_text = json_format_send_event(new_item, target_player)
ctx.broadcast_team(team, [info_text])
@@ -1044,8 +1082,8 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
f"{ctx.item_names[hint.item]} is " \
f"at {ctx.location_names[hint.location]} " \
f"{ctx.item_names[ctx.slot_info[hint.receiving_player].game][hint.item]} is " \
f"at {ctx.location_names[ctx.slot_info[hint.finding_player].game][hint.location]} " \
f"in {ctx.player_names[team, hint.finding_player]}'s World"
if hint.entrance:
@@ -1074,28 +1112,6 @@ def json_format_send_event(net_item: NetworkItem, receiving_player: int):
"item": net_item}
def get_intended_text(input_text: str, possible_answers) -> typing.Tuple[str, bool, str]:
picks = Utils.get_fuzzy_results(input_text, possible_answers, limit=2)
if len(picks) > 1:
dif = picks[0][1] - picks[1][1]
if picks[0][1] == 100:
return picks[0][0], True, "Perfect Match"
elif picks[0][1] < 75:
return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
elif dif > 5:
return picks[0][0], True, "Close Match"
else:
return picks[0][0], False, f"Too many close matches for '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
else:
if picks[0][1] > 90:
return picks[0][0], True, "Only Option Match"
else:
return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
class CommandMeta(type):
def __new__(cls, name, bases, attrs):
commands = attrs["commands"] = {}
@@ -1347,7 +1363,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
if self.ctx.remaining_mode == "enabled":
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
@@ -1360,7 +1376,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
@@ -1378,7 +1394,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
if locations:
names = [self.ctx.location_names[location] for location in locations]
game = self.ctx.slot_info[self.client.slot].game
names = [self.ctx.location_names[game][location] for location in locations]
if filter_text:
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
if filter_text in location_groups: # location group name
@@ -1403,7 +1420,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
if locations:
names = [self.ctx.location_names[location] for location in locations]
game = self.ctx.slot_info[self.client.slot].game
names = [self.ctx.location_names[game][location] for location in locations]
if filter_text:
location_groups = self.ctx.location_name_groups[self.ctx.games[self.client.slot]]
if filter_text in location_groups: # location group name
@@ -1484,10 +1502,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
elif input_text.isnumeric():
game = self.ctx.games[self.client.slot]
hint_id = int(input_text)
hint_name = self.ctx.item_names[hint_id] \
if not for_location and hint_id in self.ctx.item_names \
else self.ctx.location_names[hint_id] \
if for_location and hint_id in self.ctx.location_names \
hint_name = self.ctx.item_names[game][hint_id] \
if not for_location and hint_id in self.ctx.item_names[game] \
else self.ctx.location_names[game][hint_id] \
if for_location and hint_id in self.ctx.location_names[game] \
else None
if hint_name in self.ctx.non_hintable_names[game]:
self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
@@ -1549,6 +1567,9 @@ class ClientMessageProcessor(CommonCommandProcessor):
self.ctx.random.shuffle(not_found_hints)
# By popular vote, make hints prefer non-local placements
not_found_hints.sort(key=lambda hint: int(hint.receiving_player != hint.finding_player))
# By another popular vote, prefer early sphere
not_found_hints.sort(key=lambda hint: self.ctx.get_sphere(hint.finding_player, hint.location),
reverse=True)
hints = found_hints + old_hints
while can_pay > 0:
@@ -1558,10 +1579,10 @@ class ClientMessageProcessor(CommonCommandProcessor):
hints.append(hint)
can_pay -= 1
self.ctx.hints_used[self.client.team, self.client.slot] += 1
points_available = get_client_points(self.ctx, self.client)
self.ctx.notify_hints(self.client.team, hints)
if not_found_hints:
points_available = get_client_points(self.ctx, self.client)
if hints and cost and int((points_available // cost) == 0):
self.output(
f"There may be more hintables, however, you cannot afford to pay for any more. "
@@ -1624,10 +1645,25 @@ def get_slot_points(ctx: Context, team: int, slot: int) -> int:
ctx.get_hint_cost(slot) * ctx.hints_used[team, slot])
async def process_get(ctx: Context, client: Client, args: dict, cmd: dict):
if "keys" not in args or not isinstance(args["keys"], list):
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
"text": 'Retrieve', "original_cmd": cmd}])
return
args["cmd"] = "Retrieved"
keys = args["keys"]
args["keys"] = {
key: ctx.read_data.get(key[6:], lambda: None)() if key.startswith("_read_") else
ctx.stored_data.get(key, None)
for key in keys
}
await ctx.send_msgs(client, [args])
async def process_client_cmd(ctx: Context, client: Client, args: dict):
try:
cmd: str = args["cmd"]
except:
except Exception:
ctx.logger.exception(f"Could not get command from {args}")
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "cmd", "original_cmd": None,
"text": f"Could not get command from {args} at `cmd`"}])
@@ -1730,6 +1766,9 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
await ctx.send_msgs(client, [{"cmd": "DataPackage",
"data": {"games": ctx.gamespackage}}])
elif cmd == "Get" and args.get("keys", None) and all(key in ctx.public_stored_data_keys for key in args["keys"]):
await process_get(ctx, client, args, cmd)
elif client.auth:
if cmd == "ConnectUpdate":
if not args:
@@ -1825,18 +1864,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
await ctx.send_encoded_msgs(bounceclient, msg)
elif cmd == "Get":
if "keys" not in args or type(args["keys"]) != list:
await ctx.send_msgs(client, [{'cmd': 'InvalidPacket', "type": "arguments",
"text": 'Retrieve', "original_cmd": cmd}])
return
args["cmd"] = "Retrieved"
keys = args["keys"]
args["keys"] = {
key: ctx.read_data.get(key[6:], lambda: None)() if key.startswith("_read_") else
ctx.stored_data.get(key, None)
for key in keys
}
await ctx.send_msgs(client, [args])
await process_get(ctx, client, args, cmd)
elif cmd == "Set":
if "key" not in args or args["key"].startswith("_read_") or \

View File

@@ -247,7 +247,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
def _handle_item_id(self, node: JSONMessagePart):
item_id = int(node["text"])
node["text"] = self.ctx.item_names[item_id]
node["text"] = self.ctx.item_names.lookup_in_slot(item_id, node["player"])
return self._handle_item_name(node)
def _handle_location_name(self, node: JSONMessagePart):
@@ -255,8 +255,8 @@ class JSONtoTextParser(metaclass=HandlerMeta):
return self._handle_color(node)
def _handle_location_id(self, node: JSONMessagePart):
item_id = int(node["text"])
node["text"] = self.ctx.location_names[item_id]
location_id = int(node["text"])
node["text"] = self.ctx.location_names.lookup_in_slot(location_id, node["player"])
return self._handle_location_name(node)
def _handle_entrance_name(self, node: JSONMessagePart):

View File

@@ -12,6 +12,7 @@ from copy import deepcopy
from dataclasses import dataclass
from schema import And, Optional, Or, Schema
from typing_extensions import Self
from Utils import get_fuzzy_results, is_iterable_except_str
@@ -896,6 +897,228 @@ class ItemSet(OptionSet):
convert_name_groups = True
class PlandoText(typing.NamedTuple):
at: str
text: typing.List[str]
percentage: int = 100
PlandoTextsFromAnyType = typing.Union[
typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoText, typing.Any]], typing.Any
]
class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys):
default = ()
supports_weighting = False
display_name = "Plando Texts"
def __init__(self, value: typing.Iterable[PlandoText]) -> None:
self.value = list(deepcopy(value))
super().__init__()
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
from BaseClasses import PlandoOptions
if self.value and not (PlandoOptions.texts & plando_options):
# plando is disabled but plando options were given so overwrite the options
self.value = []
logging.warning(f"The plando texts module is turned off, "
f"so text for {player_name} will be ignored.")
@classmethod
def from_any(cls, data: PlandoTextsFromAnyType) -> Self:
texts: typing.List[PlandoText] = []
if isinstance(data, typing.Iterable):
for text in data:
if isinstance(text, typing.Mapping):
if random.random() < float(text.get("percentage", 100)/100):
at = text.get("at", None)
if at is not None:
given_text = text.get("text", [])
if isinstance(given_text, str):
given_text = [given_text]
texts.append(PlandoText(
at,
given_text,
text.get("percentage", 100)
))
elif isinstance(text, PlandoText):
if random.random() < float(text.percentage/100):
texts.append(text)
else:
raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}")
cls.verify_keys([text.at for text in texts])
return cls(texts)
else:
raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}")
@classmethod
def get_option_name(cls, value: typing.List[PlandoText]) -> str:
return str({text.at: " ".join(text.text) for text in value})
def __iter__(self) -> typing.Iterator[PlandoText]:
yield from self.value
def __getitem__(self, index: typing.SupportsIndex) -> PlandoText:
return self.value.__getitem__(index)
def __len__(self) -> int:
return self.value.__len__()
class ConnectionsMeta(AssembleOptions):
def __new__(mcs, name: str, bases: tuple[type, ...], attrs: dict[str, typing.Any]):
if name != "PlandoConnections":
assert "entrances" in attrs, f"Please define valid entrances for {name}"
attrs["entrances"] = frozenset((connection.lower() for connection in attrs["entrances"]))
assert "exits" in attrs, f"Please define valid exits for {name}"
attrs["exits"] = frozenset((connection.lower() for connection in attrs["exits"]))
if "__doc__" not in attrs:
attrs["__doc__"] = PlandoConnections.__doc__
cls = super().__new__(mcs, name, bases, attrs)
return cls
class PlandoConnection(typing.NamedTuple):
class Direction:
entrance = "entrance"
exit = "exit"
both = "both"
entrance: str
exit: str
direction: typing.Literal["entrance", "exit", "both"] # TODO: convert Direction to StrEnum once 3.8 is dropped
percentage: int = 100
PlandoConFromAnyType = typing.Union[
typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoConnection, typing.Any]], typing.Any
]
class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=ConnectionsMeta):
"""Generic connections plando. Format is:
- entrance: "Entrance Name"
exit: "Exit Name"
direction: "Direction"
percentage: 100
Direction must be one of 'entrance', 'exit', or 'both', and defaults to 'both' if omitted.
Percentage is an integer from 1 to 100, and defaults to 100 when omitted."""
display_name = "Plando Connections"
default = ()
supports_weighting = False
entrances: typing.ClassVar[typing.AbstractSet[str]]
exits: typing.ClassVar[typing.AbstractSet[str]]
duplicate_exits: bool = False
"""Whether or not exits should be allowed to be duplicate."""
def __init__(self, value: typing.Iterable[PlandoConnection]):
self.value = list(deepcopy(value))
super(PlandoConnections, self).__init__()
@classmethod
def validate_entrance_name(cls, entrance: str) -> bool:
return entrance.lower() in cls.entrances
@classmethod
def validate_exit_name(cls, exit: str) -> bool:
return exit.lower() in cls.exits
@classmethod
def can_connect(cls, entrance: str, exit: str) -> bool:
"""Checks that a given entrance can connect to a given exit.
By default, this will always return true unless overridden."""
return True
@classmethod
def validate_plando_connections(cls, connections: typing.Iterable[PlandoConnection]) -> None:
used_entrances: typing.List[str] = []
used_exits: typing.List[str] = []
for connection in connections:
entrance = connection.entrance
exit = connection.exit
direction = connection.direction
if direction not in (PlandoConnection.Direction.entrance,
PlandoConnection.Direction.exit,
PlandoConnection.Direction.both):
raise ValueError(f"Unknown direction: {direction}")
if entrance in used_entrances:
raise ValueError(f"Duplicate Entrance {entrance} not allowed.")
if not cls.duplicate_exits and exit in used_exits:
raise ValueError(f"Duplicate Exit {exit} not allowed.")
used_entrances.append(entrance)
used_exits.append(exit)
if not cls.validate_entrance_name(entrance):
raise ValueError(f"{entrance.title()} is not a valid entrance.")
if not cls.validate_exit_name(exit):
raise ValueError(f"{exit.title()} is not a valid exit.")
if not cls.can_connect(entrance, exit):
raise ValueError(f"Connection between {entrance.title()} and {exit.title()} is invalid.")
@classmethod
def from_any(cls, data: PlandoConFromAnyType) -> Self:
if not isinstance(data, typing.Iterable):
raise Exception(f"Cannot create plando connections from non-List value, got {type(data)}.")
value: typing.List[PlandoConnection] = []
for connection in data:
if isinstance(connection, typing.Mapping):
percentage = connection.get("percentage", 100)
if random.random() < float(percentage / 100):
entrance = connection.get("entrance", None)
if is_iterable_except_str(entrance):
entrance = random.choice(sorted(entrance))
exit = connection.get("exit", None)
if is_iterable_except_str(exit):
exit = random.choice(sorted(exit))
direction = connection.get("direction", "both")
if not entrance or not exit:
raise Exception("Plando connection must have an entrance and an exit.")
value.append(PlandoConnection(
entrance,
exit,
direction,
percentage
))
elif isinstance(connection, PlandoConnection):
if random.random() < float(connection.percentage / 100):
value.append(connection)
else:
raise Exception(f"Cannot create connection from non-Dict type, got {type(connection)}.")
cls.validate_plando_connections(value)
return cls(value)
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
from BaseClasses import PlandoOptions
if self.value and not (PlandoOptions.connections & plando_options):
# plando is disabled but plando options were given so overwrite the options
self.value = []
logging.warning(f"The plando connections module is turned off, "
f"so connections for {player_name} will be ignored.")
@classmethod
def get_option_name(cls, value: typing.List[PlandoConnection]) -> str:
return ", ".join(["%s %s %s" % (connection.entrance,
"<=>" if connection.direction == PlandoConnection.Direction.both else
"<=" if connection.direction == PlandoConnection.Direction.exit else
"=>",
connection.exit) for connection in value])
def __getitem__(self, index: typing.SupportsIndex) -> PlandoConnection:
return self.value.__getitem__(index)
def __iter__(self) -> typing.Iterator[PlandoConnection]:
yield from self.value
def __len__(self) -> int:
return len(self.value)
class Accessibility(Choice):
"""Set rules for reachability of your items/locations.
Locations: ensure everything can be reached and acquired.
@@ -910,8 +1133,10 @@ class Accessibility(Choice):
class ProgressionBalancing(NamedRange):
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
A lower setting means more getting stuck. A higher setting means less getting stuck."""
"""
A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
A lower setting means more getting stuck. A higher setting means less getting stuck.
"""
default = 50
range_start = 0
range_end = 99
@@ -984,7 +1209,7 @@ class LocalItems(ItemSet):
class NonLocalItems(ItemSet):
"""Forces these items to be outside their native world."""
display_name = "Not Local Items"
display_name = "Non-local Items"
class StartInventory(ItemDict):
@@ -1047,7 +1272,8 @@ class ItemLinks(OptionList):
])
@staticmethod
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, allow_item_groups: bool = True) -> typing.Set:
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world,
allow_item_groups: bool = True) -> typing.Set:
pool = set()
for item_name in items:
if item_name not in world.item_names and (not allow_item_groups or item_name not in world.item_name_groups):
@@ -1130,9 +1356,41 @@ class OptionGroup(typing.NamedTuple):
"""Name of the group to categorize these options in for display on the WebHost and in generated YAMLS."""
options: typing.List[typing.Type[Option[typing.Any]]]
"""Options to be in the defined group."""
start_collapsed: bool = False
"""Whether the group will start collapsed on the WebHost options pages."""
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints,
StartLocationHints, ExcludeLocations, PriorityLocations, ItemLinks]
"""
Options that are always populated in "Item & Location Options" Option Group. Cannot be moved to another group.
If desired, a custom "Item & Location Options" Option Group can be defined, but only for adding additional options to
it.
"""
def get_option_groups(world: typing.Type[World], visibility_level: Visibility = Visibility.template) -> typing.Dict[
str, typing.Dict[str, typing.Type[Option[typing.Any]]]]:
"""Generates and returns a dictionary for the option groups of a specified world."""
option_groups = {option: option_group.name
for option_group in world.web.option_groups
for option in option_group.options}
# add a default option group for uncategorized options to get thrown into
ordered_groups = ["Game Options"]
[ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups]
grouped_options = {group: {} for group in ordered_groups}
for option_name, option in world.options_dataclass.type_hints.items():
if visibility_level & option.visibility:
grouped_options[option_groups.get(option, "Game Options")][option_name] = option
# if the world doesn't have any ungrouped options, this group will be empty so just remove it
if not grouped_options["Game Options"]:
del grouped_options["Game Options"]
return grouped_options
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True) -> None:
import os
import yaml
@@ -1170,17 +1428,7 @@ def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], ge
for game_name, world in AutoWorldRegister.world_types.items():
if not world.hidden or generate_hidden:
option_groups = {option: option_group.name
for option_group in world.web.option_groups
for option in option_group.options}
ordered_groups = ["Game Options"]
[ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups]
grouped_options = {group: {} for group in ordered_groups}
for option_name, option in world.options_dataclass.type_hints.items():
if option.visibility >= Visibility.template:
grouped_options[option_groups.get(option, "Game Options")][option_name] = option
grouped_options = get_option_groups(world)
with open(local_path("data", "options.yaml")) as f:
file_data = f.read()
res = Template(file_data).render(

View File

@@ -1,8 +1,10 @@
# [Archipelago](https://archipelago.gg) ![Discord Shield](https://discordapp.com/api/guilds/731205301247803413/widget.png?style=shield) | [Install](https://github.com/ArchipelagoMW/Archipelago/releases)
Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases, presently, Archipelago is also the randomizer itself.
Archipelago provides a generic framework for developing multiworld capability for game randomizers. In all cases,
presently, Archipelago is also the randomizer itself.
Currently, the following games are supported:
* The Legend of Zelda: A Link to the Past
* Factorio
* Minecraft
@@ -77,36 +79,57 @@ windows binaries.
## History
Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here. The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are:
Archipelago is built upon a strong legacy of brilliant hobbyists. We want to honor that legacy by showing it here.
The repositories which Archipelago is built upon, inspired by, or otherwise owes its gratitude to are:
* [bonta0's MultiWorld](https://github.com/Bonta0/ALttPEntranceRandomizer/tree/multiworld_31)
* [AmazingAmpharos' Entrance Randomizer](https://github.com/AmazingAmpharos/ALttPEntranceRandomizer)
* [VT Web Randomizer](https://github.com/sporchia/alttp_vt_randomizer)
* [Dessyreqt's alttprandomizer](https://github.com/Dessyreqt/alttprandomizer)
* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89) and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make the vast majority of Enemizer contributions.
* [Zarby89's](https://github.com/Ijwu/Enemizer/commits?author=Zarby89)
and [sosuke3's](https://github.com/Ijwu/Enemizer/commits?author=sosuke3) contributions to Enemizer, which make up the
vast majority of Enemizer contributions.
We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the path. Just because one person's name may be in a repository title does not mean that only one person made that project happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago but we hope to honor them fairly.
We recognize that there is a strong community of incredibly smart people that have come before us and helped pave the
path. Just because one person's name may be in a repository title does not mean that only one person made that project
happen. We can't hope to perfectly cover every single contribution that lead up to Archipelago, but we hope to honor
them fairly.
### Path to the Archipelago
Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to _MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as "Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository (as opposed to a 'forked repo') and change the name (which came later) to better reflect our project.
Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a
long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to
_MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as
"Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository
(as opposed to a 'forked repo') and change the name (which came later) to better reflect our project.
## Running Archipelago
For most people, all you need to do is head over to the [releases](https://github.com/ArchipelagoMW/Archipelago/releases) page then download and run the appropriate installer, or AppImage for Linux-based systems.
If you are a developer or are running on a platform with no compiled releases available, please see our doc on [running Archipelago from source](docs/running%20from%20source.md).
For most people, all you need to do is head over to
the [releases page](https://github.com/ArchipelagoMW/Archipelago/releases), then download and run the appropriate
installer, or AppImage for Linux-based systems.
If you are a developer or are running on a platform with no compiled releases available, please see our doc on
[running Archipelago from source](docs/running%20from%20source.md).
## Related Repositories
This project makes use of multiple other projects. We wouldn't be here without these other repositories and the contributions of their developers, past and present.
This project makes use of multiple other projects. We wouldn't be here without these other repositories and the
contributions of their developers, past and present.
* [z3randomizer](https://github.com/ArchipelagoMW/z3randomizer)
* [Enemizer](https://github.com/Ijwu/Enemizer)
* [Ocarina of Time Randomizer](https://github.com/TestRunnerSRL/OoT-Randomizer)
## Contributing
For contribution guidelines, please see our [Contributing doc.](/docs/contributing.md)
To contribute to Archipelago, including the WebHost, core program, or by adding a new game, see our
[Contributing guidelines](/docs/contributing.md).
## FAQ
For Frequently asked questions, please see the website's [FAQ Page.](https://archipelago.gg/faq/en/)
For Frequently asked questions, please see the website's [FAQ Page](https://archipelago.gg/faq/en/).
## Code of Conduct
Please refer to our [code of conduct.](/docs/code_of_conduct.md)
Please refer to our [code of conduct](/docs/code_of_conduct.md).

View File

@@ -247,8 +247,8 @@ async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
toDraw = ""
for i in range(20):
if i < len(str(ctx.item_names[l.item])):
toDraw += str(ctx.item_names[l.item])[i]
if i < len(str(ctx.item_names.lookup_in_slot(l.item))):
toDraw += str(ctx.item_names.lookup_in_slot(l.item))[i]
else:
break
f.write(toDraw)

View File

@@ -46,7 +46,7 @@ class Version(typing.NamedTuple):
return ".".join(str(item) for item in self)
__version__ = "0.4.6"
__version__ = "0.5.0"
version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux")
@@ -458,6 +458,15 @@ class KeyedDefaultDict(collections.defaultdict):
"""defaultdict variant that uses the missing key as argument to default_factory"""
default_factory: typing.Callable[[typing.Any], typing.Any]
def __init__(self,
default_factory: typing.Callable[[Any], Any] = None,
seq: typing.Union[typing.Mapping, typing.Iterable, None] = None,
**kwargs):
if seq is not None:
super().__init__(default_factory, seq, **kwargs)
else:
super().__init__(default_factory, **kwargs)
def __missing__(self, key):
self[key] = value = self.default_factory(key)
return value
@@ -619,6 +628,41 @@ def get_fuzzy_results(input_word: str, word_list: typing.Collection[str], limit:
)
def get_intended_text(input_text: str, possible_answers) -> typing.Tuple[str, bool, str]:
picks = get_fuzzy_results(input_text, possible_answers, limit=2)
if len(picks) > 1:
dif = picks[0][1] - picks[1][1]
if picks[0][1] == 100:
return picks[0][0], True, "Perfect Match"
elif picks[0][1] < 75:
return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
elif dif > 5:
return picks[0][0], True, "Close Match"
else:
return picks[0][0], False, f"Too many close matches for '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
else:
if picks[0][1] > 90:
return picks[0][0], True, "Only Option Match"
else:
return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \
f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)"
def get_input_text_from_response(text: str, command: str) -> typing.Optional[str]:
if "did you mean " in text:
for question in ("Didn't find something that closely matches",
"Too many close matches"):
if text.startswith(question):
name = get_text_between(text, "did you mean '",
"'? (")
return f"!{command} {name}"
elif text.startswith("Missing: "):
return text.replace("Missing: ", "!hint_location ")
return None
def open_filename(title: str, filetypes: typing.Iterable[typing.Tuple[str, typing.Iterable[str]]], suggest: str = "") \
-> typing.Optional[str]:
logging.info(f"Opening file input dialog for {title}.")

View File

@@ -176,7 +176,7 @@ class WargrooveContext(CommonContext):
if not os.path.isfile(path):
open(path, 'w').close()
# Announcing commander unlocks
item_name = self.item_names[network_item.item]
item_name = self.item_names.lookup_in_slot(network_item.item)
if item_name in faction_table.keys():
for commander in faction_table[item_name]:
logger.info(f"{commander.name} has been unlocked!")
@@ -197,7 +197,7 @@ class WargrooveContext(CommonContext):
open(print_path, 'w').close()
with open(print_path, 'w') as f:
f.write("Received " +
self.item_names[network_item.item] +
self.item_names.lookup_in_slot(network_item.item) +
" from " +
self.player_names[network_item.player])
f.close()
@@ -342,7 +342,7 @@ class WargrooveContext(CommonContext):
faction_items = 0
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
for network_item in self.items_received:
if self.item_names[network_item.item] in faction_item_names:
if self.item_names.lookup_in_slot(network_item.item) in faction_item_names:
faction_items += 1
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
# Must be an integer larger than 0

View File

@@ -56,15 +56,6 @@ def get_datapackage():
return network_data_package
@api_endpoints.route('/datapackage_version')
@cache.cached()
def get_datapackage_versions():
from worlds import AutoWorldRegister
version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()}
return version_package
@api_endpoints.route('/datapackage_checksum')
@cache.cached()
def get_datapackage_checksums():

View File

@@ -106,9 +106,9 @@ class WebHostContext(Context):
static_gamespackage = self.gamespackage # this is shared across all rooms
static_item_name_groups = self.item_name_groups
static_location_name_groups = self.location_name_groups
self.gamespackage = {"Archipelago": static_gamespackage["Archipelago"]} # this may be modified by _load
self.item_name_groups = {}
self.location_name_groups = {}
self.gamespackage = {"Archipelago": static_gamespackage.get("Archipelago", {})} # this may be modified by _load
self.item_name_groups = {"Archipelago": static_item_name_groups.get("Archipelago", {})}
self.location_name_groups = {"Archipelago": static_location_name_groups.get("Archipelago", {})}
for game in list(multidata.get("datapackage", {})):
game_data = multidata["datapackage"][game]

View File

@@ -6,7 +6,7 @@ import random
import tempfile
import zipfile
from collections import Counter
from typing import Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Union, Set
from flask import flash, redirect, render_template, request, session, url_for
from pony.orm import commit, db_session
@@ -16,6 +16,7 @@ from Generate import PlandoOptions, handle_name
from Main import main as ERmain
from Utils import __version__
from WebHostLib import app
from settings import ServerOptions, GeneratorOptions
from worlds.alttp.EntranceRandomizer import parse_arguments
from .check import get_yaml_data, roll_options
from .models import Generation, STATE_ERROR, STATE_QUEUED, Seed, UUID
@@ -23,25 +24,22 @@ from .upload import upload_zip_to_db
def get_meta(options_source: dict, race: bool = False) -> Dict[str, Union[List[str], Dict[str, Any]]]:
plando_options = {
options_source.get("plando_bosses", ""),
options_source.get("plando_items", ""),
options_source.get("plando_connections", ""),
options_source.get("plando_texts", "")
}
plando_options -= {""}
plando_options: Set[str] = set()
for substr in ("bosses", "items", "connections", "texts"):
if options_source.get(f"plando_{substr}", substr in GeneratorOptions.plando_options):
plando_options.add(substr)
server_options = {
"hint_cost": int(options_source.get("hint_cost", 10)),
"release_mode": options_source.get("release_mode", "goal"),
"remaining_mode": options_source.get("remaining_mode", "disabled"),
"collect_mode": options_source.get("collect_mode", "disabled"),
"item_cheat": bool(int(options_source.get("item_cheat", 1))),
"hint_cost": int(options_source.get("hint_cost", ServerOptions.hint_cost)),
"release_mode": options_source.get("release_mode", ServerOptions.release_mode),
"remaining_mode": options_source.get("remaining_mode", ServerOptions.remaining_mode),
"collect_mode": options_source.get("collect_mode", ServerOptions.collect_mode),
"item_cheat": bool(int(options_source.get("item_cheat", not ServerOptions.disable_item_cheat))),
"server_password": options_source.get("server_password", None),
}
generator_options = {
"spoiler": int(options_source.get("spoiler", 0)),
"race": race
"spoiler": int(options_source.get("spoiler", GeneratorOptions.spoiler)),
"race": race,
}
if race:

View File

@@ -11,6 +11,7 @@ import Options
from Utils import local_path
from worlds.AutoWorld import AutoWorldRegister
from . import app, cache
from .generate import get_meta
def create() -> None:
@@ -27,26 +28,21 @@ def get_world_theme(game_name: str) -> str:
def render_options_page(template: str, world_name: str, is_complex: bool = False) -> Union[Response, str]:
visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui
world = AutoWorldRegister.world_types[world_name]
if world.hidden or world.web.options_page is False:
return redirect("games")
visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui
option_groups = {option: option_group.name
for option_group in world.web.option_groups
for option in option_group.options}
ordered_groups = ["Game Options", *[group.name for group in world.web.option_groups]]
grouped_options = {group: {} for group in ordered_groups}
for option_name, option in world.options_dataclass.type_hints.items():
# Exclude settings from options pages if their visibility is disabled
if visibility_flag in option.visibility:
grouped_options[option_groups.get(option, "Game Options")][option_name] = option
start_collapsed = {"Game Options": False}
for group in world.web.option_groups:
start_collapsed[group.name] = group.start_collapsed
return render_template(
template,
world_name=world_name,
world=world,
option_groups=grouped_options,
option_groups=Options.get_option_groups(world, visibility_level=visibility_flag),
start_collapsed=start_collapsed,
issubclass=issubclass,
Options=Options,
theme=get_world_theme(world_name),
@@ -55,7 +51,7 @@ def render_options_page(template: str, world_name: str, is_complex: bool = False
def generate_game(options: Dict[str, Union[dict, str]]) -> Union[Response, str]:
from .generate import start_generation
return start_generation(options, {"plando_options": ["items", "connections", "texts", "bosses"]})
return start_generation(options, get_meta({}))
def send_yaml(player_name: str, formatted_options: dict) -> Response:
@@ -80,6 +76,34 @@ def test_ordered(obj):
def option_presets(game: str) -> Response:
world = AutoWorldRegister.world_types[game]
presets = {}
for preset_name, preset in world.web.options_presets.items():
presets[preset_name] = {}
for preset_option_name, preset_option in preset.items():
if preset_option == "random":
presets[preset_name][preset_option_name] = preset_option
continue
option = world.options_dataclass.type_hints[preset_option_name].from_any(preset_option)
if isinstance(option, Options.NamedRange) and isinstance(preset_option, str):
assert preset_option in option.special_range_names, \
f"Invalid preset value '{preset_option}' for '{preset_option_name}' in '{preset_name}'. " \
f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}."
presets[preset_name][preset_option_name] = option.value
elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.ItemDict)):
presets[preset_name][preset_option_name] = option.value
elif isinstance(preset_option, str):
# Ensure the option value is valid for Choice and Toggle options
assert option.name_lookup[option.value] == preset_option, \
f"Invalid option value '{preset_option}' for '{preset_option_name}' in preset '{preset_name}'. " \
f"Values must not be resolved to a different option via option.from_text (or an alias)."
# Use the name of the option
presets[preset_name][preset_option_name] = option.current_key
else:
# Use the name of the option
presets[preset_name][preset_option_name] = option.current_key
class SetEncoder(json.JSONEncoder):
def default(self, obj):
from collections.abc import Set
@@ -87,7 +111,7 @@ def option_presets(game: str) -> Response:
return list(obj)
return json.JSONEncoder.default(self, obj)
json_data = json.dumps(world.web.options_presets, cls=SetEncoder)
json_data = json.dumps(presets, cls=SetEncoder)
response = Response(json_data)
response.headers["Content-Type"] = "application/json"
return response
@@ -173,9 +197,9 @@ def generate_yaml(game: str):
else:
options[key] = val
# Detect and build ItemDict options from their name pattern
for key, val in options.copy().items():
key_parts = key.rsplit("||", 2)
# Detect and build ItemDict options from their name pattern
if key_parts[-1] == "qty":
if key_parts[0] not in options:
options[key_parts[0]] = {}
@@ -183,6 +207,13 @@ def generate_yaml(game: str):
options[key_parts[0]][key_parts[1]] = int(val)
del options[key]
# Detect keys which end with -custom, indicating a TextChoice with a possible custom value
elif key_parts[-1].endswith("-custom"):
if val:
options[key_parts[-1][:-7]] = val
del options[key]
# Detect random-* keys and set their options accordingly
for key, val in options.copy().items():
if key.startswith("random-"):

View File

@@ -27,7 +27,7 @@ const adjustTableHeight = () => {
* @returns {string}
*/
const secondsToHours = (seconds) => {
let hours = Math.floor(seconds / 3600);
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
@@ -38,18 +38,18 @@ window.addEventListener('load', () => {
info: false,
dom: "t",
stateSave: true,
stateSaveCallback: function(settings, data) {
stateSaveCallback: function (settings, data) {
delete data.search;
localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data));
},
stateLoadCallback: function(settings) {
stateLoadCallback: function (settings) {
return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`));
},
footerCallback: function(tfoot, data, start, end, display) {
footerCallback: function (tfoot, data, start, end, display) {
if (tfoot) {
const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x));
Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText =
(activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
(activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
}
},
columnDefs: [
@@ -123,49 +123,64 @@ window.addEventListener('load', () => {
event.preventDefault();
}
});
const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker');
const target_second = document.getElementById('tracker-wrapper').getAttribute('data-second') + 3;
const target_second = parseInt(document.getElementById('tracker-wrapper').getAttribute('data-second')) + 3;
console.log("Target second of refresh: " + target_second);
function getSleepTimeSeconds(){
function getSleepTimeSeconds() {
// -40 % 60 is -40, which is absolutely wrong and should burn
var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60;
return sleepSeconds || 60;
}
let update_on_view = false;
const update = () => {
const target = $("<div></div>");
console.log("Updating Tracker...");
target.load(location.href, function (response, status) {
if (status === "success") {
target.find(".table").each(function (i, new_table) {
const new_trs = $(new_table).find("tbody>tr");
const footer_tr = $(new_table).find("tfoot>tr");
const old_table = tables.eq(i);
const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
old_table.clear();
if (footer_tr.length) {
$(old_table.table).find("tfoot").html(footer_tr);
}
old_table.rows.add(new_trs);
old_table.draw();
$(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
$(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
});
$("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
} else {
console.log("Failed to connect to Server, in order to update Table Data.");
console.log(response);
}
})
setTimeout(update, getSleepTimeSeconds()*1000);
if (document.hidden) {
console.log("Document reporting as not visible, not updating Tracker...");
update_on_view = true;
} else {
update_on_view = false;
const target = $("<div></div>");
console.log("Updating Tracker...");
target.load(location.href, function (response, status) {
if (status === "success") {
target.find(".table").each(function (i, new_table) {
const new_trs = $(new_table).find("tbody>tr");
const footer_tr = $(new_table).find("tfoot>tr");
const old_table = tables.eq(i);
const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
old_table.clear();
if (footer_tr.length) {
$(old_table.table).find("tfoot").html(footer_tr);
}
old_table.rows.add(new_trs);
old_table.draw();
$(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
$(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
});
$("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
} else {
console.log("Failed to connect to Server, in order to update Table Data.");
console.log(response);
}
})
}
updater = setTimeout(update, getSleepTimeSeconds() * 1000);
}
setTimeout(update, getSleepTimeSeconds()*1000);
let updater = setTimeout(update, getSleepTimeSeconds() * 1000);
window.addEventListener('resize', () => {
adjustTableHeight();
tables.draw();
});
window.addEventListener('visibilitychange', () => {
if (!document.hidden && update_on_view) {
console.log("Page became visible, tracker should be refreshed.");
clearTimeout(updater);
update();
}
});
adjustTableHeight();
});

View File

@@ -24,7 +24,8 @@
<br />
{% endif %}
{% if room.tracker %}
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a> enabled.
This room has a <a href="{{ url_for("get_multiworld_tracker", tracker=room.tracker) }}">Multiworld Tracker</a>
and a <a href="{{ url_for("get_multiworld_sphere_tracker", tracker=room.tracker) }}">Sphere Tracker</a> enabled.
<br />
{% endif %}
The server for this room will be paused after {{ room.timeout//60//60 }} hours of inactivity.

View File

@@ -0,0 +1,72 @@
{% extends "tablepage.html" %}
{% block head %}
{{ super() }}
<title>Multiworld Sphere Tracker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="styles/tracker.css") }}" />
<script type="application/ecmascript" src="{{ url_for("static", filename="assets/trackerCommon.js") }}"></script>
{% endblock %}
{% block body %}
{% include "header/dirtHeader.html" %}
<div id="tracker-wrapper" data-tracker="{{ room.tracker | suuid }}">
<div id="tracker-header-bar">
<input placeholder="Search" id="search" />
<div class="info">
{% if tracker_data.get_spheres() %}
This tracker lists already found locations by their logical access sphere.
It ignores items that cannot be sent
and will therefore differ from the sphere numbers in the spoiler playthrough.
This tracker will automatically update itself periodically.
{% else %}
This Multiworld has no Sphere data, likely due to being too old, cannot display data.
{% endif %}
</div>
</div>
<div id="tables-container">
{%- for team, players in tracker_data.get_all_players().items() %}
<div class="table-wrapper">
<table id="checks-table" class="table non-unique-item-table">
<thead>
<tr>
<th>Sphere</th>
{#- Mimicking hint table header for familiarity. #}
<th>Finder</th>
<th>Receiver</th>
<th>Item</th>
<th>Location</th>
<th>Game</th>
</tr>
</thead>
<tbody>
{%- for sphere in tracker_data.get_spheres() %}
{%- set current_sphere = loop.index %}
{%- for player, sphere_location_ids in sphere.items() %}
{%- set checked_locations = tracker_data.get_player_checked_locations(team, player) %}
{%- set finder_game = tracker_data.get_player_game(team, player) %}
{%- set player_location_data = tracker_data.get_player_locations(team, player) %}
{%- for location_id in sphere_location_ids.intersection(checked_locations) %}
<tr>
{%- set item_id, receiver, item_flags = player_location_data[location_id] %}
{%- set receiver_game = tracker_data.get_player_game(team, receiver) %}
<td>{{ current_sphere }}</td>
<td>{{ tracker_data.get_player_name(team, player) }}</td>
<td>{{ tracker_data.get_player_name(team, receiver) }}</td>
<td>{{ tracker_data.item_id_to_name[receiver_game][item_id] }}</td>
<td>{{ tracker_data.location_id_to_name[finder_game][location_id] }}</td>
<td>{{ finder_game }}</td>
</tr>
{%- endfor %}
{%- endfor %}
{%- endfor %}
</tbody>
</table>
</div>
{%- endfor -%}
</div>
</div>
{% endblock %}

View File

@@ -10,7 +10,7 @@
{% include "header/dirtHeader.html" %}
{% include "multitrackerNavigation.html" %}
<div id="tracker-wrapper" data-tracker="{{ room.tracker | suuid }}">
<div id="tracker-wrapper" data-tracker="{{ room.tracker | suuid }}" data-second="{{ saving_second }}">
<div id="tracker-header-bar">
<input placeholder="Search" id="search" />

View File

@@ -1,180 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ player_name }}&apos;s Tracker</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles/ootTracker.css') }}"/>
<script type="application/ecmascript" src="{{ url_for('static', filename='assets/ootTracker.js') }}"></script>
</head>
<body>
<div id="player-tracker-wrapper" data-tracker="{{ room.tracker|suuid }}">
<table id="inventory-table">
<tr>
<td><img src="{{ ocarina_url }}" class="{{ 'acquired' if 'Ocarina' in acquired_items }}" title="Ocarina" /></td>
<td><img src="{{ icons['Bombs'] }}" class="{{ 'acquired' if 'Bomb Bag' in acquired_items }}" title="Bombs" /></td>
<td><img src="{{ icons['Bow'] }}" class="{{ 'acquired' if 'Bow' in acquired_items }}" title="Fairy Bow" /></td>
<td><img src="{{ icons['Fire Arrows'] }}" class="{{ 'acquired' if 'Fire Arrows' in acquired_items }}" title="Fire Arrows" /></td>
<td><img src="{{ icons['Kokiri Sword'] }}" class="{{ 'acquired' if 'Kokiri Sword' in acquired_items }}" title="Kokiri Sword" /></td>
<td><img src="{{ icons['Biggoron Sword'] }}" class="{{ 'acquired' if 'Biggoron Sword' in acquired_items }}" title="Biggoron's Sword" /></td>
<td><img src="{{ icons['Mirror Shield'] }}" class="{{ 'acquired' if 'Mirror Shield' in acquired_items }}" title="Mirror Shield" /></td>
</tr>
<tr>
<td><img src="{{ icons['Slingshot'] }}" class="{{ 'acquired' if 'Slingshot' in acquired_items }}" title="Slingshot" /></td>
<td><img src="{{ icons['Bombchus'] }}" class="{{ 'acquired' if has_bombchus }}" title="Bombchus" /></td>
<td>
<div class="counted-item">
<img src="{{ hookshot_url }}" class="{{ 'acquired' if 'Progressive Hookshot' in acquired_items }}" title="Progressive Hookshot" />
<div class="item-count">{{ hookshot_length }}</div>
</div>
</td>
<td><img src="{{ icons['Ice Arrows'] }}" class="{{ 'acquired' if 'Ice Arrows' in acquired_items }}" title="Ice Arrows" /></td>
<td><img src="{{ strength_upgrade_url }}" class="{{ 'acquired' if 'Progressive Strength Upgrade' in acquired_items }}" title="Progressive Strength Upgrade" /></td>
<td><img src="{{ icons['Goron Tunic'] }}" class="{{ 'acquired' if 'Goron Tunic' in acquired_items }}" title="Goron Tunic" /></td>
<td><img src="{{ icons['Zora Tunic'] }}" class="{{ 'acquired' if 'Zora Tunic' in acquired_items }}" title="Zora Tunic" /></td>
</tr>
<tr>
<td><img src="{{ icons['Boomerang'] }}" class="{{ 'acquired' if 'Boomerang' in acquired_items }}" title="Boomerang" /></td>
<td><img src="{{ icons['Lens of Truth'] }}" class="{{ 'acquired' if 'Lens of Truth' in acquired_items }}" title="Lens of Truth" /></td>
<td><img src="{{ icons['Megaton Hammer'] }}" class="{{ 'acquired' if 'Megaton Hammer' in acquired_items }}" title="Megaton Hammer" /></td>
<td><img src="{{ icons['Light Arrows'] }}" class="{{ 'acquired' if 'Light Arrows' in acquired_items }}" title="Light Arrows" /></td>
<td><img src="{{ scale_url }}" class="{{ 'acquired' if 'Progressive Scale' in acquired_items }}" title="Progressive Scale" /></td>
<td><img src="{{ icons['Iron Boots'] }}" class="{{ 'acquired' if 'Iron Boots' in acquired_items }}" title="Iron Boots" /></td>
<td><img src="{{ icons['Hover Boots'] }}" class="{{ 'acquired' if 'Hover Boots' in acquired_items }}" title="Hover Boots" /></td>
</tr>
<tr>
<td>
<div class="counted-item">
<img src="{{ bottle_url }}" class="{{ 'acquired' if bottle_count > 0 }}" title="Bottles" />
<div class="item-count">{{ bottle_count if bottle_count > 0 else '' }}</div>
</div>
</td>
<td><img src="{{ icons['Dins Fire'] }}" class="{{ 'acquired' if 'Dins Fire' in acquired_items }}" title="Din's Fire" /></td>
<td><img src="{{ icons['Farores Wind'] }}" class="{{ 'acquired' if 'Farores Wind' in acquired_items }}" title="Farore's Wind" /></td>
<td><img src="{{ icons['Nayrus Love'] }}" class="{{ 'acquired' if 'Nayrus Love' in acquired_items }}" title="Nayru's Love" /></td>
<td>
<div class="counted-item">
<img src="{{ wallet_url }}" class="{{ 'acquired' if 'Progressive Wallet' in acquired_items }}" title="Progressive Wallet" />
<div class="item-count">{{ wallet_size }}</div>
</div>
</td>
<td><img src="{{ magic_meter_url }}" class="{{ 'acquired' if 'Magic Meter' in acquired_items }}" title="Magic Meter" /></td>
<td><img src="{{ icons['Gerudo Membership Card'] }}" class="{{ 'acquired' if 'Gerudo Membership Card' in acquired_items }}" title="Gerudo Membership Card" /></td>
</tr>
<tr>
<td>
<div class="counted-item">
<img src="{{ icons['Zeldas Lullaby'] }}" class="{{ 'acquired' if 'Zeldas Lullaby' in acquired_items }}" title="Zelda's Lullaby" id="lullaby"/>
<div class="item-count">Zelda</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Eponas Song'] }}" class="{{ 'acquired' if 'Eponas Song' in acquired_items }}" title="Epona's Song" id="epona" />
<div class="item-count">Epona</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Sarias Song'] }}" class="{{ 'acquired' if 'Sarias Song' in acquired_items }}" title="Saria's Song" id="saria"/>
<div class="item-count">Saria</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Suns Song'] }}" class="{{ 'acquired' if 'Suns Song' in acquired_items }}" title="Sun's Song" id="sun"/>
<div class="item-count">Sun</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Song of Time'] }}" class="{{ 'acquired' if 'Song of Time' in acquired_items }}" title="Song of Time" id="time"/>
<div class="item-count">Time</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Song of Storms'] }}" class="{{ 'acquired' if 'Song of Storms' in acquired_items }}" title="Song of Storms" />
<div class="item-count">Storms</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Gold Skulltula Token'] }}" class="{{ 'acquired' if token_count > 0 }}" title="Gold Skulltula Tokens" />
<div class="item-count">{{ token_count }}</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="counted-item">
<img src="{{ icons['Minuet of Forest'] }}" class="{{ 'acquired' if 'Minuet of Forest' in acquired_items }}" title="Minuet of Forest" />
<div class="item-count">Min</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Bolero of Fire'] }}" class="{{ 'acquired' if 'Bolero of Fire' in acquired_items }}" title="Bolero of Fire" />
<div class="item-count">Bol</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Serenade of Water'] }}" class="{{ 'acquired' if 'Serenade of Water' in acquired_items }}" title="Serenade of Water" />
<div class="item-count">Ser</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Requiem of Spirit'] }}" class="{{ 'acquired' if 'Requiem of Spirit' in acquired_items }}" title="Requiem of Spirit" />
<div class="item-count">Req</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Nocturne of Shadow'] }}" class="{{ 'acquired' if 'Nocturne of Shadow' in acquired_items }}" title="Nocturne of Shadow" />
<div class="item-count">Noc</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Prelude of Light'] }}" class="{{ 'acquired' if 'Prelude of Light' in acquired_items }}" title="Prelude of Light" />
<div class="item-count">Pre</div>
</div>
</td>
<td>
<div class="counted-item">
<img src="{{ icons['Triforce'] if game_finished else icons['Triforce Piece'] }}" class="{{ 'acquired' if game_finished or piece_count > 0 }}" title="{{ 'Triforce' if game_finished else 'Triforce Pieces' }}" id=triforce />
<div class="item-count">{{ piece_count if piece_count > 0 else '' }}</div>
</div>
</td>
</tr>
</table>
<table id="location-table">
<tr>
<td></td>
<td><img src="{{ icons['Small Key'] }}" title="Small Keys" /></td>
<td><img src="{{ icons['Boss Key'] }}" title="Boss Key" /></td>
<td class="right-align">Items</td>
</tr>
{% for area in checks_done %}
<tr class="location-category" id="{{area}}-header">
<td>{{ area }} {{'▼' if area != 'Total'}}</td>
<td class="smallkeys">{{ small_key_counts.get(area, '-') }}</td>
<td class="bosskeys">{{ boss_key_counts.get(area, '-') }}</td>
<td class="counter">{{ checks_done[area] }} / {{ checks_in_area[area] }}</td>
</tr>
<tbody class="locations hide" id="{{area}}">
{% for location in location_info[area] %}
<tr>
<td class="location-name">{{ location }}</td>
<td></td>
<td></td>
<td class="counter">{{ '✔' if location_info[area][location] else '' }}</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
</div>
</body>
</html>

View File

@@ -69,7 +69,7 @@
<div id="option-groups">
{% for group_name, group_options in option_groups.items() %}
<details class="group-container" {% if loop.index == 1 %}open{% endif %}>
<details class="group-container" {% if not start_collapsed[group_name] %}open{% endif %}>
<summary class="h2">{{ group_name }}</summary>
<div class="game-options">
<div class="left">

View File

@@ -51,7 +51,7 @@
<div id="{{ world_name }}-container">
{% for group_name, group_options in option_groups.items() %}
<details {% if loop.index == 1 %}open{% endif %}>
<details {% if not start_collapsed[group_name] %}open{% endif %}>
<summary class="h2">{{ group_name }}</summary>
{% for option_name, option in group_options.items() %}
<div class="option-wrapper">

View File

@@ -3,8 +3,9 @@ import collections
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple, Counter
from uuid import UUID
from email.utils import parsedate_to_datetime
from flask import render_template
from flask import render_template, make_response, Response, request
from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second
@@ -291,47 +292,47 @@ class TrackerData:
return video_feeds
@_cache_results
def get_spheres(self) -> List[List[int]]:
""" each sphere is { player: { location_id, ... } } """
return self._multidata.get("spheres", [])
def _process_if_request_valid(incoming_request, room: Optional[Room]) -> Optional[Response]:
if not room:
abort(404)
if_modified = incoming_request.headers.get("If-Modified-Since", None)
if if_modified:
if_modified = parsedate_to_datetime(if_modified)
# if_modified has less precision than last_activity, so we bring them to same precision
if if_modified >= room.last_activity.replace(microsecond=0):
return make_response("", 304)
@app.route("/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> str:
def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> Response:
key = f"{tracker}_{tracked_team}_{tracked_player}_{generic}"
tracker_page = cache.get(key)
if tracker_page:
return tracker_page
response: Optional[Response] = cache.get(key)
if response:
return response
timeout, tracker_page = get_timeout_and_tracker(tracker, tracked_team, tracked_player, generic)
cache.set(key, tracker_page, timeout)
return tracker_page
@app.route("/generic_tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> str:
return get_player_tracker(tracker, tracked_team, tracked_player, True)
@app.route("/tracker/<suuid:tracker>", defaults={"game": "Generic"})
@app.route("/tracker/<suuid:tracker>/<game>")
@cache.memoize(timeout=TRACKER_CACHE_TIMEOUT_IN_SECONDS)
def get_multiworld_tracker(tracker: UUID, game: str):
# Room must exist.
room = Room.get(tracker=tracker)
if not room:
abort(404)
tracker_data = TrackerData(room)
enabled_trackers = list(get_enabled_multiworld_trackers(room).keys())
if game not in _multiworld_trackers:
return render_generic_multiworld_tracker(tracker_data, enabled_trackers)
response = _process_if_request_valid(request, room)
if response:
return response
return _multiworld_trackers[game](tracker_data, enabled_trackers)
timeout, last_modified, tracker_page = get_timeout_and_player_tracker(room, tracked_team, tracked_player, generic)
response = make_response(tracker_page)
response.last_modified = last_modified
cache.set(key, response, timeout)
return response
def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool) -> Tuple[int, str]:
# Room must exist.
room = Room.get(tracker=tracker)
if not room:
abort(404)
def get_timeout_and_player_tracker(room: Room, tracked_team: int, tracked_player: int, generic: bool)\
-> Tuple[int, datetime.datetime, str]:
tracker_data = TrackerData(room)
# Load and render the game-specific player tracker, or fallback to generic tracker if none exists.
@@ -341,7 +342,48 @@ def get_timeout_and_tracker(tracker: UUID, tracked_team: int, tracked_player: in
else:
tracker = render_generic_tracker(tracker_data, tracked_team, tracked_player)
return (tracker_data.get_room_saving_second() - datetime.datetime.now().second) % 60 or 60, tracker
return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second)
% TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker)
@app.route("/generic_tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_generic_game_tracker(tracker: UUID, tracked_team: int, tracked_player: int) -> Response:
return get_player_tracker(tracker, tracked_team, tracked_player, True)
@app.route("/tracker/<suuid:tracker>", defaults={"game": "Generic"})
@app.route("/tracker/<suuid:tracker>/<game>")
def get_multiworld_tracker(tracker: UUID, game: str) -> Response:
key = f"{tracker}_{game}"
response: Optional[Response] = cache.get(key)
if response:
return response
# Room must exist.
room = Room.get(tracker=tracker)
response = _process_if_request_valid(request, room)
if response:
return response
timeout, last_modified, tracker_page = get_timeout_and_multiworld_tracker(room, game)
response = make_response(tracker_page)
response.last_modified = last_modified
cache.set(key, response, timeout)
return response
def get_timeout_and_multiworld_tracker(room: Room, game: str)\
-> Tuple[int, datetime.datetime, str]:
tracker_data = TrackerData(room)
enabled_trackers = list(get_enabled_multiworld_trackers(room).keys())
if game in _multiworld_trackers:
tracker = _multiworld_trackers[game](tracker_data, enabled_trackers)
else:
tracker = render_generic_multiworld_tracker(tracker_data, enabled_trackers)
return ((tracker_data.get_room_saving_second() - datetime.datetime.now().second)
% TRACKER_CACHE_TIMEOUT_IN_SECONDS or TRACKER_CACHE_TIMEOUT_IN_SECONDS, room.last_activity, tracker)
def get_enabled_multiworld_trackers(room: Room) -> Dict[str, Callable]:
@@ -411,9 +453,30 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
videos=tracker_data.get_room_videos(),
item_id_to_name=tracker_data.item_id_to_name,
location_id_to_name=tracker_data.location_id_to_name,
saving_second=tracker_data.get_room_saving_second(),
)
def render_generic_multiworld_sphere_tracker(tracker_data: TrackerData) -> str:
return render_template(
"multispheretracker.html",
room=tracker_data.room,
tracker_data=tracker_data,
)
@app.route("/sphere_tracker/<suuid:tracker>")
@cache.memoize(timeout=TRACKER_CACHE_TIMEOUT_IN_SECONDS)
def get_multiworld_sphere_tracker(tracker: UUID):
# Room must exist.
room = Room.get(tracker=tracker)
if not room:
abort(404)
tracker_data = TrackerData(room)
return render_generic_multiworld_sphere_tracker(tracker_data)
# TODO: This is a temporary solution until a proper Tracker API can be implemented for tracker templates and data to
# live in their respective world folders.

View File

@@ -152,7 +152,7 @@ def get_payload(ctx: ZeldaContext):
def reconcile_shops(ctx: ZeldaContext):
checked_location_names = [ctx.location_names[location] for location in ctx.checked_locations]
checked_location_names = [ctx.location_names.lookup_in_slot(location) for location in ctx.checked_locations]
shops = [location for location in checked_location_names if "Shop" in location]
left_slots = [shop for shop in shops if "Left" in shop]
middle_slots = [shop for shop in shops if "Middle" in shop]
@@ -190,7 +190,7 @@ async def parse_locations(locations_array, ctx: ZeldaContext, force: bool, zone=
locations_checked = []
location = None
for location in ctx.missing_locations:
location_name = ctx.location_names[location]
location_name = ctx.location_names.lookup_in_slot(location)
if location_name in Locations.overworld_locations and zone == "overworld":
status = locations_array[Locations.major_location_offsets[location_name]]

View File

@@ -6,10 +6,6 @@
#
# All usernames must be GitHub usernames (and are case sensitive).
###################
## Active Worlds ##
###################
# Adventure
/worlds/adventure/ @JusticePS
@@ -19,15 +15,15 @@
# A Link to the Past
/worlds/alttp/ @Berserker66
# Sudoku (APSudoku)
/worlds/apsudoku/ @EmilyV99
# Aquaria
/worlds/aquaria/ @tioui
# ArchipIDLE
/worlds/archipidle/ @LegendaryLinux
# Sudoku (BK Sudoku)
/worlds/bk_sudoku/ @Jarno458
# Blasphemous
/worlds/blasphemous/ @TRPG0
@@ -67,9 +63,6 @@
# Factorio
/worlds/factorio/ @Berserker66
# Final Fantasy
/worlds/ff1/ @jtoyoda
# Final Fantasy Mystic Quest
/worlds/ffmq/ @Alchav @wildham0
@@ -207,7 +200,7 @@
/worlds/yoshisisland/ @PinkSwitch
#Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
/worlds/yugioh06/ @rensen
/worlds/yugioh06/ @Rensen3
# Zillion
/worlds/zillion/ @beauxq
@@ -215,9 +208,22 @@
# Zork Grand Inquisitor
/worlds/zork_grand_inquisitor/ @nbrochu
##################################
## Disabled Unmaintained Worlds ##
##################################
## Active Unmaintained Worlds
# The following worlds in this repo are currently unmaintained, but currently still work in core. If any update breaks
# compatibility, these worlds may be moved to `worlds_disabled`. If you are interested in stepping up as maintainer for
# any of these worlds, please review `/docs/world maintainer.md` documentation.
# Final Fantasy (1)
# /worlds/ff1/
## Disabled Unmaintained Worlds
# The following worlds in this repo are currently unmaintained and disabled as they do not work in core. If you are
# interested in stepping up as maintainer for any of these worlds, please review `/docs/world maintainer.md`
# documentation.
# Ori and the Blind Forest
# /worlds_disabled/oribf/ <Unmaintained>
# /worlds_disabled/oribf/

View File

@@ -1,43 +1,49 @@
# Contributing
Contributions are welcome. We have a few requests for new contributors:
All contributions are welcome, though we have a few requests of contributors, whether they be for core, webhost, or new
game contributions:
* **Follow styling guidelines.**
Please take a look at the [code style documentation](/docs/style.md)
to ensure ease of communication and uniformity.
* **Ensure that critical changes are covered by tests.**
It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working.
If you wish to contribute by adding a new game, please take a look at the [logic unit test documentation](/docs/tests.md).
If you wish to contribute to the website, please take a look at [these tests](/test/webhost).
* **Ensure that critical changes are covered by tests.**
It is strongly recommended that unit tests are used to avoid regression and to ensure everything is still working.
If you wish to contribute by adding a new game, please take a look at
the [logic unit test documentation](/docs/tests.md).
If you wish to contribute to the website, please take a look at [these tests](/test/webhost).
* **Do not introduce unit test failures/regressions.**
Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test
your changes. Currently, the oldest supported version is [Python 3.8](https://www.python.org/downloads/release/python-380/).
It is recommended that automated github actions are turned on in your fork to have github run all of the unit tests after pushing.
You can turn them on here:
![Github actions example](./img/github-actions-example.png)
Archipelago supports multiple versions of Python. You may need to download older Python versions to fully test
your changes. Currently, the oldest supported version
is [Python 3.8](https://www.python.org/downloads/release/python-380/).
It is recommended that automated github actions are turned on in your fork to have github run unit tests after
pushing.
You can turn them on here:
![Github actions example](./img/github-actions-example.png)
* **When reviewing PRs, please leave a message about what was done.**
We don't have full test coverage, so manual testing can help.
For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing
or checking if all code paths are covered by automated tests is desired. The original author may not have been able
to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to
state which games or settings were rolled, if any.
Please also tell us if you looked at code, just did functional testing, did both, or did neither.
If testing the PR depends on other PRs, please state what you merged into what for testing.
We cannot determine what "LGTM" means without additional context, so that should not be the norm.
We don't have full test coverage, so manual testing can help.
For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing
or checking if all code paths are covered by automated tests is desired. The original author may not have been able
to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to
state which games or settings were rolled, if any.
Please also tell us if you looked at code, just did functional testing, did both, or did neither.
If testing the PR depends on other PRs, please state what you merged into what for testing.
We cannot determine what "LGTM" means without additional context, so that should not be the norm.
Other than these requests, we tend to judge code on a case-by-case basis.
Other than these requests, we tend to judge code on a case-by-case basis.
For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md).
If you want to contribute to the core, you will be subject to stricter review on your pull requests. It is recommended
that you get in touch with other core maintainers via the [Discord](https://archipelago.gg/discord).
If you want to add Archipelago support for a new game, please take a look at the [adding games documentation](/docs/adding%20games.md), which details what is required
to implement support for a game, as well as tips for how to get started.
If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a
[world maintainer](/docs/world%20maintainer.md).
If you want to add Archipelago support for a new game, please take a look at
the [adding games documentation](/docs/adding%20games.md)
which details what is required to implement support for a game, and has tips on to get started.
If you want to merge a new game into the main Archipelago repo, please make sure to read the responsibilities as a
[world maintainer](/docs/world%20maintainer.md).
For other questions, feel free to explore the [main documentation folder](/docs/) and ask us questions in the #archipelago-dev channel
of the [Discord](https://archipelago.gg/discord).
For other questions, feel free to explore the [main documentation folder](/docs), and ask us questions in the
#ap-world-dev channel of the [Discord](https://archipelago.gg/discord).

View File

@@ -53,7 +53,7 @@ Example:
```
## (Server -> Client)
These packets are are sent from the multiworld server to the client. They are not messages which the server accepts.
These packets are sent from the multiworld server to the client. They are not messages which the server accepts.
* [RoomInfo](#RoomInfo)
* [ConnectionRefused](#ConnectionRefused)
* [Connected](#Connected)
@@ -80,7 +80,6 @@ Sent to clients when they connect to an Archipelago server.
| hint_cost | int | The percentage of total locations that need to be checked to receive a hint from the server. |
| location_check_points | int | The amount of hint points you receive per item/location check completed. |
| games | list\[str\] | List of games present in this multiworld. |
| datapackage_versions | dict\[str, int\] | Data versions of the individual games' data packages the server will send. Used to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents). **Deprecated. Use `datapackage_checksums` instead.** |
| datapackage_checksums | dict[str, str] | Checksum hash of the individual games' data packages the server will send. Used by newer clients to decide which games' caches are outdated. See [Data Package Contents](#Data-Package-Contents) for more information. |
| seed_name | str | Uniquely identifying name of this generation |
| time | float | Unix time stamp of "now". Send for time synchronization if wanted for things like the DeathLink Bounce. |
@@ -500,9 +499,9 @@ In JSON this may look like:
{"item": 3, "location": 3, "player": 3, "flags": 0}
]
```
`item` is the item id of the item. Item ids are in the range of ± 2<sup>53</sup>-1.
`item` is the item id of the item. Item ids are only supported in the range of [-2<sup>53</sup>, 2<sup>53</sup> - 1], with anything ≤ 0 reserved for Archipelago use.
`location` is the location id of the item inside the world. Location ids are in the range of ± 2<sup>53</sup>-1.
`location` is the location id of the item inside the world. Location ids are only supported in the range of [-2<sup>53</sup>, 2<sup>53</sup> - 1], with anything ≤ 0 reserved for Archipelago use.
`player` is the player slot of the world the item is located in, except when inside an [LocationInfo](#LocationInfo) Packet then it will be the slot of the player to receive the item
@@ -646,15 +645,47 @@ class Hint(typing.NamedTuple):
```
### Data Package Contents
A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago server most easily. Currently, this package is used to send ID to name mappings so that clients need not maintain their own mappings.
A data package is a JSON object which may contain arbitrary metadata to enable a client to interact with the Archipelago
server most easily and not maintain their own mappings. Some contents include:
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different version. A special case is datapackage version 0, where it is expected the package is custom and should not be cached.
- Name to ID mappings for items and locations.
- A checksum of each game's data package for clients to tell if a cached package is invalid.
Note:
* Any ID is unique to its type across AP: Item 56 only exists once and Location 56 only exists once.
* Any Name is unique to its type across its own Game only: Single Arrow can exist in two games.
* The IDs from the game "Archipelago" may be used in any other game.
Especially Location ID -1: Cheat Console and -2: Server (typically Remote Start Inventory)
We encourage clients to cache the data package they receive on disk, or otherwise not tied to a session. You will know
when your cache is outdated if the [RoomInfo](#RoomInfo) packet or the datapackage itself denote a different checksum
than any locally cached ones.
**Important Notes about IDs and Names**:
* IDs ≤ 0 are reserved for "Archipelago" and should not be used by other world implementations.
* The IDs from the game "Archipelago" (in `worlds/generic`) may be used in any world.
* Especially Location ID `-1`: `Cheat Console` and `-2`: `Server` (typically Remote Start Inventory)
* Any names and IDs are only unique in its own world data package, but different games may reuse these names or IDs.
* At runtime, you will need to look up the game of the player to know which item or location ID/Name to lookup in the
data package. This can be easily achieved by reviewing the `slot_info` for a particular player ID prior to lookup.
* For example, a data package like this is valid (Some properties such as `checksum` were omitted):
```json
{
"games": {
"Game A": {
"location_name_to_id": {
"Boss Chest": 40
},
"item_name_to_id": {
"Item X": 12
}
},
"Game B": {
"location_name_to_id": {
"Minigame Prize": 40
},
"item_name_to_id": {
"Item X": 40
}
}
}
}
```
#### Contents
| Name | Type | Notes |
@@ -668,7 +699,6 @@ GameData is a **dict** but contains these keys and values. It's broken out into
|---------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------|
| item_name_to_id | dict[str, int] | Mapping of all item names to their respective ID. |
| location_name_to_id | dict[str, int] | Mapping of all location names to their respective ID. |
| version | int | Version number of this game's data. Deprecated. Used by older clients to request an updated datapackage if cache is outdated. |
| checksum | str | A checksum hash of this game's data. |
### Tags

View File

@@ -75,7 +75,7 @@ Name: "{commondesktop}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLaunc
[Run]
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/passive /norestart"; Check: IsVCRedist64BitNeeded; StatusMsg: "Installing VC++ redistributable..."
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Flags: nowait; Components: lttp_sprites
Filename: "{app}\ArchipelagoLttPAdjuster"; Parameters: "--update_sprites"; StatusMsg: "Updating Sprite Library..."; Components: lttp_sprites
Filename: "{app}\ArchipelagoLauncher"; Parameters: "--update_settings"; StatusMsg: "Updating host.yaml..."; Flags: runasoriginaluser runhidden
Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringChange('Launcher', '&', '&&')}}"; Flags: nowait postinstall skipifsilent

30
kvui.py
View File

@@ -64,7 +64,7 @@ from kivy.uix.popup import Popup
fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25)
from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType
from Utils import async_start
from Utils import async_start, get_input_text_from_response
if typing.TYPE_CHECKING:
import CommonClient
@@ -285,16 +285,10 @@ class SelectableLabel(RecycleDataViewBehavior, TooltipLabel):
temp = MarkupLabel(text=self.text).markup
text = "".join(part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]")))
cmdinput = App.get_running_app().textinput
if not cmdinput.text and " did you mean " in text:
for question in ("Didn't find something that closely matches, did you mean ",
"Too many close matches, did you mean "):
if text.startswith(question):
name = Utils.get_text_between(text, question,
"? (")
cmdinput.text = f"!{App.get_running_app().last_autofillable_command} {name}"
break
elif not cmdinput.text and text.startswith("Missing: "):
cmdinput.text = text.replace("Missing: ", "!hint_location ")
if not cmdinput.text:
input_text = get_input_text_from_response(text, App.get_running_app().last_autofillable_command)
if input_text is not None:
cmdinput.text = input_text
Clipboard.copy(text.replace("&amp;", "&").replace("&bl;", "[").replace("&br;", "]"))
return self.parent.select_with_touch(self.index, touch)
@@ -683,10 +677,18 @@ class HintLog(RecycleView):
for hint in hints:
data.append({
"receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
"item": {"text": self.parser.handle_node(
{"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})},
"item": {"text": self.parser.handle_node({
"type": "item_id",
"text": hint["item"],
"flags": hint["item_flags"],
"player": hint["receiving_player"],
})},
"finding": {"text": self.parser.handle_node({"type": "player_id", "text": hint["finding_player"]})},
"location": {"text": self.parser.handle_node({"type": "location_id", "text": hint["location"]})},
"location": {"text": self.parser.handle_node({
"type": "location_id",
"text": hint["location"],
"player": hint["finding_player"],
})},
"entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
"color": "blue", "text": hint["entrance"]
if hint["entrance"] else "Vanilla"})},

View File

@@ -643,17 +643,6 @@ class GeneratorOptions(Group):
PLAYTHROUGH = 2
FULL = 3
class GlitchTriforceRoom(IntEnum):
"""
Glitch to Triforce room from Ganon
When disabled, you have to have a weapon that can hurt ganon (master sword or swordless/easy item functionality
+ hammer) and have completed the goal required for killing ganon to be able to access the triforce room.
1 -> Enabled.
0 -> Disabled (except in no-logic)
"""
OFF = 0
ON = 1
class PlandoOptions(str):
"""
List of options that can be plando'd. Can be combined, for example "bosses, items"

View File

@@ -21,7 +21,7 @@ from pathlib import Path
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
try:
requirement = 'cx-Freeze>=7.0.0'
requirement = 'cx-Freeze==7.0.0'
import pkg_resources
try:
pkg_resources.require(requirement)

View File

@@ -0,0 +1,23 @@
import unittest
from Utils import get_intended_text, get_input_text_from_response
class TestClient(unittest.TestCase):
def test_autofill_hint_from_fuzzy_hint(self) -> None:
tests = (
("item", ["item1", "item2"]), # Multiple close matches
("itm", ["item1", "item21"]), # No close match, multiple option
("item", ["item1"]), # No close match, single option
("item", ["\"item\" 'item' (item)"]), # Testing different special characters
)
for input_text, possible_answers in tests:
item_name, usable, response = get_intended_text(input_text, possible_answers)
self.assertFalse(usable, "This test must be updated, it seems get_fuzzy_results behavior changed")
hint_command = get_input_text_from_response(response, "hint")
self.assertIsNotNone(hint_command,
"The response to fuzzy hints is no longer recognized by the hint autofill")
self.assertEqual(hint_command, f"!hint {item_name}",
"The hint command autofilled by the response is not correct")

View File

@@ -6,22 +6,6 @@ from . import setup_solo_multiworld
class TestIDs(unittest.TestCase):
def test_unique_items(self):
"""Tests that every game has a unique ID per item in the datapackage"""
known_item_ids = set()
for gamename, world_type in AutoWorldRegister.world_types.items():
current = len(known_item_ids)
known_item_ids |= set(world_type.item_id_to_name)
self.assertEqual(len(known_item_ids) - len(world_type.item_id_to_name), current)
def test_unique_locations(self):
"""Tests that every game has a unique ID per location in the datapackage"""
known_location_ids = set()
for gamename, world_type in AutoWorldRegister.world_types.items():
current = len(known_location_ids)
known_location_ids |= set(world_type.location_id_to_name)
self.assertEqual(len(known_location_ids) - len(world_type.location_id_to_name), current)
def test_range_items(self):
"""There are Javascript clients, which are limited to Number.MAX_SAFE_INTEGER due to 64bit float precision."""
for gamename, world_type in AutoWorldRegister.world_types.items():

View File

@@ -0,0 +1,106 @@
import unittest
import NetUtils
from CommonClient import CommonContext
class TestCommonContext(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.ctx = CommonContext()
self.ctx.slot = 1 # Pretend we're player 1 for this.
self.ctx.slot_info.update({
1: NetUtils.NetworkSlot("Player 1", "__TestGame1", NetUtils.SlotType.player),
2: NetUtils.NetworkSlot("Player 2", "__TestGame1", NetUtils.SlotType.player),
3: NetUtils.NetworkSlot("Player 3", "__TestGame2", NetUtils.SlotType.player),
})
self.ctx.consume_players_package([
NetUtils.NetworkPlayer(1, 1, "Player 1", "Player 1"),
NetUtils.NetworkPlayer(1, 2, "Player 2", "Player 2"),
NetUtils.NetworkPlayer(1, 3, "Player 3", "Player 3"),
])
# Using IDs outside the "safe range" for testing purposes only. If this fails unit tests, it's because
# another world is not following the spec for allowed ID ranges.
self.ctx.update_data_package({
"games": {
"__TestGame1": {
"location_name_to_id": {
"Test Location 1 - Safe": 2**54 + 1,
"Test Location 2 - Duplicate": 2**54 + 2,
},
"item_name_to_id": {
"Test Item 1 - Safe": 2**54 + 1,
"Test Item 2 - Duplicate": 2**54 + 2,
},
},
"__TestGame2": {
"location_name_to_id": {
"Test Location 3 - Duplicate": 2**54 + 2,
},
"item_name_to_id": {
"Test Item 3 - Duplicate": 2**54 + 2,
},
},
},
})
async def test_archipelago_datapackage_lookups_exist(self):
assert "Archipelago" in self.ctx.item_names, "Archipelago item names entry does not exist"
assert "Archipelago" in self.ctx.location_names, "Archipelago location names entry does not exist"
async def test_implicit_name_lookups(self):
# Items
assert self.ctx.item_names[2**54 + 1] == "Test Item 1 - Safe"
assert self.ctx.item_names[2**54 + 3] == f"Unknown item (ID: {2**54+3})"
assert self.ctx.item_names[-1] == "Nothing"
# Locations
assert self.ctx.location_names[2**54 + 1] == "Test Location 1 - Safe"
assert self.ctx.location_names[2**54 + 3] == f"Unknown location (ID: {2**54+3})"
assert self.ctx.location_names[-1] == "Cheat Console"
async def test_explicit_name_lookups(self):
# Items
assert self.ctx.item_names["__TestGame1"][2**54+1] == "Test Item 1 - Safe"
assert self.ctx.item_names["__TestGame1"][2**54+2] == "Test Item 2 - Duplicate"
assert self.ctx.item_names["__TestGame1"][2**54+3] == f"Unknown item (ID: {2**54+3})"
assert self.ctx.item_names["__TestGame1"][-1] == "Nothing"
assert self.ctx.item_names["__TestGame2"][2**54+1] == f"Unknown item (ID: {2**54+1})"
assert self.ctx.item_names["__TestGame2"][2**54+2] == "Test Item 3 - Duplicate"
assert self.ctx.item_names["__TestGame2"][2**54+3] == f"Unknown item (ID: {2**54+3})"
assert self.ctx.item_names["__TestGame2"][-1] == "Nothing"
# Locations
assert self.ctx.location_names["__TestGame1"][2**54+1] == "Test Location 1 - Safe"
assert self.ctx.location_names["__TestGame1"][2**54+2] == "Test Location 2 - Duplicate"
assert self.ctx.location_names["__TestGame1"][2**54+3] == f"Unknown location (ID: {2**54+3})"
assert self.ctx.location_names["__TestGame1"][-1] == "Cheat Console"
assert self.ctx.location_names["__TestGame2"][2**54+1] == f"Unknown location (ID: {2**54+1})"
assert self.ctx.location_names["__TestGame2"][2**54+2] == "Test Location 3 - Duplicate"
assert self.ctx.location_names["__TestGame2"][2**54+3] == f"Unknown location (ID: {2**54+3})"
assert self.ctx.location_names["__TestGame2"][-1] == "Cheat Console"
async def test_lookup_helper_functions(self):
# Checking own slot.
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1) == "Test Item 1 - Safe"
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2) == "Test Item 2 - Duplicate"
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 3) == f"Unknown item (ID: {2 ** 54 + 3})"
assert self.ctx.item_names.lookup_in_slot(-1) == f"Nothing"
# Checking others' slots.
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 2) == "Test Item 1 - Safe"
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 2) == "Test Item 2 - Duplicate"
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 1, 3) == f"Unknown item (ID: {2 ** 54 + 1})"
assert self.ctx.item_names.lookup_in_slot(2 ** 54 + 2, 3) == "Test Item 3 - Duplicate"
# Checking by game.
assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame1") == "Test Item 1 - Safe"
assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame1") == "Test Item 2 - Duplicate"
assert self.ctx.item_names.lookup_in_game(2 ** 54 + 3, "__TestGame1") == f"Unknown item (ID: {2 ** 54 + 3})"
assert self.ctx.item_names.lookup_in_game(2 ** 54 + 1, "__TestGame2") == f"Unknown item (ID: {2 ** 54 + 1})"
assert self.ctx.item_names.lookup_in_game(2 ** 54 + 2, "__TestGame2") == "Test Item 3 - Duplicate"
# Checking with Archipelago ids are valid in any game package.
assert self.ctx.item_names.lookup_in_slot(-1, 2) == "Nothing"
assert self.ctx.item_names.lookup_in_slot(-1, 3) == "Nothing"
assert self.ctx.item_names.lookup_in_game(-1, "__TestGame1") == "Nothing"
assert self.ctx.item_names.lookup_in_game(-1, "__TestGame2") == "Nothing"

View File

@@ -1,7 +1,7 @@
import unittest
from worlds import AutoWorldRegister
from Options import Choice, NamedRange, Toggle, Range
from Options import ItemDict, NamedRange, NumericOption, OptionList, OptionSet
class TestOptionPresets(unittest.TestCase):
@@ -14,7 +14,7 @@ class TestOptionPresets(unittest.TestCase):
with self.subTest(game=game_name, preset=preset_name, option=option_name):
try:
option = world_type.options_dataclass.type_hints[option_name].from_any(option_value)
supported_types = [Choice, Toggle, Range, NamedRange]
supported_types = [NumericOption, OptionSet, OptionList, ItemDict]
if not any([issubclass(option.__class__, t) for t in supported_types]):
self.fail(f"'{option_name}' in preset '{preset_name}' for game '{game_name}' "
f"is not a supported type for webhost. "

View File

@@ -10,10 +10,7 @@ from dataclasses import make_dataclass
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple,
TYPE_CHECKING, Type, Union)
from Options import (
ExcludeLocations, ItemLinks, LocalItems, NonLocalItems, OptionGroup, PerGameCommonOptions,
PriorityLocations, StartHints, StartInventory, StartInventoryPool, StartLocationHints
)
from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions
from BaseClasses import CollectionState
if TYPE_CHECKING:
@@ -119,13 +116,19 @@ class WebWorldRegister(type):
# don't allow an option to appear in multiple groups, allow "Item & Location Options" to appear anywhere by the
# dev, putting it at the end if they don't define options in it
option_groups: List[OptionGroup] = dct.get("option_groups", [])
item_and_loc_options = [LocalItems, NonLocalItems, StartInventory, StartInventoryPool, StartHints,
StartLocationHints, ExcludeLocations, PriorityLocations, ItemLinks]
prebuilt_options = ["Game Options", "Item & Location Options"]
seen_options = []
item_group_in_list = False
for group in option_groups:
assert group.name != "Game Options", "Game Options is a pre-determined group and can not be defined."
assert group.options, "A custom defined Option Group must contain at least one Option."
# catch incorrectly titled versions of the prebuilt groups so they don't create extra groups
title_name = group.name.title()
if title_name in prebuilt_options:
group.name = title_name
if group.name == "Item & Location Options":
assert not any(option in item_and_loc_options for option in group.options), \
f"Item and Location Options cannot be specified multiple times"
group.options.extend(item_and_loc_options)
item_group_in_list = True
else:
@@ -137,7 +140,7 @@ class WebWorldRegister(type):
assert option not in seen_options, f"{option} found in two option groups"
seen_options.append(option)
if not item_group_in_list:
option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options))
option_groups.append(OptionGroup("Item & Location Options", item_and_loc_options, True))
return super().__new__(mcs, name, bases, dct)
@@ -255,18 +258,6 @@ class World(metaclass=AutoWorldRegister):
location_name_groups: ClassVar[Dict[str, Set[str]]] = {}
"""maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}"""
data_version: ClassVar[int] = 0
"""
Increment this every time something in your world's names/id mappings changes.
When this is set to 0, that world's DataPackage is considered in "testing mode", which signals to servers/clients
that it should not be cached, and clients should request that world's DataPackage every connection. Not
recommended for production-ready worlds.
Deprecated. Clients should utilize `checksum` to determine if DataPackage has changed since last connection and
request a new DataPackage, if necessary.
"""
required_client_version: Tuple[int, int, int] = (0, 1, 6)
"""
override this if changes to a world break forward-compatibility of the client
@@ -540,7 +531,6 @@ class World(metaclass=AutoWorldRegister):
"item_name_to_id": cls.item_name_to_id,
"location_name_groups": sorted_location_name_groups,
"location_name_to_id": cls.location_name_to_id,
"version": cls.data_version,
}
res["checksum"] = data_package_checksum(res)
return res

View File

@@ -33,7 +33,6 @@ class GamesPackage(TypedDict, total=False):
location_name_groups: Dict[str, List[str]]
location_name_to_id: Dict[str, int]
checksum: str
version: int # TODO: Remove support after per game data packages API change.
class DataPackage(TypedDict):

View File

@@ -177,7 +177,8 @@ async def _game_watcher(ctx: BizHawkClientContext):
if ctx.client_handler is None:
if not showed_no_handler_message:
logger.info("No handler was found for this game")
logger.info("No handler was found for this game. Double-check that the apworld is installed "
"correctly and that you loaded the right ROM file.")
showed_no_handler_message = True
continue
else:

View File

@@ -113,7 +113,6 @@ class AdventureWorld(World):
settings: ClassVar[AdventureSettings]
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
data_version: ClassVar[int] = 1
required_client_version: Tuple[int, int, int] = (0, 3, 9)
def __init__(self, world: MultiWorld, player: int):

View File

@@ -339,7 +339,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
def new_check(location_id):
new_locations.append(location_id)
ctx.locations_checked.add(location_id)
location = ctx.location_names[location_id]
location = ctx.location_names.lookup_in_slot(location_id)
snes_logger.info(
f'New Check: {location} ' +
f'({len(ctx.checked_locations) + 1 if ctx.checked_locations else len(ctx.locations_checked)}/' +
@@ -552,9 +552,9 @@ class ALTTPSNIClient(SNIClient):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
color(ctx.item_names[item.item], 'red', 'bold'),
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, RECV_PROGRESS_ADDR,
bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))

View File

@@ -1,8 +1,11 @@
import typing
from BaseClasses import MultiWorld
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses,\
FreeText, Removed
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, \
StartInventoryPool, PlandoBosses, PlandoConnections, PlandoTexts, FreeText, Removed
from .EntranceShuffle import default_connections, default_dungeon_connections, \
inverted_default_connections, inverted_default_dungeon_connections
from .Text import TextTable
class GlitchesRequired(Choice):
@@ -721,7 +724,27 @@ class AllowCollect(DefaultOnToggle):
display_name = "Allow Collection of checks for other players"
class ALttPPlandoConnections(PlandoConnections):
entrances = set([connection[0] for connection in (
*default_connections, *default_dungeon_connections, *inverted_default_connections,
*inverted_default_dungeon_connections)])
exits = set([connection[1] for connection in (
*default_connections, *default_dungeon_connections, *inverted_default_connections,
*inverted_default_dungeon_connections)])
class ALttPPlandoTexts(PlandoTexts):
"""Text plando. Format is:
- text: 'This is your text'
at: text_key
percentage: 100
Percentage is an integer from 1 to 100, and defaults to 100 when omitted."""
valid_keys = TextTable.valid_keys
alttp_options: typing.Dict[str, type(Option)] = {
"plando_connections": ALttPPlandoConnections,
"plando_texts": ALttPPlandoTexts,
"start_inventory_from_pool": StartInventoryPool,
"goal": Goal,
"mode": Mode,

View File

@@ -1269,7 +1269,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
# set up goals for treasure hunt
rom.write_int16(0x180163, local_world.treasure_hunt_required)
rom.write_int16(0x180163, max(0, local_world.treasure_hunt_required -
sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece")))
rom.write_bytes(0x180165, [0x0E, 0x28]) # Triforce Piece Sprite
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
@@ -1372,7 +1373,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
'Red Mail', 'Blue Mail', 'Progressive Mail',
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)'}:
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)', 'Triforce Piece'}:
continue
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1),
@@ -2475,6 +2476,9 @@ def write_strings(rom, world, player):
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)]
tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)]
triforce_pieces_required = max(0, w.treasure_hunt_required -
sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece"))
if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
@@ -2482,16 +2486,16 @@ def write_strings(rom, world, player):
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
else:
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
if w.treasure_hunt_required > 1:
if triforce_pieces_required > 1:
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
"hidden in a hollow tree. If you bring\n%d Triforce pieces out of %d, I can reassemble it." % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
else:
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
"invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
"hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['pedestal']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
@@ -2500,20 +2504,20 @@ def write_strings(rom, world, player):
tt['ganon_fall_in'] = Ganon1_texts[local_random.randint(0, len(Ganon1_texts) - 1)]
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if w.treasure_hunt_required > 1:
if triforce_pieces_required > 1:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
else:
if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \
(w.treasure_hunt_required, w.treasure_hunt_total)
(triforce_pieces_required, w.treasure_hunt_total)
tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)]
@@ -2538,12 +2542,12 @@ def write_strings(rom, world, player):
tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}"
tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}"
for at, text in world.plando_texts[player].items():
for at, text, _ in world.plando_texts[player]:
if at not in tt:
raise Exception(f"No text target \"{at}\" found.")
else:
tt[at] = text
tt[at] = "\n".join(text)
rom.write_bytes(0xE0000, tt.getBytes())

View File

@@ -9,9 +9,9 @@ from worlds.generic.Rules import add_rule
from BaseClasses import CollectionState
from .SubClasses import ALttPLocation
from .EntranceShuffle import door_addresses
from .Items import item_name_groups
from .Options import small_key_shuffle, RandomizeShopInventories
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
logger = logging.getLogger("Shops")
@@ -66,6 +66,7 @@ class Shop:
return 0
def get_bytes(self) -> List[int]:
from .EntranceShuffle import door_addresses
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
entrances = self.region.entrances
config = self.item_count
@@ -181,7 +182,7 @@ def push_shop_inventories(multiworld):
def create_shops(multiworld, player: int):
from .Options import RandomizeShopInventories
player_shop_table = shop_table.copy()
if multiworld.include_witch_hut[player]:
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
@@ -304,6 +305,7 @@ shop_generation_types = {
def set_up_shops(multiworld, player: int):
from .Options import small_key_shuffle
# TODO: move hard+ mode changes for shields here, utilizing the new shops
if multiworld.retro_bow[player]:
@@ -426,7 +428,7 @@ def get_price_modifier(item):
def get_price(multiworld, item, player: int, price_type=None):
"""Converts a raw Rupee price into a special price type"""
from .Options import small_key_shuffle
if price_type:
price_types = [price_type]
else:

View File

@@ -1289,6 +1289,415 @@ class LargeCreditBottomMapper(CharTextMapper):
class TextTable(object):
SIZE = 0x7355
valid_keys = [
"set_cursor",
"set_cursor2",
"game_over_menu",
"var_test",
"follower_no_enter",
"choice_1_3",
"choice_2_3",
"choice_3_3",
"choice_1_2",
"choice_2_2",
"uncle_leaving_text",
"uncle_dying_sewer",
"tutorial_guard_1",
"tutorial_guard_2",
"tutorial_guard_3",
"tutorial_guard_4",
"tutorial_guard_5",
"tutorial_guard_6",
"tutorial_guard_7",
"priest_sanctuary_before_leave",
"sanctuary_enter",
"zelda_sanctuary_story",
"priest_sanctuary_before_pendants",
"priest_sanctuary_after_pendants_before_master_sword",
"priest_sanctuary_dying",
"zelda_save_sewers",
"priest_info",
"zelda_sanctuary_before_leave",
"telepathic_intro",
"telepathic_reminder",
"zelda_go_to_throne",
"zelda_push_throne",
"zelda_switch_room_pull",
"zelda_save_lets_go",
"zelda_save_repeat",
"zelda_before_pendants",
"zelda_after_pendants_before_master_sword",
"telepathic_zelda_right_after_master_sword",
"zelda_sewers",
"zelda_switch_room",
"kakariko_saharalasa_wife",
"kakariko_saharalasa_wife_sword_story",
"kakariko_saharalasa_wife_closing",
"kakariko_saharalasa_after_master_sword",
"kakariko_alert_guards",
"sahasrahla_quest_have_pendants",
"sahasrahla_quest_have_master_sword",
"sahasrahla_quest_information",
"sahasrahla_bring_courage",
"sahasrahla_have_ice_rod",
"telepathic_sahasrahla_beat_agahnim",
"telepathic_sahasrahla_beat_agahnim_no_pearl",
"sahasrahla_have_boots_no_icerod",
"sahasrahla_have_courage",
"sahasrahla_found",
"sign_rain_north_of_links_house",
"sign_north_of_links_house",
"sign_path_to_death_mountain",
"sign_lost_woods",
"sign_zoras",
"sign_outside_magic_shop",
"sign_death_mountain_cave_back",
"sign_east_of_links_house",
"sign_south_of_lumberjacks",
"sign_east_of_desert",
"sign_east_of_sanctuary",
"sign_east_of_castle",
"sign_north_of_lake",
"sign_desert_thief",
"sign_lumberjacks_house",
"sign_north_kakariko",
"witch_bring_mushroom",
"witch_brewing_the_item",
"witch_assistant_no_bottle",
"witch_assistant_no_empty_bottle",
"witch_assistant_informational",
"witch_assistant_no_bottle_buying",
"potion_shop_no_empty_bottles",
"item_get_lamp",
"item_get_boomerang",
"item_get_bow",
"item_get_shovel",
"item_get_magic_cape",
"item_get_powder",
"item_get_flippers",
"item_get_power_gloves",
"item_get_pendant_courage",
"item_get_pendant_power",
"item_get_pendant_wisdom",
"item_get_mushroom",
"item_get_book",
"item_get_moonpearl",
"item_get_compass",
"item_get_map",
"item_get_ice_rod",
"item_get_fire_rod",
"item_get_ether",
"item_get_bombos",
"item_get_quake",
"item_get_hammer",
"item_get_flute",
"item_get_cane_of_somaria",
"item_get_hookshot",
"item_get_bombs",
"item_get_bottle",
"item_get_big_key",
"item_get_titans_mitts",
"item_get_magic_mirror",
"item_get_fake_mastersword",
"post_item_get_mastersword",
"item_get_red_potion",
"item_get_green_potion",
"item_get_blue_potion",
"item_get_bug_net",
"item_get_blue_mail",
"item_get_red_mail",
"item_get_temperedsword",
"item_get_mirror_shield",
"item_get_cane_of_byrna",
"missing_big_key",
"missing_magic",
"item_get_pegasus_boots",
"talking_tree_info_start",
"talking_tree_info_1",
"talking_tree_info_2",
"talking_tree_info_3",
"talking_tree_info_4",
"talking_tree_other",
"item_get_pendant_power_alt",
"item_get_pendant_wisdom_alt",
"game_shooting_choice",
"game_shooting_yes",
"game_shooting_no",
"game_shooting_continue",
"pond_of_wishing",
"pond_item_select",
"pond_item_test",
"pond_will_upgrade",
"pond_item_test_no",
"pond_item_test_no_no",
"pond_item_boomerang",
"pond_item_shield",
"pond_item_silvers",
"pond_item_bottle_filled",
"pond_item_sword",
"pond_of_wishing_happiness",
"pond_of_wishing_choice",
"pond_of_wishing_bombs",
"pond_of_wishing_arrows",
"pond_of_wishing_full_upgrades",
"mountain_old_man_first",
"mountain_old_man_deadend",
"mountain_old_man_turn_right",
"mountain_old_man_lost_and_alone",
"mountain_old_man_drop_off",
"mountain_old_man_in_his_cave_pre_agahnim",
"mountain_old_man_in_his_cave",
"mountain_old_man_in_his_cave_post_agahnim",
"tavern_old_man_awake",
"tavern_old_man_unactivated_flute",
"tavern_old_man_know_tree_unactivated_flute",
"tavern_old_man_have_flute",
"chicken_hut_lady",
"running_man",
"game_race_sign",
"sign_bumper_cave",
"sign_catfish",
"sign_north_village_of_outcasts",
"sign_south_of_bumper_cave",
"sign_east_of_pyramid",
"sign_east_of_bomb_shop",
"sign_east_of_mire",
"sign_village_of_outcasts",
"sign_before_wishing_pond",
"sign_before_catfish_area",
"castle_wall_guard",
"gate_guard",
"telepathic_tile_eastern_palace",
"telepathic_tile_tower_of_hera_floor_4",
"hylian_text_1",
"mastersword_pedestal_translated",
"telepathic_tile_spectacle_rock",
"telepathic_tile_swamp_entrance",
"telepathic_tile_thieves_town_upstairs",
"telepathic_tile_misery_mire",
"hylian_text_2",
"desert_entry_translated",
"telepathic_tile_under_ganon",
"telepathic_tile_palace_of_darkness",
"telepathic_tile_desert_bonk_torch_room",
"telepathic_tile_castle_tower",
"telepathic_tile_ice_large_room",
"telepathic_tile_turtle_rock",
"telepathic_tile_ice_entrance",
"telepathic_tile_ice_stalfos_knights_room",
"telepathic_tile_tower_of_hera_entrance",
"houlihan_room",
"caught_a_bee",
"caught_a_fairy",
"no_empty_bottles",
"game_race_boy_time",
"game_race_girl",
"game_race_boy_success",
"game_race_boy_failure",
"game_race_boy_already_won",
"game_race_boy_sneaky",
"bottle_vendor_choice",
"bottle_vendor_get",
"bottle_vendor_no",
"bottle_vendor_already_collected",
"bottle_vendor_bee",
"bottle_vendor_fish",
"hobo_item_get_bottle",
"blacksmiths_what_you_want",
"blacksmiths_paywall",
"blacksmiths_extra_okay",
"blacksmiths_tempered_already",
"blacksmiths_temper_no",
"blacksmiths_bogart_sword",
"blacksmiths_get_sword",
"blacksmiths_shop_before_saving",
"blacksmiths_shop_saving",
"blacksmiths_collect_frog",
"blacksmiths_still_working",
"blacksmiths_saving_bows",
"blacksmiths_hammer_anvil",
"dark_flute_boy_storytime",
"dark_flute_boy_get_shovel",
"dark_flute_boy_no_get_shovel",
"dark_flute_boy_flute_not_found",
"dark_flute_boy_after_shovel_get",
"shop_fortune_teller_lw_hint_0",
"shop_fortune_teller_lw_hint_1",
"shop_fortune_teller_lw_hint_2",
"shop_fortune_teller_lw_hint_3",
"shop_fortune_teller_lw_hint_4",
"shop_fortune_teller_lw_hint_5",
"shop_fortune_teller_lw_hint_6",
"shop_fortune_teller_lw_hint_7",
"shop_fortune_teller_lw_no_rupees",
"shop_fortune_teller_lw",
"shop_fortune_teller_lw_post_hint",
"shop_fortune_teller_lw_no",
"shop_fortune_teller_lw_hint_8",
"shop_fortune_teller_lw_hint_9",
"shop_fortune_teller_lw_hint_10",
"shop_fortune_teller_lw_hint_11",
"shop_fortune_teller_lw_hint_12",
"shop_fortune_teller_lw_hint_13",
"shop_fortune_teller_lw_hint_14",
"shop_fortune_teller_lw_hint_15",
"dark_sanctuary",
"dark_sanctuary_hint_0",
"dark_sanctuary_no",
"dark_sanctuary_hint_1",
"dark_sanctuary_yes",
"dark_sanctuary_hint_2",
"sick_kid_no_bottle",
"sick_kid_trade",
"sick_kid_post_trade",
"desert_thief_sitting",
"desert_thief_following",
"desert_thief_question",
"desert_thief_question_yes",
"desert_thief_after_item_get",
"desert_thief_reassure",
"hylian_text_3",
"tablet_ether_book",
"tablet_bombos_book",
"magic_bat_wake",
"magic_bat_give_half_magic",
"intro_main",
"intro_throne_room",
"intro_zelda_cell",
"intro_agahnim",
"pickup_purple_chest",
"bomb_shop",
"bomb_shop_big_bomb",
"bomb_shop_big_bomb_buy",
"item_get_big_bomb",
"kiki_second_extortion",
"kiki_second_extortion_no",
"kiki_second_extortion_yes",
"kiki_first_extortion",
"kiki_first_extortion_yes",
"kiki_first_extortion_no",
"kiki_leaving_screen",
"blind_in_the_cell",
"blind_by_the_light",
"blind_not_that_way",
"aginah_l1sword_no_book",
"aginah_l1sword_with_pendants",
"aginah",
"aginah_need_better_sword",
"aginah_have_better_sword",
"catfish",
"catfish_after_item",
"lumberjack_right",
"lumberjack_left",
"lumberjack_left_post_agahnim",
"fighting_brothers_right",
"fighting_brothers_right_opened",
"fighting_brothers_left",
"maiden_crystal_1",
"maiden_crystal_2",
"maiden_crystal_3",
"maiden_crystal_4",
"maiden_crystal_5",
"maiden_crystal_6",
"maiden_crystal_7",
"maiden_ending",
"maiden_confirm_understood",
"barrier_breaking",
"maiden_crystal_7_again",
"agahnim_zelda_teleport",
"agahnim_magic_running_away",
"agahnim_hide_and_seek_found",
"agahnim_defeated",
"agahnim_final_meeting",
"zora_meeting",
"zora_tells_cost",
"zora_get_flippers",
"zora_no_cash",
"zora_no_buy_item",
"kakariko_saharalasa_grandson",
"kakariko_saharalasa_grandson_next",
"dark_palace_tree_dude",
"fairy_wishing_ponds",
"fairy_wishing_ponds_no",
"pond_of_wishing_no",
"pond_of_wishing_return_item",
"pond_of_wishing_throw",
"pond_pre_item_silvers",
"pond_of_wishing_great_luck",
"pond_of_wishing_good_luck",
"pond_of_wishing_meh_luck",
"pond_of_wishing_bad_luck",
"pond_of_wishing_fortune",
"item_get_14_heart",
"item_get_24_heart",
"item_get_34_heart",
"item_get_whole_heart",
"item_get_sanc_heart",
"fairy_fountain_refill",
"death_mountain_bullied_no_pearl",
"death_mountain_bullied_with_pearl",
"death_mountain_bully_no_pearl",
"death_mountain_bully_with_pearl",
"shop_darkworld_enter",
"game_chest_village_of_outcasts",
"game_chest_no_cash",
"game_chest_not_played",
"game_chest_played",
"game_chest_village_of_outcasts_play",
"shop_first_time",
"shop_already_have",
"shop_buy_shield",
"shop_buy_red_potion",
"shop_buy_arrows",
"shop_buy_bombs",
"shop_buy_bee",
"shop_buy_heart",
"shop_first_no_bottle_buy",
"shop_buy_no_space",
"ganon_fall_in",
"ganon_phase_3",
"lost_woods_thief",
"blinds_hut_dude",
"end_triforce",
"toppi_fallen",
"kakariko_tavern_fisherman",
"thief_money",
"thief_desert_rupee_cave",
"thief_ice_rupee_cave",
"telepathic_tile_south_east_darkworld_cave",
"cukeman",
"cukeman_2",
"potion_shop_no_cash",
"kakariko_powdered_chicken",
"game_chest_south_of_kakariko",
"game_chest_play_yes",
"game_chest_play_no",
"game_chest_lost_woods",
"kakariko_flophouse_man_no_flippers",
"kakariko_flophouse_man",
"menu_start_2",
"menu_start_3",
"menu_pause",
"game_digging_choice",
"game_digging_start",
"game_digging_no_cash",
"game_digging_end_time",
"game_digging_come_back_later",
"game_digging_no_follower",
"menu_start_4",
"ganon_fall_in_alt",
"ganon_phase_3_alt",
"sign_east_death_mountain_bridge",
"fish_money",
"sign_ganons_tower",
"sign_ganon",
"ganon_phase_3_no_bow",
"ganon_phase_3_no_silvers_alt",
"ganon_phase_3_no_silvers",
"ganon_phase_3_silvers",
"murahdahla",
]
def __init__(self):
self._text = OrderedDict()
self.setDefaultText()

View File

@@ -213,7 +213,6 @@ class ALTTPWorld(World):
item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int}
location_name_to_id = lookup_name_to_id
data_version = 9
required_client_version = (0, 4, 1)
web = ALTTPWeb()

View File

@@ -0,0 +1,34 @@
from typing import Dict
from BaseClasses import Tutorial
from ..AutoWorld import WebWorld, World
class AP_SudokuWebWorld(WebWorld):
options_page = "games/Sudoku/info/en"
theme = 'partyTime'
setup_en = Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing APSudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['EmilyV']
)
tutorials = [setup_en]
class AP_SudokuWorld(World):
"""
Play a little Sudoku while you're in BK mode to maybe get some useful hints
"""
game = "Sudoku"
web = AP_SudokuWebWorld()
item_name_to_id: Dict[str, int] = {}
location_name_to_id: Dict[str, int] = {}
@classmethod
def stage_assert_generate(cls, multiworld):
raise Exception("APSudoku cannot be used for generating worlds, the client can instead connect to any slot from any world")

View File

@@ -0,0 +1,13 @@
# APSudoku
## Hint Games
HintGames do not need to be added at the start of a seed, and do not create a 'slot'- instead, you connect the HintGame client to a different game's slot. By playing a HintGame, you can earn hints for the connected slot.
## What is this game?
Play Sudoku puzzles of varying difficulties, earning a hint for each puzzle correctly solved. Harder puzzles are more likely to grant a hint towards a Progression item, though otherwise what hint is granted is random.
## Where is the options page?
There is no options page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld.

View File

@@ -0,0 +1,37 @@
# APSudoku Setup Guide
## Required Software
- [APSudoku](https://github.com/EmilyV99/APSudoku)
- Windows (most tested on Win10)
- Other platforms might be able to build from source themselves; and may be included in the future.
## General Concept
This is a HintGame client, which can connect to any multiworld slot, allowing you to play Sudoku to unlock random hints for that slot's locations.
Does not need to be added at the start of a seed, as it does not create any slots of its own, nor does it have any YAML files.
## Installation Procedures
Go to the latest release from the [APSudoku Releases page](https://github.com/EmilyV99/APSudoku/releases). Download and extract the `APSudoku.zip` file.
## Joining a MultiWorld Game
1. Run APSudoku.exe
2. Under the 'Archipelago' tab at the top-right:
- Enter the server url & port number
- Enter the name of the slot you wish to connect to
- Enter the room password (optional)
- Select DeathLink related settings (optional)
- Press connect
3. Go back to the 'Sudoku' tab
- Click the various '?' buttons for information on how to play / control
4. Choose puzzle difficulty
5. Try to solve the Sudoku. Click 'Check' when done.
## DeathLink Support
If 'DeathLink' is enabled when you click 'Connect':
- Lose a life if you check an incorrect puzzle (not an _incomplete_ puzzle- if any cells are empty, you get off with a warning), or quit a puzzle without solving it (including disconnecting).
- Life count customizable (default 0). Dying with 0 lives left kills linked players AND resets your puzzle.
- On receiving a DeathLink from another player, your puzzle resets.

View File

@@ -77,41 +77,41 @@ class ItemData:
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
"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
"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.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume
"Seed bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
"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
"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
"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
@@ -206,9 +206,9 @@ item_table = {
"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 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
"Transturtle Simon Says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
"Transturtle Arnassi Ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
}

View File

@@ -29,213 +29,213 @@ class AquariaLocation(Location):
class AquariaLocations:
locations_verse_cave_r = {
"Verse cave, bulb in the skeleton room": 698107,
"Verse cave, bulb in the path left of the skeleton room": 698108,
"Verse cave right area, Big Seed": 698175,
"Verse Cave, bulb in the skeleton room": 698107,
"Verse Cave, bulb in the path left of the skeleton room": 698108,
"Verse Cave right area, Big Seed": 698175,
}
locations_verse_cave_l = {
"Verse cave, the Naija hint about here shield ability": 698200,
"Verse cave left area, bulb in the center part": 698021,
"Verse cave left area, bulb in the right part": 698022,
"Verse cave left area, bulb under the rock at the end of the path": 698023,
"Verse Cave, the Naija hint about the shield ability": 698200,
"Verse Cave left area, bulb in the center part": 698021,
"Verse Cave left area, bulb in the right part": 698022,
"Verse Cave left area, bulb under the rock at the end of the path": 698023,
}
locations_home_water = {
"Home water, bulb below the grouper fish": 698058,
"Home water, bulb in the path below Nautilus Prime": 698059,
"Home water, bulb in the little room above the grouper fish": 698060,
"Home water, bulb in the end of the left path from the verse cave": 698061,
"Home water, bulb in the top left path": 698062,
"Home water, bulb in the bottom left room": 698063,
"Home water, bulb close to the Naija's home": 698064,
"Home water, bulb under the rock in the left path from the verse cave": 698065,
"Home Water, bulb below the grouper fish": 698058,
"Home Water, bulb in the path below Nautilus Prime": 698059,
"Home Water, bulb in the little room above the grouper fish": 698060,
"Home Water, bulb in the end of the left path from the Verse Cave": 698061,
"Home Water, bulb in the top left path": 698062,
"Home Water, bulb in the bottom left room": 698063,
"Home Water, bulb close to Naija's Home": 698064,
"Home Water, bulb under the rock in the left path from the Verse Cave": 698065,
}
locations_home_water_nautilus = {
"Home water, Nautilus Egg": 698194,
"Home Water, Nautilus Egg": 698194,
}
locations_home_water_transturtle = {
"Home water, Transturtle": 698213,
"Home Water, Transturtle": 698213,
}
locations_naija_home = {
"Naija's home, bulb after the energy door": 698119,
"Naija's home, bulb under the rock at the right of the main path": 698120,
"Naija's Home, bulb after the energy door": 698119,
"Naija's Home, bulb under the rock at the right of the main path": 698120,
}
locations_song_cave = {
"Song cave, Erulian spirit": 698206,
"Song cave, bulb in the top left part": 698071,
"Song cave, bulb in the big anemone room": 698072,
"Song cave, bulb in the path to the singing statues": 698073,
"Song cave, bulb under the rock in the path to the singing statues": 698074,
"Song cave, bulb under the rock close to the song door": 698075,
"Song cave, Verse egg": 698160,
"Song cave, Jelly beacon": 698178,
"Song cave, Anemone seed": 698162,
"Song Cave, Erulian spirit": 698206,
"Song Cave, bulb in the top left part": 698071,
"Song Cave, bulb in the big anemone room": 698072,
"Song Cave, bulb in the path to the singing statues": 698073,
"Song Cave, bulb under the rock in the path to the singing statues": 698074,
"Song Cave, bulb under the rock close to the song door": 698075,
"Song Cave, Verse Egg": 698160,
"Song Cave, Jelly Beacon": 698178,
"Song Cave, Anemone Seed": 698162,
}
locations_energy_temple_1 = {
"Energy temple first area, beating the energy statue": 698205,
"Energy temple first area, bulb in the bottom room blocked by a rock": 698027,
"Energy Temple first area, beating the Energy Statue": 698205,
"Energy Temple first area, bulb in the bottom room blocked by a rock": 698027,
}
locations_energy_temple_idol = {
"Energy temple first area, Energy Idol": 698170,
"Energy Temple first area, Energy Idol": 698170,
}
locations_energy_temple_2 = {
"Energy temple second area, bulb under the rock": 698028,
"Energy Temple second area, bulb under the rock": 698028,
}
locations_energy_temple_altar = {
"Energy temple bottom entrance, Krotite armor": 698163,
"Energy Temple bottom entrance, Krotite Armor": 698163,
}
locations_energy_temple_3 = {
"Energy temple third area, bulb in the bottom path": 698029,
"Energy Temple third area, bulb in the bottom path": 698029,
}
locations_energy_temple_boss = {
"Energy temple boss area, Fallen god tooth": 698169,
"Energy Temple boss area, Fallen God Tooth": 698169,
}
locations_energy_temple_blaster_room = {
"Energy temple blaster room, Blaster egg": 698195,
"Energy Temple blaster room, Blaster Egg": 698195,
}
locations_openwater_tl = {
"Open water top left area, bulb under the rock in the right path": 698001,
"Open water top left area, bulb under the rock in the left path": 698002,
"Open water top left area, bulb to the right of the save cristal": 698003,
"Open Water top left area, bulb under the rock in the right path": 698001,
"Open Water top left area, bulb under the rock in the left path": 698002,
"Open Water top left area, bulb to the right of the save crystal": 698003,
}
locations_openwater_tr = {
"Open water top right area, bulb in the small path before Mithalas": 698004,
"Open water top right area, bulb in the path from the left entrance": 698005,
"Open water top right area, bulb in the clearing close to the bottom exit": 698006,
"Open water top right area, bulb in the big clearing close to the save cristal": 698007,
"Open water top right area, bulb in the big clearing to the top exit": 698008,
"Open water top right area, first urn in the Mithalas exit": 698148,
"Open water top right area, second urn in the Mithalas exit": 698149,
"Open water top right area, third urn in the Mithalas exit": 698150,
"Open Water top right area, bulb in the small path before Mithalas": 698004,
"Open Water top right area, bulb in the path from the left entrance": 698005,
"Open Water top right area, bulb in the clearing close to the bottom exit": 698006,
"Open Water top right area, bulb in the big clearing close to the save crystal": 698007,
"Open Water top right area, bulb in the big clearing to the top exit": 698008,
"Open Water top right area, first urn in the Mithalas exit": 698148,
"Open Water top right area, second urn in the Mithalas exit": 698149,
"Open Water top right area, third urn in the Mithalas exit": 698150,
}
locations_openwater_tr_turtle = {
"Open water top right area, bulb in the turtle room": 698009,
"Open water top right area, Transturtle": 698211,
"Open Water top right area, bulb in the turtle room": 698009,
"Open Water top right area, Transturtle": 698211,
}
locations_openwater_bl = {
"Open water bottom left area, bulb behind the chomper fish": 698011,
"Open water bottom left area, bulb inside the lowest fish pass": 698010,
"Open Water bottom left area, bulb behind the chomper fish": 698011,
"Open Water bottom left area, bulb inside the lowest fish pass": 698010,
}
locations_skeleton_path = {
"Open water skeleton path, bulb close to the right exit": 698012,
"Open water skeleton path, bulb behind the chomper fish": 698013,
"Open Water skeleton path, bulb close to the right exit": 698012,
"Open Water skeleton path, bulb behind the chomper fish": 698013,
}
locations_skeleton_path_sc = {
"Open water skeleton path, King skull": 698177,
"Open Water skeleton path, King Skull": 698177,
}
locations_arnassi = {
"Arnassi Ruins, bulb in the right part": 698014,
"Arnassi Ruins, bulb in the left part": 698015,
"Arnassi Ruins, bulb in the center part": 698016,
"Arnassi ruins, Song plant spore on the top of the ruins": 698179,
"Arnassi ruins, Arnassi Armor": 698191,
"Arnassi Ruins, Song Plant Spore": 698179,
"Arnassi Ruins, Arnassi Armor": 698191,
}
locations_arnassi_path = {
"Arnassi Ruins, Arnassi statue": 698164,
"Arnassi Ruins, Arnassi Statue": 698164,
"Arnassi Ruins, Transturtle": 698217,
}
locations_arnassi_crab_boss = {
"Arnassi ruins, Crab armor": 698187,
"Arnassi Ruins, Crab Armor": 698187,
}
locations_simon = {
"Kelp forest, beating Simon says": 698156,
"Simon says area, Transturtle": 698216,
"Simon Says area, beating Simon Says": 698156,
"Simon Says area, Transturtle": 698216,
}
locations_mithalas_city = {
"Mithalas city, first bulb in the left city part": 698030,
"Mithalas city, second bulb in the left city part": 698035,
"Mithalas city, bulb in the right part": 698031,
"Mithalas city, bulb at the top of the city": 698033,
"Mithalas city, first bulb in a broken home": 698034,
"Mithalas city, second bulb in a broken home": 698041,
"Mithalas city, bulb in the bottom left part": 698037,
"Mithalas city, first bulb in one of the homes": 698038,
"Mithalas city, second bulb in one of the homes": 698039,
"Mithalas city, first urn in one of the homes": 698123,
"Mithalas city, second urn in one of the homes": 698124,
"Mithalas city, first urn in the city reserve": 698125,
"Mithalas city, second urn in the city reserve": 698126,
"Mithalas city, third urn in the city reserve": 698127,
"Mithalas City, first bulb in the left city part": 698030,
"Mithalas City, second bulb in the left city part": 698035,
"Mithalas City, bulb in the right part": 698031,
"Mithalas City, bulb at the top of the city": 698033,
"Mithalas City, first bulb in a broken home": 698034,
"Mithalas City, second bulb in a broken home": 698041,
"Mithalas City, bulb in the bottom left part": 698037,
"Mithalas City, first bulb in one of the homes": 698038,
"Mithalas City, second bulb in one of the homes": 698039,
"Mithalas City, first urn in one of the homes": 698123,
"Mithalas City, second urn in one of the homes": 698124,
"Mithalas City, first urn in the city reserve": 698125,
"Mithalas City, second urn in the city reserve": 698126,
"Mithalas City, third urn in the city reserve": 698127,
}
locations_mithalas_city_top_path = {
"Mithalas city, first bulb at the end of the top path": 698032,
"Mithalas city, second bulb at the end of the top path": 698040,
"Mithalas city, bulb in the top path": 698036,
"Mithalas city, Mithalas pot": 698174,
"Mithalas city, urn in the cathedral flower tube entrance": 698128,
"Mithalas City, first bulb at the end of the top path": 698032,
"Mithalas City, second bulb at the end of the top path": 698040,
"Mithalas City, bulb in the top path": 698036,
"Mithalas City, Mithalas Pot": 698174,
"Mithalas City, urn in the Cathedral flower tube entrance": 698128,
}
locations_mithalas_city_fishpass = {
"Mithalas city, Doll": 698173,
"Mithalas city, urn inside a home fish pass": 698129,
"Mithalas City, Doll": 698173,
"Mithalas City, urn inside a home fish pass": 698129,
}
locations_cathedral_l = {
"Mithalas city castle, bulb in the flesh hole": 698042,
"Mithalas city castle, Blue banner": 698165,
"Mithalas city castle, urn in the bedroom": 698130,
"Mithalas city castle, first urn of the single lamp path": 698131,
"Mithalas city castle, second urn of the single lamp path": 698132,
"Mithalas city castle, urn in the bottom room": 698133,
"Mithalas city castle, first urn on the entrance path": 698134,
"Mithalas city castle, second urn on the entrance path": 698135,
"Mithalas City Castle, bulb in the flesh hole": 698042,
"Mithalas City Castle, Blue banner": 698165,
"Mithalas City Castle, urn in the bedroom": 698130,
"Mithalas City Castle, first urn of the single lamp path": 698131,
"Mithalas City Castle, second urn of the single lamp path": 698132,
"Mithalas City Castle, urn in the bottom room": 698133,
"Mithalas City Castle, first urn on the entrance path": 698134,
"Mithalas City Castle, second urn on the entrance path": 698135,
}
locations_cathedral_l_tube = {
"Mithalas castle, beating the priests": 698208,
"Mithalas City Castle, beating the Priests": 698208,
}
locations_cathedral_l_sc = {
"Mithalas city castle, Trident head": 698183,
"Mithalas City Castle, Trident Head": 698183,
}
locations_cathedral_r = {
"Mithalas cathedral, first urn in the top right room": 698136,
"Mithalas cathedral, second urn in the top right room": 698137,
"Mithalas cathedral, third urn in the top right room": 698138,
"Mithalas cathedral, urn in the flesh room with fleas": 698139,
"Mithalas cathedral, first urn in the bottom right path": 698140,
"Mithalas cathedral, second urn in the bottom right path": 698141,
"Mithalas cathedral, urn behind the flesh vein": 698142,
"Mithalas cathedral, urn in the top left eyes boss room": 698143,
"Mithalas cathedral, first urn in the path behind the flesh vein": 698144,
"Mithalas cathedral, second urn in the path behind the flesh vein": 698145,
"Mithalas cathedral, third urn in the path behind the flesh vein": 698146,
"Mithalas cathedral, one of the urns in the top right room": 698147,
"Mithalas cathedral, Mithalan Dress": 698189,
"Mithalas cathedral right area, urn below the left entrance": 698198,
"Mithalas Cathedral, first urn in the top right room": 698136,
"Mithalas Cathedral, second urn in the top right room": 698137,
"Mithalas Cathedral, third urn in the top right room": 698138,
"Mithalas Cathedral, urn in the flesh room with fleas": 698139,
"Mithalas Cathedral, first urn in the bottom right path": 698140,
"Mithalas Cathedral, second urn in the bottom right path": 698141,
"Mithalas Cathedral, urn behind the flesh vein": 698142,
"Mithalas Cathedral, urn in the top left eyes boss room": 698143,
"Mithalas Cathedral, first urn in the path behind the flesh vein": 698144,
"Mithalas Cathedral, second urn in the path behind the flesh vein": 698145,
"Mithalas Cathedral, third urn in the path behind the flesh vein": 698146,
"Mithalas Cathedral, fourth urn in the top right room": 698147,
"Mithalas Cathedral, Mithalan Dress": 698189,
"Mithalas Cathedral right area, urn below the left entrance": 698198,
}
locations_cathedral_underground = {
"Cathedral underground, bulb in the center part": 698113,
"Cathedral underground, first bulb in the top left part": 698114,
"Cathedral underground, second bulb in the top left part": 698115,
"Cathedral underground, third bulb in the top left part": 698116,
"Cathedral underground, bulb close to the save cristal": 698117,
"Cathedral underground, bulb in the bottom right path": 698118,
"Cathedral Underground, bulb in the center part": 698113,
"Cathedral Underground, first bulb in the top left part": 698114,
"Cathedral Underground, second bulb in the top left part": 698115,
"Cathedral Underground, third bulb in the top left part": 698116,
"Cathedral Underground, bulb close to the save crystal": 698117,
"Cathedral Underground, bulb in the bottom right path": 698118,
}
locations_cathedral_boss = {
@@ -250,8 +250,8 @@ class AquariaLocations:
}
locations_forest_tl_fp = {
"Kelp Forest top left area, bulb close to the Verse egg": 698047,
"Kelp forest top left area, Verse egg": 698158,
"Kelp Forest top left area, bulb close to the Verse Egg": 698047,
"Kelp Forest top left area, Verse Egg": 698158,
}
locations_forest_tr = {
@@ -260,7 +260,7 @@ class AquariaLocations:
"Kelp Forest top right area, bulb in the left path's big room": 698051,
"Kelp Forest top right area, bulb in the left path's small room": 698052,
"Kelp Forest top right area, bulb at the top of the center clearing": 698053,
"Kelp forest top right area, Black pearl": 698167,
"Kelp Forest top right area, Black Pearl": 698167,
}
locations_forest_tr_fp = {
@@ -269,16 +269,16 @@ class AquariaLocations:
locations_forest_bl = {
"Kelp Forest bottom left area, bulb close to the spirit crystals": 698054,
"Kelp forest bottom left area, Walker baby": 698186,
"Kelp Forest bottom left area, Walker baby": 698186,
"Kelp Forest bottom left area, Transturtle": 698212,
}
locations_forest_br = {
"Kelp forest bottom right area, Odd Container": 698168,
"Kelp Forest bottom right area, Odd Container": 698168,
}
locations_forest_boss = {
"Kelp forest boss area, beating Drunian God": 698204,
"Kelp Forest boss area, beating Drunian God": 698204,
}
locations_forest_boss_entrance = {
@@ -286,7 +286,7 @@ class AquariaLocations:
}
locations_forest_fish_cave = {
"Kelp Forest bottom left area, Fish cave puzzle": 698207,
"Kelp Forest bottom left area, Fish Cave puzzle": 698207,
}
locations_forest_sprite_cave = {
@@ -295,7 +295,7 @@ class AquariaLocations:
locations_forest_sprite_cave_tube = {
"Kelp Forest sprite cave, bulb in the second room": 698057,
"Kelp Forest Sprite Cave, Seed bag": 698176,
"Kelp Forest sprite cave, Seed Bag": 698176,
}
locations_mermog_cave = {
@@ -307,14 +307,14 @@ class AquariaLocations:
}
locations_veil_tl = {
"The veil top left area, In the Li cave": 698199,
"The veil top left area, bulb under the rock in the top right path": 698078,
"The veil top left area, bulb hidden behind the blocking rock": 698076,
"The veil top left area, Transturtle": 698209,
"The Veil top left area, In Li's cave": 698199,
"The Veil top left area, bulb under the rock in the top right path": 698078,
"The Veil top left area, bulb hidden behind the blocking rock": 698076,
"The Veil top left area, Transturtle": 698209,
}
locations_veil_tl_fp = {
"The veil top left area, bulb inside the fish pass": 698077,
"The Veil top left area, bulb inside the fish pass": 698077,
}
locations_turtle_cave = {
@@ -322,56 +322,56 @@ class AquariaLocations:
}
locations_turtle_cave_bubble = {
"Turtle cave, bulb in bubble cliff": 698000,
"Turtle cave, Urchin costume": 698193,
"Turtle cave, bulb in Bubble Cliff": 698000,
"Turtle cave, Urchin Costume": 698193,
}
locations_veil_tr_r = {
"The veil top right area, bulb in the middle of the wall jump cliff": 698079,
"The veil top right area, golden starfish at the bottom right of the bottom path": 698180,
"The Veil top right area, bulb in the middle of the wall jump cliff": 698079,
"The Veil top right area, Golden Starfish": 698180,
}
locations_veil_tr_l = {
"The veil top right area, bulb in the top of the water fall": 698080,
"The veil top right area, Transturtle": 698210,
"The Veil top right area, bulb in the top of the waterfall": 698080,
"The Veil top right area, Transturtle": 698210,
}
locations_veil_bl = {
"The veil bottom area, bulb in the left path": 698082,
"The Veil bottom area, bulb in the left path": 698082,
}
locations_veil_b_sc = {
"The veil bottom area, bulb in the spirit path": 698081,
"The Veil bottom area, bulb in the spirit path": 698081,
}
locations_veil_bl_fp = {
"The veil bottom area, Verse egg": 698157,
"The Veil bottom area, Verse Egg": 698157,
}
locations_veil_br = {
"The veil bottom area, Stone Head": 698181,
"The Veil bottom area, Stone Head": 698181,
}
locations_octo_cave_t = {
"Octopus cave, Dumbo Egg": 698196,
"Octopus Cave, Dumbo Egg": 698196,
}
locations_octo_cave_b = {
"Octopus cave, bulb in the path below the octopus cave path": 698122,
"Octopus Cave, bulb in the path below the Octopus Cave path": 698122,
}
locations_sun_temple_l = {
"Sun temple, bulb in the top left part": 698094,
"Sun temple, bulb in the top right part": 698095,
"Sun temple, bulb at the top of the high dark room": 698096,
"Sun temple, Golden Gear": 698171,
"Sun Temple, bulb in the top left part": 698094,
"Sun Temple, bulb in the top right part": 698095,
"Sun Temple, bulb at the top of the high dark room": 698096,
"Sun Temple, Golden Gear": 698171,
}
locations_sun_temple_r = {
"Sun temple, first bulb of the temple": 698091,
"Sun temple, bulb on the left part": 698092,
"Sun temple, bulb in the hidden room of the right part": 698093,
"Sun temple, Sun key": 698182,
"Sun Temple, first bulb of the temple": 698091,
"Sun Temple, bulb on the left part": 698092,
"Sun Temple, bulb in the hidden room of the right part": 698093,
"Sun Temple, Sun Key": 698182,
}
locations_sun_temple_boss_path = {
@@ -382,13 +382,13 @@ class AquariaLocations:
}
locations_sun_temple_boss = {
"Sun temple boss area, beating Sun God": 698203,
"Sun Temple boss area, beating Sun God": 698203,
}
locations_abyss_l = {
"Abyss left area, bulb in hidden path room": 698024,
"Abyss left area, bulb in the right part": 698025,
"Abyss left area, Glowing seed": 698166,
"Abyss left area, Glowing Seed": 698166,
"Abyss left area, Glowing Plant": 698172,
}
@@ -405,87 +405,87 @@ class AquariaLocations:
}
locations_ice_cave = {
"Ice cave, bulb in the room to the right": 698083,
"Ice cave, First bulbs in the top exit room": 698084,
"Ice cave, Second bulbs in the top exit room": 698085,
"Ice cave, third bulbs in the top exit room": 698086,
"Ice cave, bulb in the left room": 698087,
"Ice Cave, bulb in the room to the right": 698083,
"Ice Cave, first bulb in the top exit room": 698084,
"Ice Cave, second bulb in the top exit room": 698085,
"Ice Cave, third bulb in the top exit room": 698086,
"Ice Cave, bulb in the left room": 698087,
}
locations_bubble_cave = {
"Bubble cave, bulb in the left cave wall": 698089,
"Bubble cave, bulb in the right cave wall (behind the ice cristal)": 698090,
"Bubble Cave, bulb in the left cave wall": 698089,
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)": 698090,
}
locations_bubble_cave_boss = {
"Bubble cave, Verse egg": 698161,
"Bubble Cave, Verse Egg": 698161,
}
locations_king_jellyfish_cave = {
"King Jellyfish cave, bulb in the right path from King Jelly": 698088,
"King Jellyfish cave, Jellyfish Costume": 698188,
"King Jellyfish Cave, bulb in the right path from King Jelly": 698088,
"King Jellyfish Cave, Jellyfish Costume": 698188,
}
locations_whale = {
"The whale, Verse egg": 698159,
"The Whale, Verse Egg": 698159,
}
locations_sunken_city_r = {
"Sunken city right area, crate close to the save cristal": 698154,
"Sunken city right area, crate in the left bottom room": 698155,
"Sunken City right area, crate close to the save crystal": 698154,
"Sunken City right area, crate in the left bottom room": 698155,
}
locations_sunken_city_l = {
"Sunken city left area, crate in the little pipe room": 698151,
"Sunken city left area, crate close to the save cristal": 698152,
"Sunken city left area, crate before the bedroom": 698153,
"Sunken City left area, crate in the little pipe room": 698151,
"Sunken City left area, crate close to the save crystal": 698152,
"Sunken City left area, crate before the bedroom": 698153,
}
locations_sunken_city_l_bedroom = {
"Sunken city left area, Girl Costume": 698192,
"Sunken City left area, Girl Costume": 698192,
}
locations_sunken_city_boss = {
"Sunken city, bulb on the top of the boss area (boiler room)": 698043,
"Sunken City, bulb on top of the boss area": 698043,
}
locations_body_c = {
"The body center area, breaking li cage": 698201,
"The body main area, bulb on the main path blocking tube": 698097,
"The Body center area, breaking Li's cage": 698201,
"The Body main area, bulb on the main path blocking tube": 698097,
}
locations_body_l = {
"The body left area, first bulb in the top face room": 698066,
"The body left area, second bulb in the top face room": 698069,
"The body left area, bulb below the water stream": 698067,
"The body left area, bulb in the top path to the top face room": 698068,
"The body left area, bulb in the bottom face room": 698070,
"The Body left area, first bulb in the top face room": 698066,
"The Body left area, second bulb in the top face room": 698069,
"The Body left area, bulb below the water stream": 698067,
"The Body left area, bulb in the top path to the top face room": 698068,
"The Body left area, bulb in the bottom face room": 698070,
}
locations_body_rt = {
"The body right area, bulb in the top face room": 698100,
"The Body right area, bulb in the top face room": 698100,
}
locations_body_rb = {
"The body right area, bulb in the top path to the bottom face room": 698098,
"The body right area, bulb in the bottom face room": 698099,
"The Body right area, bulb in the top path to the bottom face room": 698098,
"The Body right area, bulb in the bottom face room": 698099,
}
locations_body_b = {
"The body bottom area, bulb in the Jelly Zap room": 698101,
"The body bottom area, bulb in the nautilus room": 698102,
"The body bottom area, Mutant Costume": 698190,
"The Body bottom area, bulb in the Jelly Zap room": 698101,
"The Body bottom area, bulb in the nautilus room": 698102,
"The Body bottom area, Mutant Costume": 698190,
}
locations_final_boss_tube = {
"Final boss area, first bulb in the turtle room": 698103,
"Final boss area, second bulbs in the turtle room": 698104,
"Final boss area, third bulbs in the turtle room": 698105,
"Final boss area, Transturtle": 698215,
"Final Boss area, first bulb in the turtle room": 698103,
"Final Boss area, second bulb in the turtle room": 698104,
"Final Boss area, third bulb in the turtle room": 698105,
"Final Boss area, Transturtle": 698215,
}
locations_final_boss = {
"Final boss area, bulb in the boss third form room": 698106,
"Final Boss area, bulb in the boss third form room": 698106,
}

View File

@@ -113,7 +113,7 @@ class BindSongNeededToGetUnderRockBulb(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 Water area so that Naija can go to open water and beyond without the bind song.
"""
display_name = "Unconfine Home Water Area"
option_off = 0

View File

@@ -36,8 +36,8 @@ def _has_li(state:CollectionState, player: int) -> bool:
def _has_damaging_item(state:CollectionState, player: int) -> bool:
"""`player` in `state` has the shield song item"""
return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby nautilus",
"Baby piranha", "Baby blaster"}, player)
return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus",
"Baby Piranha", "Baby Blaster"}, player)
def _has_shield_song(state:CollectionState, player: int) -> bool:
@@ -72,7 +72,7 @@ def _has_sun_form(state:CollectionState, player: int) -> bool:
def _has_light(state:CollectionState, player: int) -> bool:
"""`player` in `state` has the light item"""
return state.has("Baby dumbo", player) or _has_sun_form(state, player)
return state.has("Baby Dumbo", player) or _has_sun_form(state, player)
def _has_dual_form(state:CollectionState, player: int) -> bool:
@@ -237,26 +237,26 @@ class AquariaRegions:
AquariaLocations.locations_home_water_nautilus)
self.home_water_transturtle = self.__add_region("Home Water, turtle room",
AquariaLocations.locations_home_water_transturtle)
self.naija_home = self.__add_region("Naija's home", AquariaLocations.locations_naija_home)
self.song_cave = self.__add_region("Song cave", AquariaLocations.locations_song_cave)
self.naija_home = self.__add_region("Naija's Home", AquariaLocations.locations_naija_home)
self.song_cave = self.__add_region("Song Cave", AquariaLocations.locations_song_cave)
def __create_energy_temple(self) -> None:
"""
Create the `energy_temple_*` regions
"""
self.energy_temple_1 = self.__add_region("Energy temple first area",
self.energy_temple_1 = self.__add_region("Energy Temple first area",
AquariaLocations.locations_energy_temple_1)
self.energy_temple_2 = self.__add_region("Energy temple second area",
self.energy_temple_2 = self.__add_region("Energy Temple second area",
AquariaLocations.locations_energy_temple_2)
self.energy_temple_3 = self.__add_region("Energy temple third area",
self.energy_temple_3 = self.__add_region("Energy Temple third area",
AquariaLocations.locations_energy_temple_3)
self.energy_temple_altar = self.__add_region("Energy temple bottom entrance",
self.energy_temple_altar = self.__add_region("Energy Temple bottom entrance",
AquariaLocations.locations_energy_temple_altar)
self.energy_temple_boss = self.__add_region("Energy temple fallen God room",
self.energy_temple_boss = self.__add_region("Energy Temple fallen God room",
AquariaLocations.locations_energy_temple_boss)
self.energy_temple_idol = self.__add_region("Energy temple Idol room",
self.energy_temple_idol = self.__add_region("Energy Temple Idol room",
AquariaLocations.locations_energy_temple_idol)
self.energy_temple_blaster_room = self.__add_region("Energy temple blaster room",
self.energy_temple_blaster_room = self.__add_region("Energy Temple blaster room",
AquariaLocations.locations_energy_temple_blaster_room)
def __create_openwater(self) -> None:
@@ -264,18 +264,18 @@ class AquariaRegions:
Create the `openwater_*`, `skeleton_path`, `arnassi*` and `simon`
regions
"""
self.openwater_tl = self.__add_region("Open water top left area",
self.openwater_tl = self.__add_region("Open Water top left area",
AquariaLocations.locations_openwater_tl)
self.openwater_tr = self.__add_region("Open water top right area",
self.openwater_tr = self.__add_region("Open Water top right area",
AquariaLocations.locations_openwater_tr)
self.openwater_tr_turtle = self.__add_region("Open water top right area, turtle room",
self.openwater_tr_turtle = self.__add_region("Open Water top right area, turtle room",
AquariaLocations.locations_openwater_tr_turtle)
self.openwater_bl = self.__add_region("Open water bottom left area",
self.openwater_bl = self.__add_region("Open Water bottom left area",
AquariaLocations.locations_openwater_bl)
self.openwater_br = self.__add_region("Open water bottom right area", None)
self.skeleton_path = self.__add_region("Open water skeleton path",
self.openwater_br = self.__add_region("Open Water bottom right area", None)
self.skeleton_path = self.__add_region("Open Water skeleton path",
AquariaLocations.locations_skeleton_path)
self.skeleton_path_sc = self.__add_region("Open water skeleton path spirit cristal",
self.skeleton_path_sc = self.__add_region("Open Water skeleton path spirit crystal",
AquariaLocations.locations_skeleton_path_sc)
self.arnassi = self.__add_region("Arnassi Ruins", AquariaLocations.locations_arnassi)
self.arnassi_path = self.__add_region("Arnassi Ruins, back entrance path",
@@ -287,20 +287,20 @@ class AquariaRegions:
"""
Create the `mithalas_city*` and `cathedral_*` regions
"""
self.mithalas_city = self.__add_region("Mithalas city",
self.mithalas_city = self.__add_region("Mithalas City",
AquariaLocations.locations_mithalas_city)
self.mithalas_city_fishpass = self.__add_region("Mithalas city fish pass",
self.mithalas_city_fishpass = self.__add_region("Mithalas City fish pass",
AquariaLocations.locations_mithalas_city_fishpass)
self.mithalas_city_top_path = self.__add_region("Mithalas city top path",
self.mithalas_city_top_path = self.__add_region("Mithalas City top path",
AquariaLocations.locations_mithalas_city_top_path)
self.cathedral_l = self.__add_region("Mithalas castle", AquariaLocations.locations_cathedral_l)
self.cathedral_l_tube = self.__add_region("Mithalas castle, plant tube entrance",
AquariaLocations.locations_cathedral_l_tube)
self.cathedral_l_sc = self.__add_region("Mithalas castle spirit cristal",
self.cathedral_l_sc = self.__add_region("Mithalas castle spirit crystal",
AquariaLocations.locations_cathedral_l_sc)
self.cathedral_r = self.__add_region("Mithalas Cathedral",
AquariaLocations.locations_cathedral_r)
self.cathedral_underground = self.__add_region("Mithalas Cathedral underground area",
self.cathedral_underground = self.__add_region("Mithalas Cathedral Underground area",
AquariaLocations.locations_cathedral_underground)
self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room",
AquariaLocations.locations_cathedral_boss)
@@ -310,73 +310,73 @@ class AquariaRegions:
"""
Create the `forest_*` dans `mermog_cave` regions
"""
self.forest_tl = self.__add_region("Kelp forest top left area",
self.forest_tl = self.__add_region("Kelp Forest top left area",
AquariaLocations.locations_forest_tl)
self.forest_tl_fp = self.__add_region("Kelp forest top left area fish pass",
self.forest_tl_fp = self.__add_region("Kelp Forest top left area fish pass",
AquariaLocations.locations_forest_tl_fp)
self.forest_tr = self.__add_region("Kelp forest top right area",
self.forest_tr = self.__add_region("Kelp Forest top right area",
AquariaLocations.locations_forest_tr)
self.forest_tr_fp = self.__add_region("Kelp forest top right area fish pass",
self.forest_tr_fp = self.__add_region("Kelp Forest top right area fish pass",
AquariaLocations.locations_forest_tr_fp)
self.forest_bl = self.__add_region("Kelp forest bottom left area",
self.forest_bl = self.__add_region("Kelp Forest bottom left area",
AquariaLocations.locations_forest_bl)
self.forest_br = self.__add_region("Kelp forest bottom right area",
self.forest_br = self.__add_region("Kelp Forest bottom right area",
AquariaLocations.locations_forest_br)
self.forest_sprite_cave = self.__add_region("Kelp forest spirit cave",
self.forest_sprite_cave = self.__add_region("Kelp Forest spirit cave",
AquariaLocations.locations_forest_sprite_cave)
self.forest_sprite_cave_tube = self.__add_region("Kelp forest spirit cave after the plant tube",
self.forest_sprite_cave_tube = self.__add_region("Kelp Forest spirit cave after the plant tube",
AquariaLocations.locations_forest_sprite_cave_tube)
self.forest_boss = self.__add_region("Kelp forest Drunian God room",
self.forest_boss = self.__add_region("Kelp Forest Drunian God room",
AquariaLocations.locations_forest_boss)
self.forest_boss_entrance = self.__add_region("Kelp forest Drunian God room entrance",
self.forest_boss_entrance = self.__add_region("Kelp Forest Drunian God room entrance",
AquariaLocations.locations_forest_boss_entrance)
self.mermog_cave = self.__add_region("Kelp forest Mermog cave",
self.mermog_cave = self.__add_region("Kelp Forest Mermog cave",
AquariaLocations.locations_mermog_cave)
self.mermog_boss = self.__add_region("Kelp forest Mermog cave boss",
self.mermog_boss = self.__add_region("Kelp Forest Mermog cave boss",
AquariaLocations.locations_mermog_boss)
self.forest_fish_cave = self.__add_region("Kelp forest fish cave",
self.forest_fish_cave = self.__add_region("Kelp Forest fish cave",
AquariaLocations.locations_forest_fish_cave)
self.simon = self.__add_region("Kelp forest, Simon's room", AquariaLocations.locations_simon)
self.simon = self.__add_region("Kelp Forest, Simon's room", AquariaLocations.locations_simon)
def __create_veil(self) -> None:
"""
Create the `veil_*`, `octo_cave` and `turtle_cave` regions
"""
self.veil_tl = self.__add_region("The veil top left area", AquariaLocations.locations_veil_tl)
self.veil_tl_fp = self.__add_region("The veil top left area fish pass",
self.veil_tl = self.__add_region("The Veil top left area", AquariaLocations.locations_veil_tl)
self.veil_tl_fp = self.__add_region("The Veil top left area fish pass",
AquariaLocations.locations_veil_tl_fp)
self.turtle_cave = self.__add_region("The veil top left area, turtle cave",
self.turtle_cave = self.__add_region("The Veil top left area, turtle cave",
AquariaLocations.locations_turtle_cave)
self.turtle_cave_bubble = self.__add_region("The veil top left area, turtle cave bubble cliff",
self.turtle_cave_bubble = self.__add_region("The Veil top left area, turtle cave Bubble Cliff",
AquariaLocations.locations_turtle_cave_bubble)
self.veil_tr_l = self.__add_region("The veil top right area, left of temple",
self.veil_tr_l = self.__add_region("The Veil top right area, left of temple",
AquariaLocations.locations_veil_tr_l)
self.veil_tr_r = self.__add_region("The veil top right area, right of temple",
self.veil_tr_r = self.__add_region("The Veil top right area, right of temple",
AquariaLocations.locations_veil_tr_r)
self.octo_cave_t = self.__add_region("Octopus cave top entrance",
self.octo_cave_t = self.__add_region("Octopus Cave top entrance",
AquariaLocations.locations_octo_cave_t)
self.octo_cave_b = self.__add_region("Octopus cave bottom entrance",
self.octo_cave_b = self.__add_region("Octopus Cave bottom entrance",
AquariaLocations.locations_octo_cave_b)
self.veil_bl = self.__add_region("The veil bottom left area",
self.veil_bl = self.__add_region("The Veil bottom left area",
AquariaLocations.locations_veil_bl)
self.veil_b_sc = self.__add_region("The veil bottom spirit cristal area",
self.veil_b_sc = self.__add_region("The Veil bottom spirit crystal area",
AquariaLocations.locations_veil_b_sc)
self.veil_bl_fp = self.__add_region("The veil bottom left area, in the sunken ship",
self.veil_bl_fp = self.__add_region("The Veil bottom left area, in the sunken ship",
AquariaLocations.locations_veil_bl_fp)
self.veil_br = self.__add_region("The veil bottom right area",
self.veil_br = self.__add_region("The Veil bottom right area",
AquariaLocations.locations_veil_br)
def __create_sun_temple(self) -> None:
"""
Create the `sun_temple*` regions
"""
self.sun_temple_l = self.__add_region("Sun temple left area",
self.sun_temple_l = self.__add_region("Sun Temple left area",
AquariaLocations.locations_sun_temple_l)
self.sun_temple_r = self.__add_region("Sun temple right area",
self.sun_temple_r = self.__add_region("Sun Temple right area",
AquariaLocations.locations_sun_temple_r)
self.sun_temple_boss_path = self.__add_region("Sun temple before boss area",
self.sun_temple_boss_path = self.__add_region("Sun Temple before boss area",
AquariaLocations.locations_sun_temple_boss_path)
self.sun_temple_boss = self.__add_region("Sun temple boss area",
self.sun_temple_boss = self.__add_region("Sun Temple boss area",
AquariaLocations.locations_sun_temple_boss)
def __create_abyss(self) -> None:
@@ -388,9 +388,9 @@ class AquariaRegions:
AquariaLocations.locations_abyss_l)
self.abyss_lb = self.__add_region("Abyss left bottom area", AquariaLocations.locations_abyss_lb)
self.abyss_r = self.__add_region("Abyss right area", AquariaLocations.locations_abyss_r)
self.ice_cave = self.__add_region("Ice cave", AquariaLocations.locations_ice_cave)
self.bubble_cave = self.__add_region("Bubble cave", AquariaLocations.locations_bubble_cave)
self.bubble_cave_boss = self.__add_region("Bubble cave boss area", AquariaLocations.locations_bubble_cave_boss)
self.ice_cave = self.__add_region("Ice Cave", AquariaLocations.locations_ice_cave)
self.bubble_cave = self.__add_region("Bubble Cave", AquariaLocations.locations_bubble_cave)
self.bubble_cave_boss = self.__add_region("Bubble Cave boss area", AquariaLocations.locations_bubble_cave_boss)
self.king_jellyfish_cave = self.__add_region("Abyss left area, King jellyfish cave",
AquariaLocations.locations_king_jellyfish_cave)
self.whale = self.__add_region("Inside the whale", AquariaLocations.locations_whale)
@@ -400,35 +400,35 @@ class AquariaRegions:
"""
Create the `sunken_city_*` regions
"""
self.sunken_city_l = self.__add_region("Sunken city left area",
self.sunken_city_l = self.__add_region("Sunken City left area",
AquariaLocations.locations_sunken_city_l)
self.sunken_city_l_bedroom = self.__add_region("Sunken city left area, bedroom",
self.sunken_city_l_bedroom = self.__add_region("Sunken City left area, bedroom",
AquariaLocations.locations_sunken_city_l_bedroom)
self.sunken_city_r = self.__add_region("Sunken city right area",
self.sunken_city_r = self.__add_region("Sunken City right area",
AquariaLocations.locations_sunken_city_r)
self.sunken_city_boss = self.__add_region("Sunken city boss area",
self.sunken_city_boss = self.__add_region("Sunken City boss area",
AquariaLocations.locations_sunken_city_boss)
def __create_body(self) -> None:
"""
Create the `body_*` and `final_boss* regions
"""
self.body_c = self.__add_region("The body center area",
self.body_c = self.__add_region("The Body center area",
AquariaLocations.locations_body_c)
self.body_l = self.__add_region("The body left area",
self.body_l = self.__add_region("The Body left area",
AquariaLocations.locations_body_l)
self.body_rt = self.__add_region("The body right area, top path",
self.body_rt = self.__add_region("The Body right area, top path",
AquariaLocations.locations_body_rt)
self.body_rb = self.__add_region("The body right area, bottom path",
self.body_rb = self.__add_region("The Body right area, bottom path",
AquariaLocations.locations_body_rb)
self.body_b = self.__add_region("The body bottom area",
self.body_b = self.__add_region("The Body bottom area",
AquariaLocations.locations_body_b)
self.final_boss_loby = self.__add_region("The body, before final boss", None)
self.final_boss_tube = self.__add_region("The body, final boss area turtle room",
self.final_boss_loby = self.__add_region("The Body, before final boss", None)
self.final_boss_tube = self.__add_region("The Body, final boss area turtle room",
AquariaLocations.locations_final_boss_tube)
self.final_boss = self.__add_region("The body, final boss",
self.final_boss = self.__add_region("The Body, final boss",
AquariaLocations.locations_final_boss)
self.final_boss_end = self.__add_region("The body, final boss area", None)
self.final_boss_end = self.__add_region("The Body, final boss area", None)
def __connect_one_way_regions(self, source_name: str, destination_name: str,
source_region: Region,
@@ -455,99 +455,99 @@ class AquariaRegions:
"""
Connect entrances of the different regions around `home_water`
"""
self.__connect_regions("Menu", "Verse cave right area",
self.__connect_regions("Menu", "Verse Cave right area",
self.menu, self.verse_cave_r)
self.__connect_regions("Verse cave left area", "Verse cave right area",
self.__connect_regions("Verse Cave left area", "Verse Cave right area",
self.verse_cave_l, self.verse_cave_r)
self.__connect_regions("Verse cave", "Home water", self.verse_cave_l, self.home_water)
self.__connect_regions("Verse Cave", "Home Water", self.verse_cave_l, self.home_water)
self.__connect_regions("Home Water", "Haija's home", self.home_water, self.naija_home)
self.__connect_regions("Home Water", "Song cave", self.home_water, self.song_cave)
self.__connect_regions("Home Water", "Home water, nautilus nest",
self.__connect_regions("Home Water", "Song Cave", self.home_water, self.song_cave)
self.__connect_regions("Home Water", "Home Water, nautilus nest",
self.home_water, self.home_water_nautilus,
lambda state: _has_energy_form(state, self.player) and _has_bind_song(state, self.player))
self.__connect_regions("Home Water", "Home water transturtle room",
self.__connect_regions("Home Water", "Home Water transturtle room",
self.home_water, self.home_water_transturtle)
self.__connect_regions("Home Water", "Energy temple first area",
self.__connect_regions("Home Water", "Energy Temple first area",
self.home_water, self.energy_temple_1,
lambda state: _has_bind_song(state, self.player))
self.__connect_regions("Home Water", "Energy temple_altar",
self.__connect_regions("Home Water", "Energy Temple_altar",
self.home_water, self.energy_temple_altar,
lambda state: _has_energy_form(state, self.player) and
_has_bind_song(state, self.player))
self.__connect_regions("Energy temple first area", "Energy temple second area",
self.__connect_regions("Energy Temple first area", "Energy Temple second area",
self.energy_temple_1, self.energy_temple_2,
lambda state: _has_energy_form(state, self.player))
self.__connect_regions("Energy temple first area", "Energy temple idol room",
self.__connect_regions("Energy Temple first area", "Energy Temple idol room",
self.energy_temple_1, self.energy_temple_idol,
lambda state: _has_fish_form(state, self.player))
self.__connect_regions("Energy temple idol room", "Energy temple boss area",
self.__connect_regions("Energy Temple idol room", "Energy Temple boss area",
self.energy_temple_idol, self.energy_temple_boss,
lambda state: _has_energy_form(state, self.player))
self.__connect_one_way_regions("Energy temple first area", "Energy temple boss area",
self.__connect_one_way_regions("Energy Temple first area", "Energy Temple boss area",
self.energy_temple_1, self.energy_temple_boss,
lambda state: _has_beast_form(state, self.player) and
_has_energy_form(state, self.player))
self.__connect_one_way_regions("Energy temple boss area", "Energy temple first area",
self.__connect_one_way_regions("Energy Temple boss area", "Energy Temple first area",
self.energy_temple_boss, self.energy_temple_1,
lambda state: _has_energy_form(state, self.player))
self.__connect_regions("Energy temple second area", "Energy temple third area",
self.__connect_regions("Energy Temple second area", "Energy Temple third area",
self.energy_temple_2, self.energy_temple_3,
lambda state: _has_bind_song(state, self.player) and
_has_energy_form(state, self.player))
self.__connect_regions("Energy temple boss area", "Energy temple blaster room",
self.__connect_regions("Energy Temple boss area", "Energy Temple blaster room",
self.energy_temple_boss, self.energy_temple_blaster_room,
lambda state: _has_nature_form(state, self.player) and
_has_bind_song(state, self.player) and
_has_energy_form(state, self.player))
self.__connect_regions("Energy temple first area", "Energy temple blaster room",
self.__connect_regions("Energy Temple first area", "Energy Temple blaster room",
self.energy_temple_1, self.energy_temple_blaster_room,
lambda state: _has_nature_form(state, self.player) and
_has_bind_song(state, self.player) and
_has_energy_form(state, self.player) and
_has_beast_form(state, self.player))
self.__connect_regions("Home Water", "Open water top left area",
self.__connect_regions("Home Water", "Open Water top left area",
self.home_water, self.openwater_tl)
def __connect_open_water_regions(self) -> None:
"""
Connect entrances of the different regions around open water
"""
self.__connect_regions("Open water top left area", "Open water top right area",
self.__connect_regions("Open Water top left area", "Open Water top right area",
self.openwater_tl, self.openwater_tr)
self.__connect_regions("Open water top left area", "Open water bottom left area",
self.__connect_regions("Open Water top left area", "Open Water bottom left area",
self.openwater_tl, self.openwater_bl)
self.__connect_regions("Open water top left area", "forest bottom right area",
self.__connect_regions("Open Water top left area", "forest bottom right area",
self.openwater_tl, self.forest_br)
self.__connect_regions("Open water top right area", "Open water top right area, turtle room",
self.__connect_regions("Open Water top right area", "Open Water top right area, turtle room",
self.openwater_tr, self.openwater_tr_turtle,
lambda state: _has_beast_form(state, self.player))
self.__connect_regions("Open water top right area", "Open water bottom right area",
self.__connect_regions("Open Water top right area", "Open Water bottom right area",
self.openwater_tr, self.openwater_br)
self.__connect_regions("Open water top right area", "Mithalas city",
self.__connect_regions("Open Water top right area", "Mithalas City",
self.openwater_tr, self.mithalas_city)
self.__connect_regions("Open water top right area", "Veil bottom left area",
self.__connect_regions("Open Water top right area", "Veil bottom left area",
self.openwater_tr, self.veil_bl)
self.__connect_one_way_regions("Open water top right area", "Veil bottom right",
self.__connect_one_way_regions("Open Water top right area", "Veil bottom right",
self.openwater_tr, self.veil_br,
lambda state: _has_beast_form(state, self.player))
self.__connect_one_way_regions("Veil bottom right", "Open water top right area",
self.__connect_one_way_regions("Veil bottom right", "Open Water top right area",
self.veil_br, self.openwater_tr,
lambda state: _has_beast_form(state, self.player))
self.__connect_regions("Open water bottom left area", "Open water bottom right area",
self.__connect_regions("Open Water bottom left area", "Open Water bottom right area",
self.openwater_bl, self.openwater_br)
self.__connect_regions("Open water bottom left area", "Skeleton path",
self.__connect_regions("Open Water bottom left area", "Skeleton path",
self.openwater_bl, self.skeleton_path)
self.__connect_regions("Abyss left area", "Open water bottom left area",
self.__connect_regions("Abyss left area", "Open Water bottom left area",
self.abyss_l, self.openwater_bl)
self.__connect_regions("Skeleton path", "skeleton_path_sc",
self.skeleton_path, self.skeleton_path_sc,
lambda state: _has_spirit_form(state, self.player))
self.__connect_regions("Abyss right area", "Open water bottom right area",
self.__connect_regions("Abyss right area", "Open Water bottom right area",
self.abyss_r, self.openwater_br)
self.__connect_one_way_regions("Open water bottom right area", "Arnassi",
self.__connect_one_way_regions("Open Water bottom right area", "Arnassi",
self.openwater_br, self.arnassi,
lambda state: _has_beast_form(state, self.player))
self.__connect_one_way_regions("Arnassi", "Open water bottom right area",
self.__connect_one_way_regions("Arnassi", "Open Water bottom right area",
self.arnassi, self.openwater_br)
self.__connect_regions("Arnassi", "Arnassi path",
self.arnassi, self.arnassi_path)
@@ -562,23 +562,23 @@ class AquariaRegions:
"""
Connect entrances of the different regions around Mithalas
"""
self.__connect_one_way_regions("Mithalas city", "Mithalas city top path",
self.__connect_one_way_regions("Mithalas City", "Mithalas City top path",
self.mithalas_city, self.mithalas_city_top_path,
lambda state: _has_beast_form(state, self.player))
self.__connect_one_way_regions("Mithalas city_top_path", "Mithalas city",
self.__connect_one_way_regions("Mithalas City_top_path", "Mithalas City",
self.mithalas_city_top_path, self.mithalas_city)
self.__connect_regions("Mithalas city", "Mithalas city home with fishpass",
self.__connect_regions("Mithalas City", "Mithalas City home with fishpass",
self.mithalas_city, self.mithalas_city_fishpass,
lambda state: _has_fish_form(state, self.player))
self.__connect_regions("Mithalas city", "Mithalas castle",
self.__connect_regions("Mithalas City", "Mithalas castle",
self.mithalas_city, self.cathedral_l,
lambda state: _has_fish_form(state, self.player))
self.__connect_one_way_regions("Mithalas city top path", "Mithalas castle, flower tube",
self.__connect_one_way_regions("Mithalas City top path", "Mithalas castle, flower tube",
self.mithalas_city_top_path,
self.cathedral_l_tube,
lambda state: _has_nature_form(state, self.player) and
_has_energy_form(state, self.player))
self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas city top path",
self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas City top path",
self.cathedral_l_tube,
self.mithalas_city_top_path,
lambda state: _has_beast_form(state, self.player) and
@@ -690,22 +690,22 @@ class AquariaRegions:
self.veil_tl, self.veil_tr_r)
self.__connect_regions("Veil top left area", "Turtle cave",
self.veil_tl, self.turtle_cave)
self.__connect_regions("Turtle cave", "Turtle cave bubble cliff",
self.__connect_regions("Turtle cave", "Turtle cave Bubble Cliff",
self.turtle_cave, self.turtle_cave_bubble,
lambda state: _has_beast_form(state, self.player))
self.__connect_regions("Veil right of sun temple", "Sun temple right area",
self.__connect_regions("Veil right of sun temple", "Sun Temple right area",
self.veil_tr_r, self.sun_temple_r)
self.__connect_regions("Sun temple right area", "Sun temple left area",
self.__connect_regions("Sun Temple right area", "Sun Temple left area",
self.sun_temple_r, self.sun_temple_l,
lambda state: _has_bind_song(state, self.player))
self.__connect_regions("Sun temple left area", "Veil left of sun temple",
self.__connect_regions("Sun Temple left area", "Veil left of sun temple",
self.sun_temple_l, self.veil_tr_l)
self.__connect_regions("Sun temple left area", "Sun temple before boss area",
self.__connect_regions("Sun Temple left area", "Sun Temple before boss area",
self.sun_temple_l, self.sun_temple_boss_path)
self.__connect_regions("Sun temple before boss area", "Sun temple boss area",
self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area",
self.sun_temple_boss_path, self.sun_temple_boss,
lambda state: _has_energy_form(state, self.player))
self.__connect_one_way_regions("Sun temple boss area", "Veil left of sun temple",
self.__connect_one_way_regions("Sun Temple boss area", "Veil left of sun temple",
self.sun_temple_boss, self.veil_tr_l)
self.__connect_regions("Veil left of sun temple", "Octo cave top path",
self.veil_tr_l, self.octo_cave_t,
@@ -724,7 +724,7 @@ class AquariaRegions:
self.__connect_regions("Abyss left area", "Abyss bottom of left area",
self.abyss_l, self.abyss_lb,
lambda state: _has_nature_form(state, self.player))
self.__connect_regions("Abyss left bottom area", "Sunken city right area",
self.__connect_regions("Abyss left bottom area", "Sunken City right area",
self.abyss_lb, self.sunken_city_r,
lambda state: _has_li(state, self.player))
self.__connect_one_way_regions("Abyss left bottom area", "Body center area",
@@ -748,13 +748,13 @@ class AquariaRegions:
_has_sun_form(state, self.player) and
_has_bind_song(state, self.player) and
_has_energy_form(state, self.player))
self.__connect_regions("Abyss right area", "Ice cave",
self.__connect_regions("Abyss right area", "Ice Cave",
self.abyss_r, self.ice_cave,
lambda state: _has_spirit_form(state, self.player))
self.__connect_regions("Abyss right area", "Bubble cave",
self.__connect_regions("Abyss right area", "Bubble Cave",
self.ice_cave, self.bubble_cave,
lambda state: _has_beast_form(state, self.player))
self.__connect_regions("Bubble cave boss area", "Bubble cave",
self.__connect_regions("Bubble Cave boss area", "Bubble Cave",
self.bubble_cave, self.bubble_cave_boss,
lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player)
)
@@ -763,12 +763,12 @@ class AquariaRegions:
"""
Connect entrances of the different regions around The Sunken City
"""
self.__connect_regions("Sunken city right area", "Sunken city left area",
self.__connect_regions("Sunken City right area", "Sunken City left area",
self.sunken_city_r, self.sunken_city_l)
self.__connect_regions("Sunken city left area", "Sunken city bedroom",
self.__connect_regions("Sunken City left area", "Sunken City bedroom",
self.sunken_city_l, self.sunken_city_l_bedroom,
lambda state: _has_spirit_form(state, self.player))
self.__connect_regions("Sunken city left area", "Sunken city boss area",
self.__connect_regions("Sunken City left area", "Sunken City boss area",
self.sunken_city_l, self.sunken_city_boss,
lambda state: _has_beast_form(state, self.player) and
_has_energy_form(state, self.player) and
@@ -776,7 +776,7 @@ class AquariaRegions:
def __connect_body_regions(self) -> None:
"""
Connect entrances of the different regions around The body
Connect entrances of the different regions around The Body
"""
self.__connect_regions("Body center area", "Body left area",
self.body_c, self.body_l)
@@ -787,13 +787,13 @@ class AquariaRegions:
self.__connect_regions("Body center area", "Body bottom area",
self.body_c, self.body_b,
lambda state: _has_dual_form(state, self.player))
self.__connect_regions("Body bottom area", "Final boss area",
self.__connect_regions("Body bottom area", "Final Boss area",
self.body_b, self.final_boss_loby,
lambda state: _has_dual_form(state, self.player))
self.__connect_regions("Before Final boss", "Final boss tube",
self.__connect_regions("Before Final Boss", "Final Boss tube",
self.final_boss_loby, self.final_boss_tube,
lambda state: _has_nature_form(state, self.player))
self.__connect_one_way_regions("Before Final boss", "Final boss",
self.__connect_one_way_regions("Before Final Boss", "Final Boss",
self.final_boss_loby, self.final_boss,
lambda state: _has_energy_form(state, self.player) and
_has_dual_form(state, self.player) and
@@ -814,7 +814,7 @@ class AquariaRegions:
def __connect_arnassi_path_transturtle(self, item_source: str, item_target: str, region_source: Region,
region_target: Region) -> None:
"""Connect the Arnassi ruins transturtle to another one"""
"""Connect the Arnassi Ruins transturtle to another one"""
self.__connect_one_way_regions(item_source, item_target, region_source, region_target,
lambda state: state.has(item_target, self.player) and
_has_fish_form(state, self.player))
@@ -825,25 +825,25 @@ class AquariaRegions:
self.__connect_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l)
self.__connect_transturtle(item, "Transturtle Open Water top right", region, self.openwater_tr_turtle)
self.__connect_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl)
self.__connect_transturtle(item, "Transturtle Home water", region, self.home_water_transturtle)
self.__connect_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle)
self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r)
self.__connect_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube)
self.__connect_transturtle(item, "Transturtle Simon says", region, self.simon)
self.__connect_transturtle(item, "Transturtle Arnassi ruins", region, self.arnassi_path,
lambda state: state.has("Transturtle Arnassi ruins", self.player) and
self.__connect_transturtle(item, "Transturtle Simon Says", region, self.simon)
self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_path,
lambda state: state.has("Transturtle Arnassi Ruins", self.player) and
_has_fish_form(state, self.player))
def _connect_arnassi_path_transturtle_to_other(self, item: str, region: Region) -> None:
"""Connect the Arnassi ruins transturtle to all others"""
"""Connect the Arnassi Ruins transturtle to all others"""
self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top left", region, self.veil_tl)
self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l)
self.__connect_arnassi_path_transturtle(item, "Transturtle Open Water top right", region,
self.openwater_tr_turtle)
self.__connect_arnassi_path_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl)
self.__connect_arnassi_path_transturtle(item, "Transturtle Home water", region, self.home_water_transturtle)
self.__connect_arnassi_path_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle)
self.__connect_arnassi_path_transturtle(item, "Transturtle Abyss right", region, self.abyss_r)
self.__connect_arnassi_path_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube)
self.__connect_arnassi_path_transturtle(item, "Transturtle Simon says", region, self.simon)
self.__connect_arnassi_path_transturtle(item, "Transturtle Simon Says", region, self.simon)
def __connect_transturtles(self) -> None:
"""Connect every transturtle with others"""
@@ -851,11 +851,11 @@ class AquariaRegions:
self._connect_transturtle_to_other("Transturtle Veil top right", self.veil_tr_l)
self._connect_transturtle_to_other("Transturtle Open Water top right", self.openwater_tr_turtle)
self._connect_transturtle_to_other("Transturtle Forest bottom left", self.forest_bl)
self._connect_transturtle_to_other("Transturtle Home water", self.home_water_transturtle)
self._connect_transturtle_to_other("Transturtle Home Water", self.home_water_transturtle)
self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r)
self._connect_transturtle_to_other("Transturtle Final Boss", self.final_boss_tube)
self._connect_transturtle_to_other("Transturtle Simon says", self.simon)
self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi ruins", self.arnassi_path)
self._connect_transturtle_to_other("Transturtle Simon Says", self.simon)
self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_path)
def connect_regions(self) -> None:
"""
@@ -907,7 +907,7 @@ class AquariaRegions:
def __add_event_mini_bosses(self) -> None:
"""
Add every mini bosses (excluding Energy statue and Simon says)
Add every mini bosses (excluding Energy Statue and Simon Says)
events to the `world`
"""
self.__add_event_location(self.home_water_nautilus,
@@ -967,100 +967,100 @@ class AquariaRegions:
def __adjusting_urns_rules(self) -> None:
"""Since Urns need to be broken, add a damaging item to rules"""
add_rule(self.multiworld.get_location("Open water top right area, first urn in the Mithalas exit", self.player),
add_rule(self.multiworld.get_location("Open Water top right area, first urn in the Mithalas exit", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Open water top right area, second urn in the Mithalas exit", self.player),
add_rule(self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Open water top right area, third urn in the Mithalas exit", self.player),
add_rule(self.multiworld.get_location("Open Water top right area, third urn in the Mithalas exit", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, first urn in one of the homes", self.player),
add_rule(self.multiworld.get_location("Mithalas City, first urn in one of the homes", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, second urn in one of the homes", self.player),
add_rule(self.multiworld.get_location("Mithalas City, second urn in one of the homes", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, first urn in the city reserve", self.player),
add_rule(self.multiworld.get_location("Mithalas City, first urn in the city reserve", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, second urn in the city reserve", self.player),
add_rule(self.multiworld.get_location("Mithalas City, second urn in the city reserve", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, third urn in the city reserve", self.player),
add_rule(self.multiworld.get_location("Mithalas City, third urn in the city reserve", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, urn in the cathedral flower tube entrance", self.player),
add_rule(self.multiworld.get_location("Mithalas City, urn in the Cathedral flower tube entrance", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, urn in the bedroom", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bedroom", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, first urn of the single lamp path", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, first urn of the single lamp path", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, second urn of the single lamp path", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, second urn of the single lamp path", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, urn in the bottom room", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, urn in the bottom room", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, first urn on the entrance path", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, first urn on the entrance path", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city castle, second urn on the entrance path", self.player),
add_rule(self.multiworld.get_location("Mithalas City Castle, second urn on the entrance path", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Mithalas city, urn inside a home fish pass", self.player),
add_rule(self.multiworld.get_location("Mithalas City, urn inside a home fish pass", self.player),
lambda state: _has_damaging_item(state, self.player))
def __adjusting_crates_rules(self) -> None:
"""Since Crate need to be broken, add a damaging item to rules"""
add_rule(self.multiworld.get_location("Sunken city right area, crate close to the save cristal", self.player),
add_rule(self.multiworld.get_location("Sunken City right area, crate close to the save crystal", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Sunken city right area, crate in the left bottom room", self.player),
add_rule(self.multiworld.get_location("Sunken City right area, crate in the left bottom room", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Sunken city left area, crate in the little pipe room", self.player),
add_rule(self.multiworld.get_location("Sunken City left area, crate in the little pipe room", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Sunken city left area, crate close to the save cristal", self.player),
add_rule(self.multiworld.get_location("Sunken City left area, crate close to the save crystal", self.player),
lambda state: _has_damaging_item(state, self.player))
add_rule(self.multiworld.get_location("Sunken city left area, crate before the bedroom", self.player),
add_rule(self.multiworld.get_location("Sunken City left area, crate before the bedroom", self.player),
lambda state: _has_damaging_item(state, self.player))
def __adjusting_soup_rules(self) -> None:
"""
Modify rules for location that need soup
"""
add_rule(self.multiworld.get_location("Turtle cave, Urchin costume", self.player),
add_rule(self.multiworld.get_location("Turtle cave, Urchin Costume", self.player),
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player),
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("The veil top right area, bulb in the top of the water fall", self.player),
add_rule(self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall", self.player),
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
def __adjusting_under_rock_location(self) -> None:
"""
Modify rules implying bind song needed for bulb under rocks
"""
add_rule(self.multiworld.get_location("Home water, bulb under the rock in the left path from the verse cave",
add_rule(self.multiworld.get_location("Home Water, bulb under the rock in the left path from the Verse Cave",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Verse cave left area, bulb under the rock at the end of the path",
add_rule(self.multiworld.get_location("Verse Cave left area, bulb under the rock at the end of the path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Naija's home, bulb under the rock at the right of the main path",
add_rule(self.multiworld.get_location("Naija's Home, bulb under the rock at the right of the main path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Song cave, bulb under the rock in the path to the singing statues",
add_rule(self.multiworld.get_location("Song Cave, bulb under the rock in the path to the singing statues",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Song cave, bulb under the rock close to the song door",
add_rule(self.multiworld.get_location("Song Cave, bulb under the rock close to the song door",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Energy temple second area, bulb under the rock",
add_rule(self.multiworld.get_location("Energy Temple second area, bulb under the rock",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Open water top left area, bulb under the rock in the right path",
add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the right path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Open water top left area, bulb under the rock in the left path",
add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the left path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Kelp Forest top right area, bulb under the rock in the right path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("The veil top left area, bulb under the rock in the top right path",
add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Abyss right area, bulb in the middle path",
self.player), lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("The veil top left area, bulb under the rock in the top right path",
add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path",
self.player), lambda state: _has_bind_song(state, self.player))
def __adjusting_light_in_dark_place_rules(self) -> None:
add_rule(self.multiworld.get_location("Kelp forest top right area, Black pearl", self.player),
add_rule(self.multiworld.get_location("Kelp Forest top right area, Black Pearl", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_location("Kelp forest bottom right area, Odd Container", self.player),
add_rule(self.multiworld.get_location("Kelp Forest bottom right area, Odd Container", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Veil top left to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
@@ -1070,103 +1070,103 @@ class AquariaRegions:
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Forest bottom left to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Home water to Transturtle Abyss right", self.player),
add_rule(self.multiworld.get_entrance("Transturtle Home Water to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Final Boss to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Simon says to Transturtle Abyss right", self.player),
add_rule(self.multiworld.get_entrance("Transturtle Simon Says to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Transturtle Arnassi ruins to Transturtle Abyss right", self.player),
add_rule(self.multiworld.get_entrance("Transturtle Arnassi Ruins to Transturtle Abyss right", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Body center area to Abyss left bottom area", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Octo cave top path", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Open water bottom right area to Abyss right area", self.player),
add_rule(self.multiworld.get_entrance("Open Water bottom right area to Abyss right area", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Open water bottom left area to Abyss left area", self.player),
add_rule(self.multiworld.get_entrance("Open Water bottom left area to Abyss left area", self.player),
lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Sun temple left area to Sun temple right area", self.player),
add_rule(self.multiworld.get_entrance("Sun Temple left area to Sun Temple right area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
add_rule(self.multiworld.get_entrance("Sun temple right area to Sun temple left area", self.player),
add_rule(self.multiworld.get_entrance("Sun Temple right area to Sun Temple left area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun temple left area", self.player),
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun Temple left area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
def __adjusting_manual_rules(self) -> None:
add_rule(self.multiworld.get_location("Mithalas cathedral, Mithalan Dress", self.player),
add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player),
lambda state: _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("Open water bottom left area, bulb inside the lowest fish pass", self.player),
add_rule(self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player),
lambda state: _has_fish_form(state, self.player))
add_rule(self.multiworld.get_location("Kelp forest bottom left area, Walker baby", self.player),
add_rule(self.multiworld.get_location("Kelp Forest bottom left area, Walker baby", self.player),
lambda state: _has_spirit_form(state, self.player))
add_rule(self.multiworld.get_location("The veil top left area, bulb hidden behind the blocking rock", self.player),
add_rule(self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Turtle cave, Turtle Egg", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Abyss left area, bulb in the bottom fish pass", self.player),
lambda state: _has_fish_form(state, self.player))
add_rule(self.multiworld.get_location("Song cave, Anemone seed", self.player),
add_rule(self.multiworld.get_location("Song Cave, Anemone Seed", self.player),
lambda state: _has_nature_form(state, self.player))
add_rule(self.multiworld.get_location("Song cave, Verse egg", self.player),
add_rule(self.multiworld.get_location("Song Cave, Verse Egg", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Verse cave right area, Big Seed", self.player),
add_rule(self.multiworld.get_location("Verse Cave right area, Big Seed", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Arnassi ruins, Song plant spore on the top of the ruins", self.player),
add_rule(self.multiworld.get_location("Arnassi Ruins, Song Plant Spore", self.player),
lambda state: _has_beast_form(state, self.player))
add_rule(self.multiworld.get_location("Energy temple first area, bulb in the bottom room blocked by a rock",
add_rule(self.multiworld.get_location("Energy Temple first area, bulb in the bottom room blocked by a rock",
self.player), lambda state: _has_energy_form(state, self.player))
add_rule(self.multiworld.get_location("Home water, bulb in the bottom left room", self.player),
add_rule(self.multiworld.get_location("Home Water, bulb in the bottom left room", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Home water, bulb in the path below Nautilus Prime", self.player),
add_rule(self.multiworld.get_location("Home Water, bulb in the path below Nautilus Prime", self.player),
lambda state: _has_bind_song(state, self.player))
add_rule(self.multiworld.get_location("Naija's home, bulb after the energy door", self.player),
add_rule(self.multiworld.get_location("Naija's Home, bulb after the energy door", self.player),
lambda state: _has_energy_form(state, self.player))
add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", self.player),
lambda state: _has_spirit_form(state, self.player) and
_has_sun_form(state, self.player))
add_rule(self.multiworld.get_location("Arnassi ruins, Arnassi Armor", self.player),
add_rule(self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", self.player),
lambda state: _has_fish_form(state, self.player) and
_has_spirit_form(state, self.player))
def __no_progression_hard_or_hidden_location(self) -> None:
self.multiworld.get_location("Energy temple boss area, Fallen god tooth",
self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Cathedral boss area, beating Mithalan God",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Kelp forest boss area, beating Drunian God",
self.multiworld.get_location("Kelp Forest boss area, beating Drunian God",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Sun temple boss area, beating Sun God",
self.multiworld.get_location("Sun Temple boss area, beating Sun God",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Sunken city, bulb on the top of the boss area (boiler room)",
self.multiworld.get_location("Sunken City, bulb on top of the boss area",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Home water, Nautilus Egg",
self.multiworld.get_location("Home Water, Nautilus Egg",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Energy temple blaster room, Blaster egg",
self.multiworld.get_location("Energy Temple blaster room, Blaster Egg",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Mithalas castle, beating the priests",
self.multiworld.get_location("Mithalas City Castle, beating the Priests",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Mermog cave, Piranha Egg",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Octopus cave, Dumbo Egg",
self.multiworld.get_location("Octopus Cave, Dumbo Egg",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("King Jellyfish cave, bulb in the right path from King Jelly",
self.multiworld.get_location("King Jellyfish Cave, bulb in the right path from King Jelly",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("King Jellyfish cave, Jellyfish Costume",
self.multiworld.get_location("King Jellyfish Cave, Jellyfish Costume",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Final boss area, bulb in the boss third form room",
self.multiworld.get_location("Final Boss area, bulb in the boss third form room",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Sun Worm path, first cliff bulb",
@@ -1175,34 +1175,34 @@ class AquariaRegions:
self.multiworld.get_location("Sun Worm path, second cliff bulb",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("The veil top right area, bulb in the top of the water fall",
self.multiworld.get_location("The Veil top right area, bulb in the top of the waterfall",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Bubble cave, bulb in the left cave wall",
self.multiworld.get_location("Bubble Cave, bulb in the left cave wall",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Bubble cave, bulb in the right cave wall (behind the ice cristal)",
self.multiworld.get_location("Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Bubble cave, Verse egg",
self.multiworld.get_location("Bubble Cave, Verse Egg",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Kelp Forest bottom left area, bulb close to the spirit crystals",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Kelp forest bottom left area, Walker baby",
self.multiworld.get_location("Kelp Forest bottom left area, Walker baby",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Sun temple, Sun key",
self.multiworld.get_location("Sun Temple, Sun Key",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("The body bottom area, Mutant Costume",
self.multiworld.get_location("The Body bottom area, Mutant Costume",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Sun temple, bulb in the hidden room of the right part",
self.multiworld.get_location("Sun Temple, bulb in the hidden room of the right part",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
self.multiworld.get_location("Arnassi ruins, Arnassi Armor",
self.multiworld.get_location("Arnassi Ruins, Arnassi Armor",
self.player).item_rule =\
lambda item: item.classification != ItemClassification.progression
@@ -1220,19 +1220,19 @@ class AquariaRegions:
self.__adjusting_under_rock_location()
if options.mini_bosses_to_beat.value > 0:
add_rule(self.multiworld.get_entrance("Before Final boss to Final boss", self.player),
add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player),
lambda state: _has_mini_bosses(state, self.player))
if options.big_bosses_to_beat.value > 0:
add_rule(self.multiworld.get_entrance("Before Final boss to Final boss", self.player),
add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player),
lambda state: _has_big_bosses(state, self.player))
if options.objective.value == 1:
add_rule(self.multiworld.get_entrance("Before Final boss to Final boss", self.player),
add_rule(self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player),
lambda state: _has_secrets(state, self.player))
if options.unconfine_home_water.value in [0, 1]:
add_rule(self.multiworld.get_entrance("Home Water to Home water transturtle room", self.player),
add_rule(self.multiworld.get_entrance("Home Water to Home Water transturtle room", self.player),
lambda state: _has_bind_song(state, self.player))
if options.unconfine_home_water.value in [0, 2]:
add_rule(self.multiworld.get_entrance("Home Water to Open water top left area", self.player),
add_rule(self.multiworld.get_entrance("Home Water to Open Water top left area", self.player),
lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player))
if options.early_energy_form:
self.multiworld.early_items[self.player]["Energy form"] = 1

View File

@@ -71,9 +71,9 @@ class AquariaWorld(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"}
"Li and Li song", "Baby Nautilus", "Baby Piranha",
"Baby Blaster"},
"Light": {"Sun form", "Baby Dumbo"}
}
"""Grouping item make it easier to find them"""
@@ -152,20 +152,20 @@ class AquariaWorld(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)
self.__pre_fill_item("Transturtle Final Boss", "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("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",
precollected)
self.__pre_fill_item("Transturtle Forest bottom left", "Kelp Forest bottom left area, Transturtle",
precollected)
self.__pre_fill_item("Transturtle Home water", "Home water, 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)
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("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
for name, data in item_table.items():
if name in precollected:
precollected.remove(name)

View File

@@ -15,14 +15,14 @@ The locations in the randomizer are:
- All Mithalas Urns
- All Sunken City crates
- Collectible treasure locations (including pet eggs and costumes)
- Beating Simon says
- Beating Simon Says
- Li cave
- Every Transportation Turtle (also called transturtle)
- Locations where you get songs:
* Erulian spirit cristal
* Erulian spirit crystal
* Energy status mini-boss
* Beating Mithalan God boss
* Fish cave puzzle
* Fish Cave puzzle
* Beating Drunian God boss
* Beating Sun God boss
* Breaking Li cage in the body
@@ -61,4 +61,4 @@ what has been collected and who will receive it.
## When the player receives an item, what happens?
When you receive an item, a message will pop up to inform you where you received
the item from and which one it was.
the item from and which one it was.

View File

@@ -10,148 +10,148 @@ from test.bases import WorldTestBase
# 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 cristal",
"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 cristal",
"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",
"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 on the top of the ruins",
"Arnassi ruins, Arnassi Armor",
"Arnassi Ruins, Arnassi statue",
"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 cathedral 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 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, one of the urns in the top right room",
"Mithalas cathedral, Mithalan Dress",
"Mithalas cathedral right area, 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 cristal",
"Cathedral underground, bulb in the bottom right path",
"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 Cathedral 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 right area, 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",
"Cathedral 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, Jelly Egg",
"Kelp Forest top left area, bulb close to the Verse egg",
"Kelp forest top left area, Verse 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, 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, Walker baby",
"Kelp Forest bottom left area, Transturtle",
"Kelp forest bottom right area, Odd Container",
"Kelp forest boss area, beating Drunian God",
"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 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",
"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 the Li 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",
"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 at the bottom right of the bottom path",
"The veil top right area, bulb in the top of the water fall",
"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 cristal)",
"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 left part",
"Sun temple, bulb in the hidden room of the right part",
"Sun temple, Sun key",
"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 in 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 left 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",
"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 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",
@@ -159,40 +159,40 @@ after_home_water_locations = [
"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 bulbs in the top exit room",
"Ice cave, Second bulbs in the top exit room",
"Ice cave, third bulbs 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 cristal",
"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 cristal",
"Sunken city left area, crate before the bedroom",
"Sunken city left area, Girl Costume",
"Sunken city, bulb on the top of the boss area (boiler room)",
"The body center area, breaking li cage",
"The body main 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 bulbs in the turtle room",
"Final boss area, third bulbs in the turtle room",
"Final boss area, Transturtle",
"Final boss area, bulb in the boss third form room",
"Kelp forest, beating Simon says",
"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 main 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",

View File

@@ -13,33 +13,33 @@ class BeastFormAccessTest(AquariaTestBase):
def test_beast_form_location(self) -> None:
"""Test locations that require beast form"""
locations = [
"Mithalas castle, beating the priests",
"Arnassi ruins, Crab armor",
"Arnassi ruins, Song plant spore on the top of the ruins",
"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 cathedral flower tube entrance",
"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 Cathedral flower tube entrance",
"Mermog cave, Piranha Egg",
"Mithalas cathedral, Mithalan Dress",
"Turtle cave, bulb in bubble cliff",
"Turtle cave, Urchin costume",
"Mithalas Cathedral, Mithalan Dress",
"Turtle cave, bulb in Bubble Cliff",
"Turtle cave, Urchin Costume",
"Sun Worm path, first cliff bulb",
"Sun Worm path, second cliff bulb",
"The veil top right area, bulb in the top of the water fall",
"Bubble cave, bulb in the left cave wall",
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
"Bubble cave, Verse egg",
"Sunken city, bulb on the top of the boss area (boiler room)",
"Octopus cave, Dumbo Egg",
"The Veil top right area, bulb in the top of the waterfall",
"Bubble Cave, bulb in the left cave wall",
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
"Bubble Cave, Verse Egg",
"Sunken City, bulb on top of the boss area",
"Octopus Cave, Dumbo Egg",
"Beating the Golem",
"Beating Mergog",
"Beating Crabbius Maximus",
"Beating Octopus Prime",
"Beating Mantis Shrimp Prime",
"King Jellyfish cave, Jellyfish Costume",
"King Jellyfish cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"Beating King Jellyfish God Prime",
"Beating Mithalan priests",
"Sunken City cleared"

View File

@@ -17,19 +17,19 @@ class BindSongAccessTest(AquariaTestBase):
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",
"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",
*after_home_water_locations
]
items = [["Bind song"]]

View File

@@ -18,24 +18,24 @@ 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",
"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",
*after_home_water_locations
]
items = [["Bind song"]]

View File

@@ -16,5 +16,5 @@ class ConfinedHomeWaterAccessTest(AquariaTestBase):
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 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")

View File

@@ -16,10 +16,10 @@ class LiAccessTest(AquariaTestBase):
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",
"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"
]
items = [["Dual form"]]

View File

@@ -17,41 +17,41 @@ class EnergyFormAccessTest(AquariaTestBase):
def test_energy_form_location(self) -> None:
"""Test locations that require Energy form"""
locations = [
"Home water, Nautilus Egg",
"Naija's home, bulb after the energy door",
"Energy temple first area, bulb in the bottom room blocked by a rock",
"Energy temple second area, bulb under the rock",
"Energy temple bottom entrance, Krotite armor",
"Energy temple third area, bulb in the bottom path",
"Energy temple boss area, Fallen god tooth",
"Energy temple blaster room, Blaster egg",
"Mithalas castle, beating the priests",
"Mithalas cathedral, first urn in the top right room",
"Mithalas cathedral, second urn in the top right room",
"Mithalas cathedral, third urn in the top right room",
"Mithalas cathedral, urn in the flesh room with fleas",
"Mithalas cathedral, first urn in the bottom right path",
"Mithalas cathedral, second urn in the bottom right path",
"Mithalas cathedral, urn behind the flesh vein",
"Mithalas cathedral, urn in the top left eyes boss room",
"Mithalas cathedral, first urn in the path behind the flesh vein",
"Mithalas cathedral, second urn in the path behind the flesh vein",
"Mithalas cathedral, third urn in the path behind the flesh vein",
"Mithalas cathedral, one of the urns in the top right room",
"Mithalas cathedral, Mithalan Dress",
"Mithalas cathedral right area, urn below the left entrance",
"Home Water, Nautilus Egg",
"Naija's Home, bulb after the energy door",
"Energy Temple first area, bulb in the bottom room blocked by a rock",
"Energy Temple second area, bulb under the rock",
"Energy Temple bottom entrance, Krotite Armor",
"Energy Temple third area, bulb in the bottom path",
"Energy Temple boss area, Fallen God Tooth",
"Energy Temple blaster room, Blaster Egg",
"Mithalas City Castle, beating the Priests",
"Mithalas Cathedral, first urn in the top right room",
"Mithalas Cathedral, second urn in the top right room",
"Mithalas Cathedral, third urn in the top right room",
"Mithalas Cathedral, urn in the flesh room with fleas",
"Mithalas Cathedral, first urn in the bottom right path",
"Mithalas Cathedral, second urn in the bottom right path",
"Mithalas Cathedral, urn behind the flesh vein",
"Mithalas Cathedral, urn in the top left eyes boss room",
"Mithalas Cathedral, first urn in the path behind the flesh vein",
"Mithalas Cathedral, second urn in the path behind the flesh vein",
"Mithalas Cathedral, third urn in the path behind the flesh vein",
"Mithalas Cathedral, fourth urn in the top right room",
"Mithalas Cathedral, Mithalan Dress",
"Mithalas Cathedral right area, urn below the left entrance",
"Cathedral boss area, beating Mithalan God",
"Kelp Forest top left area, bulb close to the Verse egg",
"Kelp forest top left area, Verse egg",
"Kelp forest boss area, beating Drunian God",
"Kelp Forest top left area, bulb close to the Verse Egg",
"Kelp Forest top left area, Verse Egg",
"Kelp Forest boss area, beating Drunian God",
"Mermog cave, Piranha Egg",
"Octopus cave, Dumbo Egg",
"Sun temple boss area, beating Sun God",
"Arnassi ruins, Crab armor",
"King Jellyfish cave, bulb in the right path from King Jelly",
"King Jellyfish cave, Jellyfish Costume",
"Sunken city, bulb on the top of the boss area (boiler room)",
"Final boss area, bulb in the boss third form room",
"Octopus Cave, Dumbo Egg",
"Sun Temple boss area, beating Sun God",
"Arnassi Ruins, Crab Armor",
"King Jellyfish Cave, bulb in the right path from King Jelly",
"King Jellyfish Cave, Jellyfish Costume",
"Sunken City, bulb on top of the boss area",
"Final Boss area, bulb in the boss third form room",
"Beating Fallen God",
"Beating Mithalan God",
"Beating Drunian God",
@@ -69,4 +69,4 @@ class EnergyFormAccessTest(AquariaTestBase):
"Objective complete",
]
items = [["Energy form"]]
self.assertAccessDependency(locations, items)
self.assertAccessDependency(locations, items)

View File

@@ -16,22 +16,22 @@ class FishFormAccessTest(AquariaTestBase):
def test_fish_form_location(self) -> None:
"""Test locations that require fish form"""
locations = [
"The veil top left area, bulb inside the fish pass",
"Mithalas city, Doll",
"Mithalas city, urn inside a home fish pass",
"The Veil top left area, bulb inside the fish pass",
"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",
"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",
"Octopus Cave, Dumbo Egg",
"Octopus Cave, bulb in the path below the Octopus Cave path",
"Beating Octopus Prime",
"Abyss left area, bulb in the bottom fish pass",
"Arnassi ruins, Arnassi Armor"
"Arnassi Ruins, Arnassi Armor"
]
items = [["Fish form"]]
self.assertAccessDependency(locations, items)

View File

@@ -16,27 +16,27 @@ class LiAccessTest(AquariaTestBase):
def test_li_song_location(self) -> None:
"""Test locations that require Li"""
locations = [
"Sunken city right area, crate close to the save cristal",
"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 cristal",
"Sunken city left area, crate before the bedroom",
"Sunken city left area, Girl Costume",
"Sunken city, bulb on the top of the boss area (boiler room)",
"The body center area, breaking li cage",
"The body main 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",
"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 main 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"

View File

@@ -20,19 +20,19 @@ class LightAccessTest(AquariaTestBase):
# 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 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",
"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 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",
@@ -40,32 +40,32 @@ class LightAccessTest(AquariaTestBase):
"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 bulbs in the top exit room",
"Ice cave, Second bulbs in the top exit room",
"Ice cave, third bulbs 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 cristal)",
"Bubble cave, 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",
"Beating Mantis Shrimp Prime",
"King Jellyfish cave, bulb in the right path from King Jelly",
"King Jellyfish cave, Jellyfish Costume",
"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",
"The Whale, Verse Egg",
"First secret",
"Sunken city right area, crate close to the save cristal",
"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 cristal",
"Sunken city left area, crate before the bedroom",
"Sunken city left area, Girl Costume",
"Sunken city, bulb on the top of the boss area (boiler room)",
"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",
"Final Boss area, bulb in the boss third form room",
"Objective complete",
]
items = [["Sun form", "Baby dumbo", "Has sun crystal"]]
items = [["Sun form", "Baby Dumbo", "Has sun crystal"]]
self.assertAccessDependency(locations, items)

View File

@@ -16,41 +16,41 @@ class NatureFormAccessTest(AquariaTestBase):
def test_nature_form_location(self) -> None:
"""Test locations that require nature form"""
locations = [
"Song cave, Anemone seed",
"Energy temple blaster room, Blaster egg",
"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 castle, beating the priests",
"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",
"Kelp Forest sprite cave, Seed Bag",
"Beating Mithalan priests",
"Abyss left area, bulb in the bottom fish pass",
"Bubble cave, Verse egg",
"Bubble Cave, Verse Egg",
"Beating Mantis Shrimp Prime",
"Sunken city right area, crate close to the save cristal",
"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 cristal",
"Sunken city left area, crate before the bedroom",
"Sunken city left area, Girl Costume",
"Sunken city, bulb on the top of the boss area (boiler room)",
"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 cage",
"The body main 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",
"The Body center area, breaking Li's cage",
"The Body main 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"
]
items = [["Nature form"]]

View File

@@ -15,31 +15,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
}
unfillable_locations = [
"Energy temple boss area, Fallen god tooth",
"Energy Temple boss area, Fallen God Tooth",
"Cathedral boss area, beating Mithalan God",
"Kelp forest boss area, beating Drunian God",
"Sun temple boss area, beating Sun God",
"Sunken city, bulb on the top of the boss area (boiler room)",
"Home water, Nautilus Egg",
"Energy temple blaster room, Blaster egg",
"Mithalas castle, beating the priests",
"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",
"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 in the top of the water fall",
"Bubble cave, bulb in the left cave wall",
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
"Bubble cave, Verse egg",
"The Veil top right area, bulb in 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",
"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",
]
def test_unconfine_home_water_both_location_fillable(self) -> None:

View File

@@ -15,31 +15,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
}
unfillable_locations = [
"Energy temple boss area, Fallen god tooth",
"Energy Temple boss area, Fallen God Tooth",
"Cathedral boss area, beating Mithalan God",
"Kelp forest boss area, beating Drunian God",
"Sun temple boss area, beating Sun God",
"Sunken city, bulb on the top of the boss area (boiler room)",
"Home water, Nautilus Egg",
"Energy temple blaster room, Blaster egg",
"Mithalas castle, beating the priests",
"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",
"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 in the top of the water fall",
"Bubble cave, bulb in the left cave wall",
"Bubble cave, bulb in the right cave wall (behind the ice cristal)",
"Bubble cave, Verse egg",
"The Veil top right area, bulb in 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",
"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",
]
def test_unconfine_home_water_both_location_fillable(self) -> None:

View File

@@ -13,24 +13,24 @@ 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",
"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 bulbs in the top exit room",
"Ice cave, Second bulbs in the top exit room",
"Ice cave, third bulbs 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 cristal)",
"Bubble cave, Verse egg",
"Sunken city left area, Girl Costume",
"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",
"Arnassi ruins, Arnassi Armor",
"Arnassi Ruins, Arnassi Armor",
]
items = [["Spirit form"]]
self.assertAccessDependency(locations, items)

View File

@@ -14,11 +14,11 @@ class SunFormAccessTest(AquariaTestBase):
"""Test locations that require sun form"""
locations = [
"First secret",
"The whale, Verse egg",
"The Whale, Verse Egg",
"Abyss right area, bulb behind the rock in the whale room",
"Octopus cave, Dumbo Egg",
"Octopus Cave, Dumbo Egg",
"Beating Octopus Prime",
"Final boss area, bulb in the boss third form room",
"Final Boss area, bulb in the boss third form room",
"Objective complete"
]
items = [["Sun form"]]

View File

@@ -17,5 +17,5 @@ class UnconfineHomeWaterBothAccessTest(AquariaTestBase):
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 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")

View File

@@ -16,5 +16,5 @@ class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase):
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 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")

View File

@@ -17,4 +17,4 @@ class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase):
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.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area")

View File

@@ -1,44 +0,0 @@
from typing import Dict
from BaseClasses import Tutorial
from ..AutoWorld import WebWorld, World
class Bk_SudokuWebWorld(WebWorld):
options_page = "games/Sudoku/info/en"
theme = 'partyTime'
setup_en = Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing BK Sudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['Jarno']
)
setup_de = Tutorial(
tutorial_name='Setup Anleitung',
description='Eine Anleitung um BK-Sudoku zu spielen',
language='Deutsch',
file_name='setup_de.md',
link='setup/de',
authors=['Held_der_Zeit']
)
tutorials = [setup_en, setup_de]
class Bk_SudokuWorld(World):
"""
Play a little Sudoku while you're in BK mode to maybe get some useful hints
"""
game = "Sudoku"
web = Bk_SudokuWebWorld()
data_version = 1
item_name_to_id: Dict[str, int] = {}
location_name_to_id: Dict[str, int] = {}
@classmethod
def stage_assert_generate(cls, multiworld):
raise Exception("BK Sudoku cannot be used for generating worlds, the client can instead connect to any other world")

View File

@@ -1,21 +0,0 @@
# BK-Sudoku
## Was ist das für ein Spiel?
BK-Sudoku ist kein typisches Archipelago-Spiel; stattdessen ist es ein gewöhnlicher Sudoku-Client der sich zu jeder
beliebigen Multiworld verbinden kann. Einmal verbunden kannst du ein 9x9 Sudoku spielen um einen zufälligen Hinweis
für dein Spiel zu erhalten. Es ist zwar langsam, aber es gibt dir etwas zu tun, solltest du mal nicht in der Lage sein
weitere „Checks” zu erreichen.
(Wer mag kann auch einfach so Sudoku spielen. Man muss nicht mit einer Multiworld verbunden sein, um ein Sudoku zu
spielen/generieren.)
## Wie werden Hinweise freigeschalten?
Nach dem Lösen eines Sudokus wird für den verbundenen Slot ein zufällig ausgewählter Hinweis freigegeben, für einen
Gegenstand der noch nicht gefunden wurde.
## Wo ist die Seite für die Einstellungen?
Es gibt keine Seite für die Einstellungen. Dieses Spiel kann nicht in deinen YAML-Dateien benutzt werden. Stattdessen
kann sich der Client mit einem beliebigen Slot einer Multiworld verbinden. In dem Client selbst kann aber der
Schwierigkeitsgrad des Sudoku ausgewählt werden.

View File

@@ -1,13 +0,0 @@
# Bk Sudoku
## What is this game?
BK Sudoku is not a typical Archipelago game; instead, it is a generic Sudoku client that can connect to any existing multiworld. When connected, you can play Sudoku to unlock random hints for your game. While slow, it will give you something to do when you can't reach the checks in your game.
## What hints are unlocked?
After completing a Sudoku puzzle, the game will unlock 1 random hint for an unchecked location in the slot you are connected to.
## Where is the options page?
There is no options page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld.

View File

@@ -1,27 +0,0 @@
# BK-Sudoku Setup Anleitung
## Benötigte Software
- [Bk-Sudoku](https://github.com/Jarno458/sudoku)
- Windows 8 oder höher
## Generelles Konzept
Dies ist ein Client, der sich mit jedem beliebigen Slot einer Multiworld verbinden kann. Er lässt dich ein (9x9) Sudoku
spielen, um zufällige Hinweise für den verbundenen Slot freizuschalten.
Aufgrund des Fakts, dass der Sudoku-Client sich zu jedem beliebigen Slot verbinden kann, ist es daher nicht notwendig
eine YAML für dieses Spiel zu generieren, da es keinen neuen Slot zur Multiworld-Session hinzufügt.
## Installationsprozess
Gehe zu der aktuellsten (latest) Veröffentlichung der [BK-Sudoku Releases](https://github.com/Jarno458/sudoku/releases).
Downloade und extrahiere/entpacke die `Bk_Sudoku.zip`-Datei.
## Verbinden mit einer Multiworld
1. Starte `Bk_Sudoku.exe`
2. Trage den Namen des Slots ein, mit dem du dich verbinden möchtest
3. Trage die Server-URL und den Port ein
4. Drücke auf Verbinden (connect)
5. Wähle deinen Schwierigkeitsgrad
6. Versuche das Sudoku zu Lösen

View File

@@ -1,24 +0,0 @@
# BK Sudoku Setup Guide
## Required Software
- [Bk Sudoku](https://github.com/Jarno458/sudoku)
- Windows 8 or higher
## General Concept
This is a client that can connect to any multiworld slot, and lets you play Sudoku to unlock random hints for that slot's locations.
Due to the fact that the Sudoku client may connect to any slot, it is not necessary to generate a YAML for this game as it does not generate any new slots in the multiworld session.
## Installation Procedures
Go to the latest release on [BK Sudoku Releases](https://github.com/Jarno458/sudoku/releases). Download and extract the `Bk_Sudoku.zip` file.
## Joining a MultiWorld Game
1. Run Bk_Sudoku.exe
2. Enter the name of the slot you wish to connect to
3. Enter the server url & port number
4. Press connect
5. Choose difficulty
6. Try to solve the Sudoku

View File

@@ -32,7 +32,6 @@ class BlasphemousWorld(World):
game: str = "Blasphemous"
web = BlasphemousWeb()
data_version = 2
item_name_to_id = {item["name"]: (base_id + index) for index, item in enumerate(item_table)}
location_name_to_id = {loc["name"]: (base_id + index) for index, loc in enumerate(location_table)}

View File

@@ -5,17 +5,17 @@ from .Regions import Stages
def graffitiM(state: CollectionState, player: int, limit: bool, spots: int) -> bool:
return state.count_group_exclusive("graffitim", player) * 7 >= spots if limit \
return state.count_group_unique("graffitim", player) * 7 >= spots if limit \
else state.has_group("graffitim", player)
def graffitiL(state: CollectionState, player: int, limit: bool, spots: int) -> bool:
return state.count_group_exclusive("graffitil", player) * 6 >= spots if limit \
return state.count_group_unique("graffitil", player) * 6 >= spots if limit \
else state.has_group("graffitil", player)
def graffitiXL(state: CollectionState, player: int, limit: bool, spots: int) -> bool:
return state.count_group_exclusive("graffitixl", player) * 4 >= spots if limit \
return state.count_group_unique("graffitixl", player) * 4 >= spots if limit \
else state.has_group("graffitixl", player)
@@ -469,7 +469,7 @@ def spots_s_glitchless(state: CollectionState, player: int, limit: bool, access_
break
if limit:
sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5)
sprayable: int = 5 + (state.count_group_unique("characters", player) * 5)
if total <= sprayable:
return total
else:
@@ -492,7 +492,7 @@ def spots_s_glitched(state: CollectionState, player: int, limit: bool, access_ca
break
if limit:
sprayable: int = 5 + (state.count_group_exclusive("characters", player) * 5)
sprayable: int = 5 + (state.count_group_unique("characters", player) * 5)
if total <= sprayable:
return total
else:
@@ -537,7 +537,7 @@ def spots_m_glitchless(state: CollectionState, player: int, limit: bool, access_
break
if limit:
sprayable: int = state.count_group_exclusive("graffitim", player) * 7
sprayable: int = state.count_group_unique("graffitim", player) * 7
if total <= sprayable:
return total
else:
@@ -563,7 +563,7 @@ def spots_m_glitched(state: CollectionState, player: int, limit: bool, access_ca
break
if limit:
sprayable: int = state.count_group_exclusive("graffitim", player) * 7
sprayable: int = state.count_group_unique("graffitim", player) * 7
if total <= sprayable:
return total
else:
@@ -614,7 +614,7 @@ def spots_l_glitchless(state: CollectionState, player: int, limit: bool, access_
break
if limit:
sprayable: int = state.count_group_exclusive("graffitil", player) * 6
sprayable: int = state.count_group_unique("graffitil", player) * 6
if total <= sprayable:
return total
else:
@@ -641,7 +641,7 @@ def spots_l_glitched(state: CollectionState, player: int, limit: bool, access_ca
break
if limit:
sprayable: int = state.count_group_exclusive("graffitil", player) * 6
sprayable: int = state.count_group_unique("graffitil", player) * 6
if total <= sprayable:
return total
else:
@@ -685,7 +685,7 @@ def spots_xl_glitchless(state: CollectionState, player: int, limit: bool, access
break
if limit:
sprayable: int = state.count_group_exclusive("graffitixl", player) * 4
sprayable: int = state.count_group_unique("graffitixl", player) * 4
if total <= sprayable:
return total
else:
@@ -712,7 +712,7 @@ def spots_xl_glitched(state: CollectionState, player: int, limit: bool, access_c
break
if limit:
sprayable: int = state.count_group_exclusive("graffitixl", player) * 4
sprayable: int = state.count_group_unique("graffitixl", player) * 4
if total <= sprayable:
return total
else:

View File

@@ -39,8 +39,6 @@ class BumpStikWorld(World):
location_name_to_id = location_table
item_name_groups = item_groups
data_version = 1
required_client_version = (0, 3, 8)
options: BumpstikOptions

View File

@@ -33,8 +33,6 @@ class ChecksFinderWorld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = {name: data.id for name, data in advancement_table.items()}
data_version = 4
def _get_checksfinder_data(self):
return {
'world_seed': self.multiworld.per_slot_randoms[self.player].getrandbits(32),

View File

@@ -37,7 +37,6 @@ class CliqueWorld(World):
"""The greatest game of all time."""
game = "Clique"
data_version = 3
web = CliqueWebWorld()
option_definitions = clique_options
location_name_to_id = location_table

View File

@@ -64,7 +64,6 @@ class CV64World(World):
options: CV64Options
settings: typing.ClassVar[CV64Settings]
topology_present = True
data_version = 1
item_name_to_id = get_item_names_to_ids()
location_name_to_id = get_location_names_to_ids()

View File

@@ -146,7 +146,7 @@ class Castlevania64Client(BizHawkClient):
text_color = bytearray([0xA2, 0x0B])
else:
text_color = bytearray([0xA2, 0x02])
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names[next_item.item]}\n"
received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_slot(next_item.item)}\n"
f"from {ctx.player_names[next_item.player]}", 96)
await bizhawk.guarded_write(ctx.bizhawk_ctx,
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),

View File

@@ -49,7 +49,6 @@ class DarkSouls3World(World):
option_definitions = dark_souls_options
topology_present: bool = True
web = DarkSouls3Web()
data_version = 8
base_id = 100000
enabled_location_categories: Set[DS3LocationCategory]
required_client_version = (0, 4, 2)

View File

@@ -86,7 +86,7 @@ class DKC3SNIClient(SNIClient):
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
location = ctx.location_names[new_check_id]
location = ctx.location_names.lookup_in_slot(new_check_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
@@ -99,9 +99,9 @@ class DKC3SNIClient(SNIClient):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
color(ctx.item_names[item.item], 'red', 'bold'),
color(ctx.item_names.lookup_in_slot(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
ctx.location_names[item.location], recv_index, len(ctx.items_received)))
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
if item.item in item_rom_data:

View File

@@ -61,7 +61,6 @@ class DKC3World(World):
options: DKC3Options
topology_present = False
data_version = 2
#hint_blacklist = {LocationName.rocket_rush_flag}
item_name_to_id = {name: data.code for name, data in item_table.items()}

View File

@@ -43,8 +43,6 @@ class DLCqworld(World):
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = location_table
data_version = 1
options_dataclass = DLCQuestOptions
options: DLCQuestOptions

View File

@@ -42,7 +42,6 @@ class DOOM1993World(World):
options: DOOM1993Options
game = "DOOM 1993"
web = DOOM1993Web()
data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}

View File

@@ -43,7 +43,6 @@ class DOOM2World(World):
options: DOOM2Options
game = "DOOM II"
web = DOOM2Web()
data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}

View File

@@ -247,7 +247,7 @@ async def game_watcher(ctx: FactorioContext):
if ctx.locations_checked != research_data:
bridge_logger.debug(
f"New researches done: "
f"{[ctx.location_names[rid] for rid in research_data - ctx.locations_checked]}")
f"{[ctx.location_names.lookup_in_slot(rid) for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
death_link_tick = data.get("death_link_tick", 0)
@@ -360,7 +360,7 @@ async def factorio_server_watcher(ctx: FactorioContext):
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
item_id = transfer_item.item
player_name = ctx.player_names[transfer_item.player]
item_name = ctx.item_names[item_id]
item_name = ctx.item_names.lookup_in_slot(item_id)
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
commands[ctx.send_index] = f"/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}"
ctx.send_index += 1

View File

@@ -95,7 +95,6 @@ class Factorio(World):
item_name_groups = {
"Progressive": set(progressive_tech_table.keys()),
}
data_version = 8
required_client_version = (0, 4, 2)
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()

View File

@@ -40,7 +40,6 @@ class FF1World(World):
settings_key = "ffr_options"
game = "Final Fantasy"
topology_present = False
data_version = 2
ff1_items = FF1Items()
ff1_locations = FF1Locations()

View File

@@ -56,8 +56,6 @@ class FFMQWorld(World):
create_regions = create_regions
set_rules = set_rules
stage_set_rules = stage_set_rules
data_version = 1
web = FFMQWebWorld()
# settings: FFMQSettings
@@ -216,4 +214,3 @@ class FFMQWorld(World):
hint_data[self.player][location.address] += f"/{hint}"
else:
hint_data[self.player][location.address] = hint

View File

@@ -40,7 +40,6 @@ class GenericWorld(World):
}
hidden = True
web = GenericWeb()
data_version = 1
def generate_early(self):
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
@@ -69,9 +68,3 @@ class PlandoItem(NamedTuple):
raise exception(warning)
else:
self.warn(warning)
class PlandoConnection(NamedTuple):
entrance: str
exit: str
direction: str # entrance, exit or both

View File

@@ -79,7 +79,7 @@ are `description`, `name`, `game`, `requires`, and the name of the games you wan
different weights.
* `requires` details different requirements from the generator for the YAML to work as you expect it to. Generally this
is good for detailing the version of Archipelago this YAML was prepared for as, if it is rolled on an older version,
is good for detailing the version of Archipelago this YAML was prepared for. If it is rolled on an older version,
options may be missing and as such it will not work as expected. If any plando is used in the file then requiring it
here to ensure it will be used is good practice.
@@ -137,7 +137,7 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en)
locations.
* `item_links` allows players to link their items into a group with the same item link name and game. The items declared
in `item_pool` get combined and when an item is found for the group, all players in the group receive it. Item links
can also have local and non local items, forcing the items to either be placed within the worlds of the group or in
can also have local and non-local items, forcing the items to either be placed within the worlds of the group or in
worlds outside the group. If players have a varying amount of a specific item in the link, the lowest amount from the
players will be the amount put into the group.
@@ -277,7 +277,7 @@ one file, removing the need to manage separate files if one chooses to do so.
As a precautionary measure, before submitting a multi-game yaml like this one in a synchronous/sync multiworld, please
confirm that the other players in the multi are OK with what you are submitting, and please be fairly reasonable about
the submission. (ie. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered
the submission. (i.e. Multiple long games (SMZ3, OoT, HK, etc.) for a game intended to be <2 hrs is not likely considered
reasonable, but submitting a ChecksFinder alongside another game OR submitting multiple Slay the Spire runs is likely
OK)
@@ -295,7 +295,7 @@ requires:
version: 0.3.2
Super Mario 64:
progression_balancing: 50
accessibilty: items
accessibility: items
EnableCoinStars: false
StrictCapRequirements: true
StrictCannonRequirements: true
@@ -315,7 +315,7 @@ name: Minecraft
game: Minecraft
Minecraft:
progression_balancing: 50
accessibilty: items
accessibility: items
advancement_goal: 40
combat_difficulty: hard
include_hard_advancements: false
@@ -341,7 +341,7 @@ game: ChecksFinder
ChecksFinder:
progression_balancing: 50
accessibilty: items
accessibility: items
```
The above example will generate 3 worlds - one Super Mario 64, one Minecraft, and one ChecksFinder.

View File

@@ -129,7 +129,7 @@ List, set, and dict options can additionally have values added to or removed fro
option value by prefixing the option name in the trigger block with `+` (add) or `-` (remove). The exact behavior for
each will depend on the option type.
- For sets, `+` will add the value(s) to the set and `-` will remove any value(s) of the set. Sets do not allow
- For sets, `+` will add the value(s) to the set and `-` will remove the value(s) from the set. Sets do not allow
duplicates.
- For lists, `+` will add new values(s) to the list and `-` will remove the first matching values(s) it comes across.
Lists allow duplicate values.
@@ -160,6 +160,3 @@ Super Metroid:
In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be
created. If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph
Ball.
Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key
will replace that value within the dict.

View File

@@ -41,7 +41,6 @@ class HereticWorld(World):
options: HereticOptions
game = "Heretic"
web = HereticWeb()
data_version = 3
required_client_version = (0, 3, 9)
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}

View File

@@ -9,7 +9,7 @@ def set_godhome_rules(hk_world, hk_set_rule):
fn = partial(hk_set_rule, hk_world)
required_events = {
"Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player),
"Godhome_Flower_Quest": lambda state: state.count('Defeated_Pantheon_5', player) and state.count('Room_Mansion[left1]', player) and state.count('Fungus3_49[right1]', player) and state.has('Godtuner', player),
"Defeated_Pantheon_5": lambda state: state.has('GG_Atrium_Roof', player) and state.has('WINGS', player) and (state.has('LEFTCLAW', player) or state.has('RIGHTCLAW', player)) and ((state.has('Defeated_Pantheon_1', player) and state.has('Defeated_Pantheon_2', player) and state.has('Defeated_Pantheon_3', player) and state.has('Defeated_Pantheon_4', player) and state.has('COMBAT[Radiance]', player))),
"GG_Atrium_Roof": lambda state: state.has('GG_Atrium', player) and state.has('Hit_Pantheon_5_Unlock_Orb', player) and state.has('LEFTCLAW', player),

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