mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-07-18 23:40:52 -07:00
Merge branch 'main' into core_filler_creation_reason
This commit is contained in:
+131
-2
@@ -1,15 +1,28 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import abc
|
||||
from bisect import bisect_right
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union, TypeGuard
|
||||
from typing import (TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterable,
|
||||
Optional, Sequence, Tuple, TypeGuard, TypeVar, Union)
|
||||
|
||||
|
||||
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SNIClient import SNIContext
|
||||
|
||||
component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe"))
|
||||
SNES_READ_CHUNK_SIZE = 2048
|
||||
"""
|
||||
note: SNI v0.0.101 currently has a bug where reads from
|
||||
RetroArch >2048 bytes will only return the last ~2048 bytes read.
|
||||
https://github.com/alttpo/sni/issues/51
|
||||
"""
|
||||
|
||||
component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe"),
|
||||
description="A client for connecting to SNES consoles via Super Nintendo Interface.")
|
||||
components.append(component)
|
||||
|
||||
|
||||
@@ -90,3 +103,119 @@ class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister):
|
||||
def on_package(self, ctx: SNIContext, cmd: str, args: Dict[str, Any]) -> None:
|
||||
""" override this with code to handle packages from the server """
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True, order=True)
|
||||
class Read:
|
||||
""" snes memory read - address and size in bytes """
|
||||
address: int
|
||||
size: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _MemRead:
|
||||
location: Read
|
||||
data: bytes
|
||||
|
||||
|
||||
_T_Enum = TypeVar("_T_Enum", bound=enum.Enum)
|
||||
|
||||
|
||||
class SnesData(Generic[_T_Enum]):
|
||||
_ranges: Sequence[_MemRead]
|
||||
""" sorted by address """
|
||||
|
||||
def __init__(self, ranges: Sequence[tuple[Read, bytes]]) -> None:
|
||||
self._ranges = [_MemRead(r, d) for r, d in ranges]
|
||||
|
||||
def get(self, read: _T_Enum) -> bytes:
|
||||
assert isinstance(read.value, Read), read.value
|
||||
address = read.value.address
|
||||
index = bisect_right(self._ranges, address, key=lambda r: r.location.address) - 1
|
||||
assert index >= 0, (self._ranges, read.value)
|
||||
mem_read = self._ranges[index]
|
||||
sub_index = address - mem_read.location.address
|
||||
return mem_read.data[sub_index:sub_index + read.value.size]
|
||||
|
||||
|
||||
class SnesReader(Generic[_T_Enum]):
|
||||
"""
|
||||
how to use:
|
||||
```
|
||||
from enum import Enum
|
||||
from worlds.AutoSNIClient import Read, SNIClient, SnesReader
|
||||
|
||||
class MyGameMemory(Enum):
|
||||
game_mode = Read(WRAM_START + 0x0998, 1)
|
||||
send_queue = Read(SEND_QUEUE_START, 8 * 127)
|
||||
...
|
||||
|
||||
snes_reader = SnesReader(MyGameMemory)
|
||||
|
||||
snes_data = await snes_reader.read(ctx)
|
||||
if snes_data is None:
|
||||
snes_logger.info("error reading from snes")
|
||||
return
|
||||
|
||||
game_mode = snes_data.get(MyGameMemory.game_mode)
|
||||
```
|
||||
"""
|
||||
_ranges: Sequence[Read]
|
||||
""" sorted by address """
|
||||
|
||||
def __init__(self, reads: type[_T_Enum]) -> None:
|
||||
self._ranges = self._make_ranges(reads)
|
||||
|
||||
@staticmethod
|
||||
def _make_ranges(reads: type[enum.Enum]) -> Sequence[Read]:
|
||||
|
||||
unprocessed_reads: list[Read] = []
|
||||
for e in reads:
|
||||
assert isinstance(e.value, Read), (reads.__name__, e, e.value)
|
||||
unprocessed_reads.append(e.value)
|
||||
unprocessed_reads.sort()
|
||||
|
||||
ranges: list[Read] = []
|
||||
for read in unprocessed_reads:
|
||||
# v end of the previous range
|
||||
if len(ranges) == 0 or read.address - (ranges[-1].address + ranges[-1].size) > 255:
|
||||
ranges.append(read)
|
||||
else: # combine with previous range
|
||||
chunk_address = ranges[-1].address
|
||||
assert read.address >= chunk_address, "sort() didn't work? or something"
|
||||
original_chunk_size = ranges[-1].size
|
||||
new_size = max((read.address + read.size) - chunk_address,
|
||||
original_chunk_size)
|
||||
ranges[-1] = Read(chunk_address, new_size)
|
||||
logging.debug(f"{len(ranges)=} {max(r.size for r in ranges)=}")
|
||||
return ranges
|
||||
|
||||
async def read(self, ctx: "SNIContext") -> SnesData[_T_Enum] | None:
|
||||
"""
|
||||
returns `None` if reading fails,
|
||||
otherwise returns the data for the registered `Enum`
|
||||
"""
|
||||
from SNIClient import snes_read
|
||||
|
||||
reads: list[tuple[Read, bytes]] = []
|
||||
for r in self._ranges:
|
||||
if r.size < SNES_READ_CHUNK_SIZE: # most common
|
||||
response = await snes_read(ctx, r.address, r.size)
|
||||
if response is None:
|
||||
return None
|
||||
reads.append((r, response))
|
||||
else: # big read
|
||||
# Problems were reported with big reads,
|
||||
# so we chunk it into smaller pieces.
|
||||
read_so_far = 0
|
||||
collection: list[bytes] = []
|
||||
while read_so_far < r.size:
|
||||
remaining_size = r.size - read_so_far
|
||||
chunk_size = min(SNES_READ_CHUNK_SIZE, remaining_size)
|
||||
response = await snes_read(ctx, r.address + read_so_far, chunk_size)
|
||||
if response is None:
|
||||
return None
|
||||
collection.append(response)
|
||||
read_so_far += chunk_size
|
||||
reads.append((r, b"".join(collection)))
|
||||
return SnesData(reads)
|
||||
|
||||
+158
-50
@@ -6,17 +6,19 @@ import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from random import Random
|
||||
from dataclasses import make_dataclass
|
||||
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, Iterable, List, Mapping, Optional, Set, TextIO, Tuple,
|
||||
from typing import (Any, ClassVar, Dict, FrozenSet, List, Optional, Self, Set, TextIO, Tuple,
|
||||
TYPE_CHECKING, Type, Union)
|
||||
|
||||
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, Entrance
|
||||
from rule_builder.rules import CustomRuleRegister, Rule
|
||||
from Utils import Version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance
|
||||
from . import GamesPackage
|
||||
from BaseClasses import CollectionRule, Item, Location, MultiWorld, Region, Tutorial
|
||||
from NetUtils import GamesPackage, MultiData
|
||||
from settings import Group
|
||||
|
||||
|
||||
@@ -31,6 +33,10 @@ class FillerReason(enum.StrEnum):
|
||||
perf_logger = logging.getLogger("performance")
|
||||
|
||||
|
||||
class InvalidItemError(KeyError):
|
||||
pass
|
||||
|
||||
|
||||
class AutoWorldRegister(type):
|
||||
world_types: Dict[str, Type[World]] = {}
|
||||
__file__: str
|
||||
@@ -52,27 +58,31 @@ class AutoWorldRegister(type):
|
||||
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
|
||||
if "web" in dct:
|
||||
assert isinstance(dct["web"], WebWorld), "WebWorld has to be instantiated."
|
||||
# filter out any events
|
||||
dct["item_name_to_id"] = {name: id for name, id in dct["item_name_to_id"].items() if id}
|
||||
dct["location_name_to_id"] = {name: id for name, id in dct["location_name_to_id"].items() if id}
|
||||
# build reverse lookups
|
||||
dct["item_id_to_name"] = {code: name for name, code in dct["item_name_to_id"].items()}
|
||||
dct["location_id_to_name"] = {code: name for name, code in dct["location_name_to_id"].items()}
|
||||
|
||||
# build rest
|
||||
dct["item_names"] = frozenset(dct["item_name_to_id"])
|
||||
dct["item_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set
|
||||
in dct.get("item_name_groups", {}).items()}
|
||||
dct["item_name_groups"]["Everything"] = dct["item_names"]
|
||||
|
||||
dct["location_names"] = frozenset(dct["location_name_to_id"])
|
||||
dct["location_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set
|
||||
in dct.get("location_name_groups", {}).items()}
|
||||
dct["location_name_groups"]["Everywhere"] = dct["location_names"]
|
||||
dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {})))
|
||||
|
||||
# move away from get_required_client_version function
|
||||
if "game" in dct:
|
||||
assert "item_name_to_id" in dct, f"{name}: item_name_to_id is required"
|
||||
assert "location_name_to_id" in dct, f"{name}: location_name_to_id is required"
|
||||
|
||||
# filter out any events
|
||||
dct["item_name_to_id"] = {name: id for name, id in dct["item_name_to_id"].items() if id}
|
||||
dct["location_name_to_id"] = {name: id for name, id in dct["location_name_to_id"].items() if id}
|
||||
# build reverse lookups
|
||||
dct["item_id_to_name"] = {code: name for name, code in dct["item_name_to_id"].items()}
|
||||
dct["location_id_to_name"] = {code: name for name, code in dct["location_name_to_id"].items()}
|
||||
|
||||
# build rest
|
||||
dct["item_names"] = frozenset(dct["item_name_to_id"])
|
||||
dct["item_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set
|
||||
in dct.get("item_name_groups", {}).items()}
|
||||
dct["item_name_groups"]["Everything"] = dct["item_names"]
|
||||
|
||||
dct["location_names"] = frozenset(dct["location_name_to_id"])
|
||||
dct["location_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set
|
||||
in dct.get("location_name_groups", {}).items()}
|
||||
dct["location_name_groups"]["Everywhere"] = dct["location_names"]
|
||||
dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {})))
|
||||
|
||||
# move away from get_required_client_version function
|
||||
assert "get_required_client_version" not in dct, f"{name}: required_client_version is an attribute now"
|
||||
# set minimum required_client_version from bases
|
||||
if "required_client_version" in dct and bases:
|
||||
@@ -80,24 +90,20 @@ class AutoWorldRegister(type):
|
||||
if "required_client_version" in base.__dict__:
|
||||
dct["required_client_version"] = max(dct["required_client_version"],
|
||||
base.__dict__["required_client_version"])
|
||||
|
||||
# create missing options_dataclass from legacy option_definitions
|
||||
# TODO - remove this once all worlds use options dataclasses
|
||||
if "options_dataclass" not in dct and "option_definitions" in dct:
|
||||
# TODO - switch to deprecate after a version
|
||||
if __debug__:
|
||||
logging.warning(f"{name} Assigned options through option_definitions which is now deprecated. "
|
||||
"Please use options_dataclass instead.")
|
||||
dct["options_dataclass"] = make_dataclass(f"{name}Options", dct["option_definitions"].items(),
|
||||
bases=(PerGameCommonOptions,))
|
||||
if "world_version" in dct:
|
||||
if dct["world_version"] != Version(0, 0, 0):
|
||||
raise RuntimeError(f"{name} is attempting to set 'world_version' from within the class. world_version "
|
||||
f"can only be set from manifest.")
|
||||
|
||||
# construct class
|
||||
new_class = super().__new__(mcs, name, bases, dct)
|
||||
new_class.__file__ = sys.modules[new_class.__module__].__file__
|
||||
if "game" in dct:
|
||||
if dct["game"] in AutoWorldRegister.world_types:
|
||||
raise RuntimeError(f"""Game {dct["game"]} already registered.""")
|
||||
raise RuntimeError(f"""Game {dct["game"]} already registered in
|
||||
{AutoWorldRegister.world_types[dct["game"]].__file__} when attempting to register from
|
||||
{new_class.__file__}.""")
|
||||
AutoWorldRegister.world_types[dct["game"]] = new_class
|
||||
new_class.__file__ = sys.modules[new_class.__module__].__file__
|
||||
if ".apworld" in new_class.__file__:
|
||||
new_class.zip_path = pathlib.Path(new_class.__file__).parents[1]
|
||||
if "settings_key" not in dct:
|
||||
@@ -120,6 +126,16 @@ class AutoLogicRegister(type):
|
||||
elif not item_name.startswith("__"):
|
||||
if hasattr(CollectionState, item_name):
|
||||
raise Exception(f"Name conflict on Logic Mixin {name} trying to overwrite {item_name}")
|
||||
|
||||
assert callable(function) or "init_mixin" in dct, (
|
||||
f"{name} defined class variable {item_name} without also having init_mixin.\n\n"
|
||||
"Explanation:\n"
|
||||
"Class variables that will be mutated need to be inintialized as instance variables in init_mixin.\n"
|
||||
"If your LogicMixin variables aren't actually mutable / you don't intend to mutate them, "
|
||||
"there is no point in using LogixMixin.\n"
|
||||
"LogicMixin exists to track custom state variables that change when items are collected/removed."
|
||||
)
|
||||
|
||||
setattr(CollectionState, item_name, function)
|
||||
return new_class
|
||||
|
||||
@@ -172,7 +188,8 @@ def _timed_call(method: Callable[..., Any], *args: Any,
|
||||
|
||||
|
||||
def call_single(multiworld: "MultiWorld", method_name: str, player: int, *args: Any) -> Any:
|
||||
method = getattr(multiworld.worlds[player], method_name)
|
||||
world = multiworld.worlds[player]
|
||||
method = getattr(world, method_name)
|
||||
try:
|
||||
ret = _timed_call(method, *args, multiworld=multiworld, player=player)
|
||||
except Exception as e:
|
||||
@@ -183,6 +200,10 @@ def call_single(multiworld: "MultiWorld", method_name: str, player: int, *args:
|
||||
logging.error(message)
|
||||
raise e
|
||||
else:
|
||||
# Convenience for CachedRuleBuilderWorld users: Ensure that caching setup function is called
|
||||
# Can be removed once dependency system is improved
|
||||
if method_name == "set_rules" and hasattr(world, "register_rule_builder_dependencies"):
|
||||
call_single(multiworld, "register_rule_builder_dependencies", player)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -223,7 +244,7 @@ class WebWorld(metaclass=WebWorldRegister):
|
||||
tutorials: List["Tutorial"]
|
||||
"""docs folder will also be scanned for tutorial guides. Each Tutorial class is to be used for one guide."""
|
||||
|
||||
theme = "grass"
|
||||
theme: str = "grass"
|
||||
"""Choose a theme for you /game/* pages.
|
||||
Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, stone"""
|
||||
|
||||
@@ -340,6 +361,8 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""If loaded from a .apworld, this is the Path to it."""
|
||||
__file__: ClassVar[str]
|
||||
"""path it was loaded from"""
|
||||
world_version: ClassVar[Version] = Version(0, 0, 0)
|
||||
"""Optional world version loaded from archipelago.json"""
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
assert multiworld is not None
|
||||
@@ -350,7 +373,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
|
||||
def __getattr__(self, item: str) -> Any:
|
||||
if item == "settings":
|
||||
return self.__class__.settings
|
||||
return getattr(self.__class__, item)
|
||||
raise AttributeError
|
||||
|
||||
# overridable methods that get called by Main.py, sorted by execution order
|
||||
@@ -380,7 +403,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
def create_items(self) -> None:
|
||||
"""
|
||||
Method for creating and submitting items to the itempool. Items and Regions must *not* be created and submitted
|
||||
to the MultiWorld after this step. If items need to be placed during pre_fill use `get_prefill_items`.
|
||||
to the MultiWorld after this step. If items need to be placed during pre_fill use `get_pre_fill_items`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -388,6 +411,10 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""Method for setting the rules on the World's regions and locations."""
|
||||
pass
|
||||
|
||||
def connect_entrances(self) -> None:
|
||||
"""Method to finalize the source and target regions of the World's entrances"""
|
||||
pass
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
"""
|
||||
Useful for randomizing things that don't affect logic but are better to be determined before the output stage.
|
||||
@@ -413,6 +440,23 @@ class World(metaclass=AutoWorldRegister):
|
||||
This happens before progression balancing, so the items may not be in their final locations yet.
|
||||
"""
|
||||
|
||||
def finalize_multiworld(self) -> None:
|
||||
"""
|
||||
Optional Method that is called after fill and progression balancing.
|
||||
This is the last stage of generation where worlds may change logically relevant data,
|
||||
such as item placements and connections. To not break assumptions,
|
||||
only ever increase accessibility, never decrease it.
|
||||
"""
|
||||
pass
|
||||
|
||||
def pre_output(self):
|
||||
"""
|
||||
Optional method that is called before output generation.
|
||||
Items and connections are not meant to be moved anymore,
|
||||
anything that would affect logical spheres is forbidden at this point.
|
||||
"""
|
||||
pass
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
"""
|
||||
This method gets called from a threadpool, do not use multiworld.random here.
|
||||
@@ -444,7 +488,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""
|
||||
pass
|
||||
|
||||
def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata?
|
||||
def modify_multidata(self, multidata: "MultiData") -> None:
|
||||
"""For deeper modification of server multidata."""
|
||||
pass
|
||||
|
||||
@@ -477,9 +521,16 @@ class World(metaclass=AutoWorldRegister):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
"""Called when the item pool needs to be filled with additional items to match location count."""
|
||||
"""
|
||||
Called when the item pool needs to be filled with additional items to match location count.
|
||||
|
||||
Any returned item name must be for a "repeatable" item, i.e. one that it's okay to generate arbitrarily many of.
|
||||
For most worlds this will be one or more of your filler items, but the classification of these items
|
||||
does not need to be ItemClassification.filler.
|
||||
The item name returned can be for a trap, useful, and/or progression item as long as it's repeatable.
|
||||
"""
|
||||
logging.warning(f"World {self} is generating a filler item without custom filler pool.")
|
||||
return self.multiworld.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
return self.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
|
||||
@classmethod
|
||||
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World:
|
||||
@@ -487,9 +538,6 @@ class World(metaclass=AutoWorldRegister):
|
||||
Creates a group, which is an instance of World that is responsible for multiple others.
|
||||
An example case is ItemLinks creating these.
|
||||
"""
|
||||
# TODO remove loop when worlds use options dataclass
|
||||
for option_key, option in cls.options_dataclass.type_hints.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default)
|
||||
group = cls(multiworld, new_player_id)
|
||||
group.options = cls.options_dataclass(**{option_key: option.from_any(option.default)
|
||||
for option_key, option in cls.options_dataclass.type_hints.items()})
|
||||
@@ -522,7 +570,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""Called when an item is collected in to state. Useful for things such as progressive items or currency."""
|
||||
name = self.collect_item(state, item)
|
||||
if name:
|
||||
state.prog_items[self.player][name] += 1
|
||||
state.add_item(name, self.player)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -530,12 +578,14 @@ class World(metaclass=AutoWorldRegister):
|
||||
"""Called when an item is removed from to state. Useful for things such as progressive items or currency."""
|
||||
name = self.collect_item(state, item, True)
|
||||
if name:
|
||||
state.prog_items[self.player][name] -= 1
|
||||
if state.prog_items[self.player][name] < 1:
|
||||
del (state.prog_items[self.player][name])
|
||||
state.remove_item(name, self.player)
|
||||
return True
|
||||
return False
|
||||
|
||||
def reached_region(self, state: "CollectionState", region: "Region") -> None:
|
||||
"""Called when a region is newly reachable by the state."""
|
||||
pass
|
||||
|
||||
# following methods should not need to be overridden.
|
||||
def create_filler(self, reason: FillerReason = FillerReason.undefined) -> "Item":
|
||||
return self.create_item(self.get_filler_item_name())
|
||||
@@ -584,6 +634,64 @@ class World(metaclass=AutoWorldRegister):
|
||||
res["checksum"] = data_package_checksum(res)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_rule_cls(cls, name: str) -> type[Rule[Self]]:
|
||||
"""Returns the world-registered or default rule with the given name"""
|
||||
return CustomRuleRegister.get_rule_cls(cls.game, name)
|
||||
|
||||
@classmethod
|
||||
def rule_from_dict(cls, data: Mapping[str, Any]) -> Rule[Self]:
|
||||
"""Create a rule instance from a serialized dict representation"""
|
||||
name = data.get("rule", "")
|
||||
rule_class = cls.get_rule_cls(name)
|
||||
return rule_class.from_dict(data, cls)
|
||||
|
||||
def set_rule(self, spot: Location | Entrance, rule: CollectionRule | Rule[Any]) -> None:
|
||||
"""Sets an access rule for a location or entrance"""
|
||||
if isinstance(rule, Rule):
|
||||
rule = rule.resolve(self)
|
||||
self.register_rule_dependencies(rule)
|
||||
if isinstance(spot, Entrance):
|
||||
self._register_rule_indirects(rule, spot)
|
||||
spot.access_rule = rule
|
||||
|
||||
def set_completion_rule(self, rule: CollectionRule | Rule[Any]) -> None:
|
||||
"""Set the completion rule for this world"""
|
||||
if isinstance(rule, Rule):
|
||||
rule = rule.resolve(self)
|
||||
self.register_rule_dependencies(rule)
|
||||
self.multiworld.completion_condition[self.player] = rule
|
||||
|
||||
def create_entrance(
|
||||
self,
|
||||
from_region: Region,
|
||||
to_region: Region,
|
||||
rule: CollectionRule | Rule[Any] | None = None,
|
||||
name: str | None = None,
|
||||
force_creation: bool = False,
|
||||
) -> Entrance | None:
|
||||
"""Try to create an entrance between regions with the given rule,
|
||||
skipping it if the rule resolves to False (unless force_creation is True)"""
|
||||
if rule is not None and isinstance(rule, Rule):
|
||||
rule = rule.resolve(self)
|
||||
if rule.always_false and not force_creation:
|
||||
return None
|
||||
self.register_rule_dependencies(rule)
|
||||
|
||||
entrance = from_region.connect(to_region, name, rule=rule)
|
||||
if rule and isinstance(rule, Rule.Resolved):
|
||||
self._register_rule_indirects(rule, entrance)
|
||||
return entrance
|
||||
|
||||
def register_rule_dependencies(self, resolved_rule: Rule.Resolved) -> None:
|
||||
"""Hook for registering dependencies when a rule is assigned for this world"""
|
||||
pass
|
||||
|
||||
def _register_rule_indirects(self, resolved_rule: Rule.Resolved, entrance: Entrance) -> None:
|
||||
if self.explicit_indirect_conditions:
|
||||
for indirect_region in resolved_rule.region_dependencies().keys():
|
||||
self.multiworld.register_indirect_condition(self.get_region(indirect_region), entrance)
|
||||
|
||||
|
||||
# any methods attached to this can be used as part of CollectionState,
|
||||
# please use a prefix as all of them get clobbered together
|
||||
|
||||
+135
-42
@@ -6,15 +6,23 @@ import zipfile
|
||||
from enum import IntEnum
|
||||
import os
|
||||
import threading
|
||||
from io import BytesIO
|
||||
|
||||
from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload, Sequence
|
||||
from typing import (ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload, Sequence,
|
||||
TYPE_CHECKING)
|
||||
|
||||
import bsdiff4
|
||||
|
||||
semaphore = threading.Semaphore(os.cpu_count() or 4)
|
||||
|
||||
del threading
|
||||
del os
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from Utils import Version
|
||||
|
||||
|
||||
class ImproperlyConfiguredAutoPatchError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AutoPatchRegister(abc.ABCMeta):
|
||||
@@ -26,17 +34,35 @@ class AutoPatchRegister(abc.ABCMeta):
|
||||
new_class = super().__new__(mcs, name, bases, dct)
|
||||
if "game" in dct:
|
||||
AutoPatchRegister.patch_types[dct["game"]] = new_class
|
||||
if not dct["patch_file_ending"]:
|
||||
raise Exception(f"Need an expected file ending for {name}")
|
||||
|
||||
if not callable(getattr(new_class, "patch", None)):
|
||||
raise ImproperlyConfiguredAutoPatchError(
|
||||
f"Container {new_class} uses metaclass AutoPatchRegister, but does not have a patch method defined."
|
||||
)
|
||||
|
||||
patch_file_ending = dct.get("patch_file_ending")
|
||||
if patch_file_ending == ".zip":
|
||||
raise ImproperlyConfiguredAutoPatchError(
|
||||
f'Auto patch container {new_class} uses file ending ".zip", which is not allowed.'
|
||||
)
|
||||
if patch_file_ending is None:
|
||||
raise ImproperlyConfiguredAutoPatchError(
|
||||
f"Need an expected file ending for auto patch container {new_class}"
|
||||
)
|
||||
|
||||
existing_handler = AutoPatchRegister.file_endings.get(patch_file_ending)
|
||||
if existing_handler:
|
||||
raise ImproperlyConfiguredAutoPatchError(
|
||||
f"Two auto patch containers are using the same file extension: {new_class}, {existing_handler}"
|
||||
)
|
||||
|
||||
AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class
|
||||
return new_class
|
||||
|
||||
@staticmethod
|
||||
def get_handler(file: str) -> Optional[AutoPatchRegister]:
|
||||
for file_ending, handler in AutoPatchRegister.file_endings.items():
|
||||
if file.endswith(file_ending):
|
||||
return handler
|
||||
return None
|
||||
_, suffix = os.path.splitext(file)
|
||||
return AutoPatchRegister.file_endings.get(suffix, None)
|
||||
|
||||
|
||||
class AutoPatchExtensionRegister(abc.ABCMeta):
|
||||
@@ -67,7 +93,19 @@ class AutoPatchExtensionRegister(abc.ABCMeta):
|
||||
return handler
|
||||
|
||||
|
||||
container_version: int = 6
|
||||
container_version: int = 7
|
||||
|
||||
|
||||
def is_ap_player_container(game: str, data: bytes, player: int):
|
||||
if not zipfile.is_zipfile(BytesIO(data)):
|
||||
return False
|
||||
with zipfile.ZipFile(BytesIO(data), mode='r') as zf:
|
||||
if "archipelago.json" in zf.namelist():
|
||||
manifest = json.loads(zf.read("archipelago.json"))
|
||||
if "game" in manifest and "player" in manifest:
|
||||
if game == manifest["game"] and player == manifest["player"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class InvalidDataError(Exception):
|
||||
@@ -78,24 +116,15 @@ class InvalidDataError(Exception):
|
||||
|
||||
|
||||
class APContainer:
|
||||
"""A zipfile containing at least archipelago.json"""
|
||||
version: int = container_version
|
||||
compression_level: int = 9
|
||||
compression_method: int = zipfile.ZIP_DEFLATED
|
||||
game: Optional[str] = None
|
||||
|
||||
# instance attributes:
|
||||
"""A zipfile containing at least archipelago.json, which contains a manifest json payload."""
|
||||
version: ClassVar[int] = container_version
|
||||
compression_level: ClassVar[int] = 9
|
||||
compression_method: ClassVar[int] = zipfile.ZIP_DEFLATED
|
||||
manifest_path: str = "archipelago.json"
|
||||
path: Optional[str]
|
||||
player: Optional[int]
|
||||
player_name: str
|
||||
server: str
|
||||
|
||||
def __init__(self, path: Optional[str] = None, player: Optional[int] = None,
|
||||
player_name: str = "", server: str = ""):
|
||||
def __init__(self, path: Optional[str] = None):
|
||||
self.path = path
|
||||
self.player = player
|
||||
self.player_name = player_name
|
||||
self.server = server
|
||||
|
||||
def write(self, file: Optional[Union[str, BinaryIO]] = None) -> None:
|
||||
zip_file = file if file else self.path
|
||||
@@ -115,7 +144,7 @@ class APContainer:
|
||||
except Exception as e:
|
||||
raise Exception(f"Manifest {manifest} did not convert to json.") from e
|
||||
else:
|
||||
opened_zipfile.writestr("archipelago.json", manifest_str)
|
||||
opened_zipfile.writestr(self.manifest_path, manifest_str)
|
||||
|
||||
def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None:
|
||||
"""Read data into patch object. file can be file-like, such as an outer zip file's stream."""
|
||||
@@ -135,31 +164,98 @@ class APContainer:
|
||||
message = f"{arg0} - "
|
||||
raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
with opened_zipfile.open("archipelago.json", "r") as f:
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
try:
|
||||
assert self.manifest_path.endswith("archipelago.json"), "Filename should be archipelago.json"
|
||||
manifest_info = opened_zipfile.getinfo(self.manifest_path)
|
||||
except KeyError as e:
|
||||
for info in opened_zipfile.infolist():
|
||||
if info.filename.endswith("archipelago.json"):
|
||||
manifest_info = info
|
||||
self.manifest_path = info.filename
|
||||
break
|
||||
else:
|
||||
raise e
|
||||
with opened_zipfile.open(manifest_info, "r") as f:
|
||||
manifest = json.load(f)
|
||||
if manifest["compatible_version"] > self.version:
|
||||
raise Exception(f"File (version: {manifest['compatible_version']}) too new "
|
||||
f"for this handler (version: {self.version})")
|
||||
self.player = manifest["player"]
|
||||
self.server = manifest["server"]
|
||||
self.player_name = manifest["player_name"]
|
||||
return manifest
|
||||
|
||||
def get_manifest(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise
|
||||
"player": self.player,
|
||||
"player_name": self.player_name,
|
||||
"game": self.game,
|
||||
# minimum version of patch system expected for patching to be successful
|
||||
"compatible_version": 5,
|
||||
"version": container_version,
|
||||
}
|
||||
|
||||
|
||||
class APPatch(APContainer):
|
||||
class APWorldContainer(APContainer):
|
||||
"""A zipfile containing a world implementation."""
|
||||
game: str | None = None
|
||||
world_version: "Version | None" = None
|
||||
minimum_ap_version: "Version | None" = None
|
||||
maximum_ap_version: "Version | None" = None
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
from Utils import tuplize_version
|
||||
manifest = super().read_contents(opened_zipfile)
|
||||
self.game = manifest["game"]
|
||||
for version_key in ("world_version", "minimum_ap_version", "maximum_ap_version"):
|
||||
if version_key in manifest:
|
||||
setattr(self, version_key, tuplize_version(manifest[version_key]))
|
||||
return manifest
|
||||
|
||||
def get_manifest(self) -> Dict[str, Any]:
|
||||
manifest = super().get_manifest()
|
||||
manifest["game"] = self.game
|
||||
manifest["compatible_version"] = 7
|
||||
for version_key in ("world_version", "minimum_ap_version", "maximum_ap_version"):
|
||||
version = getattr(self, version_key)
|
||||
if version:
|
||||
manifest[version_key] = version.as_simple_string()
|
||||
return manifest
|
||||
|
||||
|
||||
class APPlayerContainer(APContainer):
|
||||
"""A zipfile containing at least archipelago.json meant for a player"""
|
||||
game: ClassVar[Optional[str]] = None
|
||||
patch_file_ending: str = ""
|
||||
|
||||
player: Optional[int]
|
||||
player_name: str
|
||||
server: str
|
||||
|
||||
def __init__(self, path: Optional[str] = None, player: Optional[int] = None,
|
||||
player_name: str = "", server: str = ""):
|
||||
super().__init__(path)
|
||||
self.player = player
|
||||
self.player_name = player_name
|
||||
self.server = server
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
manifest = super().read_contents(opened_zipfile)
|
||||
self.player = manifest["player"]
|
||||
self.server = manifest["server"]
|
||||
self.player_name = manifest["player_name"]
|
||||
return manifest
|
||||
|
||||
def get_manifest(self) -> Dict[str, Any]:
|
||||
manifest = super().get_manifest()
|
||||
manifest.update({
|
||||
"server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise
|
||||
"player": self.player,
|
||||
"player_name": self.player_name,
|
||||
"game": self.game,
|
||||
"patch_file_ending": self.patch_file_ending,
|
||||
})
|
||||
return manifest
|
||||
|
||||
|
||||
class APPatch(APPlayerContainer):
|
||||
"""
|
||||
An `APContainer` that represents a patch file.
|
||||
An `APPlayerContainer` that represents a patch file.
|
||||
It includes the `procedure` key in the manifest to indicate that it is a patch.
|
||||
|
||||
Your implementation should inherit from this if your output file
|
||||
@@ -192,7 +288,6 @@ class APProcedurePatch(APAutoPatchInterface):
|
||||
"""
|
||||
hash: Optional[str] # base checksum of source file
|
||||
source_data: bytes
|
||||
patch_file_ending: str = ""
|
||||
files: Dict[str, bytes]
|
||||
|
||||
@classmethod
|
||||
@@ -214,16 +309,13 @@ class APProcedurePatch(APAutoPatchInterface):
|
||||
manifest = super(APProcedurePatch, self).get_manifest()
|
||||
manifest["base_checksum"] = self.hash
|
||||
manifest["result_file_ending"] = self.result_file_ending
|
||||
manifest["patch_file_ending"] = self.patch_file_ending
|
||||
manifest["procedure"] = self.procedure
|
||||
if self.procedure == APDeltaPatch.procedure:
|
||||
manifest["compatible_version"] = 5
|
||||
return manifest
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
super(APProcedurePatch, self).read_contents(opened_zipfile)
|
||||
with opened_zipfile.open("archipelago.json", "r") as f:
|
||||
manifest = json.load(f)
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> Dict[str, Any]:
|
||||
manifest = super(APProcedurePatch, self).read_contents(opened_zipfile)
|
||||
if "procedure" not in manifest:
|
||||
# support patching files made before moving to procedures
|
||||
self.procedure = [("apply_bsdiff4", ["delta.bsdiff4"])]
|
||||
@@ -232,6 +324,7 @@ class APProcedurePatch(APAutoPatchInterface):
|
||||
for file in opened_zipfile.namelist():
|
||||
if file not in ["archipelago.json"]:
|
||||
self.files[file] = opened_zipfile.read(file)
|
||||
return manifest
|
||||
|
||||
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
|
||||
super(APProcedurePatch, self).write_contents(opened_zipfile)
|
||||
|
||||
+117
-29
@@ -5,7 +5,7 @@ import weakref
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Callable, List, Iterable, Tuple
|
||||
|
||||
from Utils import local_path, open_filename
|
||||
from Utils import local_path, open_filename, is_frozen, is_kivy_running, open_file, user_path, read_apignore
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
@@ -27,6 +27,8 @@ class Component:
|
||||
"""
|
||||
display_name: str
|
||||
"""Used as the GUI button label and the component name in the CLI args"""
|
||||
description: str
|
||||
"""Optional description displayed on the GUI underneath the display name"""
|
||||
type: Type
|
||||
"""
|
||||
Enum "Type" classification of component intent, for filtering in the Launcher GUI
|
||||
@@ -58,8 +60,9 @@ class Component:
|
||||
def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
|
||||
cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None,
|
||||
func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None,
|
||||
game_name: Optional[str] = None, supports_uri: Optional[bool] = False):
|
||||
game_name: Optional[str] = None, supports_uri: Optional[bool] = False, description: str = "") -> None:
|
||||
self.display_name = display_name
|
||||
self.description = description
|
||||
self.script_name = script_name
|
||||
self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
|
||||
self.icon = icon
|
||||
@@ -87,14 +90,21 @@ class Component:
|
||||
processes = weakref.WeakSet()
|
||||
|
||||
|
||||
def launch_subprocess(func: Callable, name: str = None, args: Tuple[str, ...] = ()) -> None:
|
||||
global processes
|
||||
def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
|
||||
import multiprocessing
|
||||
process = multiprocessing.Process(target=func, name=name, args=args)
|
||||
process.start()
|
||||
processes.add(process)
|
||||
|
||||
|
||||
def launch(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
|
||||
from Utils import is_kivy_running
|
||||
if is_kivy_running():
|
||||
launch_subprocess(func, name, args)
|
||||
else:
|
||||
func(*args)
|
||||
|
||||
|
||||
class SuffixIdentifier:
|
||||
suffixes: Iterable[str]
|
||||
|
||||
@@ -111,7 +121,7 @@ class SuffixIdentifier:
|
||||
|
||||
def launch_textclient(*args):
|
||||
import CommonClient
|
||||
launch_subprocess(CommonClient.run_as_textclient, name="TextClient", args=args)
|
||||
launch(CommonClient.run_as_textclient, name="TextClient", args=args)
|
||||
|
||||
|
||||
def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
|
||||
@@ -167,11 +177,10 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
|
||||
if module_name == loaded_name:
|
||||
found_already_loaded = True
|
||||
break
|
||||
if found_already_loaded:
|
||||
raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded,\n"
|
||||
"so a Launcher restart is required to use the new installation.\n"
|
||||
"If the Launcher is not open, no action needs to be taken.")
|
||||
world_source = worlds.WorldSource(str(target), is_zip=True)
|
||||
if found_already_loaded and is_kivy_running():
|
||||
raise Exception(f"Installed APWorld successfully, but '{module_name}' is already loaded, "
|
||||
"so a Launcher restart is required to use the new installation.")
|
||||
world_source = worlds.WorldSource(str(target), is_zip=True, relative=False)
|
||||
bisect.insort(worlds.world_sources, world_source)
|
||||
world_source.load()
|
||||
|
||||
@@ -187,7 +196,7 @@ def install_apworld(apworld_path: str = "") -> None:
|
||||
source, target = res
|
||||
except Exception as e:
|
||||
import Utils
|
||||
Utils.messagebox(e.__class__.__name__, str(e), error=True)
|
||||
Utils.messagebox("Notice", str(e), error=True)
|
||||
logging.exception(e)
|
||||
else:
|
||||
import Utils
|
||||
@@ -195,47 +204,126 @@ def install_apworld(apworld_path: str = "") -> None:
|
||||
Utils.messagebox("Install complete.", f"Installed APWorld from {source}.")
|
||||
|
||||
|
||||
def export_datapackage() -> None:
|
||||
import json
|
||||
|
||||
from worlds import network_data_package
|
||||
|
||||
path = user_path("datapackage_export.json")
|
||||
with open(path, "w") as f:
|
||||
json.dump(network_data_package, f, indent=4)
|
||||
|
||||
open_file(path)
|
||||
|
||||
|
||||
components: List[Component] = [
|
||||
# Launcher
|
||||
Component('Launcher', 'Launcher', component_type=Type.HIDDEN),
|
||||
# Core
|
||||
Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
|
||||
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
|
||||
Component('Generate', 'Generate', cli=True),
|
||||
Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld")),
|
||||
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient),
|
||||
Component('Links Awakening DX Client', 'LinksAwakeningClient',
|
||||
file_identifier=SuffixIdentifier('.apladx')),
|
||||
file_identifier=SuffixIdentifier('.archipelago', '.zip'),
|
||||
description="Host a generated multiworld on your computer."),
|
||||
Component('Generate', 'Generate', cli=True,
|
||||
description="Generate a multiworld with the YAMLs in the players folder."),
|
||||
Component("Options Creator", "OptionsCreator", "ArchipelagoOptionsCreator", component_type=Type.TOOL,
|
||||
description="Visual creator for Archipelago option files."),
|
||||
Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld"),
|
||||
description="Install an APWorld to play games not included with Archipelago by default."),
|
||||
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient,
|
||||
description="Connect to a multiworld using the text client."),
|
||||
Component('LttP Adjuster', 'LttPAdjuster'),
|
||||
# Minecraft
|
||||
Component('Minecraft Client', 'MinecraftClient', icon='mcicon', cli=True,
|
||||
file_identifier=SuffixIdentifier('.apmc')),
|
||||
# Ocarina of Time
|
||||
Component('OoT Client', 'OoTClient',
|
||||
file_identifier=SuffixIdentifier('.apz5')),
|
||||
Component('OoT Adjuster', 'OoTAdjuster'),
|
||||
# FF1
|
||||
Component('FF1 Client', 'FF1Client'),
|
||||
# TLoZ
|
||||
Component('Zelda 1 Client', 'Zelda1Client', file_identifier=SuffixIdentifier('.aptloz')),
|
||||
# ChecksFinder
|
||||
Component('ChecksFinder Client', 'ChecksFinderClient'),
|
||||
# Starcraft 2
|
||||
Component('Starcraft 2 Client', 'Starcraft2Client'),
|
||||
# Wargroove
|
||||
Component('Wargroove Client', 'WargrooveClient'),
|
||||
# Zillion
|
||||
Component('Zillion Client', 'ZillionClient',
|
||||
file_identifier=SuffixIdentifier('.apzl')),
|
||||
|
||||
#MegaMan Battle Network 3
|
||||
Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3'))
|
||||
# MegaMan Battle Network 3
|
||||
Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3')),
|
||||
|
||||
Component("Export Datapackage", func=export_datapackage, component_type=Type.TOOL,
|
||||
description="Write item/location data for installed worlds to a file and open it."),
|
||||
]
|
||||
|
||||
|
||||
# if registering an icon from within an apworld, the format "ap:module.name/path/to/file.png" can be used
|
||||
icon_paths = {
|
||||
'icon': local_path('data', 'icon.png'),
|
||||
'mcicon': local_path('data', 'mcicon.png'),
|
||||
'discord': local_path('data', 'discord-mark-blue.png'),
|
||||
}
|
||||
|
||||
if not is_frozen():
|
||||
def _build_apworlds(*launch_args: str):
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from worlds import AutoWorldRegister
|
||||
from worlds.Files import APWorldContainer
|
||||
from Launcher import open_folder
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog="Build APWorlds", description="Build script for APWorlds")
|
||||
parser.add_argument("worlds", type=str, default=(), nargs="*", help="names of APWorlds to build")
|
||||
parser.add_argument("--skip_open_folder", action="store_true", help="don't open the output build folder")
|
||||
args = parser.parse_args(launch_args)
|
||||
|
||||
if args.worlds:
|
||||
games = [(game, AutoWorldRegister.world_types.get(game, None)) for game in args.worlds]
|
||||
else:
|
||||
games = [(worldname, worldtype) for worldname, worldtype in AutoWorldRegister.world_types.items()
|
||||
if not worldtype.zip_path]
|
||||
|
||||
global_apignores = read_apignore(local_path("data", "GLOBAL.apignore"))
|
||||
if not global_apignores:
|
||||
raise RuntimeError("Could not read global apignore file for build component")
|
||||
|
||||
apworlds_folder = os.path.join("build", "apworlds")
|
||||
os.makedirs(apworlds_folder, exist_ok=True)
|
||||
for worldname, worldtype in games:
|
||||
if not worldtype:
|
||||
logging.error(f"Requested APWorld \"{worldname}\" does not exist.")
|
||||
continue
|
||||
file_name = os.path.split(os.path.dirname(worldtype.__file__))[1]
|
||||
world_directory = os.path.join("worlds", file_name)
|
||||
if os.path.isfile(os.path.join(world_directory, "archipelago.json")):
|
||||
with open(os.path.join(world_directory, "archipelago.json"), mode="r", encoding="utf-8") as manifest_file:
|
||||
manifest = json.load(manifest_file)
|
||||
|
||||
assert "game" in manifest, (
|
||||
f"World directory {world_directory} has an archipelago.json manifest file, but it "
|
||||
"does not define a \"game\"."
|
||||
)
|
||||
assert manifest["game"] == worldtype.game, (
|
||||
f"World directory {world_directory} has an archipelago.json manifest file, but value of the "
|
||||
f"\"game\" field ({manifest['game']} does not equal the World class's game ({worldtype.game})."
|
||||
)
|
||||
else:
|
||||
manifest = {}
|
||||
|
||||
zip_path = os.path.join(apworlds_folder, file_name + ".apworld")
|
||||
apworld = APWorldContainer(str(zip_path))
|
||||
apworld.game = worldtype.game
|
||||
manifest.update(apworld.get_manifest())
|
||||
apworld.manifest_path = os.path.join(file_name, "archipelago.json")
|
||||
|
||||
local_ignores = read_apignore(pathlib.Path(world_directory, ".apignore"))
|
||||
apignores = global_apignores + local_ignores if local_ignores else global_apignores
|
||||
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
for file in apignores.match_tree_files(world_directory, negate=True):
|
||||
zf.write(pathlib.Path(world_directory, file), pathlib.Path(file_name, file))
|
||||
|
||||
zf.writestr(apworld.manifest_path, json.dumps(manifest))
|
||||
|
||||
if not args.skip_open_folder:
|
||||
open_folder(apworlds_folder)
|
||||
|
||||
components.append(Component("Build APWorlds", func=_build_apworlds, cli=True,
|
||||
description="Build APWorlds from loose-file world folders."))
|
||||
|
||||
+121
-39
@@ -1,15 +1,20 @@
|
||||
import importlib
|
||||
import importlib.util
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
import zipimport
|
||||
import time
|
||||
import dataclasses
|
||||
from typing import Dict, List, TypedDict
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import List, Sequence
|
||||
from zipfile import BadZipFile
|
||||
|
||||
from Utils import local_path, user_path
|
||||
from NetUtils import DataPackage
|
||||
from Utils import local_path, user_path, Version, version_tuple, tuplize_version, messagebox
|
||||
|
||||
local_folder = os.path.dirname(__file__)
|
||||
user_folder = user_path("worlds") if user_path() != local_path() else user_path("custom_worlds")
|
||||
@@ -18,39 +23,26 @@ try:
|
||||
except OSError: # can't access/write?
|
||||
user_folder = None
|
||||
|
||||
__all__ = {
|
||||
__all__ = [
|
||||
"network_data_package",
|
||||
"AutoWorldRegister",
|
||||
"world_sources",
|
||||
"local_folder",
|
||||
"user_folder",
|
||||
"GamesPackage",
|
||||
"DataPackage",
|
||||
"failed_world_loads",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
failed_world_loads: List[str] = []
|
||||
|
||||
|
||||
class GamesPackage(TypedDict, total=False):
|
||||
item_name_groups: Dict[str, List[str]]
|
||||
item_name_to_id: Dict[str, int]
|
||||
location_name_groups: Dict[str, List[str]]
|
||||
location_name_to_id: Dict[str, int]
|
||||
checksum: str
|
||||
|
||||
|
||||
class DataPackage(TypedDict):
|
||||
games: Dict[str, GamesPackage]
|
||||
|
||||
|
||||
@dataclasses.dataclass(order=True)
|
||||
class WorldSource:
|
||||
path: str # typically relative path from this module
|
||||
is_zip: bool = False
|
||||
relative: bool = True # relative to regular world import folder
|
||||
time_taken: float = -1.0
|
||||
version: Version = Version(0, 0, 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
|
||||
@@ -64,23 +56,7 @@ class WorldSource:
|
||||
def load(self) -> bool:
|
||||
try:
|
||||
start = time.perf_counter()
|
||||
if self.is_zip:
|
||||
importer = zipimport.zipimporter(self.resolved_path)
|
||||
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
|
||||
assert spec, f"{self.path} is not a loadable module"
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
|
||||
mod.__package__ = f"worlds.{mod.__package__}"
|
||||
|
||||
mod.__name__ = f"worlds.{mod.__name__}"
|
||||
sys.modules[mod.__name__] = mod
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
|
||||
# Found no equivalent for < 3.10
|
||||
if hasattr(importer, "exec_module"):
|
||||
importer.exec_module(mod)
|
||||
else:
|
||||
importlib.import_module(f".{self.path}", "worlds")
|
||||
importlib.import_module(f".{Path(self.path).stem}", "worlds")
|
||||
self.time_taken = time.perf_counter()-start
|
||||
return True
|
||||
|
||||
@@ -117,12 +93,118 @@ for folder in (folder for folder in (user_folder, local_folder) if folder):
|
||||
|
||||
# import all submodules to trigger AutoWorldRegister
|
||||
world_sources.sort()
|
||||
apworlds: list[WorldSource] = []
|
||||
for world_source in world_sources:
|
||||
world_source.load()
|
||||
# load all loose files first:
|
||||
if world_source.is_zip:
|
||||
apworlds.append(world_source)
|
||||
else:
|
||||
world_source.load()
|
||||
|
||||
# Build the data package for each game.
|
||||
from .AutoWorld import AutoWorldRegister
|
||||
|
||||
for world_source in world_sources:
|
||||
if not world_source.is_zip:
|
||||
# look for manifest
|
||||
manifest = {}
|
||||
for dirpath, dirnames, filenames in os.walk(world_source.resolved_path):
|
||||
for file in filenames:
|
||||
if file.endswith("archipelago.json"):
|
||||
with open(os.path.join(dirpath, file), mode="r", encoding="utf-8") as manifest_file:
|
||||
manifest = json.load(manifest_file)
|
||||
break
|
||||
if manifest:
|
||||
break
|
||||
game = manifest.get("game")
|
||||
if game in AutoWorldRegister.world_types:
|
||||
AutoWorldRegister.world_types[game].world_version = tuplize_version(manifest.get("world_version", "0.0.0"))
|
||||
|
||||
if apworlds:
|
||||
# encapsulation for namespace / gc purposes
|
||||
def load_apworlds() -> None:
|
||||
global apworlds
|
||||
from .Files import APWorldContainer, InvalidDataError
|
||||
core_compatible: list[tuple[WorldSource, APWorldContainer]] = []
|
||||
|
||||
def fail_world(game_name: str, reason: str, add_as_failed_to_load: bool = True) -> None:
|
||||
if add_as_failed_to_load:
|
||||
failed_world_loads.append(game_name)
|
||||
logging.warning(reason)
|
||||
|
||||
for apworld_source in apworlds:
|
||||
apworld: APWorldContainer = APWorldContainer(apworld_source.resolved_path)
|
||||
# populate metadata
|
||||
try:
|
||||
apworld.read()
|
||||
except InvalidDataError as e:
|
||||
if version_tuple < (0, 7, 0):
|
||||
logging.error(
|
||||
f"Invalid or missing manifest file for {apworld_source.resolved_path}. "
|
||||
"This apworld will stop working with Archipelago 0.7.0."
|
||||
)
|
||||
logging.error(e)
|
||||
else:
|
||||
raise e
|
||||
except BadZipFile as e:
|
||||
err_message = (f"The world source {apworld_source.resolved_path} is not a valid zip. "
|
||||
"It is likely either corrupted, or was packaged incorrectly.")
|
||||
|
||||
if sys.stdout:
|
||||
raise RuntimeError(err_message) from e
|
||||
else:
|
||||
messagebox("Couldn't load worlds", err_message, error=True)
|
||||
sys.exit(1)
|
||||
|
||||
if apworld.minimum_ap_version and apworld.minimum_ap_version > version_tuple:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its minimum core version {apworld.minimum_ap_version} "
|
||||
f"is higher than current core version {version_tuple}.")
|
||||
elif apworld.maximum_ap_version and apworld.maximum_ap_version < version_tuple:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its maximum core version {apworld.maximum_ap_version} "
|
||||
f"is lower than current core version {version_tuple}.")
|
||||
else:
|
||||
core_compatible.append((apworld_source, apworld))
|
||||
# load highest version first
|
||||
core_compatible.sort(
|
||||
key=lambda element: element[1].world_version if element[1].world_version else Version(0, 0, 0),
|
||||
reverse=True)
|
||||
|
||||
apworld_module_specs = {}
|
||||
class APWorldModuleFinder(importlib.abc.MetaPathFinder):
|
||||
def find_spec(
|
||||
self, fullname: str, _path: Sequence[str] | None, _target: ModuleType = None
|
||||
) -> importlib.machinery.ModuleSpec | None:
|
||||
return apworld_module_specs.get(fullname)
|
||||
|
||||
sys.meta_path.insert(0, APWorldModuleFinder())
|
||||
|
||||
for apworld_source, apworld in core_compatible:
|
||||
if apworld.game and apworld.game in AutoWorldRegister.world_types:
|
||||
fail_world(apworld.game,
|
||||
f"Did not load {apworld_source.path} "
|
||||
f"as its game {apworld.game} is already loaded.",
|
||||
add_as_failed_to_load=False)
|
||||
else:
|
||||
importer = zipimport.zipimporter(apworld_source.resolved_path)
|
||||
world_name = Path(apworld.path).stem
|
||||
|
||||
spec = importer.find_spec(f"worlds.{world_name}")
|
||||
apworld_module_specs[f"worlds.{world_name}"] = spec
|
||||
|
||||
apworld_source.load()
|
||||
if apworld.game in AutoWorldRegister.world_types:
|
||||
# world could fail to load at this point
|
||||
if apworld.world_version:
|
||||
AutoWorldRegister.world_types[apworld.game].world_version = apworld.world_version
|
||||
load_apworlds()
|
||||
del load_apworlds
|
||||
|
||||
del apworlds
|
||||
|
||||
# Build the data package for each game.
|
||||
network_data_package: DataPackage = {
|
||||
"games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()},
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ async def lock(ctx) -> None
|
||||
async def unlock(ctx) -> None
|
||||
|
||||
async def get_hash(ctx) -> str
|
||||
async def get_memory_size(ctx, domain: str) -> int
|
||||
async def get_system(ctx) -> str
|
||||
async def get_cores(ctx) -> dict[str, str]
|
||||
async def ping(ctx) -> None
|
||||
@@ -168,9 +169,10 @@ select dialog and they will be associated with BizHawkClient. This does not affe
|
||||
associate the file extension with Archipelago.
|
||||
|
||||
`validate_rom` is called to figure out whether a given ROM belongs to your client. It will only be called when a ROM is
|
||||
running on a system you specified in your `system` class variable. In most cases, that will be a single system and you
|
||||
can be sure that you're not about to try to read from nonexistent domains or out of bounds. If you decide to claim this
|
||||
ROM as yours, this is where you should do setup for things like `items_handling`.
|
||||
running on a system you specified in your `system` class variable. Take extra care here, because your code will run
|
||||
against ROMs that you have no control over. If you're reading an address deep in ROM, you might want to check the size
|
||||
of ROM before you attempt to read it using `get_memory_size`. If you decide to claim this ROM as yours, this is where
|
||||
you should do setup for things like `items_handling`.
|
||||
|
||||
`game_watcher` is the "main loop" of your client where you should be checking memory and sending new items to the ROM.
|
||||
`BizHawkClient` will make sure that your `game_watcher` only runs when your client has validated the ROM, and will do
|
||||
@@ -268,6 +270,8 @@ server connection before trying to interact with it.
|
||||
- By default, the player will be asked to provide their slot name after connecting to the server and validating, and
|
||||
that input will be used to authenticate with the `Connect` command. You can override `set_auth` in your own client to
|
||||
set it automatically based on data in the ROM or on your client instance.
|
||||
- Use `get_memory_size` inside `validate_rom` if you need to read at large addresses, in case some other game has a
|
||||
smaller ROM size.
|
||||
- You can override `on_package` in your client to watch raw packages, but don't forget you also have access to a
|
||||
subclass of `CommonContext` and its API.
|
||||
- You can import `BizHawkClientContext` for type hints using `typing.TYPE_CHECKING`. Importing it without conditions at
|
||||
|
||||
+24
-14
@@ -10,7 +10,7 @@ import base64
|
||||
import enum
|
||||
import json
|
||||
import sys
|
||||
import typing
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
BIZHAWK_SOCKET_PORT_RANGE_START = 43055
|
||||
@@ -44,10 +44,10 @@ class SyncError(Exception):
|
||||
|
||||
|
||||
class BizHawkContext:
|
||||
streams: typing.Optional[typing.Tuple[asyncio.StreamReader, asyncio.StreamWriter]]
|
||||
streams: tuple[asyncio.StreamReader, asyncio.StreamWriter] | None
|
||||
connection_status: ConnectionStatus
|
||||
_lock: asyncio.Lock
|
||||
_port: typing.Optional[int]
|
||||
_port: int | None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.streams = None
|
||||
@@ -122,12 +122,12 @@ async def get_script_version(ctx: BizHawkContext) -> int:
|
||||
return int(await ctx._send_message("VERSION"))
|
||||
|
||||
|
||||
async def send_requests(ctx: BizHawkContext, req_list: typing.List[typing.Dict[str, typing.Any]]) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
async def send_requests(ctx: BizHawkContext, req_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Sends a list of requests to the BizHawk connector and returns their responses.
|
||||
|
||||
It's likely you want to use the wrapper functions instead of this."""
|
||||
responses = json.loads(await ctx._send_message(json.dumps(req_list)))
|
||||
errors: typing.List[ConnectorError] = []
|
||||
errors: list[ConnectorError] = []
|
||||
|
||||
for response in responses:
|
||||
if response["type"] == "ERROR":
|
||||
@@ -151,7 +151,7 @@ async def ping(ctx: BizHawkContext) -> None:
|
||||
|
||||
|
||||
async def get_hash(ctx: BizHawkContext) -> str:
|
||||
"""Gets the system name for the currently loaded ROM"""
|
||||
"""Gets the hash value of the currently loaded ROM"""
|
||||
res = (await send_requests(ctx, [{"type": "HASH"}]))[0]
|
||||
|
||||
if res["type"] != "HASH_RESPONSE":
|
||||
@@ -160,6 +160,16 @@ async def get_hash(ctx: BizHawkContext) -> str:
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def get_memory_size(ctx: BizHawkContext, domain: str) -> int:
|
||||
"""Gets the size in bytes of the specified memory domain"""
|
||||
res = (await send_requests(ctx, [{"type": "MEMORY_SIZE", "domain": domain}]))[0]
|
||||
|
||||
if res["type"] != "MEMORY_SIZE_RESPONSE":
|
||||
raise SyncError(f"Expected response of type MEMORY_SIZE_RESPONSE but got {res['type']}")
|
||||
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def get_system(ctx: BizHawkContext) -> str:
|
||||
"""Gets the system name for the currently loaded ROM"""
|
||||
res = (await send_requests(ctx, [{"type": "SYSTEM"}]))[0]
|
||||
@@ -170,7 +180,7 @@ async def get_system(ctx: BizHawkContext) -> str:
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def get_cores(ctx: BizHawkContext) -> typing.Dict[str, str]:
|
||||
async def get_cores(ctx: BizHawkContext) -> dict[str, str]:
|
||||
"""Gets the preferred cores for systems with multiple cores. Only systems with multiple available cores have
|
||||
entries."""
|
||||
res = (await send_requests(ctx, [{"type": "PREFERRED_CORES"}]))[0]
|
||||
@@ -223,8 +233,8 @@ async def set_message_interval(ctx: BizHawkContext, value: float) -> None:
|
||||
raise SyncError(f"Expected response of type SET_MESSAGE_INTERVAL_RESPONSE but got {res['type']}")
|
||||
|
||||
|
||||
async def guarded_read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]],
|
||||
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> typing.Optional[typing.List[bytes]]:
|
||||
async def guarded_read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]],
|
||||
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> list[bytes] | None:
|
||||
"""Reads an array of bytes at 1 or more addresses if and only if every byte in guard_list matches its expected
|
||||
value.
|
||||
|
||||
@@ -252,7 +262,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tu
|
||||
"domain": domain
|
||||
} for address, size, domain in read_list])
|
||||
|
||||
ret: typing.List[bytes] = []
|
||||
ret: list[bytes] = []
|
||||
for item in res:
|
||||
if item["type"] == "GUARD_RESPONSE":
|
||||
if not item["value"]:
|
||||
@@ -266,7 +276,7 @@ async def guarded_read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tu
|
||||
return ret
|
||||
|
||||
|
||||
async def read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
|
||||
async def read(ctx: BizHawkContext, read_list: Sequence[tuple[int, int, str]]) -> list[bytes]:
|
||||
"""Reads data at 1 or more addresses.
|
||||
|
||||
Items in `read_list` should be organized `(address, size, domain)` where
|
||||
@@ -278,8 +288,8 @@ async def read(ctx: BizHawkContext, read_list: typing.Sequence[typing.Tuple[int,
|
||||
return await guarded_read(ctx, read_list, [])
|
||||
|
||||
|
||||
async def guarded_write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]],
|
||||
guard_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> bool:
|
||||
async def guarded_write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]],
|
||||
guard_list: Sequence[tuple[int, Sequence[int], str]]) -> bool:
|
||||
"""Writes data to 1 or more addresses if and only if every byte in guard_list matches its expected value.
|
||||
|
||||
Items in `write_list` should be organized `(address, value, domain)` where
|
||||
@@ -316,7 +326,7 @@ async def guarded_write(ctx: BizHawkContext, write_list: typing.Sequence[typing.
|
||||
return True
|
||||
|
||||
|
||||
async def write(ctx: BizHawkContext, write_list: typing.Sequence[typing.Tuple[int, typing.Sequence[int], str]]) -> None:
|
||||
async def write(ctx: BizHawkContext, write_list: Sequence[tuple[int, Sequence[int], str]]) -> None:
|
||||
"""Writes data to 1 or more addresses.
|
||||
|
||||
Items in write_list should be organized `(address, value, domain)` where
|
||||
|
||||
@@ -5,9 +5,9 @@ A module containing the BizHawkClient base class and metaclass
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
|
||||
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch as launch_component
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .context import BizHawkClientContext
|
||||
@@ -15,18 +15,19 @@ if TYPE_CHECKING:
|
||||
|
||||
def launch_client(*args) -> None:
|
||||
from .context import launch
|
||||
launch_subprocess(launch, name="BizHawkClient", args=args)
|
||||
launch_component(launch, name="BizHawkClient", args=args)
|
||||
|
||||
|
||||
component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
|
||||
file_identifier=SuffixIdentifier())
|
||||
file_identifier=SuffixIdentifier(),
|
||||
description="Open the BizHawk client, to play games using the Bizhawk emulator.")
|
||||
components.append(component)
|
||||
|
||||
|
||||
class AutoBizHawkClientRegister(abc.ABCMeta):
|
||||
game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
|
||||
game_handlers: ClassVar[dict[tuple[str, ...], dict[str, BizHawkClient]]] = {}
|
||||
|
||||
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
|
||||
def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> AutoBizHawkClientRegister:
|
||||
new_class = super().__new__(cls, name, bases, namespace)
|
||||
|
||||
# Register handler
|
||||
@@ -54,7 +55,7 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
|
||||
return new_class
|
||||
|
||||
@staticmethod
|
||||
async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]:
|
||||
async def get_handler(ctx: "BizHawkClientContext", system: str) -> BizHawkClient | None:
|
||||
for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
|
||||
if system in systems:
|
||||
for handler in handlers.values():
|
||||
@@ -65,13 +66,13 @@ class AutoBizHawkClientRegister(abc.ABCMeta):
|
||||
|
||||
|
||||
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
|
||||
system: ClassVar[Union[str, Tuple[str, ...]]]
|
||||
system: ClassVar[str | tuple[str, ...]]
|
||||
"""The system(s) that the game this client is for runs on"""
|
||||
|
||||
game: ClassVar[str]
|
||||
"""The game this client is for"""
|
||||
|
||||
patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
|
||||
patch_suffix: ClassVar[str | tuple[str, ...] | None]
|
||||
"""The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
||||
+130
-21
@@ -4,16 +4,18 @@ checking or launching the client, otherwise it will probably cause circular impo
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import enum
|
||||
import subprocess
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
import settings
|
||||
from CommonClient import CommonContext, ClientCommandProcessor, get_base_parser, server_loop, logger, gui_enabled
|
||||
import Patch
|
||||
import Utils
|
||||
|
||||
from . import BizHawkContext, ConnectionStatus, NotConnectedError, RequestFailedError, connect, disconnect, get_hash, \
|
||||
get_script_version, get_system, ping
|
||||
get_script_version, get_system, ping, display_message
|
||||
from .client import BizHawkClient, AutoBizHawkClientRegister
|
||||
|
||||
|
||||
@@ -27,38 +29,137 @@ class AuthStatus(enum.IntEnum):
|
||||
AUTHENTICATED = 3
|
||||
|
||||
|
||||
class TextCategory(str, enum.Enum):
|
||||
ALL = "all"
|
||||
INCOMING = "incoming"
|
||||
OUTGOING = "outgoing"
|
||||
OTHER = "other"
|
||||
HINT = "hint"
|
||||
CHAT = "chat"
|
||||
SERVER = "server"
|
||||
|
||||
|
||||
class BizHawkClientCommandProcessor(ClientCommandProcessor):
|
||||
def _cmd_bh(self):
|
||||
"""Shows the current status of the client's connection to BizHawk"""
|
||||
if isinstance(self.ctx, BizHawkClientContext):
|
||||
if self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.NOT_CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Not Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.TENTATIVE:
|
||||
logger.info("BizHawk Connection Status: Tentatively Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Connected")
|
||||
assert isinstance(self.ctx, BizHawkClientContext)
|
||||
|
||||
if self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.NOT_CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Not Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.TENTATIVE:
|
||||
logger.info("BizHawk Connection Status: Tentatively Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Connected")
|
||||
|
||||
def _cmd_toggle_text(self, category: str | None = None, toggle: str | None = None):
|
||||
"""Sets types of incoming messages to forward to the emulator"""
|
||||
assert isinstance(self.ctx, BizHawkClientContext)
|
||||
|
||||
if category is None:
|
||||
logger.info("Usage: /toggle_text category [toggle]\n\n"
|
||||
"category: incoming, outgoing, other, hint, chat, and server\n"
|
||||
"Or \"all\" to toggle all categories at once\n\n"
|
||||
"toggle: on, off, true, or false\n"
|
||||
"Or omit to set it to the opposite of its current state\n\n"
|
||||
"Example: /toggle_text outgoing on")
|
||||
return
|
||||
|
||||
category = category.lower()
|
||||
value: bool | None
|
||||
if toggle is None:
|
||||
value = None
|
||||
elif toggle.lower() in ("on", "true"):
|
||||
value = True
|
||||
elif toggle.lower() in ("off", "false"):
|
||||
value = False
|
||||
else:
|
||||
logger.info(f'Unknown value "{toggle}", should be on|off|true|false')
|
||||
return
|
||||
|
||||
valid_categories = (
|
||||
TextCategory.ALL,
|
||||
TextCategory.OTHER,
|
||||
TextCategory.INCOMING,
|
||||
TextCategory.OUTGOING,
|
||||
TextCategory.HINT,
|
||||
TextCategory.CHAT,
|
||||
TextCategory.SERVER,
|
||||
)
|
||||
if category not in valid_categories:
|
||||
logger.info(f'Unknown value "{category}", should be {"|".join(valid_categories)}')
|
||||
return
|
||||
|
||||
if category == TextCategory.ALL:
|
||||
if value is None:
|
||||
logger.info('Must specify "on" or "off" for category "all"')
|
||||
return
|
||||
|
||||
if value:
|
||||
self.ctx.text_passthrough_categories.update((
|
||||
TextCategory.OTHER,
|
||||
TextCategory.INCOMING,
|
||||
TextCategory.OUTGOING,
|
||||
TextCategory.HINT,
|
||||
TextCategory.CHAT,
|
||||
TextCategory.SERVER,
|
||||
))
|
||||
else:
|
||||
self.ctx.text_passthrough_categories.clear()
|
||||
else:
|
||||
if value is None:
|
||||
value = category not in self.ctx.text_passthrough_categories
|
||||
|
||||
if value:
|
||||
self.ctx.text_passthrough_categories.add(category)
|
||||
else:
|
||||
self.ctx.text_passthrough_categories.remove(category)
|
||||
|
||||
logger.info(f"Currently Showing Categories: {', '.join(self.ctx.text_passthrough_categories)}")
|
||||
|
||||
|
||||
class BizHawkClientContext(CommonContext):
|
||||
command_processor = BizHawkClientCommandProcessor
|
||||
text_passthrough_categories: set[str]
|
||||
server_seed_name: str | None = None
|
||||
auth_status: AuthStatus
|
||||
password_requested: bool
|
||||
client_handler: Optional[BizHawkClient]
|
||||
slot_data: Optional[Dict[str, Any]] = None
|
||||
rom_hash: Optional[str] = None
|
||||
client_handler: BizHawkClient | None
|
||||
slot_data: dict[str, Any] | None = None
|
||||
rom_hash: str | None = None
|
||||
bizhawk_ctx: BizHawkContext
|
||||
|
||||
watcher_timeout: float
|
||||
"""The maximum amount of time the game watcher loop will wait for an update from the server before executing"""
|
||||
|
||||
def __init__(self, server_address: Optional[str], password: Optional[str]):
|
||||
def __init__(self, server_address: str | None, password: str | None):
|
||||
super().__init__(server_address, password)
|
||||
self.text_passthrough_categories = set()
|
||||
self.auth_status = AuthStatus.NOT_AUTHENTICATED
|
||||
self.password_requested = False
|
||||
self.client_handler = None
|
||||
self.bizhawk_ctx = BizHawkContext()
|
||||
self.watcher_timeout = 0.5
|
||||
|
||||
def _categorize_text(self, args: dict) -> TextCategory:
|
||||
if "type" not in args or args["type"] in {"Hint", "Join", "Part", "TagsChanged", "Goal", "Release", "Collect",
|
||||
"Countdown", "ServerChat", "ItemCheat"}:
|
||||
return TextCategory.SERVER
|
||||
elif args["type"] == "Chat":
|
||||
return TextCategory.CHAT
|
||||
elif args["type"] == "ItemSend":
|
||||
if args["item"].player == self.slot:
|
||||
return TextCategory.OUTGOING
|
||||
elif args["receiving"] == self.slot:
|
||||
return TextCategory.INCOMING
|
||||
else:
|
||||
return TextCategory.OTHER
|
||||
|
||||
def on_print_json(self, args: dict):
|
||||
super().on_print_json(args)
|
||||
if self.bizhawk_ctx.connection_status == ConnectionStatus.CONNECTED:
|
||||
if self._categorize_text(args) in self.text_passthrough_categories:
|
||||
Utils.async_start(display_message(self.bizhawk_ctx, self.rawjsontotextparser(copy.deepcopy(args["data"]))))
|
||||
|
||||
def make_gui(self):
|
||||
ui = super().make_gui()
|
||||
ui.base_title = "Archipelago BizHawk Client"
|
||||
@@ -68,6 +169,8 @@ class BizHawkClientContext(CommonContext):
|
||||
if cmd == "Connected":
|
||||
self.slot_data = args.get("slot_data", None)
|
||||
self.auth_status = AuthStatus.AUTHENTICATED
|
||||
elif cmd == "RoomInfo":
|
||||
self.server_seed_name = args.get("seed_name", None)
|
||||
|
||||
if self.client_handler is not None:
|
||||
self.client_handler.on_package(self, cmd, args)
|
||||
@@ -100,6 +203,7 @@ class BizHawkClientContext(CommonContext):
|
||||
|
||||
async def disconnect(self, allow_autoreconnect: bool=False):
|
||||
self.auth_status = AuthStatus.NOT_AUTHENTICATED
|
||||
self.server_seed_name = None
|
||||
await super().disconnect(allow_autoreconnect)
|
||||
|
||||
|
||||
@@ -201,10 +305,10 @@ async def _game_watcher(ctx: BizHawkClientContext):
|
||||
|
||||
async def _run_game(rom: str):
|
||||
import os
|
||||
auto_start = Utils.get_settings().bizhawkclient_options.rom_start
|
||||
auto_start = settings.get_settings().bizhawkclient_options.rom_start
|
||||
|
||||
if auto_start is True:
|
||||
emuhawk_path = Utils.get_settings().bizhawkclient_options.emuhawk_path
|
||||
emuhawk_path = settings.get_settings().bizhawkclient_options.emuhawk_path
|
||||
subprocess.Popen(
|
||||
[
|
||||
emuhawk_path,
|
||||
@@ -231,20 +335,28 @@ async def _run_game(rom: str):
|
||||
)
|
||||
|
||||
|
||||
async def _patch_and_run_game(patch_file: str):
|
||||
def _patch_and_run_game(patch_file: str):
|
||||
try:
|
||||
metadata, output_file = Patch.create_rom_file(patch_file)
|
||||
Utils.async_start(_run_game(output_file))
|
||||
return metadata
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
Utils.messagebox("Error Patching Game", str(exc), True)
|
||||
return {}
|
||||
|
||||
|
||||
def launch(*launch_args) -> None:
|
||||
def launch(*launch_args: str) -> None:
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
|
||||
args = parser.parse_args(launch_args)
|
||||
|
||||
if args.patch_file != "":
|
||||
metadata = _patch_and_run_game(args.patch_file)
|
||||
if "server" in metadata:
|
||||
args.connect = metadata["server"]
|
||||
|
||||
ctx = BizHawkClientContext(args.connect, args.password)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||
|
||||
@@ -252,9 +364,6 @@ def launch(*launch_args) -> None:
|
||||
ctx.run_gui()
|
||||
ctx.run_cli()
|
||||
|
||||
if args.patch_file != "":
|
||||
Utils.async_start(_patch_and_run_game(args.patch_file))
|
||||
|
||||
watcher_task = asyncio.create_task(_game_watcher(ctx), name="GameWatcher")
|
||||
|
||||
try:
|
||||
@@ -267,6 +376,6 @@ def launch(*launch_args) -> None:
|
||||
|
||||
Utils.init_logging("BizHawkClient", exception_logger="Client")
|
||||
import colorama
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -9,7 +9,7 @@ from collections import Counter
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Dict, Generator, Iterable, List, Set, Tuple, Union, final
|
||||
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
from .constants import (
|
||||
IS_PLACEHOLDER,
|
||||
|
||||
@@ -4,11 +4,11 @@ from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
from worlds._sc2common.bot import logger
|
||||
|
||||
from s2clientprotocol import debug_pb2 as debug_pb
|
||||
from s2clientprotocol import query_pb2 as query_pb
|
||||
from s2clientprotocol import raw_pb2 as raw_pb
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from s2clientprotocol import spatial_pb2 as spatial_pb
|
||||
from .proto import debug_pb2 as debug_pb
|
||||
from .proto import query_pb2 as query_pb
|
||||
from .proto import raw_pb2 as raw_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
from .proto import spatial_pb2 as spatial_pb
|
||||
|
||||
from .data import ActionResult, ChatChannel, Race, Result, Status
|
||||
from .game_data import AbilityData, GameData
|
||||
|
||||
@@ -2,7 +2,7 @@ import platform
|
||||
from pathlib import Path
|
||||
|
||||
from worlds._sc2common.bot import logger
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
from .player import Computer
|
||||
from .protocol import Protocol
|
||||
|
||||
@@ -7,11 +7,11 @@ https://github.com/Blizzard/s2client-api/blob/d9ba0a33d6ce9d233c2a4ee988360c188f
|
||||
"""
|
||||
import enum
|
||||
|
||||
from s2clientprotocol import common_pb2 as common_pb
|
||||
from s2clientprotocol import data_pb2 as data_pb
|
||||
from s2clientprotocol import error_pb2 as error_pb
|
||||
from s2clientprotocol import raw_pb2 as raw_pb
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import common_pb2 as common_pb
|
||||
from .proto import data_pb2 as data_pb
|
||||
from .proto import error_pb2 as error_pb
|
||||
from .proto import raw_pb2 as raw_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
CreateGameError = enum.Enum("CreateGameError", sc_pb.ResponseCreateGame.Error.items())
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class GameData:
|
||||
"""
|
||||
:param data:
|
||||
"""
|
||||
self.abilities: Dict[int, AbilityData] = {}
|
||||
self.abilities: Dict[int, AbilityData] = {a.ability_id: AbilityData(self, a) for a in data.abilities if a.available}
|
||||
self.units: Dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available}
|
||||
self.upgrades: Dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades}
|
||||
# Cached UnitTypeIds so that conversion does not take long. This needs to be moved elsewhere if a new GameData object is created multiple times per game
|
||||
@@ -40,7 +40,7 @@ class AbilityData:
|
||||
self._proto = proto
|
||||
|
||||
# What happens if we comment this out? Should this not be commented out? What is its purpose?
|
||||
assert self.id != 0
|
||||
# assert self.id != 0 # let the world burn
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"AbilityData(name={self._proto.button_name})"
|
||||
|
||||
@@ -15,7 +15,7 @@ import mpyq
|
||||
import portpicker
|
||||
from aiohttp import ClientSession, ClientWebSocketResponse
|
||||
from worlds._sc2common.bot import logger
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
from .bot_ai import BotAI
|
||||
from .client import Client
|
||||
|
||||
@@ -5,7 +5,7 @@ import math
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Iterable, List, Set, Tuple, Union
|
||||
|
||||
from s2clientprotocol import common_pb2 as common_pb
|
||||
from .proto import common_pb2 as common_pb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .unit import Unit
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/common.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/common.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ds2clientprotocol/common.proto\x12\x0eSC2APIProtocol\">\n\x10\x41vailableAbility\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x16\n\x0erequires_point\x18\x02 \x01(\x08\"X\n\tImageData\x12\x16\n\x0e\x62its_per_pixel\x18\x01 \x01(\x05\x12%\n\x04size\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Size2DI\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"\x1e\n\x06PointI\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05\"T\n\nRectangleI\x12\"\n\x02p0\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\x12\"\n\x02p1\x18\x02 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\"\x1f\n\x07Point2D\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\"(\n\x05Point\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"\x1f\n\x07Size2DI\x12\t\n\x01x\x18\x01 \x01(\x05\x12\t\n\x01y\x18\x02 \x01(\x05*A\n\x04Race\x12\n\n\x06NoRace\x10\x00\x12\n\n\x06Terran\x10\x01\x12\x08\n\x04Zerg\x10\x02\x12\x0b\n\x07Protoss\x10\x03\x12\n\n\x06Random\x10\x04')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.common_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_RACE']._serialized_start=429
|
||||
_globals['_RACE']._serialized_end=494
|
||||
_globals['_AVAILABLEABILITY']._serialized_start=49
|
||||
_globals['_AVAILABLEABILITY']._serialized_end=111
|
||||
_globals['_IMAGEDATA']._serialized_start=113
|
||||
_globals['_IMAGEDATA']._serialized_end=201
|
||||
_globals['_POINTI']._serialized_start=203
|
||||
_globals['_POINTI']._serialized_end=233
|
||||
_globals['_RECTANGLEI']._serialized_start=235
|
||||
_globals['_RECTANGLEI']._serialized_end=319
|
||||
_globals['_POINT2D']._serialized_start=321
|
||||
_globals['_POINT2D']._serialized_end=352
|
||||
_globals['_POINT']._serialized_start=354
|
||||
_globals['_POINT']._serialized_end=394
|
||||
_globals['_SIZE2DI']._serialized_start=396
|
||||
_globals['_SIZE2DI']._serialized_end=427
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/data.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/data.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from . import common_pb2 as s2clientprotocol_dot_common__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bs2clientprotocol/data.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\xc4\x03\n\x0b\x41\x62ilityData\x12\x12\n\nability_id\x18\x01 \x01(\r\x12\x11\n\tlink_name\x18\x02 \x01(\t\x12\x12\n\nlink_index\x18\x03 \x01(\r\x12\x13\n\x0b\x62utton_name\x18\x04 \x01(\t\x12\x15\n\rfriendly_name\x18\x05 \x01(\t\x12\x0e\n\x06hotkey\x18\x06 \x01(\t\x12\x1c\n\x14remaps_to_ability_id\x18\x07 \x01(\r\x12\x11\n\tavailable\x18\x08 \x01(\x08\x12\x32\n\x06target\x18\t \x01(\x0e\x32\".SC2APIProtocol.AbilityData.Target\x12\x15\n\rallow_minimap\x18\n \x01(\x08\x12\x16\n\x0e\x61llow_autocast\x18\x0b \x01(\x08\x12\x13\n\x0bis_building\x18\x0c \x01(\x08\x12\x18\n\x10\x66ootprint_radius\x18\r \x01(\x02\x12\x1c\n\x14is_instant_placement\x18\x0e \x01(\x08\x12\x12\n\ncast_range\x18\x0f \x01(\x02\"I\n\x06Target\x12\x08\n\x04None\x10\x01\x12\t\n\x05Point\x10\x02\x12\x08\n\x04Unit\x10\x03\x12\x0f\n\x0bPointOrUnit\x10\x04\x12\x0f\n\x0bPointOrNone\x10\x05\"J\n\x0b\x44\x61mageBonus\x12,\n\tattribute\x18\x01 \x01(\x0e\x32\x19.SC2APIProtocol.Attribute\x12\r\n\x05\x62onus\x18\x02 \x01(\x02\"\xd7\x01\n\x06Weapon\x12/\n\x04type\x18\x01 \x01(\x0e\x32!.SC2APIProtocol.Weapon.TargetType\x12\x0e\n\x06\x64\x61mage\x18\x02 \x01(\x02\x12\x31\n\x0c\x64\x61mage_bonus\x18\x03 \x03(\x0b\x32\x1b.SC2APIProtocol.DamageBonus\x12\x0f\n\x07\x61ttacks\x18\x04 \x01(\r\x12\r\n\x05range\x18\x05 \x01(\x02\x12\r\n\x05speed\x18\x06 \x01(\x02\"*\n\nTargetType\x12\n\n\x06Ground\x10\x01\x12\x07\n\x03\x41ir\x10\x02\x12\x07\n\x03\x41ny\x10\x03\"\x95\x04\n\x0cUnitTypeData\x12\x0f\n\x07unit_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\tavailable\x18\x03 \x01(\x08\x12\x12\n\ncargo_size\x18\x04 \x01(\r\x12\x14\n\x0cmineral_cost\x18\x0c \x01(\r\x12\x14\n\x0cvespene_cost\x18\r \x01(\r\x12\x15\n\rfood_required\x18\x0e \x01(\x02\x12\x15\n\rfood_provided\x18\x12 \x01(\x02\x12\x12\n\nability_id\x18\x0f \x01(\r\x12\"\n\x04race\x18\x10 \x01(\x0e\x32\x14.SC2APIProtocol.Race\x12\x12\n\nbuild_time\x18\x11 \x01(\x02\x12\x13\n\x0bhas_vespene\x18\x13 \x01(\x08\x12\x14\n\x0chas_minerals\x18\x14 \x01(\x08\x12\x13\n\x0bsight_range\x18\x19 \x01(\x02\x12\x12\n\ntech_alias\x18\x15 \x03(\r\x12\x12\n\nunit_alias\x18\x16 \x01(\r\x12\x18\n\x10tech_requirement\x18\x17 \x01(\r\x12\x18\n\x10require_attached\x18\x18 \x01(\x08\x12-\n\nattributes\x18\x08 \x03(\x0e\x32\x19.SC2APIProtocol.Attribute\x12\x16\n\x0emovement_speed\x18\t \x01(\x02\x12\r\n\x05\x61rmor\x18\n \x01(\x02\x12\'\n\x07weapons\x18\x0b \x03(\x0b\x32\x16.SC2APIProtocol.Weapon\"\x86\x01\n\x0bUpgradeData\x12\x12\n\nupgrade_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x14\n\x0cmineral_cost\x18\x03 \x01(\r\x12\x14\n\x0cvespene_cost\x18\x04 \x01(\r\x12\x15\n\rresearch_time\x18\x05 \x01(\x02\x12\x12\n\nability_id\x18\x06 \x01(\r\")\n\x08\x42uffData\x12\x0f\n\x07\x62uff_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\"T\n\nEffectData\x12\x11\n\teffect_id\x18\x01 \x01(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x15\n\rfriendly_name\x18\x03 \x01(\t\x12\x0e\n\x06radius\x18\x04 \x01(\x02*\x9e\x01\n\tAttribute\x12\t\n\x05Light\x10\x01\x12\x0b\n\x07\x41rmored\x10\x02\x12\x0e\n\nBiological\x10\x03\x12\x0e\n\nMechanical\x10\x04\x12\x0b\n\x07Robotic\x10\x05\x12\x0b\n\x07Psionic\x10\x06\x12\x0b\n\x07Massive\x10\x07\x12\r\n\tStructure\x10\x08\x12\t\n\x05Hover\x10\t\x12\n\n\x06Heroic\x10\n\x12\x0c\n\x08Summoned\x10\x0b')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.data_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_ATTRIBUTE']._serialized_start=1630
|
||||
_globals['_ATTRIBUTE']._serialized_end=1788
|
||||
_globals['_ABILITYDATA']._serialized_start=79
|
||||
_globals['_ABILITYDATA']._serialized_end=531
|
||||
_globals['_ABILITYDATA_TARGET']._serialized_start=458
|
||||
_globals['_ABILITYDATA_TARGET']._serialized_end=531
|
||||
_globals['_DAMAGEBONUS']._serialized_start=533
|
||||
_globals['_DAMAGEBONUS']._serialized_end=607
|
||||
_globals['_WEAPON']._serialized_start=610
|
||||
_globals['_WEAPON']._serialized_end=825
|
||||
_globals['_WEAPON_TARGETTYPE']._serialized_start=783
|
||||
_globals['_WEAPON_TARGETTYPE']._serialized_end=825
|
||||
_globals['_UNITTYPEDATA']._serialized_start=828
|
||||
_globals['_UNITTYPEDATA']._serialized_end=1361
|
||||
_globals['_UPGRADEDATA']._serialized_start=1364
|
||||
_globals['_UPGRADEDATA']._serialized_end=1498
|
||||
_globals['_BUFFDATA']._serialized_start=1500
|
||||
_globals['_BUFFDATA']._serialized_end=1541
|
||||
_globals['_EFFECTDATA']._serialized_start=1543
|
||||
_globals['_EFFECTDATA']._serialized_end=1627
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/debug.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/debug.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from . import common_pb2 as s2clientprotocol_dot_common__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/debug.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\xbb\x03\n\x0c\x44\x65\x62ugCommand\x12)\n\x04\x64raw\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.DebugDrawH\x00\x12\x34\n\ngame_state\x18\x02 \x01(\x0e\x32\x1e.SC2APIProtocol.DebugGameStateH\x00\x12\x36\n\x0b\x63reate_unit\x18\x03 \x01(\x0b\x32\x1f.SC2APIProtocol.DebugCreateUnitH\x00\x12\x32\n\tkill_unit\x18\x04 \x01(\x0b\x32\x1d.SC2APIProtocol.DebugKillUnitH\x00\x12\x38\n\x0ctest_process\x18\x05 \x01(\x0b\x32 .SC2APIProtocol.DebugTestProcessH\x00\x12.\n\x05score\x18\x06 \x01(\x0b\x32\x1d.SC2APIProtocol.DebugSetScoreH\x00\x12\x30\n\x08\x65nd_game\x18\x07 \x01(\x0b\x32\x1c.SC2APIProtocol.DebugEndGameH\x00\x12\x37\n\nunit_value\x18\x08 \x01(\x0b\x32!.SC2APIProtocol.DebugSetUnitValueH\x00\x42\t\n\x07\x63ommand\"\xb5\x01\n\tDebugDraw\x12\'\n\x04text\x18\x01 \x03(\x0b\x32\x19.SC2APIProtocol.DebugText\x12(\n\x05lines\x18\x02 \x03(\x0b\x32\x19.SC2APIProtocol.DebugLine\x12\'\n\x05\x62oxes\x18\x03 \x03(\x0b\x32\x18.SC2APIProtocol.DebugBox\x12,\n\x07spheres\x18\x04 \x03(\x0b\x32\x1b.SC2APIProtocol.DebugSphere\"L\n\x04Line\x12!\n\x02p0\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12!\n\x02p1\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\"(\n\x05\x43olor\x12\t\n\x01r\x18\x01 \x01(\r\x12\t\n\x01g\x18\x02 \x01(\r\x12\t\n\x01\x62\x18\x03 \x01(\r\"\xa3\x01\n\tDebugText\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\x0c\n\x04text\x18\x02 \x01(\t\x12*\n\x0bvirtual_pos\x18\x03 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12(\n\tworld_pos\x18\x04 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\x0c\n\x04size\x18\x05 \x01(\r\"U\n\tDebugLine\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\"\n\x04line\x18\x02 \x01(\x0b\x32\x14.SC2APIProtocol.Line\"x\n\x08\x44\x65\x62ugBox\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12\"\n\x03min\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\"\n\x03max\x18\x03 \x01(\x0b\x32\x15.SC2APIProtocol.Point\"`\n\x0b\x44\x65\x62ugSphere\x12$\n\x05\x63olor\x18\x01 \x01(\x0b\x32\x15.SC2APIProtocol.Color\x12 \n\x01p\x18\x02 \x01(\x0b\x32\x15.SC2APIProtocol.Point\x12\t\n\x01r\x18\x03 \x01(\x02\"k\n\x0f\x44\x65\x62ugCreateUnit\x12\x11\n\tunit_type\x18\x01 \x01(\r\x12\r\n\x05owner\x18\x02 \x01(\x05\x12$\n\x03pos\x18\x03 \x01(\x0b\x32\x17.SC2APIProtocol.Point2D\x12\x10\n\x08quantity\x18\x04 \x01(\r\"\x1c\n\rDebugKillUnit\x12\x0b\n\x03tag\x18\x01 \x03(\x04\"\x80\x01\n\x10\x44\x65\x62ugTestProcess\x12\x33\n\x04test\x18\x01 \x01(\x0e\x32%.SC2APIProtocol.DebugTestProcess.Test\x12\x10\n\x08\x64\x65lay_ms\x18\x02 \x01(\x05\"%\n\x04Test\x12\x08\n\x04hang\x10\x01\x12\t\n\x05\x63rash\x10\x02\x12\x08\n\x04\x65xit\x10\x03\"\x1e\n\rDebugSetScore\x12\r\n\x05score\x18\x01 \x01(\x02\"z\n\x0c\x44\x65\x62ugEndGame\x12:\n\nend_result\x18\x01 \x01(\x0e\x32&.SC2APIProtocol.DebugEndGame.EndResult\".\n\tEndResult\x12\r\n\tSurrender\x10\x01\x12\x12\n\x0e\x44\x65\x63lareVictory\x10\x02\"\xa5\x01\n\x11\x44\x65\x62ugSetUnitValue\x12?\n\nunit_value\x18\x01 \x01(\x0e\x32+.SC2APIProtocol.DebugSetUnitValue.UnitValue\x12\r\n\x05value\x18\x02 \x01(\x02\x12\x10\n\x08unit_tag\x18\x03 \x01(\x04\".\n\tUnitValue\x12\n\n\x06\x45nergy\x10\x01\x12\x08\n\x04Life\x10\x02\x12\x0b\n\x07Shields\x10\x03*\xb2\x01\n\x0e\x44\x65\x62ugGameState\x12\x0c\n\x08show_map\x10\x01\x12\x11\n\rcontrol_enemy\x10\x02\x12\x08\n\x04\x66ood\x10\x03\x12\x08\n\x04\x66ree\x10\x04\x12\x11\n\rall_resources\x10\x05\x12\x07\n\x03god\x10\x06\x12\x0c\n\x08minerals\x10\x07\x12\x07\n\x03gas\x10\x08\x12\x0c\n\x08\x63ooldown\x10\t\x12\r\n\ttech_tree\x10\n\x12\x0b\n\x07upgrade\x10\x0b\x12\x0e\n\nfast_build\x10\x0c')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.debug_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_DEBUGGAMESTATE']._serialized_start=1897
|
||||
_globals['_DEBUGGAMESTATE']._serialized_end=2075
|
||||
_globals['_DEBUGCOMMAND']._serialized_start=80
|
||||
_globals['_DEBUGCOMMAND']._serialized_end=523
|
||||
_globals['_DEBUGDRAW']._serialized_start=526
|
||||
_globals['_DEBUGDRAW']._serialized_end=707
|
||||
_globals['_LINE']._serialized_start=709
|
||||
_globals['_LINE']._serialized_end=785
|
||||
_globals['_COLOR']._serialized_start=787
|
||||
_globals['_COLOR']._serialized_end=827
|
||||
_globals['_DEBUGTEXT']._serialized_start=830
|
||||
_globals['_DEBUGTEXT']._serialized_end=993
|
||||
_globals['_DEBUGLINE']._serialized_start=995
|
||||
_globals['_DEBUGLINE']._serialized_end=1080
|
||||
_globals['_DEBUGBOX']._serialized_start=1082
|
||||
_globals['_DEBUGBOX']._serialized_end=1202
|
||||
_globals['_DEBUGSPHERE']._serialized_start=1204
|
||||
_globals['_DEBUGSPHERE']._serialized_end=1300
|
||||
_globals['_DEBUGCREATEUNIT']._serialized_start=1302
|
||||
_globals['_DEBUGCREATEUNIT']._serialized_end=1409
|
||||
_globals['_DEBUGKILLUNIT']._serialized_start=1411
|
||||
_globals['_DEBUGKILLUNIT']._serialized_end=1439
|
||||
_globals['_DEBUGTESTPROCESS']._serialized_start=1442
|
||||
_globals['_DEBUGTESTPROCESS']._serialized_end=1570
|
||||
_globals['_DEBUGTESTPROCESS_TEST']._serialized_start=1533
|
||||
_globals['_DEBUGTESTPROCESS_TEST']._serialized_end=1570
|
||||
_globals['_DEBUGSETSCORE']._serialized_start=1572
|
||||
_globals['_DEBUGSETSCORE']._serialized_end=1602
|
||||
_globals['_DEBUGENDGAME']._serialized_start=1604
|
||||
_globals['_DEBUGENDGAME']._serialized_end=1726
|
||||
_globals['_DEBUGENDGAME_ENDRESULT']._serialized_start=1680
|
||||
_globals['_DEBUGENDGAME_ENDRESULT']._serialized_end=1726
|
||||
_globals['_DEBUGSETUNITVALUE']._serialized_start=1729
|
||||
_globals['_DEBUGSETUNITVALUE']._serialized_end=1894
|
||||
_globals['_DEBUGSETUNITVALUE_UNITVALUE']._serialized_start=1848
|
||||
_globals['_DEBUGSETUNITVALUE_UNITVALUE']._serialized_end=1894
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/query.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/query.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from . import common_pb2 as s2clientprotocol_dot_common__pb2
|
||||
from . import error_pb2 as s2clientprotocol_dot_error__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/query.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\x1a\x1cs2clientprotocol/error.proto\"\xf0\x01\n\x0cRequestQuery\x12\x34\n\x07pathing\x18\x01 \x03(\x0b\x32#.SC2APIProtocol.RequestQueryPathing\x12\x41\n\tabilities\x18\x02 \x03(\x0b\x32..SC2APIProtocol.RequestQueryAvailableAbilities\x12\x41\n\nplacements\x18\x03 \x03(\x0b\x32-.SC2APIProtocol.RequestQueryBuildingPlacement\x12$\n\x1cignore_resource_requirements\x18\x04 \x01(\x08\"\xce\x01\n\rResponseQuery\x12\x35\n\x07pathing\x18\x01 \x03(\x0b\x32$.SC2APIProtocol.ResponseQueryPathing\x12\x42\n\tabilities\x18\x02 \x03(\x0b\x32/.SC2APIProtocol.ResponseQueryAvailableAbilities\x12\x42\n\nplacements\x18\x03 \x03(\x0b\x32..SC2APIProtocol.ResponseQueryBuildingPlacement\"\x8a\x01\n\x13RequestQueryPathing\x12,\n\tstart_pos\x18\x01 \x01(\x0b\x32\x17.SC2APIProtocol.Point2DH\x00\x12\x12\n\x08unit_tag\x18\x02 \x01(\x04H\x00\x12(\n\x07\x65nd_pos\x18\x03 \x01(\x0b\x32\x17.SC2APIProtocol.Point2DB\x07\n\x05start\"(\n\x14ResponseQueryPathing\x12\x10\n\x08\x64istance\x18\x01 \x01(\x02\"2\n\x1eRequestQueryAvailableAbilities\x12\x10\n\x08unit_tag\x18\x01 \x01(\x04\"~\n\x1fResponseQueryAvailableAbilities\x12\x33\n\tabilities\x18\x01 \x03(\x0b\x32 .SC2APIProtocol.AvailableAbility\x12\x10\n\x08unit_tag\x18\x02 \x01(\x04\x12\x14\n\x0cunit_type_id\x18\x03 \x01(\r\"z\n\x1dRequestQueryBuildingPlacement\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12+\n\ntarget_pos\x18\x02 \x01(\x0b\x32\x17.SC2APIProtocol.Point2D\x12\x18\n\x10placing_unit_tag\x18\x03 \x01(\x04\"N\n\x1eResponseQueryBuildingPlacement\x12,\n\x06result\x18\x01 \x01(\x0e\x32\x1c.SC2APIProtocol.ActionResult')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.query_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_REQUESTQUERY']._serialized_start=110
|
||||
_globals['_REQUESTQUERY']._serialized_end=350
|
||||
_globals['_RESPONSEQUERY']._serialized_start=353
|
||||
_globals['_RESPONSEQUERY']._serialized_end=559
|
||||
_globals['_REQUESTQUERYPATHING']._serialized_start=562
|
||||
_globals['_REQUESTQUERYPATHING']._serialized_end=700
|
||||
_globals['_RESPONSEQUERYPATHING']._serialized_start=702
|
||||
_globals['_RESPONSEQUERYPATHING']._serialized_end=742
|
||||
_globals['_REQUESTQUERYAVAILABLEABILITIES']._serialized_start=744
|
||||
_globals['_REQUESTQUERYAVAILABLEABILITIES']._serialized_end=794
|
||||
_globals['_RESPONSEQUERYAVAILABLEABILITIES']._serialized_start=796
|
||||
_globals['_RESPONSEQUERYAVAILABLEABILITIES']._serialized_end=922
|
||||
_globals['_REQUESTQUERYBUILDINGPLACEMENT']._serialized_start=924
|
||||
_globals['_REQUESTQUERYBUILDINGPLACEMENT']._serialized_end=1046
|
||||
_globals['_RESPONSEQUERYBUILDINGPLACEMENT']._serialized_start=1048
|
||||
_globals['_RESPONSEQUERYBUILDINGPLACEMENT']._serialized_end=1126
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/score.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/score.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cs2clientprotocol/score.proto\x12\x0eSC2APIProtocol\"\xa8\x01\n\x05Score\x12\x33\n\nscore_type\x18\x06 \x01(\x0e\x32\x1f.SC2APIProtocol.Score.ScoreType\x12\r\n\x05score\x18\x07 \x01(\x05\x12\x33\n\rscore_details\x18\x08 \x01(\x0b\x32\x1c.SC2APIProtocol.ScoreDetails\"&\n\tScoreType\x12\x0e\n\nCurriculum\x10\x01\x12\t\n\x05Melee\x10\x02\"h\n\x14\x43\x61tegoryScoreDetails\x12\x0c\n\x04none\x18\x01 \x01(\x02\x12\x0c\n\x04\x61rmy\x18\x02 \x01(\x02\x12\x0f\n\x07\x65\x63onomy\x18\x03 \x01(\x02\x12\x12\n\ntechnology\x18\x04 \x01(\x02\x12\x0f\n\x07upgrade\x18\x05 \x01(\x02\"B\n\x11VitalScoreDetails\x12\x0c\n\x04life\x18\x01 \x01(\x02\x12\x0f\n\x07shields\x18\x02 \x01(\x02\x12\x0e\n\x06\x65nergy\x18\x03 \x01(\x02\"\x8a\n\n\x0cScoreDetails\x12\x1c\n\x14idle_production_time\x18\x01 \x01(\x02\x12\x18\n\x10idle_worker_time\x18\x02 \x01(\x02\x12\x19\n\x11total_value_units\x18\x03 \x01(\x02\x12\x1e\n\x16total_value_structures\x18\x04 \x01(\x02\x12\x1a\n\x12killed_value_units\x18\x05 \x01(\x02\x12\x1f\n\x17killed_value_structures\x18\x06 \x01(\x02\x12\x1a\n\x12\x63ollected_minerals\x18\x07 \x01(\x02\x12\x19\n\x11\x63ollected_vespene\x18\x08 \x01(\x02\x12 \n\x18\x63ollection_rate_minerals\x18\t \x01(\x02\x12\x1f\n\x17\x63ollection_rate_vespene\x18\n \x01(\x02\x12\x16\n\x0espent_minerals\x18\x0b \x01(\x02\x12\x15\n\rspent_vespene\x18\x0c \x01(\x02\x12\x37\n\tfood_used\x18\r \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12=\n\x0fkilled_minerals\x18\x0e \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12<\n\x0ekilled_vespene\x18\x0f \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12;\n\rlost_minerals\x18\x10 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12:\n\x0clost_vespene\x18\x11 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x44\n\x16\x66riendly_fire_minerals\x18\x12 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x43\n\x15\x66riendly_fire_vespene\x18\x13 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12;\n\rused_minerals\x18\x14 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12:\n\x0cused_vespene\x18\x15 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12\x41\n\x13total_used_minerals\x18\x16 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12@\n\x12total_used_vespene\x18\x17 \x01(\x0b\x32$.SC2APIProtocol.CategoryScoreDetails\x12=\n\x12total_damage_dealt\x18\x18 \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12=\n\x12total_damage_taken\x18\x19 \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12\x37\n\x0ctotal_healed\x18\x1a \x01(\x0b\x32!.SC2APIProtocol.VitalScoreDetails\x12\x13\n\x0b\x63urrent_apm\x18\x1b \x01(\x02\x12\x1d\n\x15\x63urrent_effective_apm\x18\x1c \x01(\x02')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.score_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_SCORE']._serialized_start=49
|
||||
_globals['_SCORE']._serialized_end=217
|
||||
_globals['_SCORE_SCORETYPE']._serialized_start=179
|
||||
_globals['_SCORE_SCORETYPE']._serialized_end=217
|
||||
_globals['_CATEGORYSCOREDETAILS']._serialized_start=219
|
||||
_globals['_CATEGORYSCOREDETAILS']._serialized_end=323
|
||||
_globals['_VITALSCOREDETAILS']._serialized_start=325
|
||||
_globals['_VITALSCOREDETAILS']._serialized_end=391
|
||||
_globals['_SCOREDETAILS']._serialized_start=394
|
||||
_globals['_SCOREDETAILS']._serialized_end=1684
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/spatial.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/spatial.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from . import common_pb2 as s2clientprotocol_dot_common__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1es2clientprotocol/spatial.proto\x12\x0eSC2APIProtocol\x1a\x1ds2clientprotocol/common.proto\"\x88\x01\n\x17ObservationFeatureLayer\x12.\n\x07renders\x18\x01 \x01(\x0b\x32\x1d.SC2APIProtocol.FeatureLayers\x12=\n\x0fminimap_renders\x18\x02 \x01(\x0b\x32$.SC2APIProtocol.FeatureLayersMinimap\"\x9c\n\n\rFeatureLayers\x12-\n\nheight_map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0evisibility_map\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x63reep\x18\x03 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05power\x18\x04 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tplayer_id\x18\x05 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tunit_type\x18\x06 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08selected\x18\x07 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0funit_hit_points\x18\x08 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x38\n\x15unit_hit_points_ratio\x18\x11 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12.\n\x0bunit_energy\x18\t \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x34\n\x11unit_energy_ratio\x18\x12 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12/\n\x0cunit_shields\x18\n \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x35\n\x12unit_shields_ratio\x18\x13 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0fplayer_relative\x18\x0b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0funit_density_aa\x18\x0e \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12/\n\x0cunit_density\x18\x0f \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07\x65\x66\x66\x65\x63ts\x18\x14 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0ehallucinations\x18\x15 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07\x63loaked\x18\x16 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\'\n\x04\x62lip\x18\x17 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x62uffs\x18\x18 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x30\n\rbuff_duration\x18\x1a \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x61\x63tive\x18\x19 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0e\x62uild_progress\x18\x1b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tbuildable\x18\x1c \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08pathable\x18\x1d \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12.\n\x0bplaceholder\x18\x1e \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"\x90\x04\n\x14\x46\x65\x61tureLayersMinimap\x12-\n\nheight_map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x31\n\x0evisibility_map\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12(\n\x05\x63reep\x18\x03 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x63\x61mera\x18\x04 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tplayer_id\x18\x05 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12\x32\n\x0fplayer_relative\x18\x06 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08selected\x18\x07 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12)\n\x06\x61lerts\x18\t \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tbuildable\x18\n \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12+\n\x08pathable\x18\x0b \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12,\n\tunit_type\x18\x08 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"g\n\x11ObservationRender\x12&\n\x03map\x18\x01 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\x12*\n\x07minimap\x18\x02 \x01(\x0b\x32\x19.SC2APIProtocol.ImageData\"\xbb\x02\n\rActionSpatial\x12@\n\x0cunit_command\x18\x01 \x01(\x0b\x32(.SC2APIProtocol.ActionSpatialUnitCommandH\x00\x12>\n\x0b\x63\x61mera_move\x18\x02 \x01(\x0b\x32\'.SC2APIProtocol.ActionSpatialCameraMoveH\x00\x12O\n\x14unit_selection_point\x18\x03 \x01(\x0b\x32/.SC2APIProtocol.ActionSpatialUnitSelectionPointH\x00\x12M\n\x13unit_selection_rect\x18\x04 \x01(\x0b\x32..SC2APIProtocol.ActionSpatialUnitSelectionRectH\x00\x42\x08\n\x06\x61\x63tion\"\xbe\x01\n\x18\x41\x63tionSpatialUnitCommand\x12\x12\n\nability_id\x18\x01 \x01(\x05\x12\x35\n\x13target_screen_coord\x18\x02 \x01(\x0b\x32\x16.SC2APIProtocol.PointIH\x00\x12\x36\n\x14target_minimap_coord\x18\x03 \x01(\x0b\x32\x16.SC2APIProtocol.PointIH\x00\x12\x15\n\rqueue_command\x18\x04 \x01(\x08\x42\x08\n\x06target\"I\n\x17\x41\x63tionSpatialCameraMove\x12.\n\x0e\x63\x65nter_minimap\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\"\xda\x01\n\x1f\x41\x63tionSpatialUnitSelectionPoint\x12\x36\n\x16selection_screen_coord\x18\x01 \x01(\x0b\x32\x16.SC2APIProtocol.PointI\x12\x42\n\x04type\x18\x02 \x01(\x0e\x32\x34.SC2APIProtocol.ActionSpatialUnitSelectionPoint.Type\";\n\x04Type\x12\n\n\x06Select\x10\x01\x12\n\n\x06Toggle\x10\x02\x12\x0b\n\x07\x41llType\x10\x03\x12\x0e\n\nAddAllType\x10\x04\"s\n\x1e\x41\x63tionSpatialUnitSelectionRect\x12:\n\x16selection_screen_coord\x18\x01 \x03(\x0b\x32\x1a.SC2APIProtocol.RectangleI\x12\x15\n\rselection_add\x18\x02 \x01(\x08')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.spatial_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_OBSERVATIONFEATURELAYER']._serialized_start=82
|
||||
_globals['_OBSERVATIONFEATURELAYER']._serialized_end=218
|
||||
_globals['_FEATURELAYERS']._serialized_start=221
|
||||
_globals['_FEATURELAYERS']._serialized_end=1529
|
||||
_globals['_FEATURELAYERSMINIMAP']._serialized_start=1532
|
||||
_globals['_FEATURELAYERSMINIMAP']._serialized_end=2060
|
||||
_globals['_OBSERVATIONRENDER']._serialized_start=2062
|
||||
_globals['_OBSERVATIONRENDER']._serialized_end=2165
|
||||
_globals['_ACTIONSPATIAL']._serialized_start=2168
|
||||
_globals['_ACTIONSPATIAL']._serialized_end=2483
|
||||
_globals['_ACTIONSPATIALUNITCOMMAND']._serialized_start=2486
|
||||
_globals['_ACTIONSPATIALUNITCOMMAND']._serialized_end=2676
|
||||
_globals['_ACTIONSPATIALCAMERAMOVE']._serialized_start=2678
|
||||
_globals['_ACTIONSPATIALCAMERAMOVE']._serialized_end=2751
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONPOINT']._serialized_start=2754
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONPOINT']._serialized_end=2972
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONPOINT_TYPE']._serialized_start=2913
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONPOINT_TYPE']._serialized_end=2972
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONRECT']._serialized_start=2974
|
||||
_globals['_ACTIONSPATIALUNITSELECTIONRECT']._serialized_end=3089
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: s2clientprotocol/ui.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
's2clientprotocol/ui.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19s2clientprotocol/ui.proto\x12\x0eSC2APIProtocol\"\x86\x02\n\rObservationUI\x12,\n\x06groups\x18\x01 \x03(\x0b\x32\x1c.SC2APIProtocol.ControlGroup\x12-\n\x06single\x18\x02 \x01(\x0b\x32\x1b.SC2APIProtocol.SinglePanelH\x00\x12+\n\x05multi\x18\x03 \x01(\x0b\x32\x1a.SC2APIProtocol.MultiPanelH\x00\x12+\n\x05\x63\x61rgo\x18\x04 \x01(\x0b\x32\x1a.SC2APIProtocol.CargoPanelH\x00\x12\x35\n\nproduction\x18\x05 \x01(\x0b\x32\x1f.SC2APIProtocol.ProductionPanelH\x00\x42\x07\n\x05panel\"T\n\x0c\x43ontrolGroup\x12\x1b\n\x13\x63ontrol_group_index\x18\x01 \x01(\r\x12\x18\n\x10leader_unit_type\x18\x02 \x01(\r\x12\r\n\x05\x63ount\x18\x03 \x01(\r\"\x85\x02\n\x08UnitInfo\x12\x11\n\tunit_type\x18\x01 \x01(\r\x12\x17\n\x0fplayer_relative\x18\x02 \x01(\r\x12\x0e\n\x06health\x18\x03 \x01(\x05\x12\x0f\n\x07shields\x18\x04 \x01(\x05\x12\x0e\n\x06\x65nergy\x18\x05 \x01(\x05\x12\x1d\n\x15transport_slots_taken\x18\x06 \x01(\x05\x12\x16\n\x0e\x62uild_progress\x18\x07 \x01(\x02\x12(\n\x06\x61\x64\x64_on\x18\x08 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x12\n\nmax_health\x18\t \x01(\x05\x12\x13\n\x0bmax_shields\x18\n \x01(\x05\x12\x12\n\nmax_energy\x18\x0b \x01(\x05\"\x9d\x01\n\x0bSinglePanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x1c\n\x14\x61ttack_upgrade_level\x18\x02 \x01(\x05\x12\x1b\n\x13\x61rmor_upgrade_level\x18\x03 \x01(\x05\x12\x1c\n\x14shield_upgrade_level\x18\x04 \x01(\x05\x12\r\n\x05\x62uffs\x18\x05 \x03(\x05\"5\n\nMultiPanel\x12\'\n\x05units\x18\x01 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\"{\n\nCargoPanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12,\n\npassengers\x18\x02 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x17\n\x0fslots_available\x18\x03 \x01(\x05\"7\n\tBuildItem\x12\x12\n\nability_id\x18\x01 \x01(\r\x12\x16\n\x0e\x62uild_progress\x18\x02 \x01(\x02\"\x9d\x01\n\x0fProductionPanel\x12&\n\x04unit\x18\x01 \x01(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12-\n\x0b\x62uild_queue\x18\x02 \x03(\x0b\x32\x18.SC2APIProtocol.UnitInfo\x12\x33\n\x10production_queue\x18\x03 \x03(\x0b\x32\x19.SC2APIProtocol.BuildItem\"\xda\x04\n\x08\x41\x63tionUI\x12;\n\rcontrol_group\x18\x01 \x01(\x0b\x32\".SC2APIProtocol.ActionControlGroupH\x00\x12\x37\n\x0bselect_army\x18\x02 \x01(\x0b\x32 .SC2APIProtocol.ActionSelectArmyH\x00\x12\x42\n\x11select_warp_gates\x18\x03 \x01(\x0b\x32%.SC2APIProtocol.ActionSelectWarpGatesH\x00\x12\x39\n\x0cselect_larva\x18\x04 \x01(\x0b\x32!.SC2APIProtocol.ActionSelectLarvaH\x00\x12\x44\n\x12select_idle_worker\x18\x05 \x01(\x0b\x32&.SC2APIProtocol.ActionSelectIdleWorkerH\x00\x12\x37\n\x0bmulti_panel\x18\x06 \x01(\x0b\x32 .SC2APIProtocol.ActionMultiPanelH\x00\x12=\n\x0b\x63\x61rgo_panel\x18\x07 \x01(\x0b\x32&.SC2APIProtocol.ActionCargoPanelUnloadH\x00\x12P\n\x10production_panel\x18\x08 \x01(\x0b\x32\x34.SC2APIProtocol.ActionProductionPanelRemoveFromQueueH\x00\x12?\n\x0ftoggle_autocast\x18\t \x01(\x0b\x32$.SC2APIProtocol.ActionToggleAutocastH\x00\x42\x08\n\x06\x61\x63tion\"\xd4\x01\n\x12\x41\x63tionControlGroup\x12\x45\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x35.SC2APIProtocol.ActionControlGroup.ControlGroupAction\x12\x1b\n\x13\x63ontrol_group_index\x18\x02 \x01(\r\"Z\n\x12\x43ontrolGroupAction\x12\n\n\x06Recall\x10\x01\x12\x07\n\x03Set\x10\x02\x12\n\n\x06\x41ppend\x10\x03\x12\x0f\n\x0bSetAndSteal\x10\x04\x12\x12\n\x0e\x41ppendAndSteal\x10\x05\")\n\x10\x41\x63tionSelectArmy\x12\x15\n\rselection_add\x18\x01 \x01(\x08\".\n\x15\x41\x63tionSelectWarpGates\x12\x15\n\rselection_add\x18\x01 \x01(\x08\"\x13\n\x11\x41\x63tionSelectLarva\"\x82\x01\n\x16\x41\x63tionSelectIdleWorker\x12\x39\n\x04type\x18\x01 \x01(\x0e\x32+.SC2APIProtocol.ActionSelectIdleWorker.Type\"-\n\x04Type\x12\x07\n\x03Set\x10\x01\x12\x07\n\x03\x41\x64\x64\x10\x02\x12\x07\n\x03\x41ll\x10\x03\x12\n\n\x06\x41\x64\x64\x41ll\x10\x04\"\xb3\x01\n\x10\x41\x63tionMultiPanel\x12\x33\n\x04type\x18\x01 \x01(\x0e\x32%.SC2APIProtocol.ActionMultiPanel.Type\x12\x12\n\nunit_index\x18\x02 \x01(\x05\"V\n\x04Type\x12\x10\n\x0cSingleSelect\x10\x01\x12\x10\n\x0c\x44\x65selectUnit\x10\x02\x12\x13\n\x0fSelectAllOfType\x10\x03\x12\x15\n\x11\x44\x65selectAllOfType\x10\x04\",\n\x16\x41\x63tionCargoPanelUnload\x12\x12\n\nunit_index\x18\x01 \x01(\x05\":\n$ActionProductionPanelRemoveFromQueue\x12\x12\n\nunit_index\x18\x01 \x01(\x05\"*\n\x14\x41\x63tionToggleAutocast\x12\x12\n\nability_id\x18\x01 \x01(\x05')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 's2clientprotocol.ui_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_OBSERVATIONUI']._serialized_start=46
|
||||
_globals['_OBSERVATIONUI']._serialized_end=308
|
||||
_globals['_CONTROLGROUP']._serialized_start=310
|
||||
_globals['_CONTROLGROUP']._serialized_end=394
|
||||
_globals['_UNITINFO']._serialized_start=397
|
||||
_globals['_UNITINFO']._serialized_end=658
|
||||
_globals['_SINGLEPANEL']._serialized_start=661
|
||||
_globals['_SINGLEPANEL']._serialized_end=818
|
||||
_globals['_MULTIPANEL']._serialized_start=820
|
||||
_globals['_MULTIPANEL']._serialized_end=873
|
||||
_globals['_CARGOPANEL']._serialized_start=875
|
||||
_globals['_CARGOPANEL']._serialized_end=998
|
||||
_globals['_BUILDITEM']._serialized_start=1000
|
||||
_globals['_BUILDITEM']._serialized_end=1055
|
||||
_globals['_PRODUCTIONPANEL']._serialized_start=1058
|
||||
_globals['_PRODUCTIONPANEL']._serialized_end=1215
|
||||
_globals['_ACTIONUI']._serialized_start=1218
|
||||
_globals['_ACTIONUI']._serialized_end=1820
|
||||
_globals['_ACTIONCONTROLGROUP']._serialized_start=1823
|
||||
_globals['_ACTIONCONTROLGROUP']._serialized_end=2035
|
||||
_globals['_ACTIONCONTROLGROUP_CONTROLGROUPACTION']._serialized_start=1945
|
||||
_globals['_ACTIONCONTROLGROUP_CONTROLGROUPACTION']._serialized_end=2035
|
||||
_globals['_ACTIONSELECTARMY']._serialized_start=2037
|
||||
_globals['_ACTIONSELECTARMY']._serialized_end=2078
|
||||
_globals['_ACTIONSELECTWARPGATES']._serialized_start=2080
|
||||
_globals['_ACTIONSELECTWARPGATES']._serialized_end=2126
|
||||
_globals['_ACTIONSELECTLARVA']._serialized_start=2128
|
||||
_globals['_ACTIONSELECTLARVA']._serialized_end=2147
|
||||
_globals['_ACTIONSELECTIDLEWORKER']._serialized_start=2150
|
||||
_globals['_ACTIONSELECTIDLEWORKER']._serialized_end=2280
|
||||
_globals['_ACTIONSELECTIDLEWORKER_TYPE']._serialized_start=2235
|
||||
_globals['_ACTIONSELECTIDLEWORKER_TYPE']._serialized_end=2280
|
||||
_globals['_ACTIONMULTIPANEL']._serialized_start=2283
|
||||
_globals['_ACTIONMULTIPANEL']._serialized_end=2462
|
||||
_globals['_ACTIONMULTIPANEL_TYPE']._serialized_start=2376
|
||||
_globals['_ACTIONMULTIPANEL_TYPE']._serialized_end=2462
|
||||
_globals['_ACTIONCARGOPANELUNLOAD']._serialized_start=2464
|
||||
_globals['_ACTIONCARGOPANELUNLOAD']._serialized_end=2508
|
||||
_globals['_ACTIONPRODUCTIONPANELREMOVEFROMQUEUE']._serialized_start=2510
|
||||
_globals['_ACTIONPRODUCTIONPANELREMOVEFROMQUEUE']._serialized_end=2568
|
||||
_globals['_ACTIONTOGGLEAUTOCAST']._serialized_start=2570
|
||||
_globals['_ACTIONTOGGLEAUTOCAST']._serialized_end=2612
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
@@ -4,7 +4,7 @@ from contextlib import suppress
|
||||
|
||||
from aiohttp import ClientWebSocketResponse
|
||||
from worlds._sc2common.bot import logger
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
from .data import Status
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import traceback
|
||||
|
||||
from aiohttp import WSMsgType, web
|
||||
from worlds._sc2common.bot import logger
|
||||
from s2clientprotocol import sc2api_pb2 as sc_pb
|
||||
from .proto import sc2api_pb2 as sc_pb
|
||||
|
||||
from .controller import Controller
|
||||
from .data import Result, Status
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import datetime
|
||||
|
||||
from s2clientprotocol import score_pb2 as score_pb
|
||||
from .proto import score_pb2 as score_pb
|
||||
|
||||
from .position import Point2
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
s2clientprotocol>=5.0.11.90136.0
|
||||
mpyq>=0.2.5
|
||||
portpicker>=1.5.2
|
||||
aiohttp>=3.8.4
|
||||
loguru>=0.7.0
|
||||
protobuf==3.20.3
|
||||
protobuf==6.33.5
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Option, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
|
||||
|
||||
from Options import Choice, DefaultOnToggle, DeathLink, Range, Toggle, PerGameCommonOptions
|
||||
|
||||
|
||||
class FreeincarnateMax(Range):
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from BaseClasses import MultiWorld, Region, Entrance, LocationProgressType
|
||||
from Options import PerGameCommonOptions
|
||||
from .Locations import location_table, LocationData, AdventureLocation, dragon_room_to_region
|
||||
from .Locations import location_table, AdventureLocation, dragon_room_to_region
|
||||
|
||||
|
||||
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
def connect(multiworld: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
one_way=False, name=None):
|
||||
source_region = world.get_region(source, player)
|
||||
target_region = world.get_region(target, player)
|
||||
source_region = multiworld.get_region(source, player)
|
||||
target_region = multiworld.get_region(target, player)
|
||||
|
||||
if name is None:
|
||||
name = source + " to " + target
|
||||
@@ -22,7 +22,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
if not one_way:
|
||||
connect(world, player, target, source, rule, True)
|
||||
connect(multiworld, player, target, source, rule, True)
|
||||
|
||||
|
||||
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||
|
||||
+10
-9
@@ -2,15 +2,15 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
from typing import Optional, Any
|
||||
|
||||
import Utils
|
||||
from .Locations import AdventureLocation, LocationData
|
||||
from settings import get_settings
|
||||
from worlds.Files import APPatch, AutoPatchRegister
|
||||
from typing import Any
|
||||
|
||||
import bsdiff4
|
||||
|
||||
import Utils
|
||||
from settings import get_settings
|
||||
from worlds.Files import APPatch
|
||||
from .Locations import LocationData
|
||||
|
||||
ADVENTUREHASH: str = "157bddb7192754a45372be196797f284"
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class BatNoTouchLocation:
|
||||
return ret_dict
|
||||
|
||||
|
||||
class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):
|
||||
class AdventureDeltaPatch(APPatch):
|
||||
hash = ADVENTUREHASH
|
||||
game = "Adventure"
|
||||
patch_file_ending = ".apadvn"
|
||||
@@ -182,10 +182,11 @@ class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):
|
||||
json.dumps(self.rom_deltas),
|
||||
compress_type=zipfile.ZIP_LZMA)
|
||||
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile):
|
||||
super(AdventureDeltaPatch, self).read_contents(opened_zipfile)
|
||||
def read_contents(self, opened_zipfile: zipfile.ZipFile) -> dict[str, Any]:
|
||||
manifest = super(AdventureDeltaPatch, self).read_contents(opened_zipfile)
|
||||
self.foreign_items = AdventureDeltaPatch.read_foreign_items(opened_zipfile)
|
||||
self.autocollect_items = AdventureDeltaPatch.read_autocollect_items(opened_zipfile)
|
||||
return manifest
|
||||
|
||||
@classmethod
|
||||
def get_source_data(cls) -> bytes:
|
||||
|
||||
+25
-25
@@ -3,47 +3,47 @@ from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
world = self.multiworld
|
||||
multiworld = self.multiworld
|
||||
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
|
||||
|
||||
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("YellowCastlePort", self.player),
|
||||
lambda state: state.has("Yellow Key", self.player))
|
||||
set_rule(world.get_entrance("BlackCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastlePort", self.player),
|
||||
lambda state: state.has("Black Key", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePort", self.player),
|
||||
lambda state: state.has("White Key", self.player))
|
||||
|
||||
# a future thing would be to make the bat an actual item, or at least allow it to
|
||||
# be placed in a castle, which would require some additions to the rules when
|
||||
# use_bat_logic is true
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
set_rule(world.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
|
||||
dragon_slay_check = self.options.dragon_slay_check.value
|
||||
if dragon_slay_check:
|
||||
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
else:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
|
||||
# really this requires getting the dot item, and having another item or enemy
|
||||
@@ -51,37 +51,37 @@ def set_rules(self) -> None:
|
||||
# to actually make randomized, since it is invisible. May add some options
|
||||
# for how that works in the distant future, but for now, just say you need
|
||||
# the bridge and black key to get to it, as that simplifies things a lot
|
||||
set_rule(world.get_entrance("CreditsWall", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsWall", self.player),
|
||||
lambda state: state.has("Bridge", self.player) and
|
||||
state.has("Black Key", self.player))
|
||||
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("CreditsToFarSide", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsToFarSide", self.player),
|
||||
lambda state: state.has("Magnet", self.player))
|
||||
|
||||
# bridge literally does not fit in this space, I think. I'll just exclude it
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Bridge", self.player)
|
||||
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Bridge", self.player)
|
||||
# don't put magnet in locations that can pull in-logic items out of reach unless the bat is in play
|
||||
if not use_bat_logic:
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
|
||||
# and obviously we don't want to start with the game already won
|
||||
forbid_item(world.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = world.get_region("Overworld", self.player)
|
||||
forbid_item(multiworld.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = multiworld.get_region("Overworld", self.player)
|
||||
|
||||
for loc in overworld.locations:
|
||||
forbid_item(loc, "Chalice", self.player)
|
||||
|
||||
add_rule(world.get_location("Chalice Home", self.player),
|
||||
add_rule(multiworld.get_location("Chalice Home", self.player),
|
||||
lambda state: state.has("Chalice", self.player) and state.has("Yellow Key", self.player))
|
||||
|
||||
# world.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
# multiworld.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
# all_locations = world.get_locations(self.player).copy()
|
||||
# all_locations = multiworld.get_locations(self.player).copy()
|
||||
# while priority_count < get_num_items():
|
||||
# loc = world.random.choice(all_locations)
|
||||
# loc = multiworld.random.choice(all_locations)
|
||||
# if loc.progress_type == LocationProgressType.DEFAULT:
|
||||
# loc.progress_type = LocationProgressType.PRIORITY
|
||||
# priority_count += 1
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
import base64
|
||||
import copy
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import settings
|
||||
import typing
|
||||
from enum import IntFlag
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple
|
||||
from typing import ClassVar, Dict, Optional, Tuple
|
||||
|
||||
from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, Tutorial, \
|
||||
LocationProgressType
|
||||
import settings
|
||||
from BaseClasses import Item, ItemClassification, MultiWorld, Tutorial, LocationProgressType
|
||||
from Utils import __version__
|
||||
from Options import AssembleOptions
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from Fill import fill_restrictive
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, \
|
||||
AdventureOptions
|
||||
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
|
||||
AdventureAutoCollectLocation
|
||||
from worlds.LauncherComponents import Component, components, SuffixIdentifier
|
||||
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
|
||||
from .Locations import location_table, base_location_id, LocationData, get_random_room_in_regions
|
||||
from .Offsets import static_item_data_location, items_ram_start, static_item_element_size, item_position_table, \
|
||||
static_first_dragon_index, connector_port_offset, yorgle_speed_data_location, grundle_speed_data_location, \
|
||||
rhindle_speed_data_location, item_ram_addresses, start_castle_values, start_castle_offset
|
||||
from .Options import DragonRandoType, DifficultySwitchA, DifficultySwitchB, AdventureOptions
|
||||
from .Regions import create_regions
|
||||
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, AdventureAutoCollectLocation
|
||||
from .Rules import set_rules
|
||||
|
||||
|
||||
from worlds.LauncherComponents import Component, components, SuffixIdentifier
|
||||
|
||||
# Adventure
|
||||
components.append(Component('Adventure Client', 'AdventureClient', file_identifier=SuffixIdentifier('.apadvn')))
|
||||
|
||||
@@ -116,8 +105,8 @@ class AdventureWorld(World):
|
||||
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
||||
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.rom_name: Optional[bytearray] = bytearray("", "utf8" )
|
||||
self.dragon_rooms: [int] = [0x14, 0x19, 0x4]
|
||||
self.dragon_slay_check: Optional[int] = 0
|
||||
@@ -249,14 +238,12 @@ class AdventureWorld(World):
|
||||
|
||||
def create_regions(self) -> None:
|
||||
create_regions(self.options, self.multiworld, self.player, self.dragon_rooms)
|
||||
|
||||
set_rules = set_rules
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
self.multiworld.get_location("Chalice Home", self.player).place_locked_item(
|
||||
self.create_event("Victory", ItemClassification.progression))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
set_rules = set_rules
|
||||
|
||||
def pre_fill(self):
|
||||
# Place empty items in filler locations here, to limit
|
||||
# the number of exported empty items and the density of stuff in overworld.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
import Utils
|
||||
import websockets
|
||||
import functools
|
||||
@@ -208,6 +210,9 @@ async def proxy(websocket, path: str = "/", ctx: AHITContext = None):
|
||||
if not ctx.is_proxy_connected():
|
||||
break
|
||||
|
||||
if msg["cmd"] == "Bounce" and msg.get("tags") == ["DeathLink"] and "data" in msg:
|
||||
msg["data"]["time"] = time.time()
|
||||
|
||||
await ctx.send_msgs([msg])
|
||||
|
||||
except Exception as e:
|
||||
@@ -238,10 +243,10 @@ async def proxy_loop(ctx: AHITContext):
|
||||
logger.info("Aborting AHIT Proxy Client due to errors")
|
||||
|
||||
|
||||
def launch():
|
||||
def launch(*launch_args: str):
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(launch_args)
|
||||
|
||||
ctx = AHITContext(args.connect, args.password)
|
||||
logger.info("Starting A Hat in Time proxy server")
|
||||
@@ -261,6 +266,6 @@ def launch():
|
||||
# options = Utils.get_options()
|
||||
|
||||
import colorama
|
||||
colorama.init()
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -141,9 +141,12 @@ def set_dw_rules(world: "HatInTimeWorld"):
|
||||
add_dw_rules(world, all_clear)
|
||||
add_rule(main_stamp, main_objective.access_rule)
|
||||
add_rule(all_clear, main_objective.access_rule)
|
||||
# Only set bonus stamp rules if we don't auto complete bonuses
|
||||
# Only set bonus stamp rules to require All Clear if we don't auto complete bonuses
|
||||
if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name):
|
||||
add_rule(bonus_stamps, all_clear.access_rule)
|
||||
else:
|
||||
# As soon as the Main Objective is completed, the bonuses auto-complete.
|
||||
add_rule(bonus_stamps, main_objective.access_rule)
|
||||
|
||||
if world.options.DWShuffle:
|
||||
for i in range(len(world.dw_shuffle)-1):
|
||||
@@ -343,6 +346,7 @@ def create_enemy_events(world: "HatInTimeWorld"):
|
||||
|
||||
def set_enemy_rules(world: "HatInTimeWorld"):
|
||||
no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.excluded_bonuses
|
||||
difficulty = get_difficulty(world)
|
||||
|
||||
for enemy, regions in hit_list.items():
|
||||
if no_tourist and enemy in bosses:
|
||||
@@ -372,6 +376,14 @@ def set_enemy_rules(world: "HatInTimeWorld"):
|
||||
or state.has("Zipline Unlock - The Lava Cake Path", world.player)
|
||||
or state.has("Zipline Unlock - The Windmill Path", world.player))
|
||||
|
||||
elif enemy == "Toilet":
|
||||
if area == "Toilet of Doom":
|
||||
# The boss firewall is in the way and can only be skipped on Expert logic using a cherry hover.
|
||||
add_rule(event, lambda state: has_paintings(state, world, 1, allow_skip=difficulty == Difficulty.EXPERT))
|
||||
if difficulty < Difficulty.HARD:
|
||||
# Hard logic and above can cross the boss arena gap with a cherry bridge.
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
elif enemy == "Director":
|
||||
if area == "Dead Bird Studio Basement":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
@@ -430,7 +442,7 @@ hit_list = {
|
||||
# Bosses
|
||||
"Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"],
|
||||
|
||||
"Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
|
||||
"Director": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
|
||||
"Toilet": ["Toilet of Doom", "Boss Rush"],
|
||||
|
||||
"Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush",
|
||||
@@ -454,7 +466,7 @@ triple_enemy_locations = [
|
||||
|
||||
bosses = [
|
||||
"Mafia Boss",
|
||||
"Conductor",
|
||||
"Director",
|
||||
"Toilet",
|
||||
"Snatcher",
|
||||
"Toxic Flower",
|
||||
|
||||
@@ -63,6 +63,9 @@ def is_location_valid(world: "HatInTimeWorld", location: str) -> bool:
|
||||
if not world.options.ShuffleStorybookPages and location in storybook_pages.keys():
|
||||
return False
|
||||
|
||||
if not world.options.ShuffleDirectorTokens and location in director_tokens.keys():
|
||||
return False
|
||||
|
||||
if not world.options.ShuffleActContracts and location in contract_locations.keys():
|
||||
return False
|
||||
|
||||
@@ -206,7 +209,7 @@ ahit_locations = {
|
||||
"Subcon Village - Graveyard Ice Cube": LocData(2000325077, "Subcon Forest Area"),
|
||||
"Subcon Village - House Top": LocData(2000325471, "Subcon Forest Area"),
|
||||
"Subcon Village - Ice Cube House": LocData(2000325469, "Subcon Forest Area"),
|
||||
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Area", paintings=1),
|
||||
"Subcon Village - Snatcher Statue Chest": LocData(2000323730, "Subcon Forest Behind Boss Firewall"),
|
||||
"Subcon Village - Stump Platform Chest": LocData(2000323729, "Subcon Forest Area"),
|
||||
"Subcon Forest - Giant Tree Climb": LocData(2000325470, "Subcon Forest Area"),
|
||||
|
||||
@@ -233,7 +236,7 @@ ahit_locations = {
|
||||
"Subcon Forest - Long Tree Climb Chest": LocData(2000323734, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER], paintings=2),
|
||||
|
||||
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Area"),
|
||||
"Subcon Forest - Boss Arena Chest": LocData(2000323735, "Subcon Forest Boss Arena"),
|
||||
|
||||
"Subcon Forest - Manor Rooftop": LocData(2000325466, "Subcon Forest Area",
|
||||
hit_type=HitType.dweller_bell, paintings=1),
|
||||
@@ -264,7 +267,6 @@ ahit_locations = {
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Tall Tree Hookshot Swing": LocData(2000324766, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER],
|
||||
hookshot=True,
|
||||
paintings=3),
|
||||
|
||||
@@ -323,7 +325,7 @@ ahit_locations = {
|
||||
"Alpine Skyline - The Twilight Path": LocData(2000334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]),
|
||||
"Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(2000336478, "The Twilight Bell"),
|
||||
"Alpine Skyline - The Twilight Bell: Ice Platform": LocData(2000335826, "The Twilight Bell"),
|
||||
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area"),
|
||||
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area (TIHS)", hookshot=True),
|
||||
"Alpine Skyline - Windy Passage": LocData(2000334776, "Alpine Skyline Area (TIHS)", hookshot=True),
|
||||
"Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(2000336395, "The Windmill"),
|
||||
"Alpine Skyline - The Windmill: Entrance": LocData(2000335783, "The Windmill"),
|
||||
@@ -407,12 +409,12 @@ act_completions = {
|
||||
hit_type=HitType.umbrella_or_brewing, hookshot=True, paintings=1),
|
||||
|
||||
"Act Completion (Queen Vanessa's Manor)": LocData(2000312017, "Queen Vanessa's Manor",
|
||||
hit_type=HitType.umbrella, paintings=1),
|
||||
hit_type=HitType.dweller_bell, paintings=1),
|
||||
|
||||
"Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service",
|
||||
required_hats=[HatType.SPRINT]),
|
||||
|
||||
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired",
|
||||
"Act Completion (Your Contract has Expired)": LocData(2000311390, "Your Contract has Expired - Post Fight",
|
||||
hit_type=HitType.umbrella),
|
||||
|
||||
"Act Completion (Time Rift - Pipe)": LocData(2000313069, "Time Rift - Pipe", hookshot=True),
|
||||
@@ -478,7 +480,7 @@ act_completions = {
|
||||
"Act Completion (Rush Hour)": LocData(2000311210, "Rush Hour",
|
||||
dlc_flags=HatDLC.dlc2,
|
||||
hookshot=True,
|
||||
required_hats=[HatType.ICE, HatType.BREWING]),
|
||||
required_hats=[HatType.ICE, HatType.BREWING, HatType.DWELLER]),
|
||||
|
||||
"Act Completion (Time Rift - Rumbi Factory)": LocData(2000312736, "Time Rift - Rumbi Factory",
|
||||
dlc_flags=HatDLC.dlc2),
|
||||
@@ -567,6 +569,34 @@ storybook_pages = {
|
||||
"Rumbi Factory - Page: Last Area": LocData(2000345883, "Time Rift - Rumbi Factory", dlc_flags=HatDLC.dlc2),
|
||||
}
|
||||
|
||||
director_tokens = {
|
||||
"Murder on the Owl Express - Conductor Token: Cafeteria": LocData(2001104767, "Murder on the Owl Express"),
|
||||
"Murder on the Owl Express - Conductor Token: Recreational Room": LocData(2001104768, "Murder on the Owl Express"),
|
||||
"Picture Perfect - DJ Grooves Token: Cardboard Puppy": LocData(2001203990, "Picture Perfect"),
|
||||
"Picture Perfect - DJ Grooves Token: Card Guessing Game": LocData(2001203991, "Picture Perfect"),
|
||||
"Picture Perfect - DJ Grooves Token: Back Alley": LocData(2001203992, "Picture Perfect"),
|
||||
"Picture Perfect - DJ Grooves Token: Cooking Show": LocData(2001203993, "Picture Perfect"),
|
||||
"Picture Perfect - DJ Grooves Token: Pon Cluster": LocData(2001203987, "Picture Perfect"),
|
||||
"Train Rush - Time Bonus: 1st Room": LocData(2001305235, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Falling Platform": LocData(2001305189, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Acid Crates": LocData(2001305186, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Balloon": LocData(2001305239, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Ring of Fire": LocData(2001305237, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Blue Panels": LocData(2001305236, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Sinking Lava Platform": LocData(2001305234, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Lava Panels 1": LocData(2001305193, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Lava Panels 2": LocData(2001305190, "Train Rush", hookshot=True),
|
||||
"Train Rush - Time Bonus: Lava Panels 3": LocData(2001305238, "Train Rush", hookshot=True),
|
||||
"The Big Parade - DJ Grooves Token (1/8)": LocData(2001400000, "The Big Parade"),
|
||||
"The Big Parade - DJ Grooves Token (2/8)": LocData(2001400001, "The Big Parade"),
|
||||
"The Big Parade - DJ Grooves Token (3/8)": LocData(2001400002, "The Big Parade"),
|
||||
"The Big Parade - DJ Grooves Token (4/8)": LocData(2001400003, "The Big Parade"),
|
||||
"The Big Parade - DJ Grooves Token (5/8)": LocData(2001400004, "The Big Parade", hit_type=HitType.umbrella),
|
||||
"The Big Parade - DJ Grooves Token (6/8)": LocData(2001400005, "The Big Parade", hit_type=HitType.umbrella),
|
||||
"The Big Parade - DJ Grooves Token (7/8)": LocData(2001400006, "The Big Parade", hit_type=HitType.umbrella),
|
||||
"The Big Parade - DJ Grooves Token (8/8)": LocData(2001400007, "The Big Parade", hit_type=HitType.umbrella),
|
||||
}
|
||||
|
||||
shop_locations = {
|
||||
"Badge Seller - Item 1": LocData(2000301003, "Badge Seller"),
|
||||
"Badge Seller - Item 2": LocData(2000301004, "Badge Seller"),
|
||||
@@ -878,7 +908,7 @@ snatcher_coins = {
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"Snatcher Coin - Top of HQ (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of HQ",
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
hit_type=HitType.umbrella, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"Snatcher Coin - Top of Tower": LocData(0, "Mafia Town Area (HUMT)", snatcher_coin="Snatcher Coin - Top of Tower",
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
@@ -977,7 +1007,6 @@ event_locs = {
|
||||
**snatcher_coins,
|
||||
"HUMT Access": LocData(0, "Heating Up Mafia Town"),
|
||||
"TOD Access": LocData(0, "Toilet of Doom"),
|
||||
"YCHE Access": LocData(0, "Your Contract has Expired"),
|
||||
"AFR Access": LocData(0, "Alpine Free Roam"),
|
||||
"TIHS Access": LocData(0, "The Illness has Spread"),
|
||||
|
||||
@@ -1052,6 +1081,7 @@ location_table = {
|
||||
**ahit_locations,
|
||||
**act_completions,
|
||||
**storybook_pages,
|
||||
**director_tokens,
|
||||
**contract_locations,
|
||||
**shop_locations,
|
||||
}
|
||||
|
||||
+36
-3
@@ -2,7 +2,7 @@ from typing import List, TYPE_CHECKING, Dict, Any
|
||||
from schema import Schema, Optional
|
||||
from dataclasses import dataclass
|
||||
from worlds.AutoWorld import PerGameCommonOptions
|
||||
from Options import Range, Toggle, DeathLink, Choice, OptionDict, DefaultOnToggle, OptionGroup
|
||||
from Options import Range, Toggle, DeathLink, Choice, OptionDict, DefaultOnToggle, OptionGroup, StartInventoryPool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import HatInTimeWorld
|
||||
@@ -72,6 +72,7 @@ def adjust_options(world: "HatInTimeWorld"):
|
||||
world.options.EndGoal.value = EndGoal.option_seal_the_deal
|
||||
world.options.ActRandomizer.value = 0
|
||||
world.options.ShuffleAlpineZiplines.value = 0
|
||||
world.options.ShuffleDirectorTokens.value = 0
|
||||
world.options.ShuffleSubconPaintings.value = 0
|
||||
world.options.ShuffleStorybookPages.value = 0
|
||||
world.options.ShuffleActContracts.value = 0
|
||||
@@ -219,6 +220,12 @@ class ShuffleStorybookPages(DefaultOnToggle):
|
||||
display_name = "Shuffle Storybook Pages"
|
||||
|
||||
|
||||
class ShuffleDirectorTokens(Toggle):
|
||||
"""If enabled, causes the Conductor/DJ Grooves tokens found in Chapter 2 levels to become item checks.
|
||||
NOTE: This also includes the time bonus pickups from Train Rush, since the level doesn't have any tokens."""
|
||||
display_name = "Shuffle Director Tokens"
|
||||
|
||||
|
||||
class ShuffleActContracts(DefaultOnToggle):
|
||||
"""If enabled, shuffle Snatcher's act contracts into the pool as items"""
|
||||
display_name = "Shuffle Contracts"
|
||||
@@ -338,7 +345,7 @@ class MinExtraYarn(Range):
|
||||
There must be at least this much more yarn over the total number of yarn needed to craft all hats.
|
||||
For example, if this option's value is 10, and the total yarn needed to craft all hats is 40,
|
||||
there must be at least 50 yarn in the pool."""
|
||||
display_name = "Max Extra Yarn"
|
||||
display_name = "Min Extra Yarn"
|
||||
range_start = 5
|
||||
range_end = 15
|
||||
default = 10
|
||||
@@ -623,8 +630,27 @@ class ParadeTrapWeight(Range):
|
||||
default = 20
|
||||
|
||||
|
||||
class DeathLinkAmnesty(Range):
|
||||
"""Amount of forgiven deaths before sending a Death Link.
|
||||
0 means that every death will send a Death Link."""
|
||||
display_name = "Death Link Amnesty"
|
||||
range_start = 0
|
||||
range_end = 20
|
||||
default = 0
|
||||
|
||||
|
||||
class DWDeathLinkAmnesty(Range):
|
||||
"""Amount of forgiven deaths before sending a Death Link during Death Wish levels."""
|
||||
display_name = "Death Wish Amnesty"
|
||||
range_start = 0
|
||||
range_end = 30
|
||||
default = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class AHITOptions(PerGameCommonOptions):
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
|
||||
EndGoal: EndGoal
|
||||
ActRandomizer: ActRandomizer
|
||||
ActPlando: ActPlando
|
||||
@@ -639,6 +665,7 @@ class AHITOptions(PerGameCommonOptions):
|
||||
StartWithCompassBadge: StartWithCompassBadge
|
||||
CompassBadgeMode: CompassBadgeMode
|
||||
ShuffleStorybookPages: ShuffleStorybookPages
|
||||
ShuffleDirectorTokens: ShuffleDirectorTokens
|
||||
ShuffleActContracts: ShuffleActContracts
|
||||
ShuffleSubconPaintings: ShuffleSubconPaintings
|
||||
NoPaintingSkips: NoPaintingSkips
|
||||
@@ -698,10 +725,13 @@ class AHITOptions(PerGameCommonOptions):
|
||||
ParadeTrapWeight: ParadeTrapWeight
|
||||
|
||||
death_link: DeathLink
|
||||
death_link_amnesty: DeathLinkAmnesty
|
||||
dw_death_link_amnesty: DWDeathLinkAmnesty
|
||||
|
||||
|
||||
ahit_option_groups: Dict[str, List[Any]] = {
|
||||
"General Options": [EndGoal, ShuffleStorybookPages, ShuffleAlpineZiplines, ShuffleSubconPaintings,
|
||||
"General Options": [EndGoal, ShuffleStorybookPages, ShuffleDirectorTokens,
|
||||
ShuffleAlpineZiplines, ShuffleSubconPaintings,
|
||||
ShuffleActContracts, MinPonCost, MaxPonCost, BadgeSellerMinItems, BadgeSellerMaxItems,
|
||||
LogicDifficulty, NoPaintingSkips, CTRLogic],
|
||||
|
||||
@@ -738,6 +768,7 @@ slot_data_options: List[str] = [
|
||||
"StartWithCompassBadge",
|
||||
"CompassBadgeMode",
|
||||
"ShuffleStorybookPages",
|
||||
"ShuffleDirectorTokens",
|
||||
"ShuffleActContracts",
|
||||
"ShuffleSubconPaintings",
|
||||
"NoPaintingSkips",
|
||||
@@ -767,4 +798,6 @@ slot_data_options: List[str] = [
|
||||
"MaxPonCost",
|
||||
|
||||
"death_link",
|
||||
"death_link_amnesty",
|
||||
"dw_death_link_amnesty",
|
||||
]
|
||||
|
||||
+29
-3
@@ -1,7 +1,7 @@
|
||||
from BaseClasses import Region, Entrance, ItemClassification, Location, LocationProgressType
|
||||
from .Types import ChapterIndex, Difficulty, HatInTimeLocation, HatInTimeItem
|
||||
from .Locations import location_table, storybook_pages, event_locs, is_location_valid, \
|
||||
shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard
|
||||
shop_locations, TASKSANITY_START_ID, snatcher_coins, zero_jumps, zero_jumps_expert, zero_jumps_hard, director_tokens
|
||||
from typing import TYPE_CHECKING, List, Dict, Optional
|
||||
from .Rules import set_rift_rules, get_difficulty
|
||||
from .Options import ActRandomizer, EndGoal
|
||||
@@ -243,7 +243,7 @@ guaranteed_first_acts = [
|
||||
"Time Rift - Mafia of Cooks",
|
||||
"Time Rift - Dead Bird Studio",
|
||||
"Time Rift - Sleepy Subcon",
|
||||
"Time Rift - Alpine Skyline"
|
||||
"Time Rift - Alpine Skyline",
|
||||
"Time Rift - Tour",
|
||||
"Time Rift - Rumbi Factory",
|
||||
]
|
||||
@@ -347,7 +347,7 @@ def create_regions(world: "HatInTimeWorld"):
|
||||
sf_act3 = create_region_and_connect(world, "Toilet of Doom", "Subcon Forest - Act 3", subcon_forest)
|
||||
sf_act4 = create_region_and_connect(world, "Queen Vanessa's Manor", "Subcon Forest - Act 4", subcon_forest)
|
||||
sf_act5 = create_region_and_connect(world, "Mail Delivery Service", "Subcon Forest - Act 5", subcon_forest)
|
||||
create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
|
||||
sf_finale = create_region_and_connect(world, "Your Contract has Expired", "Subcon Forest - Finale", subcon_forest)
|
||||
|
||||
# ------------------------------------------- ALPINE SKYLINE ------------------------------------------ #
|
||||
alpine_skyline = create_region_and_connect(world, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship)
|
||||
@@ -386,11 +386,24 @@ def create_regions(world: "HatInTimeWorld"):
|
||||
create_rift_connections(world, create_region(world, "Time Rift - Bazaar"))
|
||||
|
||||
sf_area: Region = create_region(world, "Subcon Forest Area")
|
||||
sf_behind_boss_firewall: Region = create_region(world, "Subcon Forest Behind Boss Firewall")
|
||||
sf_boss_arena: Region = create_region(world, "Subcon Forest Boss Arena")
|
||||
sf_area.connect(sf_behind_boss_firewall, "SF Area -> SF Behind Boss Firewall")
|
||||
sf_behind_boss_firewall.connect(sf_boss_arena, "SF Behind Boss Firewall -> SF Boss Arena")
|
||||
sf_act1.connect(sf_area, "Subcon Forest Entrance CO")
|
||||
sf_act2.connect(sf_area, "Subcon Forest Entrance SW")
|
||||
sf_act3.connect(sf_area, "Subcon Forest Entrance TOD")
|
||||
sf_act4.connect(sf_area, "Subcon Forest Entrance QVM")
|
||||
sf_act5.connect(sf_area, "Subcon Forest Entrance MDS")
|
||||
# YCHE puts the player directly in the boss arena, with no access to the rest of Subcon Forest by default.
|
||||
sf_finale.connect(sf_boss_arena, "Subcon Forest Entrance YCHE")
|
||||
# To support the Snatcher Hover expert logic for Act Completion (Your Contract has Expired), the act completion has
|
||||
# to go in a separate region because the Snatcher Hover gives direct access to the Act Completion, but does not
|
||||
# give access to the act itself.
|
||||
sf_finale_post_fight: Region = create_region(world, "Your Contract has Expired - Post Fight")
|
||||
# This connection must never have any rules placed on it because they will not be inherited when setting up act
|
||||
# connections, only the rules for the entrances to the act and the rules for the Act Completion are inherited.
|
||||
sf_finale.connect(sf_finale_post_fight, "YCHE -> YCHE - Post Fight")
|
||||
|
||||
create_rift_connections(world, create_region(world, "Time Rift - Sleepy Subcon"))
|
||||
create_rift_connections(world, create_region(world, "Time Rift - Pipe"))
|
||||
@@ -846,6 +859,9 @@ def create_region(world: "HatInTimeWorld", name: str) -> Region:
|
||||
if key in storybook_pages.keys() and not world.options.ShuffleStorybookPages:
|
||||
continue
|
||||
|
||||
if key in director_tokens.keys() and not world.options.ShuffleDirectorTokens:
|
||||
continue
|
||||
|
||||
location = HatInTimeLocation(world.player, key, data.id, reg)
|
||||
reg.locations.append(location)
|
||||
if location.name in shop_locations:
|
||||
@@ -947,6 +963,16 @@ def get_shuffled_region(world: "HatInTimeWorld", region: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def get_region_shuffled_to(world: "HatInTimeWorld", region: str) -> str:
|
||||
if world.options.ActRandomizer:
|
||||
original_ci: str = chapter_act_info[region]
|
||||
shuffled_ci = world.act_connections[original_ci]
|
||||
return next(act_name for act_name, ci in chapter_act_info.items()
|
||||
if ci == shuffled_ci)
|
||||
else:
|
||||
return region
|
||||
|
||||
|
||||
def get_region_location_count(world: "HatInTimeWorld", region_name: str, included_only: bool = True) -> int:
|
||||
count = 0
|
||||
region = world.multiworld.get_region(region_name, world.player)
|
||||
|
||||
+65
-26
@@ -414,7 +414,7 @@ def set_moderate_rules(world: "HatInTimeWorld"):
|
||||
|
||||
# Moderate: Mystifying Time Mesa time trial without hats
|
||||
set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
|
||||
lambda state: can_use_hookshot(state, world))
|
||||
lambda state: True)
|
||||
|
||||
# Moderate: Goat Refinery from TIHS with Sprint only
|
||||
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
|
||||
@@ -455,7 +455,7 @@ def set_moderate_rules(world: "HatInTimeWorld"):
|
||||
if "Pink Paw Station Thug" in key and is_location_valid(world, key):
|
||||
set_rule(world.multiworld.get_location(key, world.player), lambda state: True)
|
||||
|
||||
# Moderate: clear Rush Hour without Hookshot
|
||||
# Moderate: clear Rush Hour without Hookshot or Dweller Mask
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
lambda state: state.has("Metro Ticket - Pink", world.player)
|
||||
and state.has("Metro Ticket - Yellow", world.player)
|
||||
@@ -481,9 +481,8 @@ def set_hard_rules(world: "HatInTimeWorld"):
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player),
|
||||
lambda state: has_paintings(state, world, 3))
|
||||
|
||||
# Cherry bridge over boss arena gap (painting still expected)
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
|
||||
# Cherry bridge over boss arena gap
|
||||
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"), lambda state: True)
|
||||
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player),
|
||||
lambda state: has_paintings(state, world, 2, True))
|
||||
@@ -493,9 +492,6 @@ def set_hard_rules(world: "HatInTimeWorld"):
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
# SDJ
|
||||
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or")
|
||||
|
||||
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
|
||||
|
||||
@@ -533,7 +529,10 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True)
|
||||
# There are not enough buckets/beach balls to bucket/ball hover in Heating Up Mafia Town, so any other Mafia Town
|
||||
# act is required.
|
||||
add_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player),
|
||||
lambda state: state.can_reach_region("Mafia Town Area", world.player), "or")
|
||||
|
||||
# Expert: Clear Dead Bird Studio with nothing
|
||||
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
|
||||
@@ -566,31 +565,65 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
lambda state: True)
|
||||
|
||||
# Expert: Cherry Hovering
|
||||
subcon_area = world.multiworld.get_region("Subcon Forest Area", world.player)
|
||||
yche = world.multiworld.get_region("Your Contract has Expired", world.player)
|
||||
entrance = yche.connect(subcon_area, "Subcon Forest Entrance YCHE")
|
||||
# Skipping the boss firewall is possible with a Cherry Hover.
|
||||
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
|
||||
lambda state: has_paintings(state, world, 1, True))
|
||||
# The boss arena gap can be crossed in reverse with a Cherry Hover.
|
||||
subcon_boss_arena = world.get_region("Subcon Forest Boss Arena")
|
||||
subcon_behind_boss_firewall = world.get_region("Subcon Forest Behind Boss Firewall")
|
||||
subcon_boss_arena.connect(subcon_behind_boss_firewall, "SF Boss Arena -> SF Behind Boss Firewall")
|
||||
|
||||
if world.options.NoPaintingSkips:
|
||||
add_rule(entrance, lambda state: has_paintings(state, world, 1))
|
||||
subcon_area = world.get_region("Subcon Forest Area")
|
||||
|
||||
# The boss firewall can be skipped in reverse with a Cherry Hover, but it is not possible to remove the boss
|
||||
# firewall from reverse because the paintings to burn to remove the firewall are on the other side of the firewall.
|
||||
# Therefore, a painting skip is required. The paintings could be burned by already having access to
|
||||
# "Subcon Forest Area" through another entrance, but making a new connection to "Subcon Forest Area" in that case
|
||||
# would be pointless.
|
||||
if not world.options.NoPaintingSkips:
|
||||
# The import cannot be done at the module-level because it would cause a circular import.
|
||||
from .Regions import get_region_shuffled_to
|
||||
|
||||
subcon_behind_boss_firewall.connect(subcon_area, "SF Behind Boss Firewall -> SF Area")
|
||||
|
||||
# Because the Your Contract has Expired entrance can now reach "Subcon Forest Area", it needs to be connected to
|
||||
# each of the Subcon Forest Time Rift entrances, like the other Subcon Forest Acts.
|
||||
yche = world.get_region("Your Contract has Expired")
|
||||
|
||||
def connect_to_shuffled_act_at(original_act_name):
|
||||
region_name = get_region_shuffled_to(world, original_act_name)
|
||||
return yche.connect(world.get_region(region_name), f"{original_act_name} Portal - Entrance YCHE")
|
||||
|
||||
# Rules copied from `Rules.set_rift_rules()` with painting logic removed because painting skips must be
|
||||
# available.
|
||||
entrance = connect_to_shuffled_act_at("Time Rift - Pipe")
|
||||
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 2"))
|
||||
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 2").connected_region, entrance)
|
||||
|
||||
entrance = connect_to_shuffled_act_at("Time Rift - Village")
|
||||
add_rule(entrance, lambda state: can_clear_required_act(state, world, "Subcon Forest - Act 4"))
|
||||
reg_act_connection(world, world.get_entrance("Subcon Forest - Act 4").connected_region, entrance)
|
||||
|
||||
entrance = connect_to_shuffled_act_at("Time Rift - Sleepy Subcon")
|
||||
add_rule(entrance, lambda state: has_relic_combo(state, world, "UFO"))
|
||||
|
||||
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
|
||||
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
|
||||
and has_paintings(state, world, 1, True))
|
||||
|
||||
# Set painting rules only. Skipping paintings is determined in has_paintings
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: has_paintings(state, world, 1, True))
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player),
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
# You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him
|
||||
subcon_area.connect(yche, "Snatcher Hover")
|
||||
set_rule(world.multiworld.get_location("Act Completion (Your Contract has Expired)", world.player),
|
||||
lambda state: True)
|
||||
yche_post_fight = world.get_region("Your Contract has Expired - Post Fight")
|
||||
subcon_area.connect(yche_post_fight, "Snatcher Hover")
|
||||
# Cherry Hover from YCHE also works, so there are no requirements for the Act Completion.
|
||||
set_rule(world.get_location("Act Completion (Your Contract has Expired)"), lambda state: True)
|
||||
|
||||
if world.is_dlc2():
|
||||
# Expert: clear Rush Hour with nothing
|
||||
if not world.options.NoTicketSkips:
|
||||
if world.options.NoTicketSkips != NoTicketSkips.option_true:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
|
||||
else:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
@@ -681,12 +714,18 @@ def set_subcon_rules(world: "HatInTimeWorld"):
|
||||
lambda state: can_use_hat(state, world, HatType.BREWING) or state.has("Umbrella", world.player)
|
||||
or can_use_hat(state, world, HatType.DWELLER))
|
||||
|
||||
# You can't skip over the boss arena wall without cherry hover, so these two need to be set this way
|
||||
set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player),
|
||||
lambda state: state.has("TOD Access", world.player) and can_use_hookshot(state, world)
|
||||
and has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player))
|
||||
# You can't skip over the boss arena wall without cherry hover.
|
||||
set_rule(world.get_entrance("SF Area -> SF Behind Boss Firewall"),
|
||||
lambda state: has_paintings(state, world, 1, False))
|
||||
|
||||
# The painting wall can't be skipped without cherry hover, which is Expert
|
||||
# The hookpoints to cross the boss arena gap are only present in Toilet of Doom.
|
||||
set_rule(world.get_entrance("SF Behind Boss Firewall -> SF Boss Arena"),
|
||||
lambda state: state.has("TOD Access", world.player)
|
||||
and can_use_hookshot(state, world))
|
||||
|
||||
# The Act Completion is in the Toilet of Doom region, so the same rules as passing the boss firewall and crossing
|
||||
# the boss arena gap are required. "TOD Access" is implied from the region so does not need to be included in the
|
||||
# rule.
|
||||
set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player),
|
||||
lambda state: can_use_hookshot(state, world) and can_hit(state, world)
|
||||
and has_paintings(state, world, 1, False))
|
||||
@@ -739,7 +778,7 @@ def set_dlc1_rules(world: "HatInTimeWorld"):
|
||||
|
||||
# This particular item isn't present in Act 3 for some reason, yes in vanilla too
|
||||
add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player),
|
||||
lambda state: state.can_reach("Bon Voyage!", "Region", world.player)
|
||||
lambda state: (state.can_reach("Bon Voyage!", "Region", world.player) and can_use_hookshot(state, world))
|
||||
or state.can_reach("Ship Shape", "Region", world.player))
|
||||
|
||||
|
||||
|
||||
+9
-10
@@ -1,6 +1,6 @@
|
||||
from BaseClasses import Item, ItemClassification, Tutorial, Location, MultiWorld
|
||||
from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool, get_shop_trap_name, \
|
||||
calculate_yarn_costs, alps_hooks
|
||||
calculate_yarn_costs, alps_hooks, junk_weights
|
||||
from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region
|
||||
from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \
|
||||
get_total_locations
|
||||
@@ -12,13 +12,13 @@ from .DeathWishRules import set_dw_rules, create_enemy_events, hit_list, bosses
|
||||
from worlds.AutoWorld import World, WebWorld, CollectionState
|
||||
from worlds.generic.Rules import add_rule
|
||||
from typing import List, Dict, TextIO
|
||||
from worlds.LauncherComponents import Component, components, icon_paths, launch_subprocess, Type
|
||||
from worlds.LauncherComponents import Component, components, icon_paths, launch as launch_component, Type
|
||||
from Utils import local_path
|
||||
|
||||
|
||||
def launch_client():
|
||||
def launch_client(*args: str):
|
||||
from .Client import launch
|
||||
launch_subprocess(launch, name="AHITClient")
|
||||
launch_component(launch, name="AHITClient", args=args)
|
||||
|
||||
|
||||
components.append(Component("A Hat in Time Client", "AHITClient", func=launch_client,
|
||||
@@ -34,7 +34,7 @@ class AWebInTime(WebWorld):
|
||||
"Multiworld Setup Guide",
|
||||
"A guide for setting up A Hat in Time to be played in Archipelago.",
|
||||
"English",
|
||||
"ahit_en.md",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["CookieCat"]
|
||||
)]
|
||||
@@ -78,6 +78,9 @@ class HatInTimeWorld(World):
|
||||
self.nyakuza_thug_items: Dict[str, int] = {}
|
||||
self.badge_seller_count: int = 0
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.random.choices(list(junk_weights.keys()), weights=junk_weights.values(), k=1)[0]
|
||||
|
||||
def generate_early(self):
|
||||
adjust_options(self)
|
||||
|
||||
@@ -257,11 +260,7 @@ class HatInTimeWorld(World):
|
||||
f"{item_name} ({self.multiworld.get_player_name(loc.item.player)})")
|
||||
|
||||
slot_data["ShopItemNames"] = shop_item_names
|
||||
|
||||
for name, value in self.options.as_dict(*self.options_dataclass.type_hints).items():
|
||||
if name in slot_data_options:
|
||||
slot_data[name] = value
|
||||
|
||||
slot_data.update(self.options.as_dict(*slot_data_options))
|
||||
return slot_data
|
||||
|
||||
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]):
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
|
||||
3. Click the **Betas** tab. In the **Beta Participation** dropdown, select `tcplink`.
|
||||
While it downloads, you can subscribe to the [Archipelago workshop mod.]((https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601))
|
||||
While it downloads, you can subscribe to the [Archipelago workshop mod](https://steamcommunity.com/sharedfiles/filedetails/?id=3026842601).
|
||||
|
||||
|
||||
4. Once the game finishes downloading, start it up.
|
||||
@@ -62,4 +62,4 @@ The level that the relic set unlocked will stay unlocked.
|
||||
|
||||
### When I start a new save file, the intro cinematic doesn't get skipped, Hat Kid's body is missing and the mod doesn't work!
|
||||
There is a bug on older versions of A Hat in Time that causes save file creation to fail to work properly
|
||||
if you have too many save files. Delete them and it should fix the problem.
|
||||
if you have too many save files. Delete them and it should fix the problem.
|
||||
|
||||
@@ -102,7 +102,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
state.has('Fire Rod', player) or
|
||||
(
|
||||
state.has('Bombos', player) and
|
||||
(has_sword(state, player) or state.multiworld.swordless[player])
|
||||
(has_sword(state, player) or state.multiworld.worlds[player].options.swordless)
|
||||
)
|
||||
) and
|
||||
(
|
||||
@@ -111,7 +111,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
(
|
||||
state.has('Fire Rod', player) and
|
||||
state.has('Bombos', player) and
|
||||
state.multiworld.swordless[player] and
|
||||
state.multiworld.worlds[player].options.swordless and
|
||||
can_extend_magic(state, player, 16)
|
||||
)
|
||||
)
|
||||
@@ -119,7 +119,9 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
|
||||
|
||||
def VitreousDefeatRule(state, player: int) -> bool:
|
||||
return can_shoot_arrows(state, player) or has_melee_weapon(state, player)
|
||||
return ((can_shoot_arrows(state, player) and can_use_bombs(state, player, 10))
|
||||
or can_shoot_arrows(state, player, 35) or state.has("Silver Bow", player)
|
||||
or has_melee_weapon(state, player))
|
||||
|
||||
|
||||
def TrinexxDefeatRule(state, player: int) -> bool:
|
||||
@@ -135,7 +137,7 @@ def AgahnimDefeatRule(state, player: int) -> bool:
|
||||
|
||||
|
||||
def GanonDefeatRule(state, player: int) -> bool:
|
||||
if state.multiworld.swordless[player]:
|
||||
if state.multiworld.worlds[player].options.swordless:
|
||||
return state.has('Hammer', player) and \
|
||||
has_fire_source(state, player) and \
|
||||
state.has('Silver Bow', player) and \
|
||||
@@ -144,7 +146,7 @@ def GanonDefeatRule(state, player: int) -> bool:
|
||||
can_hurt = has_beam_sword(state, player)
|
||||
common = can_hurt and has_fire_source(state, player)
|
||||
# silverless ganon may be needed in anything higher than no glitches
|
||||
if state.multiworld.glitches_required[player] != 'no_glitches':
|
||||
if state.multiworld.worlds[player].options.glitches_required != 'no_glitches':
|
||||
# need to light torch a sufficient amount of times
|
||||
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
|
||||
state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or
|
||||
@@ -246,7 +248,7 @@ for location in boss_location_table:
|
||||
|
||||
def place_boss(world: "ALTTPWorld", boss: str, location: str, level: Optional[str]) -> None:
|
||||
player = world.player
|
||||
if location == 'Ganons Tower' and world.multiworld.mode[player] == 'inverted':
|
||||
if location == 'Ganons Tower' and world.options.mode == 'inverted':
|
||||
location = 'Inverted Ganons Tower'
|
||||
logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else ''))
|
||||
world.dungeons[location].bosses[level] = BossFactory(boss, player)
|
||||
@@ -258,9 +260,8 @@ def format_boss_location(location_name: str, level: str) -> str:
|
||||
|
||||
def place_bosses(world: "ALTTPWorld") -> None:
|
||||
multiworld = world.multiworld
|
||||
player = world.player
|
||||
# will either be an int or a lower case string with ';' between options
|
||||
boss_shuffle: Union[str, int] = multiworld.boss_shuffle[player].value
|
||||
boss_shuffle: Union[str, int] = world.options.boss_shuffle.value
|
||||
already_placed_bosses: List[str] = []
|
||||
remaining_locations: List[Tuple[str, str]] = []
|
||||
# handle plando
|
||||
|
||||
@@ -464,7 +464,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool:
|
||||
snes_logger.info(f"Discarding recent {len(new_locations)} checks as ROM Status has changed.")
|
||||
return False
|
||||
else:
|
||||
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": new_locations}])
|
||||
await ctx.check_locations(new_locations)
|
||||
await snes_flush_writes(ctx)
|
||||
return True
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ def create_dungeons(world: "ALTTPWorld"):
|
||||
|
||||
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
|
||||
dungeon = Dungeon(name, dungeon_regions, big_key,
|
||||
[] if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal else small_keys,
|
||||
[] if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal else small_keys,
|
||||
dungeon_items, player)
|
||||
for item in dungeon.all_items:
|
||||
item.dungeon = dungeon
|
||||
@@ -143,7 +143,7 @@ def create_dungeons(world: "ALTTPWorld"):
|
||||
item_factory(['Small Key (Turtle Rock)'] * 6, world),
|
||||
item_factory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], world))
|
||||
|
||||
if multiworld.mode[player] != 'inverted':
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None,
|
||||
item_factory(['Small Key (Agahnims Tower)'] * 4, world), [])
|
||||
GT = make_dungeon('Ganons Tower', 'Agahnim2',
|
||||
@@ -209,8 +209,8 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
if localized:
|
||||
in_dungeon_items = [item for item in get_dungeon_item_pool(multiworld) if (item.player, item.name) in localized]
|
||||
if in_dungeon_items:
|
||||
restricted_players = {player for player, restricted in multiworld.restrict_dungeon_item_on_boss.items() if
|
||||
restricted}
|
||||
restricted_players = {world.player for world in multiworld.get_game_worlds("A Link to the Past") if
|
||||
world.options.restrict_dungeon_item_on_boss}
|
||||
locations: typing.List["ALttPLocation"] = [
|
||||
location for location in get_unfilled_dungeon_locations(multiworld)
|
||||
# filter boss
|
||||
@@ -239,7 +239,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
multiworld.worlds[item.player].collect(all_state_base, item)
|
||||
pre_fill_items = []
|
||||
for player in in_dungeon_player_ids:
|
||||
pre_fill_items += multiworld.worlds[player].get_pre_fill_items()
|
||||
pre_fill_items += [item for item in multiworld.worlds[player].get_pre_fill_items() if not item.crystal]
|
||||
for item in in_dungeon_items:
|
||||
try:
|
||||
pre_fill_items.remove(item)
|
||||
@@ -255,8 +255,9 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
if all_state_base.has("Triforce", player):
|
||||
all_state_base.remove(multiworld.worlds[player].create_item("Triforce"))
|
||||
|
||||
for (player, key_drop_shuffle) in multiworld.key_drop_shuffle.items():
|
||||
if not key_drop_shuffle and player not in multiworld.groups:
|
||||
for lttp_world in multiworld.get_game_worlds("A Link to the Past"):
|
||||
if not lttp_world.options.key_drop_shuffle and lttp_world.player not in multiworld.groups:
|
||||
player = lttp_world.player
|
||||
for key_loc in key_drop_data:
|
||||
key_data = key_drop_data[key_loc]
|
||||
all_state_base.remove(item_factory(key_data[3], multiworld.worlds[player]))
|
||||
|
||||
@@ -54,16 +54,13 @@ def parse_arguments(argv, no_defaults=False):
|
||||
ret = parser.parse_args(argv)
|
||||
|
||||
# cannot be set through CLI currently
|
||||
ret.plando_items = []
|
||||
ret.plando_texts = {}
|
||||
ret.plando_connections = []
|
||||
|
||||
if multiargs.multi:
|
||||
defaults = copy.deepcopy(ret)
|
||||
for player in range(1, multiargs.multi + 1):
|
||||
playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True)
|
||||
|
||||
for name in ["plando_items", "plando_texts", "plando_connections", "game", "sprite", "sprite_pool"]:
|
||||
for name in ["game", "sprite", "sprite_pool"]:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
|
||||
+523
-515
File diff suppressed because it is too large
Load Diff
+247
-246
@@ -1,15 +1,16 @@
|
||||
import collections
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from .Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region
|
||||
from .SubClasses import LTTPRegionType
|
||||
|
||||
|
||||
def create_inverted_regions(world, player):
|
||||
def create_inverted_regions(multiworld: MultiWorld, player: int):
|
||||
|
||||
world.regions += [
|
||||
create_dw_region(world, player, 'Menu', None,
|
||||
multiworld.regions += [
|
||||
create_dw_region(multiworld, player, 'Menu', None,
|
||||
['Links House S&Q', 'Dark Sanctuary S&Q', 'Old Man S&Q', 'Castle Ledge S&Q']),
|
||||
create_lw_region(world, player, 'Light World',
|
||||
create_lw_region(multiworld, player, 'Light World',
|
||||
['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest',
|
||||
'Bombos Tablet'],
|
||||
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
|
||||
@@ -35,184 +36,184 @@ def create_inverted_regions(world, player):
|
||||
'Hyrule Castle Entrance (South)', 'Secret Passage Outer Bushes',
|
||||
'Bush Covered Lawn Outer Bushes',
|
||||
'Potion Shop Outer Bushes', 'Graveyard Cave Outer Bushes', 'Bomb Hut Outer Bushes']),
|
||||
create_lw_region(world, player, 'Bush Covered Lawn', None,
|
||||
create_lw_region(multiworld, player, 'Bush Covered Lawn', None,
|
||||
['Bush Covered House', 'Bush Covered Lawn Inner Bushes', 'Bush Covered Lawn Mirror Spot']),
|
||||
create_lw_region(world, player, 'Bomb Hut Area', None,
|
||||
create_lw_region(multiworld, player, 'Bomb Hut Area', None,
|
||||
['Light World Bomb Hut', 'Bomb Hut Inner Bushes', 'Bomb Hut Mirror Spot']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Secret Entrance Area', None,
|
||||
create_lw_region(multiworld, player, 'Hyrule Castle Secret Entrance Area', None,
|
||||
['Hyrule Castle Secret Entrance Stairs', 'Secret Passage Inner Bushes']),
|
||||
create_lw_region(world, player, 'Death Mountain Entrance', None,
|
||||
create_lw_region(multiworld, player, 'Death Mountain Entrance', None,
|
||||
['Old Man Cave (West)', 'Death Mountain Entrance Drop', 'Bumper Cave Entrance Mirror Spot']),
|
||||
create_lw_region(world, player, 'Lake Hylia Central Island', None,
|
||||
create_lw_region(multiworld, player, 'Lake Hylia Central Island', None,
|
||||
['Capacity Upgrade', 'Lake Hylia Central Island Mirror Spot']),
|
||||
create_cave_region(world, player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
|
||||
create_cave_region(multiworld, player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
|
||||
"Blind\'s Hideout - Left",
|
||||
"Blind\'s Hideout - Right",
|
||||
"Blind\'s Hideout - Far Left",
|
||||
"Blind\'s Hideout - Far Right"]),
|
||||
create_lw_region(world, player, 'Northeast Light World', None,
|
||||
create_lw_region(multiworld, player, 'Northeast Light World', None,
|
||||
['Zoras River', 'Waterfall of Wishing Cave', 'Potion Shop Outer Rock', 'Catfish Mirror Spot',
|
||||
'Northeast Light World Warp']),
|
||||
create_lw_region(world, player, 'Waterfall of Wishing Cave', None,
|
||||
create_lw_region(multiworld, player, 'Waterfall of Wishing Cave', None,
|
||||
['Waterfall of Wishing', 'Northeast Light World Return']),
|
||||
create_lw_region(world, player, 'Potion Shop Area', None,
|
||||
create_lw_region(multiworld, player, 'Potion Shop Area', None,
|
||||
['Potion Shop', 'Potion Shop Inner Bushes', 'Potion Shop Inner Rock',
|
||||
'Potion Shop Mirror Spot', 'Potion Shop River Drop']),
|
||||
create_lw_region(world, player, 'Graveyard Cave Area', None,
|
||||
create_lw_region(multiworld, player, 'Graveyard Cave Area', None,
|
||||
['Graveyard Cave', 'Graveyard Cave Inner Bushes', 'Graveyard Cave Mirror Spot']),
|
||||
create_lw_region(world, player, 'River', None, ['Light World Pier', 'Potion Shop Pier']),
|
||||
create_cave_region(world, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit',
|
||||
create_lw_region(multiworld, player, 'River', None, ['Light World Pier', 'Potion Shop Pier']),
|
||||
create_cave_region(multiworld, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit',
|
||||
['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
create_lw_region(world, player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region(world, player, 'Waterfall of Wishing', 'a cave with two chests',
|
||||
create_lw_region(multiworld, player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region(multiworld, player, 'Waterfall of Wishing', 'a cave with two chests',
|
||||
['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_lw_region(world, player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region(world, player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||
create_cave_region(world, player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||
create_cave_region(world, player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']),
|
||||
create_cave_region(world, player, 'Inverted Links House', 'your house', ['Link\'s House'],
|
||||
create_lw_region(multiworld, player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region(multiworld, player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||
create_cave_region(multiworld, player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||
create_cave_region(multiworld, player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']),
|
||||
create_cave_region(multiworld, player, 'Inverted Links House', 'your house', ['Link\'s House'],
|
||||
['Inverted Links House Exit']),
|
||||
create_cave_region(world, player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region(world, player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
|
||||
create_cave_region(world, player, 'Elder House', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region(multiworld, player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
|
||||
create_cave_region(multiworld, player, 'Elder House', 'a connector', None,
|
||||
['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||
create_cave_region(world, player, 'Snitch Lady (East)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Snitch Lady (West)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Bush Covered House', 'the grass man'),
|
||||
create_cave_region(world, player, 'Tavern (Front)', 'the tavern'),
|
||||
create_cave_region(world, player, 'Light World Bomb Hut', 'a restock room'),
|
||||
create_cave_region(world, player, 'Kakariko Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Fortune Teller (Light)', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Lake Hylia Fortune Teller', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Lumberjack House', 'a boring house'),
|
||||
create_cave_region(world, player, 'Bonk Fairy (Light)', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Bonk Fairy (Dark)', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Swamp Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(world, player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(world, player, 'Sahasrahlas Hut', 'Sahasrahla',
|
||||
create_cave_region(multiworld, player, 'Snitch Lady (East)', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Snitch Lady (West)', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Bush Covered House', 'the grass man'),
|
||||
create_cave_region(multiworld, player, 'Tavern (Front)', 'the tavern'),
|
||||
create_cave_region(multiworld, player, 'Light World Bomb Hut', 'a restock room'),
|
||||
create_cave_region(multiworld, player, 'Kakariko Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Fortune Teller (Light)', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Lake Hylia Fortune Teller', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Lumberjack House', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Bonk Fairy (Light)', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Bonk Fairy (Dark)', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Swamp Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(multiworld, player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(multiworld, player, 'Sahasrahlas Hut', 'Sahasrahla',
|
||||
['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right',
|
||||
'Sahasrahla']),
|
||||
create_cave_region(world, player, 'Kakariko Well (top)', 'a drop\'s exit',
|
||||
create_cave_region(multiworld, player, 'Kakariko Well (top)', 'a drop\'s exit',
|
||||
['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||
create_cave_region(world, player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(world, player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(world, player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(world, player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(world, player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(world, player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(world, player, 'Hobo Bridge', ['Hobo']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'],
|
||||
create_cave_region(multiworld, player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(multiworld, player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(multiworld, player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(multiworld, player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(multiworld, player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(multiworld, player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(multiworld, player, 'Hobo Bridge', ['Hobo']),
|
||||
create_cave_region(multiworld, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'],
|
||||
['Lost Woods Hideout (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None,
|
||||
create_cave_region(multiworld, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None,
|
||||
['Lost Woods Hideout Exit']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'],
|
||||
create_cave_region(multiworld, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'],
|
||||
['Lumberjack Tree (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
|
||||
create_cave_region(world, player, 'Cave 45', 'a cave with an item', ['Cave 45']),
|
||||
create_cave_region(world, player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
|
||||
create_cave_region(world, player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
|
||||
create_cave_region(world, player, 'Long Fairy Cave', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Mini Moldorm Cave', 'a bounty of five items',
|
||||
create_cave_region(multiworld, player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
|
||||
create_cave_region(multiworld, player, 'Cave 45', 'a cave with an item', ['Cave 45']),
|
||||
create_cave_region(multiworld, player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
|
||||
create_cave_region(multiworld, player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
|
||||
create_cave_region(multiworld, player, 'Long Fairy Cave', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Mini Moldorm Cave', 'a bounty of five items',
|
||||
['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']),
|
||||
create_cave_region(world, player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
|
||||
create_cave_region(world, player, 'Good Bee Cave', 'a cold bee'),
|
||||
create_cave_region(world, player, '20 Rupee Cave', 'a cave with some cash'),
|
||||
create_cave_region(world, player, 'Cave Shop (Lake Hylia)', 'a common shop'),
|
||||
create_cave_region(world, player, 'Cave Shop (Dark Death Mountain)', 'a common shop'),
|
||||
create_cave_region(world, player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']),
|
||||
create_cave_region(world, player, 'Library', 'the library', ['Library']),
|
||||
create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'),
|
||||
create_cave_region(world, player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Island', ['Lake Hylia Island']),
|
||||
create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
|
||||
create_cave_region(world, player, 'Two Brothers House', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
|
||||
create_cave_region(multiworld, player, 'Good Bee Cave', 'a cold bee'),
|
||||
create_cave_region(multiworld, player, '20 Rupee Cave', 'a cave with some cash'),
|
||||
create_cave_region(multiworld, player, 'Cave Shop (Lake Hylia)', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Cave Shop (Dark Death Mountain)', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']),
|
||||
create_cave_region(multiworld, player, 'Library', 'the library', ['Library']),
|
||||
create_cave_region(multiworld, player, 'Kakariko Gamble Game', 'a game of chance'),
|
||||
create_cave_region(multiworld, player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
|
||||
create_lw_region(multiworld, player, 'Lake Hylia Island', ['Lake Hylia Island']),
|
||||
create_cave_region(multiworld, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
|
||||
create_cave_region(multiworld, player, 'Two Brothers House', 'a connector', None,
|
||||
['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||
create_lw_region(world, player, 'Maze Race Ledge', ['Maze Race'],
|
||||
create_lw_region(multiworld, player, 'Maze Race Ledge', ['Maze Race'],
|
||||
['Two Brothers House (West)', 'Maze Race Mirror Spot']),
|
||||
create_cave_region(world, player, '50 Rupee Cave', 'a cave with some cash'),
|
||||
create_lw_region(world, player, 'Desert Ledge', ['Desert Ledge'],
|
||||
create_cave_region(multiworld, player, '50 Rupee Cave', 'a cave with some cash'),
|
||||
create_lw_region(multiworld, player, 'Desert Ledge', ['Desert Ledge'],
|
||||
['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)',
|
||||
'Desert Ledge Drop']),
|
||||
create_lw_region(world, player, 'Desert Palace Stairs', None,
|
||||
create_lw_region(multiworld, player, 'Desert Palace Stairs', None,
|
||||
['Desert Palace Entrance (South)', 'Desert Palace Stairs Mirror Spot']),
|
||||
create_lw_region(world, player, 'Desert Palace Lone Stairs', None,
|
||||
create_lw_region(multiworld, player, 'Desert Palace Lone Stairs', None,
|
||||
['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region(world, player, 'Desert Palace Entrance (North) Spot', None,
|
||||
create_lw_region(multiworld, player, 'Desert Palace Entrance (North) Spot', None,
|
||||
['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks',
|
||||
'Desert Palace North Mirror Spot']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace North', 'Desert Palace',
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace North', 'Desert Palace',
|
||||
['Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key',
|
||||
'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
'Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
create_dungeon_region(world, player, 'Eastern Palace', 'Eastern Palace',
|
||||
create_dungeon_region(multiworld, player, 'Eastern Palace', 'Eastern Palace',
|
||||
['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
|
||||
'Eastern Palace - Cannonball Chest',
|
||||
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'],
|
||||
['Eastern Palace Exit']),
|
||||
create_lw_region(world, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(world, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
|
||||
create_dungeon_region(world, player, 'Hyrule Castle', 'Hyrule Castle',
|
||||
create_lw_region(multiworld, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(multiworld, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(multiworld, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
|
||||
create_dungeon_region(multiworld, player, 'Hyrule Castle', 'Hyrule Castle',
|
||||
['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest',
|
||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||
'Hyrule Castle - Big Key Drop'],
|
||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)',
|
||||
'Throne Room']),
|
||||
create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
|
||||
create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
create_dungeon_region(multiworld, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(multiworld, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(multiworld, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
|
||||
create_dungeon_region(multiworld, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right']),
|
||||
create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(world, player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(world, player, 'Old Man Cave', 'a connector', ['Old Man'],
|
||||
create_dungeon_region(multiworld, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(multiworld, player, 'Old Man Cave', 'a connector', ['Old Man'],
|
||||
['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
create_cave_region(world, player, 'Old Man House', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Old Man House', 'a connector', None,
|
||||
['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||
create_cave_region(world, player, 'Old Man House Back', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Old Man House Back', 'a connector', None,
|
||||
['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||
create_lw_region(world, player, 'Death Mountain', None,
|
||||
create_lw_region(multiworld, player, 'Death Mountain', None,
|
||||
['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
|
||||
'Death Mountain Return Cave (East)', 'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)',
|
||||
'Death Mountain Mirror Spot']),
|
||||
create_cave_region(world, player, 'Death Mountain Return Cave', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Death Mountain Return Cave', 'a connector', None,
|
||||
['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
||||
create_lw_region(world, player, 'Death Mountain Return Ledge', None,
|
||||
create_lw_region(multiworld, player, 'Death Mountain Return Ledge', None,
|
||||
['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)',
|
||||
'Bumper Cave Ledge Mirror Spot']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'],
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'],
|
||||
['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None,
|
||||
['Spectacle Rock Cave Exit']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Peak)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Peak)', 'a connector', None,
|
||||
['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region(world, player, 'East Death Mountain (Bottom)', None,
|
||||
create_lw_region(multiworld, player, 'East Death Mountain (Bottom)', None,
|
||||
['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)',
|
||||
'East Death Mountain Mirror Spot (Bottom)', 'Hookshot Fairy',
|
||||
'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||
create_cave_region(world, player, 'Paradox Cave Front', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||
create_cave_region(multiworld, player, 'Paradox Cave Front', 'a connector', None,
|
||||
['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)',
|
||||
'Light World Death Mountain Shop']),
|
||||
create_cave_region(world, player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
|
||||
create_cave_region(multiworld, player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
|
||||
'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right',
|
||||
'Paradox Cave Lower - Far Right',
|
||||
@@ -220,273 +221,273 @@ def create_inverted_regions(world, player):
|
||||
'Paradox Cave Upper - Left',
|
||||
'Paradox Cave Upper - Right'],
|
||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
||||
create_cave_region(world, player, 'Paradox Cave', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Paradox Cave', 'a connector', None,
|
||||
['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||
create_cave_region(world, player, 'Light World Death Mountain Shop', 'a common shop'),
|
||||
create_lw_region(world, player, 'East Death Mountain (Top)', ['Floating Island'],
|
||||
create_cave_region(multiworld, player, 'Light World Death Mountain Shop', 'a common shop'),
|
||||
create_lw_region(multiworld, player, 'East Death Mountain (Top)', ['Floating Island'],
|
||||
['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access',
|
||||
'East Death Mountain Drop', 'East Death Mountain Mirror Spot (Top)',
|
||||
'Fairy Ascension Ledge Access', 'Mimic Cave Ledge Access',
|
||||
'Floating Island Mirror Spot']),
|
||||
create_lw_region(world, player, 'Spiral Cave Ledge', None,
|
||||
create_lw_region(multiworld, player, 'Spiral Cave Ledge', None,
|
||||
['Spiral Cave', 'Spiral Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (West)']),
|
||||
create_lw_region(world, player, 'Mimic Cave Ledge', None,
|
||||
create_lw_region(multiworld, player, 'Mimic Cave Ledge', None,
|
||||
['Mimic Cave', 'Mimic Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (East)']),
|
||||
create_cave_region(world, player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'],
|
||||
create_cave_region(multiworld, player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'],
|
||||
['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']),
|
||||
create_lw_region(world, player, 'Fairy Ascension Plateau', None,
|
||||
create_cave_region(multiworld, player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']),
|
||||
create_lw_region(multiworld, player, 'Fairy Ascension Plateau', None,
|
||||
['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Bottom)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Bottom)', 'a connector', None,
|
||||
['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Drop)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Drop)', 'a connector', None,
|
||||
['Fairy Ascension Cave Pots']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Top)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Top)', 'a connector', None,
|
||||
['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']),
|
||||
create_lw_region(world, player, 'Fairy Ascension Ledge', None,
|
||||
create_lw_region(multiworld, player, 'Fairy Ascension Ledge', None,
|
||||
['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)', 'Laser Bridge Mirror Spot']),
|
||||
create_lw_region(world, player, 'Death Mountain (Top)', ['Ether Tablet', 'Spectacle Rock'],
|
||||
create_lw_region(multiworld, player, 'Death Mountain (Top)', ['Ether Tablet', 'Spectacle Rock'],
|
||||
['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop',
|
||||
'Death Mountain (Top) Mirror Spot']),
|
||||
create_dw_region(world, player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'],
|
||||
create_dw_region(multiworld, player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'],
|
||||
['Bumper Cave Ledge Drop', 'Bumper Cave (Top)']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']),
|
||||
|
||||
create_dw_region(world, player, 'East Dark World', ['Pyramid'],
|
||||
create_dw_region(multiworld, player, 'East Dark World', ['Pyramid'],
|
||||
['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness',
|
||||
'Dark Lake Hylia Drop (East)',
|
||||
'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint',
|
||||
'Northeast Dark World Broken Bridge Pass', 'East Dark World Teleporter', 'EDW Flute']),
|
||||
create_dw_region(world, player, 'Catfish', ['Catfish'], ['Catfish Exit Rock']),
|
||||
create_dw_region(world, player, 'Northeast Dark World', None,
|
||||
create_dw_region(multiworld, player, 'Catfish', ['Catfish'], ['Catfish Exit Rock']),
|
||||
create_dw_region(multiworld, player, 'Northeast Dark World', None,
|
||||
['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass',
|
||||
'NEDW Flute', 'Dark Lake Hylia Teleporter', 'Catfish Entrance Rock']),
|
||||
create_cave_region(world, player, 'Palace of Darkness Hint', 'a storyteller'),
|
||||
create_cave_region(world, player, 'East Dark World Hint', 'a storyteller'),
|
||||
create_dw_region(world, player, 'South Dark World', ['Stumpy', 'Digging Game'],
|
||||
create_cave_region(multiworld, player, 'Palace of Darkness Hint', 'a storyteller'),
|
||||
create_cave_region(multiworld, player, 'East Dark World Hint', 'a storyteller'),
|
||||
create_dw_region(multiworld, player, 'South Dark World', ['Stumpy', 'Digging Game'],
|
||||
['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock',
|
||||
'East Dark World Bridge', 'Inverted Links House', 'Archery Game', 'Bonk Fairy (Dark)',
|
||||
'Dark Lake Hylia Shop', 'South Dark World Teleporter', 'Post Aga Teleporter', 'SDW Flute']),
|
||||
create_cave_region(world, player, 'Inverted Big Bomb Shop', 'the bomb shop'),
|
||||
create_cave_region(world, player, 'Archery Game', 'a game of skill'),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia', None,
|
||||
create_cave_region(multiworld, player, 'Inverted Big Bomb Shop', 'the bomb shop'),
|
||||
create_cave_region(multiworld, player, 'Archery Game', 'a game of skill'),
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia', None,
|
||||
['East Dark World Pier', 'Dark Lake Hylia Ledge Pier', 'Ice Palace Missing Wall']),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia Central Island', None,
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia Central Island', None,
|
||||
['Dark Lake Hylia Shallows', 'Ice Palace', 'Dark Lake Hylia Central Island Teleporter']),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia Ledge', None,
|
||||
['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint',
|
||||
'Dark Lake Hylia Ledge Spike Cave', 'DLHL Flute']),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'),
|
||||
create_cave_region(world, player, 'Hype Cave', 'a bounty of five items',
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'),
|
||||
create_cave_region(multiworld, player, 'Hype Cave', 'a bounty of five items',
|
||||
['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
|
||||
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
|
||||
create_dw_region(world, player, 'West Dark World', ['Frog', 'Flute Activation Spot'],
|
||||
create_dw_region(multiworld, player, 'West Dark World', ['Frog', 'Flute Activation Spot'],
|
||||
['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House',
|
||||
'Chest Game', 'Thieves Town', 'Bumper Cave Entrance Rock',
|
||||
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks',
|
||||
'Red Shield Shop', 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)',
|
||||
'Dark World Lumberjack Shop',
|
||||
'West Dark World Teleporter', 'WDW Flute']),
|
||||
create_dw_region(world, player, 'Dark Grassy Lawn', None,
|
||||
create_dw_region(multiworld, player, 'Dark Grassy Lawn', None,
|
||||
['Grassy Lawn Pegs', 'Village of Outcasts Shop', 'Dark Grassy Lawn Flute']),
|
||||
create_dw_region(world, player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'],
|
||||
create_dw_region(multiworld, player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'],
|
||||
['Dark World Hammer Peg Cave', 'Peg Area Rocks', 'Hammer Peg Area Flute']),
|
||||
create_dw_region(world, player, 'Bumper Cave Entrance', None,
|
||||
create_dw_region(multiworld, player, 'Bumper Cave Entrance', None,
|
||||
['Bumper Cave (Bottom)', 'Bumper Cave Entrance Drop']),
|
||||
create_cave_region(world, player, 'Fortune Teller (Dark)', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Village of Outcasts Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Lumberjack Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Potion Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']),
|
||||
create_cave_region(world, player, 'Pyramid Fairy', 'a cave with two chests',
|
||||
create_cave_region(multiworld, player, 'Fortune Teller (Dark)', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Village of Outcasts Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Lumberjack Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Potion Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']),
|
||||
create_cave_region(multiworld, player, 'Pyramid Fairy', 'a cave with two chests',
|
||||
['Pyramid Fairy - Left', 'Pyramid Fairy - Right']),
|
||||
create_cave_region(world, player, 'Brewery', 'a house with a chest', ['Brewery']),
|
||||
create_cave_region(world, player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
|
||||
create_cave_region(world, player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
|
||||
create_cave_region(world, player, 'Red Shield Shop', 'the rare shop'),
|
||||
create_cave_region(world, player, 'Inverted Dark Sanctuary', 'a storyteller', None,
|
||||
create_cave_region(multiworld, player, 'Brewery', 'a house with a chest', ['Brewery']),
|
||||
create_cave_region(multiworld, player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
|
||||
create_cave_region(multiworld, player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
|
||||
create_cave_region(multiworld, player, 'Red Shield Shop', 'the rare shop'),
|
||||
create_cave_region(multiworld, player, 'Inverted Dark Sanctuary', 'a storyteller', None,
|
||||
['Inverted Dark Sanctuary Exit']),
|
||||
create_cave_region(world, player, 'Bumper Cave', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Bumper Cave', 'a connector', None,
|
||||
['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
|
||||
create_dw_region(world, player, 'Skull Woods Forest', None,
|
||||
create_dw_region(multiworld, player, 'Skull Woods Forest', None,
|
||||
['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)',
|
||||
'Skull Woods First Section Hole (North)',
|
||||
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
|
||||
create_dw_region(world, player, 'Skull Woods Forest (West)', None,
|
||||
create_dw_region(multiworld, player, 'Skull Woods Forest (West)', None,
|
||||
['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)',
|
||||
'Skull Woods Final Section']),
|
||||
create_dw_region(world, player, 'Dark Desert', None,
|
||||
create_dw_region(multiworld, player, 'Dark Desert', None,
|
||||
['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy', 'DD Flute']),
|
||||
create_dw_region(world, player, 'Dark Desert Ledge', None, ['Dark Desert Drop', 'Dark Desert Teleporter']),
|
||||
create_cave_region(world, player, 'Mire Shed', 'a cave with two chests',
|
||||
create_dw_region(multiworld, player, 'Dark Desert Ledge', None, ['Dark Desert Drop', 'Dark Desert Teleporter']),
|
||||
create_cave_region(multiworld, player, 'Mire Shed', 'a cave with two chests',
|
||||
['Mire Shed - Left', 'Mire Shed - Right']),
|
||||
create_cave_region(world, player, 'Dark Desert Hint', 'a storyteller'),
|
||||
create_dw_region(world, player, 'Dark Death Mountain', None,
|
||||
create_cave_region(multiworld, player, 'Dark Desert Hint', 'a storyteller'),
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain', None,
|
||||
['Dark Death Mountain Drop (East)', 'Inverted Agahnims Tower', 'Superbunny Cave (Top)',
|
||||
'Hookshot Cave', 'Turtle Rock',
|
||||
'Spike Cave', 'Dark Death Mountain Fairy', 'Dark Death Mountain Teleporter (West)',
|
||||
'Turtle Rock Tail Drop', 'DDM Flute']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain Ledge', None,
|
||||
['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)']),
|
||||
create_dw_region(world, player, 'Turtle Rock (Top)', None,
|
||||
create_dw_region(multiworld, player, 'Turtle Rock (Top)', None,
|
||||
['Dark Death Mountain Teleporter (East)', 'Turtle Rock Drop']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain Isolated Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain Isolated Ledge', None,
|
||||
['Turtle Rock Isolated Ledge Entrance']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain (East Bottom)', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain (East Bottom)', None,
|
||||
['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)',
|
||||
'Dark Death Mountain Teleporter (East Bottom)', 'EDDM Flute']),
|
||||
create_cave_region(world, player, 'Superbunny Cave (Top)', 'a connector',
|
||||
create_cave_region(multiworld, player, 'Superbunny Cave (Top)', 'a connector',
|
||||
['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Superbunny Cave (Bottom)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Superbunny Cave (Bottom)', 'a connector', None,
|
||||
['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']),
|
||||
create_cave_region(world, player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(world, player, 'Hookshot Cave', 'a connector',
|
||||
create_cave_region(multiworld, player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(multiworld, player, 'Hookshot Cave', 'a connector',
|
||||
['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
|
||||
'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']),
|
||||
create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
|
||||
create_cave_region(multiworld, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
|
||||
'Hookshot Cave Bomb Wall (North)']),
|
||||
create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None,
|
||||
create_dw_region(multiworld, player, 'Death Mountain Floating Island (Dark World)', None,
|
||||
['Floating Island Drop', 'Hookshot Cave Back Entrance']),
|
||||
create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
create_cave_region(multiworld, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
'Swamp Palace - Trench 1 Pot Key'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
'Swamp Palace - Trench 2 Pot Key'], ['Swamp Palace (North)', 'Swamp Palace (West)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterway Pot Key', 'Swamp Palace - Waterfall Room',
|
||||
'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
create_dungeon_region(multiworld, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
create_dungeon_region(multiworld, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Hallway Pot Key',
|
||||
'Thieves\' Town - Spike Switch Pot Key',
|
||||
'Thieves\' Town - Blind\'s Cell'],
|
||||
['Blind Fight']),
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
create_dungeon_region(multiworld, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Many Pots Pot Key',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest',
|
||||
'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
|
||||
'Misery Mire - Conveyor Crystal Key Drop'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Entrance to Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'],
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'],
|
||||
['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock',
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Second Section)', 'Turtle Rock',
|
||||
['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||
['Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door',
|
||||
'Turtle Rock Second Section Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Eye Bridge Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Inverted Ganons Tower (Entrance)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Inverted Ganons Tower (Entrance)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Conveyor Cross Pot Key'],
|
||||
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door',
|
||||
'Inverted Ganons Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'],
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'],
|
||||
['Ganons Tower (Tile Room) Key Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left',
|
||||
'Ganons Tower - Compass Room - Bottom Right',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
['Ganons Tower (Bottom) (East)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||
'Ganons Tower - Double Switch Pot Key'],
|
||||
['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Randomizer Room - Top Left',
|
||||
'Ganons Tower - Randomizer Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left',
|
||||
'Ganons Tower - Randomizer Room - Bottom Right'],
|
||||
['Ganons Tower (Bottom) (West)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest',
|
||||
'Ganons Tower - Big Key Room - Left',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Mini Helmasaur Room - Left',
|
||||
'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Mini Helmasaur Key Drop'],
|
||||
['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(world, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(world, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(world, player, 'Pyramid Ledge', None, ['Pyramid Drop']), # houlihan room exits here in inverted
|
||||
create_dungeon_region(multiworld, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(multiworld, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(multiworld, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(multiworld, player, 'Pyramid Ledge', None, ['Pyramid Drop']), # houlihan room exits here in inverted
|
||||
|
||||
# to simplify flute connections
|
||||
create_cave_region(world, player, 'The Sky', 'A Dark Sky', None,
|
||||
create_cave_region(multiworld, player, 'The Sky', 'A Dark Sky', None,
|
||||
['DDM Landing', 'NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing',
|
||||
'DLHL Landing']),
|
||||
|
||||
create_lw_region(world, player, 'Desert Northern Cliffs'),
|
||||
create_lw_region(world, player, 'Death Mountain Bunny Descent Area')
|
||||
create_lw_region(multiworld, player, 'Desert Northern Cliffs'),
|
||||
create_lw_region(multiworld, player, 'Death Mountain Bunny Descent Area')
|
||||
]
|
||||
|
||||
|
||||
def mark_dark_world_regions(world, player):
|
||||
def mark_dark_world_regions(multiworld: MultiWorld, player: int):
|
||||
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
||||
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
||||
queue = collections.deque(region for region in world.get_regions(player) if region.type == LTTPRegionType.DarkWorld)
|
||||
queue = collections.deque(region for region in multiworld.get_regions(player) if region.type == LTTPRegionType.DarkWorld)
|
||||
seen = set(queue)
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
@@ -499,7 +500,7 @@ def mark_dark_world_regions(world, player):
|
||||
seen.add(exit.connected_region)
|
||||
queue.append(exit.connected_region)
|
||||
|
||||
queue = collections.deque(region for region in world.get_regions(player) if region.type == LTTPRegionType.LightWorld)
|
||||
queue = collections.deque(region for region in multiworld.get_regions(player) if region.type == LTTPRegionType.LightWorld)
|
||||
seen = set(queue)
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
+142
-124
@@ -1,8 +1,9 @@
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from Fill import FillError
|
||||
from BaseClasses import ItemClassification, MultiWorld
|
||||
from Options import OptionError
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType
|
||||
from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType
|
||||
@@ -14,6 +15,9 @@ from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_sh
|
||||
from .StateHelpers import has_triforce_pieces, has_melee_weapon
|
||||
from .Regions import key_drop_data
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ALTTPWorld
|
||||
|
||||
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||
# Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
|
||||
@@ -222,29 +226,29 @@ items_reduction_table = (
|
||||
)
|
||||
|
||||
|
||||
def generate_itempool(world):
|
||||
player = world.player
|
||||
def generate_itempool(world: "ALTTPWorld"):
|
||||
player: int = world.player
|
||||
multiworld = world.multiworld
|
||||
|
||||
if multiworld.item_pool[player].current_key not in difficulties:
|
||||
raise NotImplementedError(f"Diffulty {multiworld.item_pool[player]}")
|
||||
if multiworld.goal[player] not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt',
|
||||
'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals',
|
||||
'ganon_pedestal'):
|
||||
raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}")
|
||||
if multiworld.mode[player] not in ('open', 'standard', 'inverted'):
|
||||
raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}")
|
||||
if multiworld.timer[player] not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'):
|
||||
raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}")
|
||||
if world.options.item_pool.current_key not in difficulties:
|
||||
raise NotImplementedError(f"Diffulty {world.options.item_pool}")
|
||||
if world.options.goal not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt',
|
||||
'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals',
|
||||
'ganon_pedestal'):
|
||||
raise NotImplementedError(f"Goal {world.options.goal} for player {player}")
|
||||
if world.options.mode not in ('open', 'standard', 'inverted'):
|
||||
raise NotImplementedError(f"Mode {world.options.mode} for player {player}")
|
||||
if world.options.timer not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'):
|
||||
raise NotImplementedError(f"Timer {world.options.timer} for player {player}")
|
||||
|
||||
if multiworld.timer[player] in ['ohko', 'timed_ohko']:
|
||||
if world.options.timer in ['ohko', 'timed_ohko']:
|
||||
world.can_take_damage = False
|
||||
if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
|
||||
if world.options.goal in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
|
||||
multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False)
|
||||
else:
|
||||
multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Triforce', world), False)
|
||||
|
||||
if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']:
|
||||
if world.options.goal in ['triforce_hunt', 'local_triforce_hunt']:
|
||||
region = multiworld.get_region('Light World', player)
|
||||
|
||||
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
||||
@@ -263,7 +267,6 @@ def generate_itempool(world):
|
||||
('Frog', 'Get Frog'),
|
||||
('Missing Smith', 'Return Smith'),
|
||||
('Floodgate', 'Open Floodgate'),
|
||||
('Agahnim 1', 'Beat Agahnim 1'),
|
||||
('Flute Activation Spot', 'Activated Flute'),
|
||||
('Capacity Upgrade Shop', 'Capacity Upgrade Shop')
|
||||
]
|
||||
@@ -280,7 +283,6 @@ def generate_itempool(world):
|
||||
if multiworld.custom:
|
||||
pool, placed_items, precollected_items, clock_mode, treasure_hunt_required = (
|
||||
make_custom_item_pool(multiworld, player))
|
||||
multiworld.rupoor_cost = min(multiworld.customitemarray[67], 9999)
|
||||
else:
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_required, treasure_hunt_total,
|
||||
additional_triforce_pieces) = get_pool_core(multiworld, player)
|
||||
@@ -288,7 +290,7 @@ def generate_itempool(world):
|
||||
for item in precollected_items:
|
||||
multiworld.push_precollected(item_factory(item, world))
|
||||
|
||||
if multiworld.mode[player] == 'standard' and not has_melee_weapon(multiworld.state, player):
|
||||
if world.options.mode == 'standard' and not has_melee_weapon(multiworld.state, player):
|
||||
if "Link's Uncle" not in placed_items:
|
||||
found_sword = False
|
||||
found_bow = False
|
||||
@@ -304,10 +306,10 @@ def generate_itempool(world):
|
||||
elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
|
||||
if item not in possible_weapons:
|
||||
possible_weapons.append(item)
|
||||
elif (item == 'Bombs (10)' and (not multiworld.bombless_start[player]) and item not in
|
||||
elif (item == 'Bombs (10)' and (not world.options.bombless_start) and item not in
|
||||
possible_weapons):
|
||||
possible_weapons.append(item)
|
||||
elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and multiworld.bombless_start[player] and item
|
||||
elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and world.options.bombless_start and item
|
||||
not in possible_weapons):
|
||||
possible_weapons.append(item)
|
||||
|
||||
@@ -315,21 +317,21 @@ def generate_itempool(world):
|
||||
placed_items["Link's Uncle"] = starting_weapon
|
||||
pool.remove(starting_weapon)
|
||||
if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)',
|
||||
'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']):
|
||||
if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]:
|
||||
'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and world.options.enemy_health not in ['default', 'easy']):
|
||||
if world.options.bombless_start and "Bomb Upgrade" not in placed_items["Link's Uncle"]:
|
||||
if 'Bow' in placed_items["Link's Uncle"]:
|
||||
multiworld.worlds[player].escape_assist.append('arrows')
|
||||
world.escape_assist.append('arrows')
|
||||
elif 'Cane' in placed_items["Link's Uncle"]:
|
||||
multiworld.worlds[player].escape_assist.append('magic')
|
||||
world.escape_assist.append('magic')
|
||||
else:
|
||||
multiworld.worlds[player].escape_assist.append('bombs')
|
||||
world.escape_assist.append('bombs')
|
||||
|
||||
for (location, item) in placed_items.items():
|
||||
multiworld.get_location(location, player).place_locked_item(item_factory(item, world))
|
||||
|
||||
items = item_factory(pool, world)
|
||||
# convert one Progressive Bow into Progressive Bow (Alt), in ID only, for ganon silvers hint text
|
||||
if multiworld.worlds[player].has_progressive_bows:
|
||||
if world.has_progressive_bows:
|
||||
for item in items:
|
||||
if item.code == 0x64: # Progressive Bow
|
||||
item.code = 0x65 # Progressive Bow (Alt)
|
||||
@@ -338,21 +340,21 @@ def generate_itempool(world):
|
||||
if clock_mode:
|
||||
world.clock_mode = clock_mode
|
||||
|
||||
multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999
|
||||
multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total
|
||||
world.treasure_hunt_required = treasure_hunt_required % 999
|
||||
world.treasure_hunt_total = treasure_hunt_total
|
||||
|
||||
dungeon_items = [item for item in get_dungeon_item_pool_player(world)
|
||||
if item.name not in multiworld.worlds[player].dungeon_local_item_names]
|
||||
if item.name not in world.dungeon_local_item_names]
|
||||
|
||||
for key_loc in key_drop_data:
|
||||
key_data = key_drop_data[key_loc]
|
||||
drop_item = item_factory(key_data[3], world)
|
||||
if not multiworld.key_drop_shuffle[player]:
|
||||
if not world.options.key_drop_shuffle:
|
||||
if drop_item in dungeon_items:
|
||||
dungeon_items.remove(drop_item)
|
||||
else:
|
||||
dungeon = drop_item.name.split("(")[1].split(")")[0]
|
||||
if multiworld.mode[player] == 'inverted':
|
||||
if world.options.mode == 'inverted':
|
||||
if dungeon == "Agahnims Tower":
|
||||
dungeon = "Inverted Agahnims Tower"
|
||||
if dungeon == "Ganons Tower":
|
||||
@@ -365,7 +367,7 @@ def generate_itempool(world):
|
||||
loc = multiworld.get_location(key_loc, player)
|
||||
loc.place_locked_item(drop_item)
|
||||
loc.address = None
|
||||
elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
elif "Small" in key_data[3] and world.options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
# key drop shuffle and universal keys are on. Add universal keys in place of key drop keys.
|
||||
multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world))
|
||||
dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2
|
||||
@@ -373,10 +375,10 @@ def generate_itempool(world):
|
||||
|
||||
for x in range(len(dungeon_items)-1, -1, -1):
|
||||
item = dungeon_items[x]
|
||||
if ((multiworld.small_key_shuffle[player] == small_key_shuffle.option_start_with and item.type == 'SmallKey')
|
||||
or (multiworld.big_key_shuffle[player] == big_key_shuffle.option_start_with and item.type == 'BigKey')
|
||||
or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass')
|
||||
or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')):
|
||||
if ((world.options.small_key_shuffle == small_key_shuffle.option_start_with and item.type == 'SmallKey')
|
||||
or (world.options.big_key_shuffle == big_key_shuffle.option_start_with and item.type == 'BigKey')
|
||||
or (world.options.compass_shuffle == compass_shuffle.option_start_with and item.type == 'Compass')
|
||||
or (world.options.map_shuffle == map_shuffle.option_start_with and item.type == 'Map')):
|
||||
dungeon_items.pop(x)
|
||||
multiworld.push_precollected(item)
|
||||
multiworld.itempool.append(item_factory(dungeon_item_replacements.pop(), world))
|
||||
@@ -384,10 +386,10 @@ def generate_itempool(world):
|
||||
|
||||
set_up_shops(multiworld, player)
|
||||
|
||||
if multiworld.retro_bow[player]:
|
||||
if world.options.retro_bow:
|
||||
shop_items = 0
|
||||
shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if
|
||||
shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if
|
||||
shop_locations = [location for shop_locations in (shop.region.locations for shop in world.shops if
|
||||
shop.type == ShopType.Shop) for location in shop_locations if
|
||||
location.shop_slot is not None]
|
||||
for location in shop_locations:
|
||||
if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow":
|
||||
@@ -395,12 +397,12 @@ def generate_itempool(world):
|
||||
else:
|
||||
shop_items += 1
|
||||
else:
|
||||
shop_items = min(multiworld.shop_item_slots[player], 30 if multiworld.include_witch_hut[player] else 27)
|
||||
shop_items = min(world.options.shop_item_slots, 30 if world.options.include_witch_hut else 27)
|
||||
|
||||
if multiworld.shuffle_capacity_upgrades[player]:
|
||||
if world.options.shuffle_capacity_upgrades:
|
||||
shop_items += 2
|
||||
chance_100 = int(multiworld.retro_bow[player]) * 0.25 + int(
|
||||
multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5
|
||||
chance_100 = int(world.options.retro_bow) * 0.25 + int(
|
||||
world.options.small_key_shuffle == small_key_shuffle.option_universal) * 0.5
|
||||
for _ in range(shop_items):
|
||||
if multiworld.random.random() < chance_100:
|
||||
items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (100)"), world))
|
||||
@@ -410,19 +412,20 @@ def generate_itempool(world):
|
||||
multiworld.random.shuffle(items)
|
||||
pool_count = len(items)
|
||||
new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)]
|
||||
if multiworld.shuffle_capacity_upgrades[player] or multiworld.bombless_start[player]:
|
||||
progressive = multiworld.progressive[player]
|
||||
progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on'
|
||||
if multiworld.shuffle_capacity_upgrades[player] == "on_combined":
|
||||
if world.options.shuffle_capacity_upgrades or world.options.bombless_start:
|
||||
progressive = world.options.progressive.want_progressives(world.random)
|
||||
if world.options.shuffle_capacity_upgrades == "on_combined":
|
||||
new_items.append("Bomb Upgrade (50)")
|
||||
elif multiworld.shuffle_capacity_upgrades[player] == "on":
|
||||
elif world.options.shuffle_capacity_upgrades == "on":
|
||||
new_items += ["Bomb Upgrade (+5)"] * 6
|
||||
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
||||
if multiworld.shuffle_capacity_upgrades[player] != "on_combined" and multiworld.bombless_start[player]:
|
||||
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
||||
if world.options.bombless_start:
|
||||
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
||||
elif world.options.bombless_start:
|
||||
new_items.append("Bomb Upgrade (+10)")
|
||||
|
||||
if multiworld.shuffle_capacity_upgrades[player] and not multiworld.retro_bow[player]:
|
||||
if multiworld.shuffle_capacity_upgrades[player] == "on_combined":
|
||||
if world.options.shuffle_capacity_upgrades and not world.options.retro_bow:
|
||||
if world.options.shuffle_capacity_upgrades == "on_combined":
|
||||
new_items += ["Arrow Upgrade (70)"]
|
||||
else:
|
||||
new_items += ["Arrow Upgrade (+5)"] * 6
|
||||
@@ -467,6 +470,9 @@ def generate_itempool(world):
|
||||
items_were_cut = items_were_cut or cut_item(items, *reduce_item)
|
||||
elif len(reduce_item) == 4:
|
||||
items_were_cut = items_were_cut or condense_items(items, *reduce_item)
|
||||
if reduce_item[0] == "Piece of Heart" and world.logical_heart_pieces:
|
||||
world.logical_heart_pieces -= reduce_item[2]
|
||||
world.logical_heart_containers += reduce_item[3]
|
||||
elif len(reduce_item) == 1: # Bottles
|
||||
bottles = [item for item in items if item.name in item_name_groups["Bottles"]]
|
||||
if len(bottles) > 4:
|
||||
@@ -477,35 +483,38 @@ def generate_itempool(world):
|
||||
if items_were_cut:
|
||||
break
|
||||
else:
|
||||
raise Exception(f"Failed to limit item pool size for player {player}")
|
||||
raise OptionError(f"Failed to limit item pool size for player {player}")
|
||||
if len(items) < pool_count:
|
||||
items += removed_filler[len(items) - pool_count:]
|
||||
|
||||
if multiworld.randomize_cost_types[player]:
|
||||
if world.options.randomize_cost_types:
|
||||
# Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic
|
||||
for item in items:
|
||||
if (item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart")
|
||||
or "Arrow Upgrade" in item.name):
|
||||
if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"):
|
||||
item.classification = ItemClassification.progression
|
||||
else:
|
||||
# Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
|
||||
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
|
||||
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
|
||||
if multiworld.item_pool[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0):
|
||||
next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression
|
||||
elif multiworld.item_pool[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4):
|
||||
try:
|
||||
next(item for item in items if item.name == 'Boss Heart Container').classification \
|
||||
|= ItemClassification.progression
|
||||
except StopIteration:
|
||||
adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart')
|
||||
for i in range(4):
|
||||
next(adv_heart_pieces).classification = ItemClassification.progression
|
||||
try:
|
||||
next(adv_heart_pieces).classification |= ItemClassification.progression
|
||||
except StopIteration:
|
||||
break # logically health tanking is an option, so rules should still resolve to something beatable
|
||||
|
||||
world.required_medallions = (multiworld.misery_mire_medallion[player].current_key.title(),
|
||||
multiworld.turtle_rock_medallion[player].current_key.title())
|
||||
world.required_medallions = (world.options.misery_mire_medallion.current_key.title(),
|
||||
world.options.turtle_rock_medallion.current_key.title())
|
||||
|
||||
place_bosses(world)
|
||||
|
||||
multiworld.itempool += items
|
||||
|
||||
if multiworld.retro_caves[player]:
|
||||
if world.options.retro_caves:
|
||||
set_up_take_anys(multiworld, world, player) # depends on world.itempool to be set
|
||||
|
||||
|
||||
@@ -526,9 +535,9 @@ take_any_locations_inverted.sort()
|
||||
take_any_locations.sort()
|
||||
|
||||
|
||||
def set_up_take_anys(multiworld, world, player):
|
||||
def set_up_take_anys(multiworld: MultiWorld, world: "ALTTPWorld", player: int):
|
||||
# these are references, do not modify these lists in-place
|
||||
if multiworld.mode[player] == 'inverted':
|
||||
if world.options.mode == 'inverted':
|
||||
take_any_locs = take_any_locations_inverted
|
||||
else:
|
||||
take_any_locs = take_any_locations
|
||||
@@ -543,12 +552,14 @@ def set_up_take_anys(multiworld, world, player):
|
||||
connect_entrance(multiworld, entrance.name, old_man_take_any.name, player)
|
||||
entrance.target = 0x58
|
||||
old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots)
|
||||
multiworld.shops.append(old_man_take_any.shop)
|
||||
world.shops.append(old_man_take_any.shop)
|
||||
|
||||
swords = [item for item in multiworld.itempool if item.player == player and item.type == 'Sword']
|
||||
if swords:
|
||||
sword = multiworld.random.choice(swords)
|
||||
multiworld.itempool.remove(sword)
|
||||
sword_indices = [
|
||||
index for index, item in enumerate(multiworld.itempool) if item.player == player and item.type == 'Sword'
|
||||
]
|
||||
if sword_indices:
|
||||
sword_index = multiworld.random.choice(sword_indices)
|
||||
sword = multiworld.itempool.pop(sword_index)
|
||||
multiworld.itempool.append(item_factory('Rupees (20)', world))
|
||||
old_man_take_any.shop.add_inventory(0, sword.name, 0, 0)
|
||||
loc_name = "Old Man Sword Cave"
|
||||
@@ -569,7 +580,7 @@ def set_up_take_anys(multiworld, world, player):
|
||||
connect_entrance(multiworld, entrance.name, take_any.name, player)
|
||||
entrance.target = target
|
||||
take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1)
|
||||
multiworld.shops.append(take_any.shop)
|
||||
world.shops.append(take_any.shop)
|
||||
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
||||
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
||||
location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any)
|
||||
@@ -578,15 +589,15 @@ def set_up_take_anys(multiworld, world, player):
|
||||
location.place_locked_item(item_factory("Boss Heart Container", world))
|
||||
|
||||
|
||||
def get_pool_core(world, player: int):
|
||||
shuffle = world.entrance_shuffle[player].current_key
|
||||
difficulty = world.item_pool[player].current_key
|
||||
timer = world.timer[player].current_key
|
||||
goal = world.goal[player].current_key
|
||||
mode = world.mode[player].current_key
|
||||
swordless = world.swordless[player]
|
||||
retro_bow = world.retro_bow[player]
|
||||
logic = world.glitches_required[player]
|
||||
def get_pool_core(multiworld: MultiWorld, player: int):
|
||||
shuffle = multiworld.worlds[player].options.entrance_shuffle.current_key
|
||||
difficulty = multiworld.worlds[player].options.item_pool.current_key
|
||||
timer = multiworld.worlds[player].options.timer.current_key
|
||||
goal = multiworld.worlds[player].options.goal.current_key
|
||||
mode = multiworld.worlds[player].options.mode.current_key
|
||||
swordless = multiworld.worlds[player].options.swordless
|
||||
retro_bow = multiworld.worlds[player].options.retro_bow
|
||||
logic = multiworld.worlds[player].options.glitches_required
|
||||
|
||||
pool = []
|
||||
placed_items = {}
|
||||
@@ -603,13 +614,13 @@ def get_pool_core(world, player: int):
|
||||
placed_items[loc] = item
|
||||
|
||||
# provide boots to major glitch dependent seeds
|
||||
if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]:
|
||||
if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and multiworld.worlds[player].options.glitch_boots:
|
||||
precollected_items.append('Pegasus Boots')
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
want_progressives = world.progressive[player].want_progressives
|
||||
want_progressives = multiworld.worlds[player].options.progressive.want_progressives
|
||||
|
||||
if want_progressives(world.random):
|
||||
if want_progressives(multiworld.random):
|
||||
pool.extend(diff.progressiveglove)
|
||||
else:
|
||||
pool.extend(diff.basicglove)
|
||||
@@ -633,27 +644,27 @@ def get_pool_core(world, player: int):
|
||||
thisbottle = None
|
||||
for _ in range(diff.bottle_count):
|
||||
if not diff.same_bottle or not thisbottle:
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
thisbottle = multiworld.random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
if want_progressives(world.random):
|
||||
if want_progressives(multiworld.random):
|
||||
pool.extend(diff.progressiveshield)
|
||||
else:
|
||||
pool.extend(diff.basicshield)
|
||||
|
||||
if want_progressives(world.random):
|
||||
if want_progressives(multiworld.random):
|
||||
pool.extend(diff.progressivearmor)
|
||||
else:
|
||||
pool.extend(diff.basicarmor)
|
||||
|
||||
if want_progressives(world.random):
|
||||
if want_progressives(multiworld.random):
|
||||
pool.extend(diff.progressivemagic)
|
||||
else:
|
||||
pool.extend(diff.basicmagic)
|
||||
|
||||
if want_progressives(world.random):
|
||||
if want_progressives(multiworld.random):
|
||||
pool.extend(diff.progressivebow)
|
||||
world.worlds[player].has_progressive_bows = True
|
||||
multiworld.worlds[player].has_progressive_bows = True
|
||||
elif (swordless or logic == 'no_glitches'):
|
||||
swordless_bows = ['Bow', 'Silver Bow']
|
||||
if difficulty == "easy":
|
||||
@@ -665,7 +676,7 @@ def get_pool_core(world, player: int):
|
||||
if swordless:
|
||||
pool.extend(diff.swordless)
|
||||
else:
|
||||
progressive_swords = want_progressives(world.random)
|
||||
progressive_swords = want_progressives(multiworld.random)
|
||||
pool.extend(diff.progressivesword if progressive_swords else diff.basicsword)
|
||||
|
||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
||||
@@ -681,59 +692,66 @@ def get_pool_core(world, player: int):
|
||||
additional_pieces_to_place = 0
|
||||
if 'triforce_hunt' in goal:
|
||||
|
||||
if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra:
|
||||
treasure_hunt_total = (world.triforce_pieces_required[player].value
|
||||
+ world.triforce_pieces_extra[player].value)
|
||||
elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage:
|
||||
percentage = float(world.triforce_pieces_percentage[player].value) / 100
|
||||
treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0))
|
||||
if multiworld.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_extra:
|
||||
treasure_hunt_total = (multiworld.worlds[player].options.triforce_pieces_required.value
|
||||
+ multiworld.worlds[player].options.triforce_pieces_extra.value)
|
||||
elif multiworld.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_percentage:
|
||||
percentage = float(multiworld.worlds[player].options.triforce_pieces_percentage.value) / 100
|
||||
treasure_hunt_total = int(round(multiworld.worlds[player].options.triforce_pieces_required.value * percentage, 0))
|
||||
else: # available
|
||||
treasure_hunt_total = world.triforce_pieces_available[player].value
|
||||
treasure_hunt_total = multiworld.worlds[player].options.triforce_pieces_available.value
|
||||
|
||||
triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value))
|
||||
triforce_pieces = min(90, max(treasure_hunt_total, multiworld.worlds[player].options.triforce_pieces_required.value))
|
||||
|
||||
pieces_in_core = min(extraitems, triforce_pieces)
|
||||
additional_pieces_to_place = triforce_pieces - pieces_in_core
|
||||
pool.extend(["Triforce Piece"] * pieces_in_core)
|
||||
extraitems -= pieces_in_core
|
||||
treasure_hunt_required = world.triforce_pieces_required[player].value
|
||||
treasure_hunt_required = multiworld.worlds[player].options.triforce_pieces_required.value
|
||||
|
||||
for extra in diff.extras:
|
||||
if extraitems >= len(extra):
|
||||
pool.extend(extra)
|
||||
extraitems -= len(extra)
|
||||
elif extraitems > 0:
|
||||
pool.extend(world.random.sample(extra, extraitems))
|
||||
pool.extend(multiworld.random.sample(extra, extraitems))
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
if retro_bow:
|
||||
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'}
|
||||
pool = ['Rupees (5)' if item in replace else item for item in pool]
|
||||
|
||||
if goal == 'pedestal':
|
||||
place_item('Master Sword Pedestal', 'Triforce')
|
||||
pool.remove("Rupees (20)")
|
||||
for rupee_name in ("Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)"):
|
||||
try:
|
||||
pool.remove(rupee_name)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
if retro_bow:
|
||||
replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (50)'}
|
||||
pool = ['Rupees (5)' if item in replace else item for item in pool]
|
||||
if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
pool.extend(diff.universal_keys)
|
||||
if mode == 'standard':
|
||||
if world.key_drop_shuffle[player]:
|
||||
if multiworld.worlds[player].options.key_drop_shuffle:
|
||||
key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_location = multiworld.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Hyrule Castle - Boomerang Guard Key Drop', 'Hyrule Castle - Boomerang Chest',
|
||||
'Hyrule Castle - Map Chest']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_location = multiworld.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_location = multiworld.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Sewers - Key Rat Key Drop']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_location = multiworld.random.choice(key_locations)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
pool = pool[:-3]
|
||||
|
||||
@@ -742,11 +760,11 @@ def get_pool_core(world, player: int):
|
||||
|
||||
|
||||
def make_custom_item_pool(world, player):
|
||||
shuffle = world.entrance_shuffle[player]
|
||||
difficulty = world.item_pool[player]
|
||||
timer = world.timer[player]
|
||||
goal = world.goal[player]
|
||||
mode = world.mode[player]
|
||||
shuffle = world.worlds[player].options.entrance_shuffle
|
||||
difficulty = world.worlds[player].options.item_pool
|
||||
timer = world.worlds[player].options.timer
|
||||
goal = world.worlds[player].options.goal
|
||||
mode = world.worlds[player].options.mode
|
||||
customitemarray = world.customitemarray
|
||||
|
||||
pool = []
|
||||
@@ -846,10 +864,10 @@ def make_custom_item_pool(world, player):
|
||||
thisbottle = world.random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
if "triforce" in world.goal[player]:
|
||||
pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player])
|
||||
itemtotal += world.triforce_pieces_available[player]
|
||||
treasure_hunt_required = world.triforce_pieces_required[player]
|
||||
if "triforce" in world.worlds[player].options.goal:
|
||||
pool.extend(["Triforce Piece"] * world.worlds[player].options.triforce_pieces_available)
|
||||
itemtotal += world.worlds[player].options.triforce_pieces_available
|
||||
treasure_hunt_required = world.worlds[player].options.triforce_pieces_required
|
||||
|
||||
if timer in ['display', 'timed', 'timed_countdown']:
|
||||
clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch'
|
||||
@@ -863,7 +881,7 @@ def make_custom_item_pool(world, player):
|
||||
itemtotal = itemtotal + 1
|
||||
|
||||
if mode == 'standard':
|
||||
if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
key_location = world.random.choice(
|
||||
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
@@ -886,9 +904,9 @@ def make_custom_item_pool(world, player):
|
||||
pool.extend(['Magic Mirror'] * customitemarray[22])
|
||||
pool.extend(['Moon Pearl'] * customitemarray[28])
|
||||
|
||||
if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode
|
||||
if world.key_drop_shuffle[player]:
|
||||
if world.worlds[player].options.key_drop_shuffle:
|
||||
itemtotal = itemtotal - (len(key_drop_data) - 1)
|
||||
if itemtotal < total_items_to_place:
|
||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||
|
||||
+10
-10
@@ -1,24 +1,24 @@
|
||||
import typing
|
||||
|
||||
from BaseClasses import ItemClassification as IC
|
||||
from BaseClasses import MultiWorld, ItemClassification as IC
|
||||
from worlds.AutoWorld import World
|
||||
|
||||
|
||||
def GetBeemizerItem(world, player: int, item):
|
||||
def GetBeemizerItem(multiworld: MultiWorld, player: int, item):
|
||||
item_name = item if isinstance(item, str) else item.name
|
||||
|
||||
if item_name not in trap_replaceable:
|
||||
if item_name not in trap_replaceable or player in multiworld.groups:
|
||||
return item
|
||||
|
||||
# first roll - replaceable item should be replaced, within beemizer_total_chance
|
||||
if not world.beemizer_total_chance[player] or world.random.random() > (world.beemizer_total_chance[player] / 100):
|
||||
if not multiworld.worlds[player].options.beemizer_total_chance or multiworld.random.random() > (multiworld.worlds[player].options.beemizer_total_chance / 100):
|
||||
return item
|
||||
|
||||
# second roll - bee replacement should be trap, within beemizer_trap_chance
|
||||
if not world.beemizer_trap_chance[player] or world.random.random() > (world.beemizer_trap_chance[player] / 100):
|
||||
return "Bee" if isinstance(item, str) else world.create_item("Bee", player)
|
||||
if not multiworld.worlds[player].options.beemizer_trap_chance or multiworld.random.random() > (multiworld.worlds[player].options.beemizer_trap_chance / 100):
|
||||
return "Bee" if isinstance(item, str) else multiworld.create_item("Bee", player)
|
||||
else:
|
||||
return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player)
|
||||
return "Bee Trap" if isinstance(item, str) else multiworld.create_item("Bee Trap", player)
|
||||
|
||||
|
||||
def item_factory(items: typing.Union[str, typing.Iterable[str]], world: World):
|
||||
@@ -110,9 +110,9 @@ item_table = {'Bow': ItemData(IC.progression, None, 0x0B, 'You have\nchosen the\
|
||||
'Crystal 7': ItemData(IC.progression, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, "a blue crystal"),
|
||||
'Single Arrow': ItemData(IC.filler, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
|
||||
'Arrows (10)': ItemData(IC.filler, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
|
||||
'Arrow Upgrade (+10)': ItemData(IC.useful, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Arrow Upgrade (+5)': ItemData(IC.useful, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Arrow Upgrade (70)': ItemData(IC.useful, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Arrow Upgrade (+10)': ItemData(IC.progression_skip_balancing, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Arrow Upgrade (+5)': ItemData(IC.progression_skip_balancing, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Arrow Upgrade (70)': ItemData(IC.progression_skip_balancing, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||
'Single Bomb': ItemData(IC.filler, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
|
||||
'Bombs (3)': ItemData(IC.filler, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
|
||||
'Bombs (10)': ItemData(IC.filler, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
|
||||
|
||||
@@ -154,13 +154,13 @@ class OpenPyramid(Choice):
|
||||
alias_true = option_open
|
||||
alias_false = option_closed
|
||||
|
||||
def to_bool(self, world: MultiWorld, player: int) -> bool:
|
||||
def to_bool(self, multiworld: MultiWorld, player: int) -> bool:
|
||||
if self.value == self.option_goal:
|
||||
return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'}
|
||||
return multiworld.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'}
|
||||
elif self.value == self.option_auto:
|
||||
return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \
|
||||
and (world.entrance_shuffle[player].current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not
|
||||
world.shuffle_ganon)
|
||||
return multiworld.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \
|
||||
and (multiworld.worlds[player].options.entrance_shuffle.current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not
|
||||
multiworld.shuffle_ganon)
|
||||
elif self.value == self.option_open:
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
Helper functions to deliver entrance/exit/region sets to OWG rules.
|
||||
"""
|
||||
|
||||
from BaseClasses import Entrance
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from .StateHelpers import can_lift_heavy_rocks, can_boots_clip_lw, can_boots_clip_dw, can_get_glitched_speed_dw
|
||||
|
||||
|
||||
@@ -202,7 +201,7 @@ def get_mirror_offset_spots_dw():
|
||||
yield ('Dark Death Mountain Offset Mirror', 'Dark Death Mountain (West Bottom)', 'East Dark World')
|
||||
|
||||
|
||||
def get_mirror_offset_spots_lw(player):
|
||||
def get_mirror_offset_spots_lw(player: int):
|
||||
"""
|
||||
Mirror shenanigans placing a mirror portal with a broken camera
|
||||
"""
|
||||
@@ -220,54 +219,54 @@ def get_invalid_bunny_revival_dungeons():
|
||||
yield 'Sanctuary'
|
||||
|
||||
|
||||
def overworld_glitch_connections(world, player):
|
||||
def overworld_glitch_connections(multiworld: MultiWorld, player: int):
|
||||
# Boots-accessible locations.
|
||||
create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'))
|
||||
create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player))
|
||||
create_owg_connections(player, multiworld, get_boots_clip_exits_lw(multiworld.worlds[player].options.mode == 'inverted'))
|
||||
create_owg_connections(player, multiworld, get_boots_clip_exits_dw(multiworld.worlds[player].options.mode == 'inverted', player))
|
||||
|
||||
# Glitched speed drops.
|
||||
create_owg_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'))
|
||||
create_owg_connections(player, multiworld, get_glitched_speed_drops_dw(multiworld.worlds[player].options.mode == 'inverted'))
|
||||
|
||||
# Mirror clip spots.
|
||||
if world.mode[player] != 'inverted':
|
||||
create_owg_connections(player, world, get_mirror_clip_spots_dw())
|
||||
create_owg_connections(player, world, get_mirror_offset_spots_dw())
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
create_owg_connections(player, multiworld, get_mirror_clip_spots_dw())
|
||||
create_owg_connections(player, multiworld, get_mirror_offset_spots_dw())
|
||||
else:
|
||||
create_owg_connections(player, world, get_mirror_offset_spots_lw(player))
|
||||
create_owg_connections(player, multiworld, get_mirror_offset_spots_lw(player))
|
||||
|
||||
|
||||
def overworld_glitches_rules(world, player):
|
||||
def overworld_glitches_rules(multiworld: MultiWorld, player: int):
|
||||
|
||||
# Boots-accessible locations.
|
||||
set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: can_boots_clip_lw(state, player))
|
||||
set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player), lambda state: can_boots_clip_dw(state, player))
|
||||
set_owg_connection_rules(player, multiworld, get_boots_clip_exits_lw(multiworld.worlds[player].options.mode == 'inverted'), lambda state: can_boots_clip_lw(state, player))
|
||||
set_owg_connection_rules(player, multiworld, get_boots_clip_exits_dw(multiworld.worlds[player].options.mode == 'inverted', player), lambda state: can_boots_clip_dw(state, player))
|
||||
|
||||
# Glitched speed drops.
|
||||
set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player))
|
||||
set_owg_connection_rules(player, multiworld, get_glitched_speed_drops_dw(multiworld.worlds[player].options.mode == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player))
|
||||
# Dark Death Mountain Ledge Clip Spot also accessible with mirror.
|
||||
if world.mode[player] != 'inverted':
|
||||
add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player))
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
add_alternate_rule(multiworld.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player))
|
||||
|
||||
# Mirror clip spots.
|
||||
if world.mode[player] != 'inverted':
|
||||
set_owg_connection_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player))
|
||||
set_owg_connection_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and can_boots_clip_lw(state, player))
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
set_owg_connection_rules(player, multiworld, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player))
|
||||
set_owg_connection_rules(player, multiworld, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and can_boots_clip_lw(state, player))
|
||||
else:
|
||||
set_owg_connection_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and can_boots_clip_dw(state, player))
|
||||
set_owg_connection_rules(player, multiworld, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and can_boots_clip_dw(state, player))
|
||||
|
||||
# Regions that require the boots and some other stuff.
|
||||
if world.mode[player] != 'inverted':
|
||||
world.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (can_boots_clip_lw(state, player) or can_lift_heavy_rocks(state, player)) and state.has('Hammer', player)
|
||||
add_alternate_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Moon Pearl', player) or state.has('Pegasus Boots', player))
|
||||
if multiworld.worlds[player].options.mode != 'inverted':
|
||||
multiworld.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (can_boots_clip_lw(state, player) or can_lift_heavy_rocks(state, player)) and state.has('Hammer', player)
|
||||
add_alternate_rule(multiworld.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Moon Pearl', player) or state.has('Pegasus Boots', player))
|
||||
else:
|
||||
add_alternate_rule(world.get_entrance('Waterfall of Wishing Cave', player), lambda state: state.has('Moon Pearl', player))
|
||||
add_alternate_rule(multiworld.get_entrance('Waterfall of Wishing Cave', player), lambda state: state.has('Moon Pearl', player))
|
||||
|
||||
world.get_entrance('Dark Desert Teleporter', player).access_rule = lambda state: (state.has('Flute', player) or state.has('Pegasus Boots', player)) and can_lift_heavy_rocks(state, player)
|
||||
add_alternate_rule(world.get_entrance('Catfish Exit Rock', player), lambda state: can_boots_clip_dw(state, player))
|
||||
add_alternate_rule(world.get_entrance('East Dark World Broken Bridge Pass', player), lambda state: can_boots_clip_dw(state, player))
|
||||
multiworld.get_entrance('Dark Desert Teleporter', player).access_rule = lambda state: (state.has('Flute', player) or state.has('Pegasus Boots', player)) and can_lift_heavy_rocks(state, player)
|
||||
add_alternate_rule(multiworld.get_entrance('Catfish Exit Rock', player), lambda state: can_boots_clip_dw(state, player))
|
||||
add_alternate_rule(multiworld.get_entrance('East Dark World Broken Bridge Pass', player), lambda state: can_boots_clip_dw(state, player))
|
||||
|
||||
# Zora's Ledge via waterwalk setup.
|
||||
add_alternate_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has('Pegasus Boots', player))
|
||||
add_alternate_rule(multiworld.get_location('Zora\'s Ledge', player), lambda state: state.has('Pegasus Boots', player))
|
||||
|
||||
|
||||
def add_alternate_rule(entrance, rule):
|
||||
@@ -275,26 +274,22 @@ def add_alternate_rule(entrance, rule):
|
||||
entrance.access_rule = lambda state: old_rule(state) or rule(state)
|
||||
|
||||
|
||||
def create_no_logic_connections(player, world, connections):
|
||||
def create_no_logic_connections(player: int, multiworld: MultiWorld, connections):
|
||||
for entrance, parent_region, target_region, *rule_override in connections:
|
||||
parent = world.get_region(parent_region, player)
|
||||
target = world.get_region(target_region, player)
|
||||
connection = Entrance(player, entrance, parent)
|
||||
parent.exits.append(connection)
|
||||
connection.connect(target)
|
||||
parent = multiworld.get_region(parent_region, player)
|
||||
target = multiworld.get_region(target_region, player)
|
||||
parent.connect(target, entrance)
|
||||
|
||||
|
||||
def create_owg_connections(player, world, connections):
|
||||
def create_owg_connections(player: int, multiworld: MultiWorld, connections):
|
||||
for entrance, parent_region, target_region, *rule_override in connections:
|
||||
parent = world.get_region(parent_region, player)
|
||||
target = world.get_region(target_region, player)
|
||||
connection = Entrance(player, entrance, parent)
|
||||
parent.exits.append(connection)
|
||||
connection.connect(target)
|
||||
parent = multiworld.get_region(parent_region, player)
|
||||
target = multiworld.get_region(target_region, player)
|
||||
parent.connect(target, entrance)
|
||||
|
||||
|
||||
def set_owg_connection_rules(player, world, connections, default_rule):
|
||||
def set_owg_connection_rules(player: int, multiworld: MultiWorld, connections, default_rule):
|
||||
for entrance, _, _, *rule_override in connections:
|
||||
connection = world.get_entrance(entrance, player)
|
||||
connection = multiworld.get_entrance(entrance, player)
|
||||
rule = rule_override[0] if len(rule_override) > 0 else default_rule
|
||||
connection.access_rule = rule
|
||||
|
||||
+258
-258
@@ -1,19 +1,19 @@
|
||||
import collections
|
||||
import typing
|
||||
|
||||
from BaseClasses import Entrance, MultiWorld
|
||||
from .SubClasses import LTTPRegion, LTTPRegionType
|
||||
from BaseClasses import MultiWorld
|
||||
from .SubClasses import LTTPEntrance, LTTPRegion, LTTPRegionType
|
||||
|
||||
|
||||
def is_main_entrance(entrance: Entrance) -> bool:
|
||||
def is_main_entrance(entrance: LTTPEntrance) -> bool:
|
||||
return entrance.parent_region.type in {LTTPRegionType.DarkWorld, LTTPRegionType.LightWorld} if entrance.parent_region.type else True
|
||||
|
||||
|
||||
def create_regions(world, player):
|
||||
def create_regions(multiworld: MultiWorld, player: int):
|
||||
|
||||
world.regions += [
|
||||
create_lw_region(world, player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
|
||||
create_lw_region(world, player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure',
|
||||
multiworld.regions += [
|
||||
create_lw_region(multiworld, player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
|
||||
create_lw_region(multiworld, player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure',
|
||||
'Purple Chest', 'Flute Activation Spot'],
|
||||
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
|
||||
'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
|
||||
@@ -24,122 +24,122 @@ def create_regions(world, player):
|
||||
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
|
||||
'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing', 'Hyrule Castle Main Gate',
|
||||
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']),
|
||||
create_lw_region(world, player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
|
||||
create_cave_region(world, player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
|
||||
create_lw_region(multiworld, player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']),
|
||||
create_lw_region(multiworld, player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
|
||||
create_cave_region(multiworld, player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
|
||||
"Blind\'s Hideout - Left",
|
||||
"Blind\'s Hideout - Right",
|
||||
"Blind\'s Hideout - Far Left",
|
||||
"Blind\'s Hideout - Far Right"]),
|
||||
create_cave_region(world, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
create_lw_region(world, player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region(world, player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_lw_region(world, player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region(world, player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||
create_cave_region(world, player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||
create_cave_region(world, player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']),
|
||||
create_cave_region(world, player, 'Links House', 'your house', ['Link\'s House'], ['Links House Exit']),
|
||||
create_cave_region(world, player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region(world, player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
|
||||
create_cave_region(world, player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||
create_cave_region(world, player, 'Snitch Lady (East)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Snitch Lady (West)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Bush Covered House', 'the grass man'),
|
||||
create_cave_region(world, player, 'Tavern (Front)', 'the tavern'),
|
||||
create_cave_region(world, player, 'Light World Bomb Hut', 'a restock room'),
|
||||
create_cave_region(world, player, 'Kakariko Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Fortune Teller (Light)', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Lake Hylia Fortune Teller', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Lumberjack House', 'a boring house'),
|
||||
create_cave_region(world, player, 'Bonk Fairy (Light)', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Bonk Fairy (Dark)', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Swamp Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(world, player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(world, player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||
create_cave_region(world, player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
create_cave_region(multiworld, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
create_lw_region(multiworld, player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region(multiworld, player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_lw_region(multiworld, player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region(multiworld, player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||
create_cave_region(multiworld, player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||
create_cave_region(multiworld, player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']),
|
||||
create_cave_region(multiworld, player, 'Links House', 'your house', ['Link\'s House'], ['Links House Exit']),
|
||||
create_cave_region(multiworld, player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region(multiworld, player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
|
||||
create_cave_region(multiworld, player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||
create_cave_region(multiworld, player, 'Snitch Lady (East)', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Snitch Lady (West)', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Bush Covered House', 'the grass man'),
|
||||
create_cave_region(multiworld, player, 'Tavern (Front)', 'the tavern'),
|
||||
create_cave_region(multiworld, player, 'Light World Bomb Hut', 'a restock room'),
|
||||
create_cave_region(multiworld, player, 'Kakariko Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Fortune Teller (Light)', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Lake Hylia Fortune Teller', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Lumberjack House', 'a boring house'),
|
||||
create_cave_region(multiworld, player, 'Bonk Fairy (Light)', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Bonk Fairy (Dark)', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Swamp Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Desert Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(multiworld, player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(multiworld, player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||
create_cave_region(multiworld, player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||
create_cave_region(world, player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(world, player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(world, player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(world, player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(world, player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(world, player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(world, player, 'Hobo Bridge', ['Hobo']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
|
||||
create_lw_region(world, player, 'Cave 45 Ledge', None, ['Cave 45']),
|
||||
create_cave_region(world, player, 'Cave 45', 'a cave with an item', ['Cave 45']),
|
||||
create_lw_region(world, player, 'Graveyard Ledge', None, ['Graveyard Cave']),
|
||||
create_cave_region(world, player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
|
||||
create_cave_region(world, player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
|
||||
create_cave_region(world, player, 'Long Fairy Cave', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
create_cave_region(multiworld, player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(multiworld, player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(multiworld, player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(multiworld, player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(multiworld, player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(multiworld, player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(multiworld, player, 'Hobo Bridge', ['Hobo']),
|
||||
create_cave_region(multiworld, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
|
||||
create_cave_region(multiworld, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']),
|
||||
create_cave_region(multiworld, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']),
|
||||
create_cave_region(multiworld, player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
|
||||
create_lw_region(multiworld, player, 'Cave 45 Ledge', None, ['Cave 45']),
|
||||
create_cave_region(multiworld, player, 'Cave 45', 'a cave with an item', ['Cave 45']),
|
||||
create_lw_region(multiworld, player, 'Graveyard Ledge', None, ['Graveyard Cave']),
|
||||
create_cave_region(multiworld, player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
|
||||
create_cave_region(multiworld, player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
|
||||
create_cave_region(multiworld, player, 'Long Fairy Cave', 'a fairy fountain'),
|
||||
create_cave_region(multiworld, player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']),
|
||||
create_cave_region(world, player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
|
||||
create_cave_region(world, player, 'Good Bee Cave', 'a cold bee'),
|
||||
create_cave_region(world, player, '20 Rupee Cave', 'a cave with some cash'),
|
||||
create_cave_region(world, player, 'Cave Shop (Lake Hylia)', 'a common shop'),
|
||||
create_cave_region(world, player, 'Cave Shop (Dark Death Mountain)', 'a common shop'),
|
||||
create_cave_region(world, player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']),
|
||||
create_cave_region(world, player, 'Library', 'the library', ['Library']),
|
||||
create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'),
|
||||
create_cave_region(world, player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Island', ['Lake Hylia Island']),
|
||||
create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
|
||||
create_cave_region(world, player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||
create_lw_region(world, player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']),
|
||||
create_cave_region(world, player, '50 Rupee Cave', 'a cave with some cash'),
|
||||
create_lw_region(world, player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']),
|
||||
create_lw_region(world, player, 'Desert Ledge (Northeast)', None, ['Checkerboard Cave']),
|
||||
create_lw_region(world, player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)']),
|
||||
create_lw_region(world, player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region(world, player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
create_cave_region(multiworld, player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
|
||||
create_cave_region(multiworld, player, 'Good Bee Cave', 'a cold bee'),
|
||||
create_cave_region(multiworld, player, '20 Rupee Cave', 'a cave with some cash'),
|
||||
create_cave_region(multiworld, player, 'Cave Shop (Lake Hylia)', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Cave Shop (Dark Death Mountain)', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']),
|
||||
create_cave_region(multiworld, player, 'Library', 'the library', ['Library']),
|
||||
create_cave_region(multiworld, player, 'Kakariko Gamble Game', 'a game of chance'),
|
||||
create_cave_region(multiworld, player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
|
||||
create_lw_region(multiworld, player, 'Lake Hylia Island', ['Lake Hylia Island']),
|
||||
create_cave_region(multiworld, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
|
||||
create_cave_region(multiworld, player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||
create_lw_region(multiworld, player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']),
|
||||
create_cave_region(multiworld, player, '50 Rupee Cave', 'a cave with some cash'),
|
||||
create_lw_region(multiworld, player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']),
|
||||
create_lw_region(multiworld, player, 'Desert Ledge (Northeast)', None, ['Checkerboard Cave']),
|
||||
create_lw_region(multiworld, player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)']),
|
||||
create_lw_region(multiworld, player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region(multiworld, player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
'Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
create_dungeon_region(world, player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest',
|
||||
create_dungeon_region(multiworld, player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest',
|
||||
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', 'Eastern Palace - Big Key Chest',
|
||||
'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Palace Exit']),
|
||||
create_lw_region(world, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(world, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(world, player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']),
|
||||
create_dungeon_region(world, player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest',
|
||||
create_lw_region(multiworld, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(multiworld, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(multiworld, player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']),
|
||||
create_lw_region(multiworld, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest',
|
||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop', 'Hyrule Castle - Big Key Drop'],
|
||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']),
|
||||
create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
|
||||
create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
create_dungeon_region(multiworld, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(multiworld, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(multiworld, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
|
||||
create_dungeon_region(multiworld, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right']),
|
||||
create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(world, player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
create_cave_region(world, player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||
create_cave_region(world, player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||
create_lw_region(world, player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']),
|
||||
create_cave_region(world, player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
||||
create_lw_region(world, player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region(world, player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||
create_cave_region(world, player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||
create_cave_region(world, player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
|
||||
create_dungeon_region(multiworld, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Agahnims Tower Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(multiworld, player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
create_cave_region(multiworld, player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||
create_cave_region(multiworld, player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||
create_lw_region(multiworld, player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']),
|
||||
create_cave_region(multiworld, player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
||||
create_lw_region(multiworld, player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']),
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
|
||||
create_cave_region(multiworld, player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region(multiworld, player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||
create_cave_region(multiworld, player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||
create_cave_region(multiworld, player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||
create_cave_region(multiworld, player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
|
||||
'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right',
|
||||
'Paradox Cave Lower - Far Right',
|
||||
@@ -147,270 +147,270 @@ def create_regions(world, player):
|
||||
'Paradox Cave Upper - Left',
|
||||
'Paradox Cave Upper - Right'],
|
||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
||||
create_cave_region(world, player, 'Paradox Cave', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Paradox Cave', 'a connector', None,
|
||||
['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||
create_cave_region(world, player, 'Light World Death Mountain Shop', 'a common shop'),
|
||||
create_lw_region(world, player, 'East Death Mountain (Top)', None,
|
||||
create_cave_region(multiworld, player, 'Light World Death Mountain Shop', 'a common shop'),
|
||||
create_lw_region(multiworld, player, 'East Death Mountain (Top)', None,
|
||||
['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access',
|
||||
'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']),
|
||||
create_lw_region(world, player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
|
||||
create_cave_region(world, player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'],
|
||||
create_lw_region(multiworld, player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
|
||||
create_cave_region(multiworld, player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'],
|
||||
['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']),
|
||||
create_lw_region(world, player, 'Fairy Ascension Plateau', None,
|
||||
create_cave_region(multiworld, player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']),
|
||||
create_lw_region(multiworld, player, 'Fairy Ascension Plateau', None,
|
||||
['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Bottom)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Bottom)', 'a connector', None,
|
||||
['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Drop)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Drop)', 'a connector', None,
|
||||
['Fairy Ascension Cave Pots']),
|
||||
create_cave_region(world, player, 'Fairy Ascension Cave (Top)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Fairy Ascension Cave (Top)', 'a connector', None,
|
||||
['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']),
|
||||
create_lw_region(world, player, 'Fairy Ascension Ledge', None,
|
||||
create_lw_region(multiworld, player, 'Fairy Ascension Ledge', None,
|
||||
['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)']),
|
||||
create_lw_region(world, player, 'Death Mountain (Top)', ['Ether Tablet'],
|
||||
create_lw_region(multiworld, player, 'Death Mountain (Top)', ['Ether Tablet'],
|
||||
['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop']),
|
||||
create_lw_region(world, player, 'Spectacle Rock', ['Spectacle Rock'], ['Spectacle Rock Drop']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Bottom)', 'Tower of Hera',
|
||||
create_lw_region(multiworld, player, 'Spectacle Rock', ['Spectacle Rock'], ['Spectacle Rock Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Bottom)', 'Tower of Hera',
|
||||
['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'],
|
||||
['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Basement)', 'Tower of Hera',
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Basement)', 'Tower of Hera',
|
||||
['Tower of Hera - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Top)', 'Tower of Hera',
|
||||
create_dungeon_region(multiworld, player, 'Tower of Hera (Top)', 'Tower of Hera',
|
||||
['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss',
|
||||
'Tower of Hera - Prize']),
|
||||
|
||||
create_dw_region(world, player, 'East Dark World', ['Pyramid'],
|
||||
create_dw_region(multiworld, player, 'East Dark World', ['Pyramid'],
|
||||
['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness',
|
||||
'Dark Lake Hylia Drop (East)',
|
||||
'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint',
|
||||
'East Dark World Hint', 'Pyramid Hole', 'Northeast Dark World Broken Bridge Pass', ]),
|
||||
create_dw_region(world, player, 'Catfish', ['Catfish'], ['Catfish Exit Rock']),
|
||||
create_dw_region(world, player, 'Northeast Dark World', None,
|
||||
create_dw_region(multiworld, player, 'Catfish', ['Catfish'], ['Catfish Exit Rock']),
|
||||
create_dw_region(multiworld, player, 'Northeast Dark World', None,
|
||||
['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass',
|
||||
'Catfish Entrance Rock', 'Dark Lake Hylia Teleporter']),
|
||||
create_cave_region(world, player, 'Palace of Darkness Hint', 'a storyteller'),
|
||||
create_cave_region(world, player, 'East Dark World Hint', 'a storyteller'),
|
||||
create_dw_region(world, player, 'South Dark World', ['Stumpy', 'Digging Game'],
|
||||
create_cave_region(multiworld, player, 'Palace of Darkness Hint', 'a storyteller'),
|
||||
create_cave_region(multiworld, player, 'East Dark World Hint', 'a storyteller'),
|
||||
create_dw_region(multiworld, player, 'South Dark World', ['Stumpy', 'Digging Game'],
|
||||
['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock',
|
||||
'Maze Race Mirror Spot',
|
||||
'Cave 45 Mirror Spot', 'East Dark World Bridge', 'Big Bomb Shop', 'Archery Game',
|
||||
'Bonk Fairy (Dark)', 'Dark Lake Hylia Shop',
|
||||
'Bombos Tablet Mirror Spot']),
|
||||
create_lw_region(world, player, 'Bombos Tablet Ledge', ['Bombos Tablet']),
|
||||
create_cave_region(world, player, 'Big Bomb Shop', 'the bomb shop'),
|
||||
create_cave_region(world, player, 'Archery Game', 'a game of skill'),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia', None,
|
||||
create_lw_region(multiworld, player, 'Bombos Tablet Ledge', ['Bombos Tablet']),
|
||||
create_cave_region(multiworld, player, 'Big Bomb Shop', 'the bomb shop'),
|
||||
create_cave_region(multiworld, player, 'Archery Game', 'a game of skill'),
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia', None,
|
||||
['Lake Hylia Island Mirror Spot', 'East Dark World Pier', 'Dark Lake Hylia Ledge']),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia Central Island', None,
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia Central Island', None,
|
||||
['Ice Palace', 'Lake Hylia Central Island Mirror Spot']),
|
||||
create_dw_region(world, player, 'Dark Lake Hylia Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Lake Hylia Ledge', None,
|
||||
['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint',
|
||||
'Dark Lake Hylia Ledge Spike Cave']),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'),
|
||||
create_cave_region(world, player, 'Hype Cave', 'a bounty of five items',
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'),
|
||||
create_cave_region(multiworld, player, 'Hype Cave', 'a bounty of five items',
|
||||
['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
|
||||
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
|
||||
create_dw_region(world, player, 'West Dark World', ['Frog'],
|
||||
create_dw_region(multiworld, player, 'West Dark World', ['Frog'],
|
||||
['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House',
|
||||
'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot',
|
||||
'Bumper Cave Entrance Rock',
|
||||
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks',
|
||||
'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)',
|
||||
'Dark World Lumberjack Shop']),
|
||||
create_dw_region(world, player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Village of Outcasts Shop']),
|
||||
create_dw_region(world, player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'],
|
||||
create_dw_region(multiworld, player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Village of Outcasts Shop']),
|
||||
create_dw_region(multiworld, player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'],
|
||||
['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
|
||||
create_dw_region(world, player, 'Bumper Cave Entrance', None,
|
||||
create_dw_region(multiworld, player, 'Bumper Cave Entrance', None,
|
||||
['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']),
|
||||
create_cave_region(world, player, 'Fortune Teller (Dark)', 'a fortune teller'),
|
||||
create_cave_region(world, player, 'Village of Outcasts Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark Lake Hylia Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Lumberjack Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Potion Shop', 'a common shop'),
|
||||
create_cave_region(world, player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']),
|
||||
create_cave_region(world, player, 'Pyramid Fairy', 'a cave with two chests',
|
||||
create_cave_region(multiworld, player, 'Fortune Teller (Dark)', 'a fortune teller'),
|
||||
create_cave_region(multiworld, player, 'Village of Outcasts Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark Lake Hylia Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Lumberjack Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Potion Shop', 'a common shop'),
|
||||
create_cave_region(multiworld, player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']),
|
||||
create_cave_region(multiworld, player, 'Pyramid Fairy', 'a cave with two chests',
|
||||
['Pyramid Fairy - Left', 'Pyramid Fairy - Right']),
|
||||
create_cave_region(world, player, 'Brewery', 'a house with a chest', ['Brewery']),
|
||||
create_cave_region(world, player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
|
||||
create_cave_region(world, player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
|
||||
create_cave_region(world, player, 'Red Shield Shop', 'the rare shop'),
|
||||
create_cave_region(world, player, 'Dark Sanctuary Hint', 'a storyteller'),
|
||||
create_cave_region(world, player, 'Bumper Cave', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Brewery', 'a house with a chest', ['Brewery']),
|
||||
create_cave_region(multiworld, player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
|
||||
create_cave_region(multiworld, player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
|
||||
create_cave_region(multiworld, player, 'Red Shield Shop', 'the rare shop'),
|
||||
create_cave_region(multiworld, player, 'Dark Sanctuary Hint', 'a storyteller'),
|
||||
create_cave_region(multiworld, player, 'Bumper Cave', 'a connector', None,
|
||||
['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
|
||||
create_dw_region(world, player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'],
|
||||
create_dw_region(multiworld, player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'],
|
||||
['Bumper Cave Ledge Drop', 'Bumper Cave (Top)', 'Bumper Cave Ledge Mirror Spot']),
|
||||
create_dw_region(world, player, 'Skull Woods Forest', None,
|
||||
create_dw_region(multiworld, player, 'Skull Woods Forest', None,
|
||||
['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)',
|
||||
'Skull Woods First Section Hole (North)',
|
||||
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
|
||||
create_dw_region(world, player, 'Skull Woods Forest (West)', None,
|
||||
create_dw_region(multiworld, player, 'Skull Woods Forest (West)', None,
|
||||
['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)',
|
||||
'Skull Woods Final Section']),
|
||||
create_dw_region(world, player, 'Dark Desert', None,
|
||||
create_dw_region(multiworld, player, 'Dark Desert', None,
|
||||
['Misery Mire', 'Mire Shed', 'Desert Ledge (Northeast) Mirror Spot',
|
||||
'Desert Ledge Mirror Spot', 'Desert Palace Stairs Mirror Spot',
|
||||
'Desert Palace Entrance (North) Mirror Spot', 'Dark Desert Hint', 'Dark Desert Fairy']),
|
||||
create_cave_region(world, player, 'Mire Shed', 'a cave with two chests',
|
||||
create_cave_region(multiworld, player, 'Mire Shed', 'a cave with two chests',
|
||||
['Mire Shed - Left', 'Mire Shed - Right']),
|
||||
create_cave_region(world, player, 'Dark Desert Hint', 'a storyteller'),
|
||||
create_dw_region(world, player, 'Dark Death Mountain (West Bottom)', None,
|
||||
create_cave_region(multiworld, player, 'Dark Desert Hint', 'a storyteller'),
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain (West Bottom)', None,
|
||||
['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain (Top)', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain (Top)', None,
|
||||
['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower',
|
||||
'Superbunny Cave (Top)',
|
||||
'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain Ledge', None,
|
||||
['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)',
|
||||
'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain Isolated Ledge', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain Isolated Ledge', None,
|
||||
['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']),
|
||||
create_dw_region(world, player, 'Dark Death Mountain (East Bottom)', None,
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain (East Bottom)', None,
|
||||
['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)',
|
||||
'Fairy Ascension Mirror Spot']),
|
||||
create_cave_region(world, player, 'Superbunny Cave (Top)', 'a connector',
|
||||
create_cave_region(multiworld, player, 'Superbunny Cave (Top)', 'a connector',
|
||||
['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Superbunny Cave (Bottom)', 'a connector', None,
|
||||
create_cave_region(multiworld, player, 'Superbunny Cave (Bottom)', 'a connector', None,
|
||||
['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']),
|
||||
create_cave_region(world, player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(world, player, 'Hookshot Cave', 'a connector',
|
||||
create_cave_region(multiworld, player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(multiworld, player, 'Hookshot Cave', 'a connector',
|
||||
['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
|
||||
'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']),
|
||||
create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
|
||||
create_cave_region(multiworld, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
|
||||
'Hookshot Cave Bomb Wall (North)']),
|
||||
create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None,
|
||||
create_dw_region(multiworld, player, 'Death Mountain Floating Island (Dark World)', None,
|
||||
['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']),
|
||||
create_lw_region(world, player, 'Death Mountain Floating Island (Light World)', ['Floating Island']),
|
||||
create_dw_region(world, player, 'Turtle Rock (Top)', None, ['Turtle Rock Drop']),
|
||||
create_lw_region(world, player, 'Mimic Cave Ledge', None, ['Mimic Cave']),
|
||||
create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
create_lw_region(multiworld, player, 'Death Mountain Floating Island (Light World)', ['Floating Island']),
|
||||
create_dw_region(multiworld, player, 'Turtle Rock (Top)', None, ['Turtle Rock Drop']),
|
||||
create_lw_region(multiworld, player, 'Mimic Cave Ledge', None, ['Mimic Cave']),
|
||||
create_cave_region(multiworld, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
'Swamp Palace - Trench 1 Pot Key'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
'Swamp Palace - Trench 2 Pot Key'], ['Swamp Palace (North)', 'Swamp Palace (West)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterway Pot Key', 'Swamp Palace - Waterfall Room',
|
||||
'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
create_dungeon_region(multiworld, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
create_dungeon_region(multiworld, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Hallway Pot Key',
|
||||
'Thieves\' Town - Spike Switch Pot Key',
|
||||
'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']),
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
create_dungeon_region(multiworld, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Many Pots Pot Key',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest',
|
||||
'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
|
||||
'Misery Mire - Conveyor Crystal Key Drop'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(multiworld, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Entrance to Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], ['Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door', 'Turtle Rock Second Section Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], ['Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door', 'Turtle Rock Second Section Bomb Wall']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Second Section Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Second Section from Bomb Wall']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Eye Bridge Bomb Wall)', 'Turtle Rock', None, ['Turtle Rock Isolated Ledge Exit', 'Turtle Rock Eye Bridge from Bomb Wall']),
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Eye Bridge Bomb Wall']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
create_dungeon_region(multiworld, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(multiworld, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Conveyor Cross Pot Key'],
|
||||
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door', 'Ganons Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
['Ganons Tower (Bottom) (East)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||
'Ganons Tower - Double Switch Pot Key'],
|
||||
['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'],
|
||||
['Ganons Tower (Bottom) (West)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Mini Helmasaur Key Drop'], ['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(world, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(world, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(world, player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']),
|
||||
create_lw_region(world, player, 'Desert Northern Cliffs'),
|
||||
create_dw_region(world, player, 'Dark Death Mountain Bunny Descent Area')
|
||||
create_dungeon_region(multiworld, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(multiworld, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(multiworld, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(multiworld, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(multiworld, player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']),
|
||||
create_lw_region(multiworld, player, 'Desert Northern Cliffs'),
|
||||
create_dw_region(multiworld, player, 'Dark Death Mountain Bunny Descent Area')
|
||||
]
|
||||
|
||||
|
||||
def create_lw_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
return _create_region(world, player, name, LTTPRegionType.LightWorld, 'Light World', locations, exits)
|
||||
def create_lw_region(multiworld: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
return _create_region(multiworld, player, name, LTTPRegionType.LightWorld, 'Light World', locations, exits)
|
||||
|
||||
|
||||
def create_dw_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
return _create_region(world, player, name, LTTPRegionType.DarkWorld, 'Dark World', locations, exits)
|
||||
def create_dw_region(multiworld: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
return _create_region(multiworld, player, name, LTTPRegionType.DarkWorld, 'Dark World', locations, exits)
|
||||
|
||||
|
||||
def create_cave_region(world: MultiWorld, player: int, name: str, hint: str, locations=None, exits=None):
|
||||
return _create_region(world, player, name, LTTPRegionType.Cave, hint, locations, exits)
|
||||
def create_cave_region(multiworld: MultiWorld, player: int, name: str, hint: str, locations=None, exits=None):
|
||||
return _create_region(multiworld, player, name, LTTPRegionType.Cave, hint, locations, exits)
|
||||
|
||||
|
||||
def create_dungeon_region(world: MultiWorld, player: int, name: str, hint: str, locations=None, exits=None):
|
||||
return _create_region(world, player, name, LTTPRegionType.Dungeon, hint, locations, exits)
|
||||
def create_dungeon_region(multiworld: MultiWorld, player: int, name: str, hint: str, locations=None, exits=None):
|
||||
return _create_region(multiworld, player, name, LTTPRegionType.Dungeon, hint, locations, exits)
|
||||
|
||||
|
||||
def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionType, hint: str, locations=None,
|
||||
def _create_region(multiworld: MultiWorld, player: int, name: str, type: LTTPRegionType, hint: str, locations=None,
|
||||
exits=None):
|
||||
from .SubClasses import ALttPLocation
|
||||
ret = LTTPRegion(name, type, hint, player, world)
|
||||
ret = LTTPRegion(name, type, hint, player, multiworld)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
ret.create_exit(exit)
|
||||
if locations:
|
||||
for location in locations:
|
||||
if location in key_drop_data:
|
||||
@@ -422,10 +422,10 @@ def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionTy
|
||||
return ret
|
||||
|
||||
|
||||
def mark_light_world_regions(world, player: int):
|
||||
def mark_light_world_regions(multiworld: MultiWorld, player: int):
|
||||
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
||||
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
||||
queue = collections.deque(region for region in world.get_regions(player) if region.type == LTTPRegionType.LightWorld)
|
||||
queue = collections.deque(region for region in multiworld.get_regions(player) if region.type == LTTPRegionType.LightWorld)
|
||||
seen = set(queue)
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
@@ -438,7 +438,7 @@ def mark_light_world_regions(world, player: int):
|
||||
seen.add(exit.connected_region)
|
||||
queue.append(exit.connected_region)
|
||||
|
||||
queue = collections.deque(region for region in world.get_regions(player) if region.type == LTTPRegionType.DarkWorld)
|
||||
queue = collections.deque(region for region in multiworld.get_regions(player) if region.type == LTTPRegionType.DarkWorld)
|
||||
seen = set(queue)
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
+275
-275
File diff suppressed because it is too large
Load Diff
+559
-545
File diff suppressed because it is too large
Load Diff
+57
-55
@@ -7,15 +7,13 @@ from Utils import int16_as_bytes
|
||||
|
||||
from worlds.generic.Rules import add_rule
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, Item, MultiWorld
|
||||
from .SubClasses import ALttPLocation
|
||||
|
||||
from .Items import item_name_groups
|
||||
|
||||
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
|
||||
|
||||
logger = logging.getLogger("Shops")
|
||||
|
||||
|
||||
@unique
|
||||
class ShopType(IntEnum):
|
||||
@@ -161,59 +159,63 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||
ShopType.TakeAny: TakeAny}
|
||||
|
||||
|
||||
def push_shop_inventories(multiworld):
|
||||
shop_slots = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type
|
||||
def push_shop_inventories(multiworld: MultiWorld):
|
||||
all_shops = []
|
||||
for world in multiworld.get_game_worlds(ALttPLocation.game):
|
||||
all_shops.extend(world.shops)
|
||||
shop_slots = [location for shop_locations in (shop.region.locations for shop in all_shops if shop.type
|
||||
!= ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None]
|
||||
|
||||
for location in shop_slots:
|
||||
item_name = location.item.name
|
||||
# Retro Bow arrows will already have been pushed
|
||||
if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player)
|
||||
if (not multiworld.worlds[location.player].options.retro_bow) or ((item_name, location.item.player)
|
||||
!= ("Single Arrow", location.player)):
|
||||
location.shop.push_inventory(location.shop_slot, item_name, location.shop_price,
|
||||
location.shop.push_inventory(location.shop_slot, item_name,
|
||||
round(location.shop_price * get_price_modifier(location.item)),
|
||||
1, location.item.player if location.item.player != location.player else 0,
|
||||
location.shop_price_type)
|
||||
location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price,
|
||||
get_price(multiworld, location.shop.inventory[location.shop_slot], location.player,
|
||||
location.shop_price_type)[1])
|
||||
|
||||
for world in multiworld.get_game_worlds("A Link to the Past"):
|
||||
for world in multiworld.get_game_worlds(ALttPLocation.game):
|
||||
world.pushed_shop_inventories.set()
|
||||
|
||||
|
||||
def create_shops(multiworld, player: int):
|
||||
def create_shops(multiworld: MultiWorld, player: int):
|
||||
from .Options import RandomizeShopInventories
|
||||
player_shop_table = shop_table.copy()
|
||||
if multiworld.include_witch_hut[player]:
|
||||
if multiworld.worlds[player].options.include_witch_hut:
|
||||
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
|
||||
dynamic_shop_slots = total_dynamic_shop_slots + 3
|
||||
else:
|
||||
dynamic_shop_slots = total_dynamic_shop_slots
|
||||
if multiworld.shuffle_capacity_upgrades[player]:
|
||||
if multiworld.worlds[player].options.shuffle_capacity_upgrades:
|
||||
player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False)
|
||||
|
||||
num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player])
|
||||
num_slots = min(dynamic_shop_slots, multiworld.worlds[player].options.shop_item_slots)
|
||||
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
||||
multiworld.random.shuffle(single_purchase_slots)
|
||||
|
||||
if multiworld.randomize_shop_inventories[player]:
|
||||
if multiworld.worlds[player].options.randomize_shop_inventories:
|
||||
default_shop_table = [i for l in
|
||||
[shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if
|
||||
not multiworld.retro_bow[player] or x != 'arrows'] for i in l]
|
||||
not multiworld.worlds[player].options.retro_bow or x != 'arrows'] for i in l]
|
||||
new_basic_shop = multiworld.random.sample(default_shop_table, k=3)
|
||||
new_dark_shop = multiworld.random.sample(default_shop_table, k=3)
|
||||
for name, shop in player_shop_table.items():
|
||||
typ, shop_id, keeper, custom, locked, items, sram_offset = shop
|
||||
if not locked:
|
||||
new_items = multiworld.random.sample(default_shop_table, k=len(items))
|
||||
if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type:
|
||||
if multiworld.worlds[player].options.randomize_shop_inventories == RandomizeShopInventories.option_randomize_by_shop_type:
|
||||
if items == _basic_shop_defaults:
|
||||
new_items = new_basic_shop
|
||||
elif items == _dark_world_shop_defaults:
|
||||
new_items = new_dark_shop
|
||||
keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF])
|
||||
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
|
||||
if multiworld.mode[player] == "inverted":
|
||||
if multiworld.worlds[player].options.mode == "inverted":
|
||||
# make sure that blue potion is available in inverted, special case locked = None; lock when done.
|
||||
player_shop_table["Dark Lake Hylia Shop"] = \
|
||||
player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
|
||||
@@ -224,7 +226,7 @@ def create_shops(multiworld, player: int):
|
||||
if locked is None:
|
||||
shop.locked = True
|
||||
region.shop = shop
|
||||
multiworld.shops.append(shop)
|
||||
multiworld.worlds[player].shops.append(shop)
|
||||
for index, item in enumerate(inventory):
|
||||
shop.add_inventory(index, *item)
|
||||
if not locked and (num_slots or type == ShopType.UpgradeShop):
|
||||
@@ -236,7 +238,7 @@ def create_shops(multiworld, player: int):
|
||||
add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot))
|
||||
loc.shop = shop
|
||||
loc.shop_slot = index
|
||||
if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop))
|
||||
if ((not (multiworld.worlds[player].options.shuffle_capacity_upgrades and type == ShopType.UpgradeShop))
|
||||
and not single_purchase_slots.pop()):
|
||||
loc.shop_slot_disabled = True
|
||||
loc.locked = True
|
||||
@@ -304,54 +306,54 @@ shop_generation_types = {
|
||||
}
|
||||
|
||||
|
||||
def set_up_shops(multiworld, player: int):
|
||||
def set_up_shops(multiworld: 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]:
|
||||
local_world = multiworld.worlds[player]
|
||||
|
||||
if local_world.options.retro_bow:
|
||||
rss = multiworld.get_region('Red Shield Shop', player).shop
|
||||
# Can't just replace the single arrow with 10 arrows as retro doesn't need them.
|
||||
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
|
||||
['Blue Shield', 50], ['Small Heart',
|
||||
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
|
||||
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
['Blue Shield', 50], ['Small Heart', 10]]
|
||||
if local_world.options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
replacement_items.append(['Small Key (Universal)', 100])
|
||||
replacement_item = multiworld.random.choice(replacement_items)
|
||||
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
|
||||
rss.locked = True
|
||||
|
||||
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]:
|
||||
for shop in multiworld.random.sample([s for s in multiworld.shops if
|
||||
s.custom and not s.locked and s.type == ShopType.Shop
|
||||
and s.region.player == player], 5):
|
||||
if local_world.options.small_key_shuffle == small_key_shuffle.option_universal or local_world.options.retro_bow:
|
||||
for shop in multiworld.random.sample([s for s in local_world.shops if
|
||||
s.custom and not s.locked and s.type == ShopType.Shop], 5):
|
||||
shop.locked = True
|
||||
slots = [0, 1, 2]
|
||||
multiworld.random.shuffle(slots)
|
||||
slots = iter(slots)
|
||||
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if local_world.options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
|
||||
if multiworld.retro_bow[player]:
|
||||
if local_world.options.retro_bow:
|
||||
shop.push_inventory(next(slots), 'Single Arrow', 80)
|
||||
|
||||
if multiworld.shuffle_capacity_upgrades[player]:
|
||||
for shop in multiworld.shops:
|
||||
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
|
||||
if local_world.options.shuffle_capacity_upgrades:
|
||||
for shop in local_world.shops:
|
||||
if shop.type == ShopType.UpgradeShop and \
|
||||
shop.region.name == "Capacity Upgrade":
|
||||
shop.clear_inventory()
|
||||
|
||||
if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player]
|
||||
or multiworld.randomize_cost_types[player]):
|
||||
if (local_world.options.shuffle_shop_inventories or local_world.options.randomize_shop_prices
|
||||
or local_world.options.randomize_cost_types):
|
||||
shops = []
|
||||
total_inventory = []
|
||||
for shop in multiworld.shops:
|
||||
if shop.region.player == player:
|
||||
if shop.type == ShopType.Shop and not shop.locked:
|
||||
shops.append(shop)
|
||||
total_inventory.extend(shop.inventory)
|
||||
for shop in local_world.shops:
|
||||
if shop.type == ShopType.Shop and not shop.locked:
|
||||
shops.append(shop)
|
||||
total_inventory.extend(shop.inventory)
|
||||
|
||||
for item in total_inventory:
|
||||
item["price_type"], item["price"] = get_price(multiworld, item, player)
|
||||
|
||||
if multiworld.shuffle_shop_inventories[player]:
|
||||
if local_world.options.shuffle_shop_inventories:
|
||||
multiworld.random.shuffle(total_inventory)
|
||||
|
||||
i = 0
|
||||
@@ -406,7 +408,7 @@ price_rate_display = {
|
||||
}
|
||||
|
||||
|
||||
def get_price_modifier(item):
|
||||
def get_price_modifier(item: Item) -> float:
|
||||
if item.game == "A Link to the Past":
|
||||
if any(x in item.name for x in
|
||||
['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
|
||||
@@ -417,60 +419,60 @@ def get_price_modifier(item):
|
||||
elif any(x in item.name for x in ['Small Key', 'Heart']):
|
||||
return 0.5
|
||||
else:
|
||||
return 1
|
||||
return 1.0
|
||||
if item.advancement:
|
||||
return 1
|
||||
return 1.0
|
||||
elif item.useful:
|
||||
return 0.5
|
||||
else:
|
||||
return 0.25
|
||||
|
||||
|
||||
def get_price(multiworld, item, player: int, price_type=None):
|
||||
def get_price(multiworld: MultiWorld, item: 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:
|
||||
price_types = [ShopPriceType.Rupees] # included as a chance to not change price
|
||||
if multiworld.randomize_cost_types[player]:
|
||||
if multiworld.worlds[player].options.randomize_cost_types:
|
||||
price_types += [
|
||||
ShopPriceType.Hearts,
|
||||
ShopPriceType.Bombs,
|
||||
ShopPriceType.Magic,
|
||||
]
|
||||
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
if item and item["item"] == "Small Key (Universal)":
|
||||
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys
|
||||
else:
|
||||
price_types.append(ShopPriceType.Keys)
|
||||
if multiworld.retro_bow[player]:
|
||||
if multiworld.worlds[player].options.retro_bow:
|
||||
if item and item["item"] == "Single Arrow":
|
||||
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows
|
||||
else:
|
||||
price_types.append(ShopPriceType.Arrows)
|
||||
diff = multiworld.item_pool[player].value
|
||||
diff = multiworld.worlds[player].options.item_pool.value
|
||||
if item:
|
||||
# This is for a shop's regular inventory, the item is already determined, and we will decide the price here
|
||||
price = item["price"]
|
||||
if multiworld.randomize_shop_prices[player]:
|
||||
if multiworld.worlds[player].options.randomize_shop_prices:
|
||||
adjust = 2 if price < 100 else 5
|
||||
price = int((price / adjust) * (0.5 + multiworld.per_slot_randoms[player].random() * 1.5)) * adjust
|
||||
multiworld.per_slot_randoms[player].shuffle(price_types)
|
||||
price = int((price / adjust) * (0.5 + multiworld.worlds[player].random.random() * 1.5)) * adjust
|
||||
multiworld.worlds[player].random.shuffle(price_types)
|
||||
for p_type in price_types:
|
||||
if any(x in item['item'] for x in price_blacklist[p_type]):
|
||||
continue
|
||||
return p_type, price_chart[p_type](price, diff)
|
||||
else:
|
||||
# This is an AP location and the price will be adjusted after an item is shuffled into it
|
||||
p_type = multiworld.per_slot_randoms[player].choice(price_types)
|
||||
return p_type, price_chart[p_type](min(int(multiworld.per_slot_randoms[player].randint(8, 56)
|
||||
* multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff)
|
||||
p_type = multiworld.worlds[player].random.choice(price_types)
|
||||
return p_type, price_chart[p_type](min(int(multiworld.worlds[player].random.randint(8, 56)
|
||||
* multiworld.worlds[player].options.shop_price_modifier / 100) * 5, 9999), diff)
|
||||
|
||||
|
||||
def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation):
|
||||
if location.shop_price_type == ShopPriceType.Hearts:
|
||||
return has_hearts(state, player, (location.shop_price / 8) + 1)
|
||||
return has_hearts(state, player, (location.shop_price // 8) + 1)
|
||||
elif location.shop_price_type == ShopPriceType.Bombs:
|
||||
return can_use_bombs(state, player, location.shop_price)
|
||||
elif location.shop_price_type == ShopPriceType.Arrows:
|
||||
|
||||
@@ -6,7 +6,7 @@ def is_not_bunny(state: CollectionState, region: LTTPRegion, player: int) -> boo
|
||||
if state.has('Moon Pearl', player):
|
||||
return True
|
||||
|
||||
return region.is_light_world if state.multiworld.mode[player] != 'inverted' else region.is_dark_world
|
||||
return region.is_light_world if state.multiworld.worlds[player].options.mode != 'inverted' else region.is_dark_world
|
||||
|
||||
|
||||
def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool:
|
||||
@@ -14,19 +14,19 @@ def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bo
|
||||
|
||||
|
||||
def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool:
|
||||
return any(shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.shops)
|
||||
return any(shop.has_unlimited(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.worlds[player].shops)
|
||||
|
||||
|
||||
def can_buy(state: CollectionState, item: str, player: int) -> bool:
|
||||
return any(shop.region.player == player and shop.has(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.shops)
|
||||
return any(shop.has(item) and shop.region.can_reach(state) for
|
||||
shop in state.multiworld.worlds[player].shops)
|
||||
|
||||
|
||||
def can_shoot_arrows(state: CollectionState, player: int) -> bool:
|
||||
if state.multiworld.retro_bow[player]:
|
||||
def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool:
|
||||
if state.multiworld.worlds[player].options.retro_bow:
|
||||
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player)
|
||||
return state.has('Bow', player) or state.has('Silver Bow', player)
|
||||
return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count)
|
||||
|
||||
|
||||
def has_triforce_pieces(state: CollectionState, player: int) -> bool:
|
||||
@@ -59,24 +59,25 @@ def has_hearts(state: CollectionState, player: int, count: int) -> int:
|
||||
|
||||
def heart_count(state: CollectionState, player: int) -> int:
|
||||
# Warning: This only considers items that are marked as advancement items
|
||||
diff = state.multiworld.worlds[player].difficulty_requirements
|
||||
return min(state.count('Boss Heart Container', player), diff.boss_heart_container_limit) \
|
||||
+ state.count('Sanctuary Heart Container', player) \
|
||||
+ min(state.count('Piece of Heart', player), diff.heart_piece_limit) // 4 \
|
||||
+ 3 # starting hearts
|
||||
max_heart_pieces = state.multiworld.worlds[player].logical_heart_pieces
|
||||
max_heart_containers = state.multiworld.worlds[player].logical_heart_containers
|
||||
return min(state.count('Boss Heart Container', player), max_heart_containers) \
|
||||
+ state.count('Sanctuary Heart Container', player) \
|
||||
+ min(state.count('Piece of Heart', player), max_heart_pieces) // 4 \
|
||||
+ 3 # starting hearts
|
||||
|
||||
|
||||
def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
|
||||
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
|
||||
fullrefill: bool = False): # This reflects the total magic Link has, not the total extra he has.
|
||||
basemagic = 8
|
||||
if state.has('Magic Upgrade (1/4)', player):
|
||||
basemagic = 32
|
||||
elif state.has('Magic Upgrade (1/2)', player):
|
||||
basemagic = 16
|
||||
if can_buy_unlimited(state, 'Green Potion', player) or can_buy_unlimited(state, 'Blue Potion', player):
|
||||
if state.multiworld.item_functionality[player] == 'hard' and not fullrefill:
|
||||
if state.multiworld.worlds[player].options.item_functionality == 'hard' and not fullrefill:
|
||||
basemagic = basemagic + int(basemagic * 0.5 * bottle_count(state, player))
|
||||
elif state.multiworld.item_functionality[player] == 'expert' and not fullrefill:
|
||||
elif state.multiworld.worlds[player].options.item_functionality == 'expert' and not fullrefill:
|
||||
basemagic = basemagic + int(basemagic * 0.25 * bottle_count(state, player))
|
||||
else:
|
||||
basemagic = basemagic + basemagic * bottle_count(state, player)
|
||||
@@ -84,20 +85,27 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
|
||||
|
||||
|
||||
def can_hold_arrows(state: CollectionState, player: int, quantity: int):
|
||||
arrows = 30 + ((state.count("Arrow Upgrade (+5)", player) * 5) + (state.count("Arrow Upgrade (+10)", player) * 10)
|
||||
+ (state.count("Bomb Upgrade (50)", player) * 50))
|
||||
# Arrow Upgrade (+5) beyond the 6th gives +10
|
||||
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
|
||||
return min(70, arrows) >= quantity
|
||||
if state.multiworld.worlds[player].options.shuffle_capacity_upgrades:
|
||||
if quantity == 0:
|
||||
return True
|
||||
if state.has("Arrow Upgrade (70)", player):
|
||||
arrows = 70
|
||||
else:
|
||||
arrows = (30 + (state.count("Arrow Upgrade (+5)", player) * 5)
|
||||
+ (state.count("Arrow Upgrade (+10)", player) * 10))
|
||||
# Arrow Upgrade (+5) beyond the 6th gives +10
|
||||
arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
|
||||
return min(70, arrows) >= quantity
|
||||
return quantity <= 30 or state.has("Capacity Upgrade Shop", player)
|
||||
|
||||
|
||||
def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool:
|
||||
bombs = 0 if state.multiworld.bombless_start[player] else 10
|
||||
bombs = 0 if state.multiworld.worlds[player].options.bombless_start else 10
|
||||
bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10)
|
||||
+ (state.count("Bomb Upgrade (50)", player) * 50))
|
||||
# Bomb Upgrade (+5) beyond the 6th gives +10
|
||||
bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10))
|
||||
if (not state.multiworld.shuffle_capacity_upgrades[player]) and state.has("Capacity Upgrade Shop", player):
|
||||
if (not state.multiworld.worlds[player].options.shuffle_capacity_upgrades) and state.has("Capacity Upgrade Shop", player):
|
||||
bombs += 40
|
||||
return bombs >= min(quantity, 50)
|
||||
|
||||
@@ -113,7 +121,7 @@ def can_activate_crystal_switch(state: CollectionState, player: int) -> bool:
|
||||
|
||||
|
||||
def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool:
|
||||
if state.multiworld.enemy_shuffle[player]:
|
||||
if state.multiworld.worlds[player].options.enemy_shuffle:
|
||||
# I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any.
|
||||
# Just go with maximal requirements for now.
|
||||
return (has_melee_weapon(state, player)
|
||||
@@ -128,10 +136,20 @@ def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5)
|
||||
or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
|
||||
or can_shoot_arrows(state, player)
|
||||
or state.has('Fire Rod', player)
|
||||
or (state.multiworld.enemy_health[player] in ("easy", "default")
|
||||
or (state.multiworld.worlds[player].options.enemy_health in ("easy", "default")
|
||||
and can_use_bombs(state, player, enemies * 4)))
|
||||
|
||||
|
||||
def can_kill_standard_start(state: CollectionState, player: int, enemies: int = 5) -> bool:
|
||||
# Enemizer does not randomize standard start enemies
|
||||
return (has_melee_weapon(state, player)
|
||||
or state.has('Cane of Somaria', player)
|
||||
or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
|
||||
or state.has_any(["Bow", "Progressive Bow"], player)
|
||||
or state.has('Fire Rod', player)
|
||||
or can_use_bombs(state, player, enemies)) # Escape assist is set
|
||||
|
||||
|
||||
def can_get_good_bee(state: CollectionState, player: int) -> bool:
|
||||
cave = state.multiworld.get_region('Good Bee Cave', player)
|
||||
return (
|
||||
@@ -145,20 +163,20 @@ def can_get_good_bee(state: CollectionState, player: int) -> bool:
|
||||
|
||||
def can_retrieve_tablet(state: CollectionState, player: int) -> bool:
|
||||
return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or
|
||||
(state.multiworld.swordless[player] and
|
||||
state.has("Hammer", player)))
|
||||
(state.multiworld.worlds[player].options.swordless and
|
||||
state.has("Hammer", player)))
|
||||
|
||||
|
||||
def has_sword(state: CollectionState, player: int) -> bool:
|
||||
return state.has('Fighter Sword', player) \
|
||||
or state.has('Master Sword', player) \
|
||||
or state.has('Tempered Sword', player) \
|
||||
or state.has('Golden Sword', player)
|
||||
or state.has('Master Sword', player) \
|
||||
or state.has('Tempered Sword', player) \
|
||||
or state.has('Golden Sword', player)
|
||||
|
||||
|
||||
def has_beam_sword(state: CollectionState, player: int) -> bool:
|
||||
return state.has('Master Sword', player) or state.has('Tempered Sword', player) or state.has('Golden Sword',
|
||||
player)
|
||||
player)
|
||||
|
||||
|
||||
def has_melee_weapon(state: CollectionState, player: int) -> bool:
|
||||
@@ -171,9 +189,9 @@ def has_fire_source(state: CollectionState, player: int) -> bool:
|
||||
|
||||
def can_melt_things(state: CollectionState, player: int) -> bool:
|
||||
return state.has('Fire Rod', player) or \
|
||||
(state.has('Bombos', player) and
|
||||
(state.multiworld.swordless[player] or
|
||||
has_sword(state, player)))
|
||||
(state.has('Bombos', player) and
|
||||
(state.multiworld.worlds[player].options.swordless or
|
||||
has_sword(state, player)))
|
||||
|
||||
|
||||
def has_misery_mire_medallion(state: CollectionState, player: int) -> bool:
|
||||
@@ -185,19 +203,19 @@ def has_turtle_rock_medallion(state: CollectionState, player: int) -> bool:
|
||||
|
||||
|
||||
def can_boots_clip_lw(state: CollectionState, player: int) -> bool:
|
||||
if state.multiworld.mode[player] == 'inverted':
|
||||
if state.multiworld.worlds[player].options.mode == 'inverted':
|
||||
return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player)
|
||||
return state.has('Pegasus Boots', player)
|
||||
|
||||
|
||||
def can_boots_clip_dw(state: CollectionState, player: int) -> bool:
|
||||
if state.multiworld.mode[player] != 'inverted':
|
||||
if state.multiworld.worlds[player].options.mode != 'inverted':
|
||||
return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player)
|
||||
return state.has('Pegasus Boots', player)
|
||||
|
||||
|
||||
def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool:
|
||||
rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])]
|
||||
if state.multiworld.mode[player] != 'inverted':
|
||||
if state.multiworld.worlds[player].options.mode != 'inverted':
|
||||
rules.append(state.has('Moon Pearl', player))
|
||||
return all(rules)
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from enum import IntEnum
|
||||
|
||||
from BaseClasses import Location, Item, ItemClassification, Region, MultiWorld
|
||||
from BaseClasses import Entrance, Location, Item, ItemClassification, Region, MultiWorld
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .Dungeons import Dungeon
|
||||
from .Regions import LTTPRegion
|
||||
|
||||
|
||||
class ALttPLocation(Location):
|
||||
@@ -77,6 +76,19 @@ class ALttPItem(Item):
|
||||
return self.type
|
||||
|
||||
|
||||
Addresses = int | list[int] | tuple[int, int, int, int, int, int, int, int, int, int, int, int, int]
|
||||
|
||||
|
||||
class LTTPEntrance(Entrance):
|
||||
addresses: Addresses | None = None
|
||||
target: int | None = None
|
||||
|
||||
def connect(self, region: Region, addresses: Addresses | None = None, target: int | None = None) -> None:
|
||||
super().connect(region)
|
||||
self.addresses = addresses
|
||||
self.target = target
|
||||
|
||||
|
||||
class LTTPRegionType(IntEnum):
|
||||
LightWorld = 1
|
||||
DarkWorld = 2
|
||||
@@ -90,6 +102,7 @@ class LTTPRegionType(IntEnum):
|
||||
|
||||
|
||||
class LTTPRegion(Region):
|
||||
entrance_type = LTTPEntrance
|
||||
type: LTTPRegionType
|
||||
|
||||
# will be set after making connections.
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
from BaseClasses import Entrance
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from .StateHelpers import can_bomb_clip, has_sword, has_beam_sword, has_fire_source, can_melt_things, has_misery_mire_medallion
|
||||
from .SubClasses import LTTPEntrance
|
||||
|
||||
|
||||
# We actually need the logic to properly "mark" these regions as Light or Dark world.
|
||||
# Therefore we need to make these connections during the normal link_entrances stage, rather than during set_rules.
|
||||
def underworld_glitch_connections(world, player):
|
||||
specrock = world.get_region('Spectacle Rock Cave (Bottom)', player)
|
||||
mire = world.get_region('Misery Mire (West)', player)
|
||||
def underworld_glitch_connections(multiworld: MultiWorld, player: int):
|
||||
specrock = multiworld.get_region('Spectacle Rock Cave (Bottom)', player)
|
||||
mire = multiworld.get_region('Misery Mire (West)', player)
|
||||
|
||||
kikiskip = Entrance(player, 'Kiki Skip', specrock)
|
||||
mire_to_hera = Entrance(player, 'Mire to Hera Clip', mire)
|
||||
mire_to_swamp = Entrance(player, 'Hera to Swamp Clip', mire)
|
||||
specrock.exits.append(kikiskip)
|
||||
mire.exits.extend([mire_to_hera, mire_to_swamp])
|
||||
kikiskip = specrock.create_exit('Kiki Skip')
|
||||
mire_to_hera = mire.create_exit('Mire to Hera Clip')
|
||||
mire_to_swamp = mire.create_exit('Hera to Swamp Clip')
|
||||
|
||||
if world.worlds[player].fix_fake_world:
|
||||
kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region)
|
||||
mire_to_hera.connect(world.get_entrance('Tower of Hera Exit', player).connected_region)
|
||||
mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit', player).connected_region)
|
||||
else:
|
||||
kikiskip.connect(world.get_region('Palace of Darkness (Entrance)', player))
|
||||
mire_to_hera.connect(world.get_region('Tower of Hera (Bottom)', player))
|
||||
mire_to_swamp.connect(world.get_region('Swamp Palace (Entrance)', player))
|
||||
if multiworld.worlds[player].fix_fake_world:
|
||||
kikiskip.connect(multiworld.get_entrance('Palace of Darkness Exit', player).connected_region)
|
||||
mire_to_hera.connect(multiworld.get_entrance('Tower of Hera Exit', player).connected_region)
|
||||
mire_to_swamp.connect(multiworld.get_entrance('Swamp Palace Exit', player).connected_region)
|
||||
else:
|
||||
kikiskip.connect(multiworld.get_region('Palace of Darkness (Entrance)', player))
|
||||
mire_to_hera.connect(multiworld.get_region('Tower of Hera (Bottom)', player))
|
||||
mire_to_swamp.connect(multiworld.get_region('Swamp Palace (Entrance)', player))
|
||||
|
||||
|
||||
# For some entrances, we need to fake having pearl, because we're in fake DW/LW.
|
||||
# This creates a copy of the input state that has Moon Pearl.
|
||||
def fake_pearl_state(state, player):
|
||||
def fake_pearl_state(state: CollectionState, player: int):
|
||||
if state.has('Moon Pearl', player):
|
||||
return state
|
||||
fake_state = state.copy()
|
||||
@@ -37,11 +36,11 @@ def fake_pearl_state(state, player):
|
||||
|
||||
# Sets the rules on where we can actually go using this clip.
|
||||
# Behavior differs based on what type of ER shuffle we're playing.
|
||||
def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str):
|
||||
fix_dungeon_exits = world.worlds[player].fix_palaceofdarkness_exit
|
||||
fix_fake_worlds = world.worlds[player].fix_fake_world
|
||||
def dungeon_reentry_rules(multiworld: MultiWorld, player: int, clip: LTTPEntrance, dungeon_region: str, dungeon_exit: str):
|
||||
fix_dungeon_exits = multiworld.worlds[player].fix_palaceofdarkness_exit
|
||||
fix_fake_worlds = multiworld.worlds[player].fix_fake_world
|
||||
|
||||
dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0]
|
||||
dungeon_entrance = [r for r in multiworld.get_region(dungeon_region, player).entrances if r.name != clip.name][0]
|
||||
if not fix_dungeon_exits: # vanilla, simple, restricted, dungeons_simple; should never have fake worlds fix
|
||||
# Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially.
|
||||
if dungeon_entrance.name == 'Skull Woods Final Section':
|
||||
@@ -51,64 +50,64 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du
|
||||
elif dungeon_entrance.name == 'Agahnims Tower':
|
||||
add_rule(clip, lambda state: state.has('Cape', player) or has_beam_sword(state, player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier
|
||||
# Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally.
|
||||
add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
|
||||
add_rule(multiworld.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
|
||||
elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix
|
||||
# Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region.
|
||||
add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player)))
|
||||
# exiting restriction
|
||||
add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
|
||||
add_rule(multiworld.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
|
||||
# Otherwise, the shuffle type is crossed, dungeons_crossed, or insanity; all of these do not need additional rules on where we can go,
|
||||
# since the clip links directly to the exterior region.
|
||||
|
||||
|
||||
def underworld_glitches_rules(world, player):
|
||||
def underworld_glitches_rules(multiworld: MultiWorld, player: int):
|
||||
# Ice Palace Entrance Clip
|
||||
# This is the easiest one since it's a simple internal clip.
|
||||
# Need to also add melting to freezor chest since it's otherwise assumed.
|
||||
# Also can pick up the first jelly key from behind.
|
||||
add_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: can_bomb_clip(state, world.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
add_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: can_melt_things(state, player))
|
||||
add_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_bomb_clip(state, world.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
add_rule(multiworld.get_entrance('Ice Palace (Main)', player), lambda state: can_bomb_clip(state, multiworld.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
add_rule(multiworld.get_location('Ice Palace - Freezor Chest', player), lambda state: can_melt_things(state, player))
|
||||
add_rule(multiworld.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_bomb_clip(state, multiworld.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
|
||||
|
||||
# Kiki Skip
|
||||
kikiskip = world.get_entrance('Kiki Skip', player)
|
||||
kikiskip = multiworld.get_entrance('Kiki Skip', player)
|
||||
set_rule(kikiskip, lambda state: can_bomb_clip(state, kikiskip.parent_region, player))
|
||||
dungeon_reentry_rules(world, player, kikiskip, 'Palace of Darkness (Entrance)', 'Palace of Darkness Exit')
|
||||
dungeon_reentry_rules(multiworld, player, kikiskip, 'Palace of Darkness (Entrance)', 'Palace of Darkness Exit')
|
||||
|
||||
|
||||
# Mire -> Hera -> Swamp
|
||||
# Using mire keys on other dungeon doors
|
||||
mire = world.get_region('Misery Mire (West)', player)
|
||||
mire = multiworld.get_region('Misery Mire (West)', player)
|
||||
mire_clip = lambda state: state.can_reach('Misery Mire (West)', 'Region', player) and can_bomb_clip(state, mire, player) and has_fire_source(state, player)
|
||||
hera_clip = lambda state: state.can_reach('Tower of Hera (Top)', 'Region', player) and can_bomb_clip(state, world.get_region('Tower of Hera (Top)', player), player)
|
||||
add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: mire_clip(state) and state.has('Big Key (Misery Mire)', player), combine='or')
|
||||
add_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: mire_clip(state), combine='or')
|
||||
add_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: mire_clip(state) or hera_clip(state), combine='or')
|
||||
hera_clip = lambda state: state.can_reach('Tower of Hera (Top)', 'Region', player) and can_bomb_clip(state, multiworld.get_region('Tower of Hera (Top)', player), player)
|
||||
add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: mire_clip(state) and state.has('Big Key (Misery Mire)', player), combine='or')
|
||||
add_rule(multiworld.get_entrance('Swamp Palace Small Key Door', player), lambda state: mire_clip(state), combine='or')
|
||||
add_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: mire_clip(state) or hera_clip(state), combine='or')
|
||||
|
||||
# Build the rule for SP moat.
|
||||
# We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT.
|
||||
# First we require a certain type of entrance shuffle, then build the rule from its pieces.
|
||||
if not world.worlds[player].swamp_patch_required:
|
||||
if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
|
||||
if not multiworld.worlds[player].swamp_patch_required:
|
||||
if multiworld.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
|
||||
rule_map = {
|
||||
'Misery Mire (Entrance)': (lambda state: True),
|
||||
'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player))
|
||||
}
|
||||
inverted = world.mode[player] == 'inverted'
|
||||
inverted = multiworld.worlds[player].options.mode == 'inverted'
|
||||
hera_rule = lambda state: (state.has('Moon Pearl', player) or not inverted) and \
|
||||
rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state)
|
||||
rule_map.get(multiworld.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state)
|
||||
gt_rule = lambda state: (state.has('Moon Pearl', player) or inverted) and \
|
||||
rule_map.get(world.get_entrance(('Ganons Tower' if not inverted else 'Inverted Ganons Tower'), player).connected_region.name, lambda state: False)(state)
|
||||
rule_map.get(multiworld.get_entrance(('Ganons Tower' if not inverted else 'Inverted Ganons Tower'), player).connected_region.name, lambda state: False)(state)
|
||||
mirrorless_moat_rule = lambda state: state.can_reach('Old Man S&Q', 'Entrance', player) and mire_clip(state) and (hera_rule(state) or gt_rule(state))
|
||||
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or mirrorless_moat_rule(state))
|
||||
add_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player) or mirrorless_moat_rule(state))
|
||||
else:
|
||||
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player))
|
||||
add_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player))
|
||||
|
||||
# Using the entrances for various ER types. Hera -> Swamp never matters because you can only logically traverse with the mire keys
|
||||
mire_to_hera = world.get_entrance('Mire to Hera Clip', player)
|
||||
mire_to_swamp = world.get_entrance('Hera to Swamp Clip', player)
|
||||
mire_to_hera = multiworld.get_entrance('Mire to Hera Clip', player)
|
||||
mire_to_swamp = multiworld.get_entrance('Hera to Swamp Clip', player)
|
||||
set_rule(mire_to_hera, mire_clip)
|
||||
set_rule(mire_to_swamp, lambda state: mire_clip(state) and state.has('Flippers', player))
|
||||
dungeon_reentry_rules(world, player, mire_to_hera, 'Tower of Hera (Bottom)', 'Tower of Hera Exit')
|
||||
dungeon_reentry_rules(world, player, mire_to_swamp, 'Swamp Palace (Entrance)', 'Swamp Palace Exit')
|
||||
dungeon_reentry_rules(multiworld, player, mire_to_hera, 'Tower of Hera (Bottom)', 'Tower of Hera Exit')
|
||||
dungeon_reentry_rules(multiworld, player, mire_to_swamp, 'Swamp Palace (Entrance)', 'Swamp Palace Exit')
|
||||
|
||||
+127
-103
@@ -121,6 +121,7 @@ class ALTTPWeb(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_de, setup_es, setup_fr, msu, msu_es, msu_fr, plando, oof_sound]
|
||||
game_info_languages = ["en", "fr"]
|
||||
|
||||
|
||||
class ALTTPWorld(World):
|
||||
@@ -140,7 +141,7 @@ class ALTTPWorld(World):
|
||||
item_name_groups = item_name_groups
|
||||
location_name_groups = {
|
||||
"Blind's Hideout": {"Blind's Hideout - Top", "Blind's Hideout - Left", "Blind's Hideout - Right",
|
||||
"Blind's Hideout - Far Left", "Blind's Hideout - Far Right"},
|
||||
"Blind's Hideout - Far Left", "Blind's Hideout - Far Right"},
|
||||
"Kakariko Well": {"Kakariko Well - Top", "Kakariko Well - Left", "Kakariko Well - Middle",
|
||||
"Kakariko Well - Right", "Kakariko Well - Bottom"},
|
||||
"Mini Moldorm Cave": {"Mini Moldorm Cave - Far Left", "Mini Moldorm Cave - Left", "Mini Moldorm Cave - Right",
|
||||
@@ -153,15 +154,23 @@ class ALTTPWorld(World):
|
||||
"Hookshot Cave": {"Hookshot Cave - Top Right", "Hookshot Cave - Top Left", "Hookshot Cave - Bottom Right",
|
||||
"Hookshot Cave - Bottom Left"},
|
||||
"Hyrule Castle": {"Hyrule Castle - Boomerang Chest", "Hyrule Castle - Map Chest",
|
||||
"Hyrule Castle - Zelda's Chest", "Sewers - Dark Cross", "Sewers - Secret Room - Left",
|
||||
"Sewers - Secret Room - Middle", "Sewers - Secret Room - Right"},
|
||||
"Hyrule Castle - Zelda's Chest", "Hyrule Castle - Big Key Drop",
|
||||
"Hyrule Castle - Boomerang Guard Key Drop", "Hyrule Castle - Map Guard Key Drop",
|
||||
"Sewers - Dark Cross", "Sewers - Secret Room - Left",
|
||||
"Sewers - Secret Room - Middle", "Sewers - Secret Room - Right",
|
||||
"Sewers - Key Rat Key Drop"},
|
||||
"Eastern Palace": {"Eastern Palace - Compass Chest", "Eastern Palace - Big Chest",
|
||||
"Eastern Palace - Cannonball Chest", "Eastern Palace - Big Key Chest",
|
||||
"Eastern Palace - Dark Eyegore Key Drop", "Eastern Palace - Dark Square Pot Key",
|
||||
"Eastern Palace - Map Chest", "Eastern Palace - Boss"},
|
||||
"Desert Palace": {"Desert Palace - Big Chest", "Desert Palace - Torch", "Desert Palace - Map Chest",
|
||||
"Desert Palace - Compass Chest", "Desert Palace - Big Key Chest", "Desert Palace - Boss"},
|
||||
"Desert Palace - Beamos Hall Pot Key", "Desert Palace - Desert Tiles 1 Pot Key",
|
||||
"Desert Palace - Desert Tiles 2 Pot Key", "Desert Palace - Compass Chest",
|
||||
"Desert Palace - Big Key Chest", "Desert Palace - Boss"},
|
||||
"Tower of Hera": {"Tower of Hera - Basement Cage", "Tower of Hera - Map Chest", "Tower of Hera - Big Key Chest",
|
||||
"Tower of Hera - Compass Chest", "Tower of Hera - Big Chest", "Tower of Hera - Boss"},
|
||||
"Castle Tower": {"Castle Tower - Room 03", "Castle Tower - Dark Maze",
|
||||
"Castle Tower - Dark Archer Key Drop", "Castle Tower - Circle of Pots Key Drop"},
|
||||
"Palace of Darkness": {"Palace of Darkness - Shooter Room", "Palace of Darkness - The Arena - Bridge",
|
||||
"Palace of Darkness - Stalfos Basement", "Palace of Darkness - Big Key Chest",
|
||||
"Palace of Darkness - The Arena - Ledge", "Palace of Darkness - Map Chest",
|
||||
@@ -172,25 +181,33 @@ class ALTTPWorld(World):
|
||||
"Swamp Palace": {"Swamp Palace - Entrance", "Swamp Palace - Map Chest", "Swamp Palace - Big Chest",
|
||||
"Swamp Palace - Compass Chest", "Swamp Palace - Big Key Chest", "Swamp Palace - West Chest",
|
||||
"Swamp Palace - Flooded Room - Left", "Swamp Palace - Flooded Room - Right",
|
||||
"Swamp Palace - Waterfall Room", "Swamp Palace - Boss"},
|
||||
"Swamp Palace - Hookshot Pot Key", "Swamp Palace - Pot Row Pot Key",
|
||||
"Swamp Palace - Trench 1 Pot Key", "Swamp Palace - Trench 2 Pot Key",
|
||||
"Swamp Palace - Waterway Pot Key", "Swamp Palace - Waterfall Room", "Swamp Palace - Boss"},
|
||||
"Thieves' Town": {"Thieves' Town - Big Key Chest", "Thieves' Town - Map Chest", "Thieves' Town - Compass Chest",
|
||||
"Thieves' Town - Ambush Chest", "Thieves' Town - Attic", "Thieves' Town - Big Chest",
|
||||
"Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key",
|
||||
"Thieves' Town - Blind's Cell", "Thieves' Town - Boss"},
|
||||
"Skull Woods": {"Skull Woods - Map Chest", "Skull Woods - Pinball Room", "Skull Woods - Compass Chest",
|
||||
"Skull Woods - Pot Prison", "Skull Woods - Big Chest", "Skull Woods - Big Key Chest",
|
||||
"Skull Woods - Spike Corner Key Drop", "Skull Woods - West Lobby Pot Key",
|
||||
"Skull Woods - Bridge Room", "Skull Woods - Boss"},
|
||||
"Ice Palace": {"Ice Palace - Compass Chest", "Ice Palace - Freezor Chest", "Ice Palace - Big Chest",
|
||||
"Ice Palace - Freezor Chest", "Ice Palace - Big Chest", "Ice Palace - Iced T Room",
|
||||
"Ice Palace - Spike Room", "Ice Palace - Big Key Chest", "Ice Palace - Map Chest",
|
||||
"Ice Palace - Conveyor Key Drop", "Ice Palace - Hammer Block Key Drop",
|
||||
"Ice Palace - Jelly Key Drop", "Ice Palace - Many Pots Pot Key",
|
||||
"Ice Palace - Boss"},
|
||||
"Misery Mire": {"Misery Mire - Big Chest", "Misery Mire - Map Chest", "Misery Mire - Main Lobby",
|
||||
"Misery Mire - Bridge Chest", "Misery Mire - Spike Chest", "Misery Mire - Compass Chest",
|
||||
"Misery Mire - Big Key Chest", "Misery Mire - Boss"},
|
||||
"Misery Mire - Conveyor Crystal Key Drop", "Misery Mire - Fishbone Pot Key",
|
||||
"Misery Mire - Spikes Pot Key", "Misery Mire - Big Key Chest", "Misery Mire - Boss"},
|
||||
"Turtle Rock": {"Turtle Rock - Compass Chest", "Turtle Rock - Roller Room - Left",
|
||||
"Turtle Rock - Roller Room - Right", "Turtle Rock - Chain Chomps", "Turtle Rock - Big Key Chest",
|
||||
"Turtle Rock - Big Chest", "Turtle Rock - Crystaroller Room",
|
||||
"Turtle Rock - Eye Bridge - Bottom Left", "Turtle Rock - Eye Bridge - Bottom Right",
|
||||
"Turtle Rock - Eye Bridge - Top Left", "Turtle Rock - Eye Bridge - Top Right",
|
||||
"Turtle Rock - Pokey 1 Key Drop", "Turtle Rock - Pokey 2 Key Drop",
|
||||
"Turtle Rock - Boss"},
|
||||
"Ganons Tower": {"Ganons Tower - Bob's Torch", "Ganons Tower - Hope Room - Left",
|
||||
"Ganons Tower - Hope Room - Right", "Ganons Tower - Tile Room",
|
||||
@@ -203,10 +220,13 @@ class ALTTPWorld(World):
|
||||
"Ganons Tower - Randomizer Room - Bottom Left", "Ganons Tower - Randomizer Room - Bottom Right",
|
||||
"Ganons Tower - Bob's Chest", "Ganons Tower - Big Chest", "Ganons Tower - Big Key Room - Left",
|
||||
"Ganons Tower - Big Key Room - Right", "Ganons Tower - Big Key Chest",
|
||||
"Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right",
|
||||
"Ganons Tower - Pre-Moldorm Chest", "Ganons Tower - Validation Chest"},
|
||||
"Ganons Tower - Conveyor Cross Pot Key", "Ganons Tower - Conveyor Star Pits Pot Key",
|
||||
"Ganons Tower - Double Switch Pot Key", "Ganons Tower - Mini Helmasaur Room - Left",
|
||||
"Ganons Tower - Mini Helmasaur Room - Right", "Ganons Tower - Pre-Moldorm Chest",
|
||||
"Ganons Tower - Mini Helmasaur Key Drop", "Ganons Tower - Validation Chest"},
|
||||
"Ganons Tower Climb": {"Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right",
|
||||
"Ganons Tower - Pre-Moldorm Chest", "Ganons Tower - Validation Chest"},
|
||||
"Ganons Tower - Mini Helmasaur Key Drop", "Ganons Tower - Pre-Moldorm Chest",
|
||||
"Ganons Tower - Validation Chest"},
|
||||
}
|
||||
hint_blacklist = {"Triforce"}
|
||||
|
||||
@@ -216,6 +236,8 @@ class ALTTPWorld(World):
|
||||
required_client_version = (0, 4, 1)
|
||||
web = ALTTPWeb()
|
||||
|
||||
shops: list[Shop]
|
||||
|
||||
pedestal_credit_texts: typing.Dict[int, str] = \
|
||||
{data.item_code: data.pedestal_credit for data in item_table.values() if data.pedestal_credit}
|
||||
sickkid_credit_texts: typing.Dict[int, str] = \
|
||||
@@ -262,6 +284,10 @@ class ALTTPWorld(World):
|
||||
clock_mode: str = ""
|
||||
treasure_hunt_required: int = 0
|
||||
treasure_hunt_total: int = 0
|
||||
light_world_light_cone: bool = False
|
||||
dark_world_light_cone: bool = False
|
||||
save_and_quit_from_boss: bool = True
|
||||
rupoor_cost: int = 10
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.dungeon_local_item_names = set()
|
||||
@@ -278,6 +304,9 @@ class ALTTPWorld(World):
|
||||
self.fix_trock_exit = None
|
||||
self.required_medallions = ["Ether", "Quake"]
|
||||
self.escape_assist = []
|
||||
self.shops = []
|
||||
self.logical_heart_containers = 10
|
||||
self.logical_heart_pieces = 24
|
||||
super(ALTTPWorld, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
@@ -293,74 +322,62 @@ class ALTTPWorld(World):
|
||||
break
|
||||
|
||||
def generate_early(self):
|
||||
# write old options
|
||||
import dataclasses
|
||||
is_first = self.player == min(self.multiworld.get_game_players(self.game))
|
||||
|
||||
for field in dataclasses.fields(self.options_dataclass):
|
||||
if is_first:
|
||||
setattr(self.multiworld, field.name, {})
|
||||
getattr(self.multiworld, field.name)[self.player] = getattr(self.options, field.name)
|
||||
# end of old options re-establisher
|
||||
|
||||
player = self.player
|
||||
multiworld = self.multiworld
|
||||
|
||||
self.fix_trock_doors = (multiworld.entrance_shuffle[player] != 'vanilla'
|
||||
or multiworld.mode[player] == 'inverted')
|
||||
self.fix_skullwoods_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted',
|
||||
'dungeons_simple']
|
||||
self.fix_palaceofdarkness_exit = multiworld.entrance_shuffle[player] not in ['dungeons_simple', 'vanilla',
|
||||
'simple', 'restricted']
|
||||
self.fix_trock_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted',
|
||||
'dungeons_simple']
|
||||
self.fix_trock_doors = (self.options.entrance_shuffle != 'vanilla' or self.options.mode == 'inverted')
|
||||
self.fix_skullwoods_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']
|
||||
self.fix_palaceofdarkness_exit = self.options.entrance_shuffle not in ['dungeons_simple', 'vanilla', 'simple', 'restricted']
|
||||
self.fix_trock_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']
|
||||
|
||||
# fairy bottle fills
|
||||
bottle_options = [
|
||||
"Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)",
|
||||
"Bottle (Bee)", "Bottle (Good Bee)"
|
||||
]
|
||||
if multiworld.item_pool[player] not in ["hard", "expert"]:
|
||||
if self.options.item_pool not in ["hard", "expert"]:
|
||||
bottle_options.append("Bottle (Fairy)")
|
||||
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
|
||||
|
||||
if multiworld.mode[player] == 'standard':
|
||||
if multiworld.small_key_shuffle[player]:
|
||||
if (multiworld.small_key_shuffle[player] not in
|
||||
(small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons,
|
||||
small_key_shuffle.option_start_with)):
|
||||
if self.options.mode == 'standard':
|
||||
if self.options.small_key_shuffle:
|
||||
if (self.options.small_key_shuffle not in
|
||||
(small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons,
|
||||
small_key_shuffle.option_start_with)):
|
||||
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
|
||||
self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)")
|
||||
self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)")
|
||||
if multiworld.big_key_shuffle[player]:
|
||||
self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)")
|
||||
self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)")
|
||||
self.options.local_items.value.add("Small Key (Hyrule Castle)")
|
||||
self.options.non_local_items.value.discard("Small Key (Hyrule Castle)")
|
||||
if self.options.big_key_shuffle:
|
||||
self.options.local_items.value.add("Big Key (Hyrule Castle)")
|
||||
self.options.non_local_items.value.discard("Big Key (Hyrule Castle)")
|
||||
|
||||
# system for sharing ER layouts
|
||||
self.er_seed = str(multiworld.random.randint(0, 2 ** 64))
|
||||
|
||||
if multiworld.entrance_shuffle[player] != "vanilla" and multiworld.entrance_shuffle_seed[player] != "random":
|
||||
shuffle = multiworld.entrance_shuffle[player].current_key
|
||||
if self.options.entrance_shuffle != "vanilla" and self.options.entrance_shuffle_seed != "random":
|
||||
shuffle = self.options.entrance_shuffle.current_key
|
||||
if shuffle == "vanilla":
|
||||
self.er_seed = "vanilla"
|
||||
elif (not multiworld.entrance_shuffle_seed[player].value.isdigit()) or multiworld.is_race:
|
||||
elif (not self.options.entrance_shuffle_seed.value.isdigit()) or multiworld.is_race:
|
||||
self.er_seed = get_same_seed(multiworld, (
|
||||
shuffle, multiworld.entrance_shuffle_seed[player].value, multiworld.retro_caves[player], multiworld.mode[player],
|
||||
multiworld.glitches_required[player]))
|
||||
shuffle, self.options.entrance_shuffle_seed.value,
|
||||
self.options.retro_caves,
|
||||
self.options.mode,
|
||||
self.options.glitches_required
|
||||
))
|
||||
else: # not a race or group seed, use set seed as is.
|
||||
self.er_seed = int(multiworld.entrance_shuffle_seed[player].value)
|
||||
elif multiworld.entrance_shuffle[player] == "vanilla":
|
||||
self.er_seed = int(self.options.entrance_shuffle_seed.value)
|
||||
elif self.options.entrance_shuffle == "vanilla":
|
||||
self.er_seed = "vanilla"
|
||||
|
||||
for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]:
|
||||
option = getattr(multiworld, dungeon_item)[player]
|
||||
option = getattr(self.options, dungeon_item)
|
||||
if option == "own_world":
|
||||
multiworld.local_items[player].value |= self.item_name_groups[option.item_name_group]
|
||||
self.options.local_items.value |= self.item_name_groups[option.item_name_group]
|
||||
elif option == "different_world":
|
||||
multiworld.non_local_items[player].value |= self.item_name_groups[option.item_name_group]
|
||||
if multiworld.mode[player] == "standard":
|
||||
multiworld.non_local_items[player].value -= {"Small Key (Hyrule Castle)"}
|
||||
self.options.non_local_items.value |= self.item_name_groups[option.item_name_group]
|
||||
if self.options.mode == "standard":
|
||||
self.options.non_local_items.value -= {"Small Key (Hyrule Castle)"}
|
||||
elif option.in_dungeon:
|
||||
self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group]
|
||||
if option == "original_dungeon":
|
||||
@@ -368,15 +385,17 @@ class ALTTPWorld(World):
|
||||
else:
|
||||
self.options.local_items.value |= self.dungeon_local_item_names
|
||||
|
||||
self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key]
|
||||
self.difficulty_requirements = difficulties[self.options.item_pool.current_key]
|
||||
self.logical_heart_pieces = self.difficulty_requirements.heart_piece_limit
|
||||
self.logical_heart_containers = self.difficulty_requirements.boss_heart_container_limit
|
||||
|
||||
# enforce pre-defined local items.
|
||||
if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
|
||||
multiworld.local_items[player].value.add('Triforce Piece')
|
||||
if self.options.goal in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
|
||||
self.options.local_items.value.add('Triforce Piece')
|
||||
|
||||
# Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too).
|
||||
multiworld.non_local_items[player].value -= item_name_groups['Pendants']
|
||||
multiworld.non_local_items[player].value -= item_name_groups['Crystals']
|
||||
self.options.non_local_items.value -= item_name_groups['Pendants']
|
||||
self.options.non_local_items.value -= item_name_groups['Crystals']
|
||||
|
||||
create_dungeons = create_dungeons
|
||||
|
||||
@@ -384,15 +403,15 @@ class ALTTPWorld(World):
|
||||
player = self.player
|
||||
multiworld = self.multiworld
|
||||
|
||||
if multiworld.mode[player] != 'inverted':
|
||||
if self.options.mode != 'inverted':
|
||||
create_regions(multiworld, player)
|
||||
else:
|
||||
create_inverted_regions(multiworld, player)
|
||||
create_shops(multiworld, player)
|
||||
self.create_dungeons()
|
||||
|
||||
if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and
|
||||
multiworld.entrance_shuffle[player] in [
|
||||
if (self.options.glitches_required not in ["no_glitches", "minor_glitches"] and
|
||||
self.options.entrance_shuffle in [
|
||||
"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]):
|
||||
self.fix_fake_world = False
|
||||
|
||||
@@ -400,7 +419,7 @@ class ALTTPWorld(World):
|
||||
old_random = multiworld.random
|
||||
multiworld.random = random.Random(self.er_seed)
|
||||
|
||||
if multiworld.mode[player] != 'inverted':
|
||||
if self.options.mode != 'inverted':
|
||||
link_entrances(multiworld, player)
|
||||
mark_light_world_regions(multiworld, player)
|
||||
else:
|
||||
@@ -485,8 +504,9 @@ class ALTTPWorld(World):
|
||||
if state.has('Silver Bow', item.player):
|
||||
return
|
||||
elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2
|
||||
or self.multiworld.glitches_required[self.player] == 'no_glitches'
|
||||
or self.multiworld.swordless[self.player]): # modes where silver bow is always required for ganon
|
||||
or self.options.glitches_required == 'no_glitches'
|
||||
or self.options.swordless):
|
||||
# modes where silver bow is always required for ganon
|
||||
return 'Silver Bow'
|
||||
elif self.difficulty_requirements.progressive_bow_limit >= 1:
|
||||
return 'Bow'
|
||||
@@ -496,20 +516,21 @@ class ALTTPWorld(World):
|
||||
def pre_fill(self):
|
||||
from Fill import fill_restrictive, FillError
|
||||
attempts = 5
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
all_state = world.get_all_state(use_cache=True)
|
||||
all_state = self.multiworld.get_all_state(perform_sweep=False)
|
||||
crystals = [self.create_item(name) for name in ['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']]
|
||||
crystal_locations = [world.get_location('Turtle Rock - Prize', player),
|
||||
world.get_location('Eastern Palace - Prize', player),
|
||||
world.get_location('Desert Palace - Prize', player),
|
||||
world.get_location('Tower of Hera - Prize', player),
|
||||
world.get_location('Palace of Darkness - Prize', player),
|
||||
world.get_location('Thieves\' Town - Prize', player),
|
||||
world.get_location('Skull Woods - Prize', player),
|
||||
world.get_location('Swamp Palace - Prize', player),
|
||||
world.get_location('Ice Palace - Prize', player),
|
||||
world.get_location('Misery Mire - Prize', player)]
|
||||
for crystal in crystals:
|
||||
all_state.remove(crystal)
|
||||
all_state.sweep_for_advancements()
|
||||
crystal_locations = [self.get_location('Turtle Rock - Prize'),
|
||||
self.get_location('Eastern Palace - Prize'),
|
||||
self.get_location('Desert Palace - Prize'),
|
||||
self.get_location('Tower of Hera - Prize'),
|
||||
self.get_location('Palace of Darkness - Prize'),
|
||||
self.get_location('Thieves\' Town - Prize'),
|
||||
self.get_location('Skull Woods - Prize'),
|
||||
self.get_location('Swamp Palace - Prize'),
|
||||
self.get_location('Ice Palace - Prize'),
|
||||
self.get_location('Misery Mire - Prize')]
|
||||
placed_prizes = {loc.item.name for loc in crystal_locations if loc.item}
|
||||
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
|
||||
empty_crystal_locations = [loc for loc in crystal_locations if not loc.item]
|
||||
@@ -517,8 +538,8 @@ class ALTTPWorld(World):
|
||||
try:
|
||||
prizepool = unplaced_prizes.copy()
|
||||
prize_locs = empty_crystal_locations.copy()
|
||||
world.random.shuffle(prize_locs)
|
||||
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True,
|
||||
self.multiworld.random.shuffle(prize_locs)
|
||||
fill_restrictive(self.multiworld, all_state, prize_locs, prizepool, True, lock=True,
|
||||
name="LttP Dungeon Prizes")
|
||||
except FillError as e:
|
||||
lttp_logger.exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
|
||||
@@ -529,10 +550,10 @@ class ALTTPWorld(World):
|
||||
break
|
||||
else:
|
||||
raise FillError('Unable to place dungeon prizes')
|
||||
if world.mode[player] == 'standard' and world.small_key_shuffle[player] \
|
||||
and world.small_key_shuffle[player] != small_key_shuffle.option_universal and \
|
||||
world.small_key_shuffle[player] != small_key_shuffle.option_own_dungeons:
|
||||
world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1
|
||||
if self.options.mode == 'standard' and self.options.small_key_shuffle \
|
||||
and self.options.small_key_shuffle != small_key_shuffle.option_universal and \
|
||||
self.options.small_key_shuffle != small_key_shuffle.option_own_dungeons:
|
||||
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
|
||||
|
||||
@classmethod
|
||||
def stage_pre_fill(cls, world):
|
||||
@@ -572,27 +593,27 @@ class ALTTPWorld(World):
|
||||
multiworld.spoiler.hashes[player] = get_hash_string(rom.hash)
|
||||
|
||||
palettes_options = {
|
||||
'dungeon': multiworld.uw_palettes[player],
|
||||
'overworld': multiworld.ow_palettes[player],
|
||||
'hud': multiworld.hud_palettes[player],
|
||||
'sword': multiworld.sword_palettes[player],
|
||||
'shield': multiworld.shield_palettes[player],
|
||||
'dungeon': self.options.uw_palettes,
|
||||
'overworld': self.options.ow_palettes,
|
||||
'hud': self.options.hud_palettes,
|
||||
'sword': self.options.sword_palettes,
|
||||
'shield': self.options.shield_palettes,
|
||||
# 'link': world.link_palettes[player]
|
||||
}
|
||||
palettes_options = {key: option.current_key for key, option in palettes_options.items()}
|
||||
|
||||
apply_rom_settings(rom, multiworld.heartbeep[player].current_key,
|
||||
multiworld.heartcolor[player].current_key,
|
||||
multiworld.quickswap[player],
|
||||
multiworld.menuspeed[player].current_key,
|
||||
multiworld.music[player],
|
||||
apply_rom_settings(rom, self.options.heartbeep.current_key,
|
||||
self.options.heartcolor.current_key,
|
||||
self.options.quickswap,
|
||||
self.options.menuspeed.current_key,
|
||||
self.options.music,
|
||||
multiworld.sprite[player],
|
||||
None,
|
||||
palettes_options, multiworld, player, True,
|
||||
reduceflashing=multiworld.reduceflashing[player] or multiworld.is_race,
|
||||
triforcehud=multiworld.triforcehud[player].current_key,
|
||||
deathlink=multiworld.death_link[player],
|
||||
allowcollect=multiworld.allow_collect[player])
|
||||
reduceflashing=self.options.reduceflashing or multiworld.is_race,
|
||||
triforcehud=self.options.triforcehud.current_key,
|
||||
deathlink=self.options.death_link,
|
||||
allowcollect=self.options.allow_collect)
|
||||
|
||||
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
|
||||
rom.write_to_file(rompath)
|
||||
@@ -609,7 +630,7 @@ class ALTTPWorld(World):
|
||||
@classmethod
|
||||
def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]):
|
||||
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
|
||||
world.entrance_shuffle[player] != "vanilla" or world.retro_caves[player]}
|
||||
world.worlds[player].options.entrance_shuffle != "vanilla" or world.worlds[player].options.retro_caves}
|
||||
|
||||
for region in world.regions:
|
||||
if region.player in er_hint_data and region.locations:
|
||||
@@ -725,7 +746,7 @@ class ALTTPWorld(World):
|
||||
f" {self.pyramid_fairy_bottle_fill}")
|
||||
spoiler_handle.write(f"\nWaterfall Fairy ({player_name}):"
|
||||
f" {self.waterfall_fairy_bottle_fill}")
|
||||
if self.multiworld.boss_shuffle[self.player] != "none":
|
||||
if self.options.boss_shuffle != "none":
|
||||
def create_boss_map() -> typing.Dict:
|
||||
boss_map = {
|
||||
"Eastern Palace": self.dungeons["Eastern Palace"].boss.name,
|
||||
@@ -742,7 +763,7 @@ class ALTTPWorld(World):
|
||||
"Ganons Tower": "Agahnim 2",
|
||||
"Ganon": "Ganon"
|
||||
}
|
||||
if self.multiworld.mode[self.player] != 'inverted':
|
||||
if self.options.mode != 'inverted':
|
||||
boss_map.update({
|
||||
"Ganons Tower Basement":
|
||||
self.dungeons["Ganons Tower"].bosses["bottom"].name,
|
||||
@@ -790,7 +811,7 @@ class ALTTPWorld(World):
|
||||
|
||||
return shop_data
|
||||
|
||||
if shop_info := [build_shop_info(shop) for shop in self.multiworld.shops if shop.custom]:
|
||||
if shop_info := [build_shop_info(shop) for shop in self.shops if shop.custom]:
|
||||
spoiler_handle.write('\n\nShops:\n\n')
|
||||
for shop_data in shop_info:
|
||||
spoiler_handle.write("{} [{}]\n {}\n".format(shop_data['location'], shop_data['type'], "\n ".join(
|
||||
@@ -802,12 +823,15 @@ class ALTTPWorld(World):
|
||||
return GetBeemizerItem(self.multiworld, self.player, item)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
res = []
|
||||
res = [self.create_item(name) for name in ('Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1',
|
||||
'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5',
|
||||
'Crystal 6')]
|
||||
if self.dungeon_local_item_names:
|
||||
for dungeon in self.dungeons.values():
|
||||
for item in dungeon.all_items:
|
||||
if item.name in self.dungeon_local_item_names:
|
||||
res.append(item)
|
||||
|
||||
return res
|
||||
|
||||
def fill_slot_data(self):
|
||||
@@ -827,7 +851,7 @@ class ALTTPWorld(World):
|
||||
"triforce_pieces_available", "triforce_pieces_extra",
|
||||
]
|
||||
|
||||
slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options}
|
||||
slot_data = {option_name: getattr(self.options, option_name).value for option_name in slot_options}
|
||||
|
||||
slot_data.update({
|
||||
'mm_medalion': self.required_medallions[0],
|
||||
@@ -848,8 +872,8 @@ def get_same_seed(world, seed_def: tuple) -> str:
|
||||
|
||||
class ALttPLogic(LogicMixin):
|
||||
def _lttp_has_key(self, item, player, count: int = 1):
|
||||
if self.multiworld.glitches_required[player] == 'no_logic':
|
||||
if self.multiworld.worlds[player].options.glitches_required == 'no_logic':
|
||||
return True
|
||||
if self.multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
||||
if self.multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal:
|
||||
return can_buy_unlimited(self, 'Small Key (Universal)', player)
|
||||
return self.prog_items[player][item] >= count
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"game": "A Link to the Past",
|
||||
"minimum_ap_version": "0.6.6",
|
||||
"world_version": "5.1.0",
|
||||
"authors": ["Berserker"]
|
||||
}
|
||||
@@ -88,9 +88,8 @@ You only have to do these steps once.
|
||||
1. Enter the RetroArch main menu screen.
|
||||
2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON.
|
||||
3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default
|
||||
Network Command Port at 55355.
|
||||
|
||||

|
||||
Network Command Port at 55355. \
|
||||

|
||||
4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury
|
||||
Performance)".
|
||||
|
||||
|
||||
@@ -1,224 +1,122 @@
|
||||
# Guía de instalación para A Link to the Past Randomizer Multiworld
|
||||
|
||||
<div id="tutorial-video-container">
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/mJKEHaiyR_Y" frameborder="0"
|
||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
## Software requerido
|
||||
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
- [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Incluido en Multiworld Utilities)
|
||||
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES
|
||||
- Un emulador capaz de ejecutar scripts Lua
|
||||
([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
|
||||
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||
- [SNI](https://github.com/alttpo/sni/releases). Esto está incluido automáticamente en la instalación de Archipelago.
|
||||
- SNI no es compatible con (Q)Usb2Snes.
|
||||
- Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES, por ejemplo:
|
||||
- Un emulador capaz de conectarse a SNI
|
||||
([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases),
|
||||
[BSNES-plus](https://github.com/black-sliver/bsnes-plus),
|
||||
[BizHawk](https://tasvideos.org/BizHawk), o
|
||||
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo). O,
|
||||
- Un flashcart SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), o otro hardware compatible
|
||||
[RetroArch](https://retroarch.com?page=platforms) 1.10.1 o más nuevo).
|
||||
- Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), u otro hardware compatible. **nota:
|
||||
Las SNES minis modificadas no tienen soporte de SNI. Algunos usuarios dicen haber tenido éxito con Qusb2Snes para esta consola,
|
||||
pero no tiene soporte.**
|
||||
- Tu archivo ROM japones v1.0, probablemente se llame `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc`
|
||||
|
||||
## Procedimiento de instalación
|
||||
|
||||
### Instalación en Windows
|
||||
|
||||
1. Descarga e instala MultiWorld Utilities desde el enlace anterior, asegurando que instalamos la versión más reciente.
|
||||
**El archivo esta localizado en la sección "assets" en la parte inferior de la información de versión**. Si tu
|
||||
intención es jugar la versión normal de multiworld, necesitarás el archivo `Setup.Archipelago.exe`
|
||||
- Si estas interesado en jugar la variante que aleatoriza las puertas internas de las mazmorras, necesitaras bajar '
|
||||
Setup.BerserkerMultiWorld.Doors.exe'
|
||||
- Durante el proceso de instalación, se te pedirá donde esta situado tu archivo ROM japonés v1.0. Si ya habías
|
||||
instalado este software con anterioridad y simplemente estas actualizando, no se te pedirá la localización del
|
||||
archivo una segunda vez.
|
||||
- Puede ser que el programa pida la instalación de Microsoft Visual C++. Si ya lo tienes en tu ordenador (
|
||||
posiblemente por que un juego de Steam ya lo haya instalado), el instalador no te pedirá su instalación.
|
||||
|
||||
2. Si estas usando un emulador, deberías asignar la versión capaz de ejecutar scripts Lua como programa por defecto para
|
||||
lanzar ficheros de ROM de SNES.
|
||||
1. Extrae tu emulador al escritorio, o cualquier sitio que después recuerdes.
|
||||
2. Haz click derecho en un fichero de ROM (ha de tener la extensión sfc) y selecciona **Abrir con...**
|
||||
3. Marca la opción **Usar siempre esta aplicación para abrir los archivos .sfc**
|
||||
4. Baja hasta el final de la lista y haz click en la opción **Buscar otra aplicación en el equipo** (Si usas Windows
|
||||
10 es posible que debas hacer click en **Más aplicaciones**)
|
||||
5. Busca el archivo .exe de tu emulador y haz click en **Abrir**. Este archivo debe estar en el directorio donde
|
||||
extrajiste en el paso 1.
|
||||
|
||||
### Instalación en Macintosh
|
||||
|
||||
- ¡Necesitamos voluntarios para rellenar esta seccion! Contactad con **Farrak Kilhn** (en inglés) en Discord si queréis
|
||||
ayudar.
|
||||
|
||||
## Configurar tu archivo YAML
|
||||
|
||||
### Que es un archivo YAML y por qué necesito uno?
|
||||
|
||||
Tu archivo YAML contiene un conjunto de opciones de configuración que proveen al generador con información sobre como
|
||||
debe generar tu juego. Cada jugador en una partida de multiworld proveerá su propio fichero YAML. Esta configuración
|
||||
permite que cada jugador disfrute de una experiencia personalizada a su gusto, y cada jugador dentro de la misma partida
|
||||
de multiworld puede tener diferentes opciones.
|
||||
|
||||
### Donde puedo obtener un fichero YAML?
|
||||
|
||||
La página "[Generate Game](/games/A%20Link%20to%20the%20Past/player-options)" en el sitio web te permite configurar tu
|
||||
configuración personal y descargar un fichero "YAML".
|
||||
|
||||
### Configuración YAML avanzada
|
||||
|
||||
Una version mas avanzada del fichero Yaml puede ser creada usando la pagina
|
||||
["Weighted settings"](/games/A Link to the Past/weighted-options),
|
||||
la cual te permite tener almacenadas hasta 3 preajustes. La pagina "Weighted Settings" tiene muchas opciones
|
||||
representadas con controles deslizantes. Esto permite elegir cuan probable los valores de una categoría pueden ser
|
||||
elegidos sobre otros de la misma.
|
||||
|
||||
Por ejemplo, imagina que el generador crea un cubo llamado "map_shuffle", y pone trozos de papel doblado en él por cada
|
||||
sub-opción. Ademas imaginemos que tu valor elegido para "on" es 20 y el elegido para "off" es 40.
|
||||
|
||||
Por tanto, en este ejemplo, habrán 60 trozos de papel. 20 para "on" y 40 para "off". Cuando el generador esta decidiendo
|
||||
si activar o no "map shuffle" para tu partida, meterá la mano en el cubo y sacara un trozo de papel al azar. En este
|
||||
ejemplo, es mucho mas probable (2 de cada 3 veces (40/60)) que "map shuffle" esté desactivado.
|
||||
|
||||
Si quieres que una opción no pueda ser escogida, simplemente asigna el valor 0 a dicha opción. Recuerda que cada opción
|
||||
debe tener al menos un valor mayor que cero, si no la generación fallará.
|
||||
|
||||
### Verificando tu archivo YAML
|
||||
|
||||
Si quieres validar que tu fichero YAML para asegurarte que funciona correctamente, puedes hacerlo en la pagina
|
||||
[YAML Validator](/check).
|
||||
|
||||
## Generar una partida para un jugador
|
||||
|
||||
1. Navega a [la pagina Generate game](/games/A%20Link%20to%20the%20Past/player-options), configura tus opciones, haz
|
||||
click en el boton "Generate game".
|
||||
2. Se te redigirá a una pagina "Seed Info", donde puedes descargar tu archivo de parche.
|
||||
3. Haz doble click en tu fichero de parche, y el emulador debería ejecutar tu juego automáticamente. Como el Cliente no
|
||||
es necesario para partidas de un jugador, puedes cerrarlo junto a la pagina web (que tiene como titulo "Multiworld
|
||||
WebUI") que se ha abierto automáticamente.
|
||||
|
||||
## Unirse a una partida MultiWorld
|
||||
1. Descarga e instala [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
||||
**El archivo del instalador se encuentra en la sección de assets al final de la información de version**.
|
||||
2. La primera vez que realices una generación local o parchees tu juego, se te pedirá que ubiques tu archivo ROM base.
|
||||
Este es tu archivo ROM de Link to the Past japonés. Esto sólo debe hacerse una vez.
|
||||
|
||||
4. Si estás usando un emulador, deberías de asignar tu emulador con compatibilidad con Lua como el programa por defecto para abrir archivos
|
||||
ROM.
|
||||
1. Extrae la carpeta de tu emulador al Escritorio, o algún otro sitio que vayas a recordar.
|
||||
2. Haz click derecho en un archivo ROM y selecciona **Abrir con...**
|
||||
3. Marca la casilla junto a **Usar siempre este programa para abrir archivos .sfc**
|
||||
4. Baja al final de la lista y haz click en el texto gris **Buscar otro programa en este PC**
|
||||
5. Busca el archivo `.exe` de tu emulador y haz click en **Abrir**. Este archivo debería de encontrarse dentro de la carpeta que
|
||||
extrajiste en el paso uno.
|
||||
|
||||
### Obtener el fichero de parche y crea tu ROM
|
||||
|
||||
Cuando te unes a una partida multiworld, debes proveer tu fichero YAML a quien sea el creador de la partida. Una vez
|
||||
Cuando te unas a una partida multiworld, se te pedirá enviarle tu archivo de configuración a quien quiera que esté creando. Una vez eso
|
||||
este hecho, el creador te devolverá un enlace para descargar el parche o un fichero zip conteniendo todos los ficheros
|
||||
de parche de la partida Tu fichero de parche debe tener la extensión `.aplttp`.
|
||||
de parche de la partida. Tu fichero de parche debe de tener la extensión `.aplttp`.
|
||||
|
||||
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y haz doble click. Esto debería ejecutar
|
||||
automáticamente el cliente, y ademas creara la rom en el mismo directorio donde este el fichero de parche.
|
||||
Pon tu fichero de parche en el escritorio o en algún sitio conveniente, y hazle doble click. Esto debería ejecutar
|
||||
automáticamente el cliente, y además creará la rom en el mismo directorio donde este el fichero de parche.
|
||||
|
||||
### Conectar al cliente
|
||||
|
||||
#### Con emulador
|
||||
|
||||
Cuando el cliente se lance automáticamente, QUsb2Snes debería haberse ejecutado también. Si es la primera vez que lo
|
||||
ejecutas, puedes ser que el firewall de Windows te pregunte si le permites la comunicación.
|
||||
Cuando el cliente se lance automáticamente, SNI debería de ejecutarse en segundo plano. Si es la
|
||||
primera vez que se ejecuta, tal vez se te pida permitir que se comunique a través del firewall de Windows
|
||||
|
||||
#### snes9x-nwa
|
||||
|
||||
1. Haz click en el menu Network y marca 'Enable Emu Network Control
|
||||
2. Carga tu archivo ROM si no lo habías hecho antes
|
||||
|
||||
##### snes9x-rr
|
||||
|
||||
1. Carga tu fichero de ROM, si no lo has hecho ya
|
||||
1. Carga tu fichero ROM, si no lo has hecho ya
|
||||
2. Abre el menu "File" y situa el raton en **Lua Scripting**
|
||||
3. Haz click en **New Lua Script Window...**
|
||||
4. En la nueva ventana, haz click en **Browse...**
|
||||
5. Navega hacia el directorio donde este situado snes9x-rr, entra en el directorio `lua`, y
|
||||
escoge `multibridge.lua`
|
||||
6. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
|
||||
nombre en la esquina superior izquierda.
|
||||
5. Selecciona el archivo lua conector incluido con tu cliente
|
||||
- Busca en la carpeta de Archipelago `/SNI/lua/`.
|
||||
6. Si ves un error mientras carga el script que dice `socket.dll missing` o algo similar, ve a la carpeta de
|
||||
el lua que estas usando en tu gestor de archivos y copia el `socket.dll` a la raíz de tu instalación de snes9x.
|
||||
|
||||
##### BNES-Plus
|
||||
|
||||
1. Cargue su archivo ROM si aún no se ha cargado.
|
||||
2. El emulador debería conectarse automáticamente mientras SNI se está ejecutando.
|
||||
|
||||
##### BizHawk
|
||||
|
||||
1. Asegurate que se ha cargado el nucleo BSNES. Debes hacer esto en el menu Tools y siguiento estas opciones:
|
||||
`Config --> Cores --> SNES --> BSNES`
|
||||
Una vez cambiado el nucleo cargado, BizHawk ha de ser reiniciado.
|
||||
1. Asegurate que se ha cargado el núcleo BSNES. Se hace en la barra de menú principal, bajo:
|
||||
- (≤ 2.8) `Config` 〉 `Cores` 〉 `SNES` 〉 `BSNES`
|
||||
- (≥ 2.9) `Config` 〉 `Preferred Cores` 〉 `SNES` 〉 `BSNESv115+`
|
||||
2. Carga tu fichero de ROM, si no lo has hecho ya.
|
||||
3. Haz click en el menu Tools y en la opción **Lua Console**
|
||||
4. Haz click en el botón para abrir un nuevo script Lua.
|
||||
5. Navega al directorio de instalación de MultiWorld Utilities, y en los siguiente directorios:
|
||||
`QUsb2Snes/Qusb2Snes/LuaBridge`
|
||||
6. Selecciona `luabridge.lua` y haz click en Abrir.
|
||||
7. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo
|
||||
nombre en la esquina superior izquierda.
|
||||
Si has cambiado tu preferencia de núcleo tras haber cargado la ROM, no te olvides de volverlo a cargar (atajo por defecto: Ctrl+R).
|
||||
3. Arrastra el archivo `Connector.lua` que has descargado a la ventana principal de EmuHawk.
|
||||
- Busca en la carpeta de Archipelago `/SNI/lua/`.
|
||||
- También podrías abrir la consola de Lua manualmente, hacer click en `Script` 〉 `Open Script`, e ir a `Connector.lua`
|
||||
con el selector de archivos.
|
||||
|
||||
##### RetroArch 1.10.1 o más nuevo
|
||||
|
||||
Sólo hay que segiur estos pasos una vez.
|
||||
Sólo hay que seguir estos pasos una vez.
|
||||
|
||||
1. Comienza en la pantalla del menú principal de RetroArch.
|
||||
2. Ve a Ajustes --> Interfaz de usario. Configura "Mostrar ajustes avanzados" en ON.
|
||||
3. Ve a Ajustes --> Red. Configura "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 (el
|
||||
default) el Puerto de comandos de red.
|
||||
|
||||

|
||||
3. Ve a Ajustes --> Red. Pon "Comandos de red" en ON. (Se encuentra bajo Request Device 16.) Deja en 55355 el valor por defecto,
|
||||
el Puerto de comandos de red. \
|
||||

|
||||
4. Ve a Menú principal --> Actualizador en línea --> Descargador de núcleos. Desplázate y selecciona "Nintendo - SNES /
|
||||
SFC (bsnes-mercury Performance)".
|
||||
|
||||
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los sólos núcleos que permiten
|
||||
Cuando cargas un ROM, asegúrate de seleccionar un núcleo **bsnes-mercury**. Estos son los únicos núcleos que permiten
|
||||
que herramientas externas lean datos del ROM.
|
||||
|
||||
#### Con Hardware
|
||||
|
||||
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, hazlo ahora. Los
|
||||
Esta guía asume que ya has descargado el firmware correcto para tu dispositivo. Si no lo has hecho ya, por favor hazlo ahora. Los
|
||||
usuarios de SD2SNES y FXPak Pro pueden descargar el firmware apropiado
|
||||
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Los usuarios de otros dispositivos pueden encontrar información
|
||||
[aqui](https://github.com/RedGuyyyy/sd2snes/releases). Puede que los usuarios de otros dispositivos encuentren informacion útil
|
||||
[en esta página](http://usb2snes.com/#supported-platforms).
|
||||
|
||||
1. Cierra tu emulador, el cual debe haberse autoejecutado.
|
||||
2. Cierra QUsb2Snes, el cual fue ejecutado junto al cliente.
|
||||
3. Ejecuta la version correcta de QUsb2Snes (v0.7.16).
|
||||
4. Enciende tu dispositivo y carga la ROM.
|
||||
5. Observa en el cliente que ahora muestra "SNES Device: Connected", y aparece el nombre del dispositivo.
|
||||
2. Enciende tu dispositivo y carga la ROM.
|
||||
|
||||
### Conecta al MultiServer
|
||||
### Conecta al Servidor Archipelago
|
||||
|
||||
El fichero de parche que ha lanzado el cliente debe haberte conectado automaticamente al MultiServer. Hay algunas
|
||||
razonas por las que esto puede que no pase, incluyendo que el juego este hospedado en el sitio web pero se genero en
|
||||
algún otro sitio. Si el cliente muestra "Server Status: Not Connected", preguntale al creador de la partida la dirección
|
||||
del servidor, copiala en el campo "Server" y presiona Enter.
|
||||
El fichero de parche que ha lanzado el cliente debería de haberte conectado automaticamente al MultiServer. Sin embargo hay algunas
|
||||
razones por las que puede que esto no suceda, como que la partida este hospedada en la página web pero generada en otra parte. Si la
|
||||
ventana del cliente muestra "Server Status: Not Connected", simplemente preguntale al creador de la partida la dirección
|
||||
del servidor, cópiala en el campo "Server" y presiona Enter.
|
||||
|
||||
El cliente intentara conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" en algún momento.
|
||||
Si el cliente no se conecta al cabo de un rato, puede ser que necesites refrescar la pagina web.
|
||||
El cliente intentará conectarse a esta nueva dirección, y debería mostrar "Server Status: Connected" momentáneamente.
|
||||
|
||||
### Jugando
|
||||
### Jugar al juego
|
||||
|
||||
Cuando ambos SNES Device and Server aparezcan como "connected", estas listo para empezar a jugar. Felicidades por unirte
|
||||
satisfactoriamente a una partida de multiworld!
|
||||
|
||||
## Hospedando una partida de multiworld
|
||||
|
||||
La manera recomendad para hospedar una partida es usar el servicio proveído en
|
||||
[el sitio web](/generate). El proceso es relativamente sencillo:
|
||||
|
||||
1. Recolecta los ficheros YAML de todos los jugadores que participen.
|
||||
2. Crea un fichero ZIP conteniendo esos ficheros.
|
||||
3. Carga el fichero zip en el sitio web enlazado anteriormente.
|
||||
4. Espera a que la seed sea generada.
|
||||
5. Cuando esto acabe, se te redigirá a una pagina titulada "Seed Info".
|
||||
6. Haz click en "Create New Room". Esto te llevara a la pagina del servidor. Pasa el enlace a esta pagina a los
|
||||
jugadores para que puedan descargar los ficheros de parche de ahi.
|
||||
**Nota:** Los ficheros de parche de esta pagina permiten a los jugadores conectarse al servidor automaticamente,
|
||||
mientras que los de la pagina "Seed info" no.
|
||||
7. Hay un enlace a un MultiWorld Tracker en la parte superior de la pagina de la sala. Deberías pasar también este
|
||||
enlace a los jugadores para que puedan ver el progreso de la partida. A los observadores también se les puede pasar
|
||||
este enlace.
|
||||
8. Una vez todos los jugadores se han unido, podeis empezar a jugar.
|
||||
|
||||
## Auto-Tracking
|
||||
|
||||
Si deseas usar auto-tracking para tu partida, varios programas ofrecen esta funcionalidad.
|
||||
El programa recomentdado actualmente es:
|
||||
[OpenTracker](https://github.com/trippsc2/OpenTracker/releases).
|
||||
|
||||
### Instalación
|
||||
|
||||
1. Descarga el fichero de instalacion apropiado para tu ordenador (Usuarios de windows quieren el fichero ".msi").
|
||||
2. Durante el proceso de insatalación, puede que se te pida instalar Microsoft Visual Studio Build Tools. Un enlace este
|
||||
programa se muestra durante la proceso, y debe ser ejecutado manualmente.
|
||||
|
||||
### Activar auto-tracking
|
||||
|
||||
1. Con OpenTracker ejecutado, haz click en el menu Tracking en la parte superior de la ventana, y elige **
|
||||
AutoTracker...**
|
||||
2. Click the **Get Devices** button
|
||||
3. Selecciona tu "SNES device" de la lista
|
||||
4. Si quieres que las llaves y los objetos de mazmorra tambien sean marcados, activa la caja con nombre **Race Illegal
|
||||
Tracking**
|
||||
5. Haz click en el boton **Start Autotracking**
|
||||
6. Cierra la ventana AutoTracker, ya que deja de ser necesaria
|
||||
Cuando el cliente muestre tanto el dispositivo SNES como el servidor como conectados, estas listo para empezar a jugar. Felicidades por
|
||||
haberte unido a una partida multiworld con exito! Puedes ejecutar varios comandos en tu cliente. Para mas informacion
|
||||
acerca de estos comando puedes usar `/help` para comandos locales del cliente y `!help` para comandos de servidor.
|
||||
|
||||
@@ -89,9 +89,8 @@ Vous n'avez qu'à faire ces étapes qu'une fois.
|
||||
1. Entrez dans le menu principal RetroArch
|
||||
2. Allez dans Réglages --> Interface utilisateur. Mettez "Afficher les réglages avancés" sur ON.
|
||||
3. Allez dans Réglages --> Réseau. Mettez "Commandes Réseau" sur ON. (trouvé sous Request Device 16.) Laissez le
|
||||
Port des commandes réseau à 555355.
|
||||
|
||||

|
||||
Port des commandes réseau à 555355. \
|
||||

|
||||
4. Allez dans Menu Principal --> Mise à jour en ligne --> Téléchargement de cœurs. Descendez jusqu'a"Nintendo - SNES / SFC (bsnes-mercury Performance)" et
|
||||
sélectionnez le.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,2 +1,2 @@
|
||||
maseya-z3pr>=1.0.0rc1
|
||||
xxtea>=3.0.0
|
||||
maseya-z3pr==1.0.0rc1
|
||||
xxtea==3.7.0
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
from worlds import AutoWorldRegister
|
||||
|
||||
|
||||
class LTTPTestBase(unittest.TestCase):
|
||||
def world_setup(self):
|
||||
from worlds.alttp.Options import Medallion
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[1] = "A Link to the Past"
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
|
||||
self.multiworld.set_options(args)
|
||||
self.world = self.multiworld.worlds[1]
|
||||
# by default medallion access is randomized, for unittests we set it to vanilla
|
||||
self.world.options.misery_mire_medallion.value = Medallion.option_ether
|
||||
self.world.options.turtle_rock_medallion.value = Medallion.option_quake
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, ItemClassification
|
||||
from worlds import AutoWorldRegister
|
||||
from ..Items import item_factory
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
multiworld: MultiWorld
|
||||
_state_cache = {}
|
||||
|
||||
def get_state(self, items):
|
||||
if (self.multiworld, tuple(items)) in self._state_cache:
|
||||
return self._state_cache[self.multiworld, tuple(items)]
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item, prevent_sweep=True)
|
||||
state.sweep_for_advancements()
|
||||
state.update_reachable_regions(1)
|
||||
self._state_cache[self.multiworld, tuple(items)] = state
|
||||
return state
|
||||
|
||||
def get_path(self, state, region):
|
||||
def flist_to_iter(node):
|
||||
while node:
|
||||
value, node = node
|
||||
yield value
|
||||
|
||||
from itertools import zip_longest
|
||||
reversed_path_as_flist = state.path.get(region, (region, None))
|
||||
string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist))))
|
||||
# Now we combine the flat string list into (region, exit) pairs
|
||||
pathsiter = iter(string_path_flat)
|
||||
pathpairs = zip_longest(pathsiter, pathsiter)
|
||||
return list(pathpairs)
|
||||
|
||||
def run_location_tests(self, access_pool):
|
||||
for i, (location, access, *item_pool) in enumerate(access_pool):
|
||||
items = item_pool[0]
|
||||
all_except = item_pool[1] if len(item_pool) > 1 else None
|
||||
state = self._get_items(item_pool, all_except)
|
||||
path = self.get_path(state, self.multiworld.get_location(location, 1).parent_region)
|
||||
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
|
||||
all_except=all_except, path=path, entry=i):
|
||||
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access,
|
||||
f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
|
||||
|
||||
# check for partial solution
|
||||
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
|
||||
for missing_item in item_pool[0]:
|
||||
with self.subTest(msg="Location reachable without required item", location=location,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False,
|
||||
f"failed {self.multiworld.get_location(location, 1)}: succeeded with "
|
||||
f"{missing_item} removed from: {item_pool}")
|
||||
|
||||
def run_entrance_tests(self, access_pool):
|
||||
for i, (entrance, access, *item_pool) in enumerate(access_pool):
|
||||
items = item_pool[0]
|
||||
all_except = item_pool[1] if len(item_pool) > 1 else None
|
||||
state = self._get_items(item_pool, all_except)
|
||||
path = self.get_path(state, self.multiworld.get_entrance(entrance, 1).parent_region)
|
||||
with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items,
|
||||
all_except=all_except, path=path, entry=i):
|
||||
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), access)
|
||||
|
||||
# check for partial solution
|
||||
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
|
||||
for missing_item in item_pool[0]:
|
||||
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False,
|
||||
f"failed {self.multiworld.get_entrance(entrance, 1)} with: {item_pool}")
|
||||
|
||||
def _get_items(self, item_pool, all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
items = self.multiworld.itempool[:]
|
||||
items = [item for item in items if
|
||||
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
|
||||
items.extend(item_factory(item_pool[0], self.multiworld.worlds[1]))
|
||||
else:
|
||||
items = item_factory(item_pool[0], self.multiworld.worlds[1])
|
||||
return self.get_state(items)
|
||||
|
||||
def _get_items_partial(self, item_pool, missing_item):
|
||||
new_items = item_pool[0].copy()
|
||||
new_items.remove(missing_item)
|
||||
items = item_factory(new_items, self.multiworld.worlds[1])
|
||||
return self.get_state(items)
|
||||
|
||||
|
||||
class LTTPTestBase(unittest.TestCase):
|
||||
def world_setup(self):
|
||||
from worlds.alttp.Options import Medallion
|
||||
self.multiworld = MultiWorld(1)
|
||||
self.multiworld.game[1] = "A Link to the Past"
|
||||
self.multiworld.set_seed(None)
|
||||
args = Namespace()
|
||||
for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items():
|
||||
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.state = CollectionState(self.multiworld)
|
||||
self.world = self.multiworld.worlds[1]
|
||||
# by default medallion access is randomized, for unittests we set it to vanilla
|
||||
self.world.options.misery_mire_medallion.value = Medallion.option_ether
|
||||
self.world.options.turtle_rock_medallion.value = Medallion.option_quake
|
||||
@@ -5,7 +5,7 @@ from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from worlds.alttp.Regions import create_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from worlds.alttp.test.bases import LTTPTestBase
|
||||
|
||||
|
||||
class TestDungeon(LTTPTestBase):
|
||||
@@ -14,8 +14,8 @@ class TestDungeon(LTTPTestBase):
|
||||
self.starting_regions = [] # Where to start exploring
|
||||
self.remove_exits = [] # Block dungeon exits
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
create_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_dungeons()
|
||||
create_shops(self.multiworld, 1)
|
||||
|
||||
@@ -130,19 +130,21 @@ class TestGanonsTower(TestDungeon):
|
||||
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, []],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
|
||||
|
||||
["Ganons Tower - Validation Chest", False, []],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Progressive Bow']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Bomb Upgrade (50)']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Bomb Upgrade (50)', 'Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
|
||||
])
|
||||
@@ -32,8 +32,8 @@ class TestMiseryMire(TestDungeon):
|
||||
["Misery Mire - Main Lobby", False, []],
|
||||
["Misery Mire - Main Lobby", False, [], ['Pegasus Boots', 'Hookshot']],
|
||||
["Misery Mire - Main Lobby", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']],
|
||||
["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']],
|
||||
["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']],
|
||||
["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']],
|
||||
["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']],
|
||||
|
||||
["Misery Mire - Big Key Chest", False, []],
|
||||
["Misery Mire - Big Key Chest", False, [], ['Fire Rod', 'Lamp']],
|
||||
@@ -77,5 +77,5 @@ class TestMiseryMire(TestDungeon):
|
||||
["Misery Mire - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
|
||||
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']],
|
||||
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']],
|
||||
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']],
|
||||
["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Arrow Upgrade (+5)', 'Pegasus Boots']],
|
||||
])
|
||||
@@ -24,7 +24,7 @@ class TestSwampPalace(TestDungeon):
|
||||
["Swamp Palace - Big Key Chest", False, [], ['Open Floodgate']],
|
||||
["Swamp Palace - Big Key Chest", False, [], ['Hammer']],
|
||||
["Swamp Palace - Big Key Chest", False, [], ['Small Key (Swamp Palace)']],
|
||||
["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
|
||||
["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
|
||||
|
||||
["Swamp Palace - Map Chest", False, []],
|
||||
["Swamp Palace - Map Chest", False, [], ['Flippers']],
|
||||
@@ -38,7 +38,7 @@ class TestSwampPalace(TestDungeon):
|
||||
["Swamp Palace - West Chest", False, [], ['Open Floodgate']],
|
||||
["Swamp Palace - West Chest", False, [], ['Hammer']],
|
||||
["Swamp Palace - West Chest", False, [], ['Small Key (Swamp Palace)']],
|
||||
["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
|
||||
["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
|
||||
|
||||
["Swamp Palace - Compass Chest", False, []],
|
||||
["Swamp Palace - Compass Chest", False, [], ['Flippers']],
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from worlds.alttp.Regions import mark_light_world_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from test.bases import TestBase
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...EntranceShuffle import link_inverted_entrances
|
||||
from ...InvertedRegions import create_inverted_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Regions import mark_light_world_regions
|
||||
from ...Shops import create_shops
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestInverted(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.mode.value = 2
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
create_shops(self.multiworld, 1)
|
||||
|
||||
@@ -4,7 +4,7 @@ from worlds.alttp.EntranceShuffle import connect_entrance, Inverted_LW_Entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Rules import set_inverted_big_bomb_rules
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from worlds.alttp.test.bases import LTTPTestBase
|
||||
|
||||
|
||||
class TestInvertedBombRules(LTTPTestBase):
|
||||
@@ -12,7 +12,7 @@ class TestInvertedBombRules(LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.worlds[1].options.mode.value = 2
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.multiworld.worlds[1].create_dungeons()
|
||||
|
||||
|
||||
@@ -79,12 +79,12 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']],
|
||||
@@ -97,9 +97,9 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
|
||||
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
|
||||
|
||||
@@ -117,12 +117,12 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
[location, False, [], ['Magic Mirror', 'Lamp']],
|
||||
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
|
||||
# Mirroring into Eye Bridge does not require Cane of Somaria
|
||||
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from worlds.alttp.Regions import mark_light_world_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from test.bases import TestBase
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...EntranceShuffle import link_inverted_entrances
|
||||
from ...InvertedRegions import create_inverted_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Options import GlitchesRequired
|
||||
from ...Regions import mark_light_world_regions
|
||||
from ...Shops import create_shops
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestInvertedMinor(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.mode.value = 2
|
||||
self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
|
||||
@@ -80,12 +80,12 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Lamp', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria']],
|
||||
@@ -98,9 +98,9 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
|
||||
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
|
||||
])
|
||||
@@ -116,12 +116,12 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
[location, False, [], ['Magic Mirror', 'Lamp']],
|
||||
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Bomb Upgrade (50)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
|
||||
# Mirroring into Eye Bridge does not require Cane of Somaria
|
||||
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],
|
||||
|
||||
@@ -102,7 +102,7 @@ class TestDungeons(TestInvertedOWG):
|
||||
["Turtle Rock - Chain Chomps", True, ['Progressive Sword', 'Progressive Sword', 'Pegasus Boots']],
|
||||
|
||||
["Turtle Rock - Crystaroller Room", False, []],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)', 'Bomb Upgrade (50)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Lamp', 'Cane of Somaria']],
|
||||
|
||||
["Ganons Tower - Hope Room - Left", False, []],
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from worlds.alttp.Regions import mark_light_world_regions
|
||||
from worlds.alttp.Shops import create_shops
|
||||
from test.bases import TestBase
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...EntranceShuffle import link_inverted_entrances
|
||||
from ...InvertedRegions import create_inverted_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Options import GlitchesRequired
|
||||
from ...Regions import mark_light_world_regions
|
||||
from ...Shops import create_shops
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestInvertedOWG(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.mode[1].value = 2
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.worlds[1].options.mode.value = 2
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
create_inverted_regions(self.multiworld, 1)
|
||||
self.world.create_dungeons()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from test.bases import TestBase
|
||||
from ...ItemPool import difficulties
|
||||
from ..bases import TestBase
|
||||
|
||||
base_items = 41
|
||||
extra_counts = (15, 15, 10, 5, 25)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...InvertedRegions import mark_dark_world_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Options import GlitchesRequired
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestMinor(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches")
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.world.er_seed = 0
|
||||
self.world.create_regions()
|
||||
|
||||
@@ -23,7 +23,7 @@ class GoalPyramidTest(PyramidTestBase):
|
||||
}
|
||||
|
||||
def testCrystalsGoalAccess(self):
|
||||
self.multiworld.goal[1].value = 1 # crystals
|
||||
self.multiworld.worlds[1].options.goal.value = 1 # crystals
|
||||
self.assertFalse(self.can_reach_entrance("Pyramid Hole"))
|
||||
self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"])
|
||||
self.assertTrue(self.can_reach_entrance("Pyramid Hole"))
|
||||
|
||||
@@ -38,7 +38,7 @@ class DungeonFillTestBase(TestCase):
|
||||
def test_original_dungeons(self):
|
||||
self.generate_with_options(DungeonItem.option_original_dungeon)
|
||||
for location in self.multiworld.get_filled_locations():
|
||||
with (self.subTest(location=location)):
|
||||
with (self.subTest(location_name=location.name)):
|
||||
if location.parent_region.dungeon is None:
|
||||
self.assertIs(location.item.dungeon, None)
|
||||
else:
|
||||
@@ -52,7 +52,7 @@ class DungeonFillTestBase(TestCase):
|
||||
def test_own_dungeons(self):
|
||||
self.generate_with_options(DungeonItem.option_own_dungeons)
|
||||
for location in self.multiworld.get_filled_locations():
|
||||
with self.subTest(location=location):
|
||||
with self.subTest(location_name=location.name):
|
||||
if location.parent_region.dungeon is None:
|
||||
self.assertIs(location.item.dungeon, None)
|
||||
else:
|
||||
|
||||
@@ -120,8 +120,8 @@ class TestDungeons(TestVanillaOWG):
|
||||
#todo: does clip require sword?
|
||||
#["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Progressive Sword']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Turtle Rock)', 'Hookshot', 'Bomb Upgrade (50)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Big Key (Turtle Rock)', 'Bomb Upgrade (50)']],
|
||||
|
||||
["Ganons Tower - Hope Room - Left", False, []],
|
||||
["Ganons Tower - Hope Room - Left", False, ['Moon Pearl', 'Crystal 1']],
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...InvertedRegions import mark_dark_world_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Options import GlitchesRequired
|
||||
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestVanillaOWG(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches")
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from worlds.alttp.Shops import shop_table
|
||||
from test.bases import TestBase
|
||||
from ...Shops import shop_table
|
||||
from ..bases import TestBase
|
||||
|
||||
|
||||
class TestSram(TestBase):
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||
from worlds.alttp.ItemPool import difficulties
|
||||
from worlds.alttp.Items import item_factory
|
||||
from test.bases import TestBase
|
||||
from worlds.alttp.Options import GlitchesRequired
|
||||
from worlds.alttp.test import LTTPTestBase
|
||||
from ...Dungeons import get_dungeon_item_pool
|
||||
from ...InvertedRegions import mark_dark_world_regions
|
||||
from ...ItemPool import difficulties
|
||||
from ...Items import item_factory
|
||||
from ...Options import GlitchesRequired
|
||||
|
||||
from ..bases import LTTPTestBase, TestBase
|
||||
|
||||
|
||||
class TestVanilla(TestBase, LTTPTestBase):
|
||||
def setUp(self):
|
||||
self.world_setup()
|
||||
self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches")
|
||||
self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("no_glitches")
|
||||
self.multiworld.worlds[1].difficulty_requirements = difficulties['normal']
|
||||
self.multiworld.bombless_start[1].value = True
|
||||
self.multiworld.shuffle_capacity_upgrades[1].value = 2
|
||||
self.multiworld.worlds[1].options.bombless_start.value = True
|
||||
self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2
|
||||
self.multiworld.worlds[1].er_seed = 0
|
||||
self.multiworld.worlds[1].create_regions()
|
||||
self.multiworld.worlds[1].create_items()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
This apworld is meant as a learning tool for new apworld devs.
|
||||
It is a completely standalone resource, but there will be links to additional resources when appropriate.
|
||||
|
||||
#################
|
||||
# Prerequisites #
|
||||
#################
|
||||
|
||||
APQuest will only explain how to write the generation-side code for your game, not how to write a client or mod for it.
|
||||
For a more zoomed out view of how to add a game to Archipelago, you can read this document:
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/adding%20games.md
|
||||
|
||||
APQuest assumes you already vaguely know what an apworld is.
|
||||
If you don't know, read this first:
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/apworld%20specification.md
|
||||
|
||||
To write an apworld, you need to be running Archipelago from source (Python) instead of using e.g. the .exe build.
|
||||
Here's an explanation for how to do that.
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/running%20from%20source.md
|
||||
|
||||
#######################
|
||||
# How to read APQuest #
|
||||
#######################
|
||||
|
||||
You'll want to start with __init__.py, then move to world.py.
|
||||
If you also want to learn how to write unit tests, go to test/__init__.py.
|
||||
|
||||
You can ignore the game/ folder, it contains the actual game code, graphics and music.
|
||||
|
||||
The client/ folder is NOT meant for teaching.
|
||||
While the client was written to the best of its author's ability, it does not meet the same standard as the world code.
|
||||
The client code is also lacking the explanatory comments.
|
||||
Copy from it at your own risk.
|
||||
|
||||
###################
|
||||
# Further reading #
|
||||
###################
|
||||
|
||||
APQuest is a very simple game, so not every edge case will be covered.
|
||||
The world API document goes a lot more in-depth on certain topics:
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md
|
||||
|
||||
There is also the "APWorld dev FAQ" document with common emergent problems:
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/apworld_dev_faq.md
|
||||
|
||||
In general, but especially if you want your apworld to be verified by core, you should follow our style guide:
|
||||
https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md
|
||||
@@ -0,0 +1,12 @@
|
||||
# The first thing you should make for your world is an archipelago.json manifest file.
|
||||
# You can reference APQuest's, but you should change the "game" field (obviously),
|
||||
# and you should also change the "minimum_ap_version" - probably to the current value of Utils.__version__.
|
||||
|
||||
# Apart from the regular apworld code that allows generating multiworld seeds with your game,
|
||||
# your apworld might have other "components" that should be launchable from the Archipelago Launcher.
|
||||
# You can ignore this for now. If you are specifically interested in components, you can read components.py.
|
||||
from . import components as components
|
||||
|
||||
# The main thing we do in our __init__.py is importing our world class from our world.py to initialize it.
|
||||
# Obviously, this world class needs to exist first. For this, read world.py.
|
||||
from .world import APQuestWorld as APQuestWorld
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"game": "APQuest",
|
||||
"minimum_ap_version": "0.6.7",
|
||||
"world_version": "2.0.0",
|
||||
"authors": ["NewSoupVi"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# !!! IMPORTANT !!!
|
||||
# The client implementation is *not* meant for teaching.
|
||||
# Obviously, it is written to the best of its author's abilities,
|
||||
# but it is not to the same standard as the rest of the apworld.
|
||||
# Copy things from here at your own risk.
|
||||
@@ -0,0 +1,59 @@
|
||||
<ConfettiView>:
|
||||
size_hint: None, None
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.5}
|
||||
spacing: 0
|
||||
padding: 0
|
||||
|
||||
<APQuestGrid>:
|
||||
cols: 12
|
||||
rows: 11
|
||||
spacing: 0
|
||||
padding: 0
|
||||
size_hint: None, None
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.5}
|
||||
|
||||
<APQuestGameView>:
|
||||
RelativeLayout:
|
||||
id: game_container
|
||||
|
||||
<APQuestControlsView>:
|
||||
Label:
|
||||
markup: True
|
||||
font_size: "20sp"
|
||||
valign: "middle"
|
||||
pos_hint: {"center_x": 0.5, "center_y": 0.5}
|
||||
text:
|
||||
"""[b]Controls:[/b]
|
||||
|
||||
WASD or Arrow Keys to move
|
||||
Space to attack or interact
|
||||
C to fire available Confetti Cannons
|
||||
Number Keys + Backspace for Math Trap\n
|
||||
|
||||
[b]Click to move also works![/b]
|
||||
|
||||
Click/tap Confetti Cannon to fire it
|
||||
Submit Math Trap solution in the command line at the bottom"""
|
||||
|
||||
<VolumeSliderView>:
|
||||
orientation: "horizontal"
|
||||
size_hint: 1, None
|
||||
padding: 0
|
||||
height: 50
|
||||
|
||||
Label:
|
||||
size_hint: None, 1
|
||||
text: "Volume:"
|
||||
|
||||
Slider:
|
||||
id: volume_slider
|
||||
size_hint: 1, 1
|
||||
min: 0
|
||||
max: 100
|
||||
step: 1
|
||||
value: 50
|
||||
orientation: "horizontal"
|
||||
|
||||
Label:
|
||||
size_hint: None, 1
|
||||
text: str(int(volume_slider.value))
|
||||
@@ -0,0 +1,354 @@
|
||||
import asyncio
|
||||
import sys
|
||||
from argparse import Namespace
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop
|
||||
from NetUtils import ClientStatus
|
||||
from Utils import gui_enabled
|
||||
|
||||
from ..game.events import ConfettiFired, LocationClearedEvent, MathProblemSolved, MathProblemStarted, VictoryEvent
|
||||
from ..game.game import Game
|
||||
from ..game.inputs import Input
|
||||
from ..game.items import Item
|
||||
from ..game.locations import Location
|
||||
from .game_manager import APQuestManager
|
||||
from .graphics import PlayerSprite
|
||||
from .item_quality import get_quality_for_network_item
|
||||
from .sounds import (
|
||||
CONFETTI_CANNON,
|
||||
ITEM_JINGLES,
|
||||
MATH_PROBLEM_SOLVED_JINGLE,
|
||||
MATH_PROBLEM_STARTED_JINGLE,
|
||||
VICTORY_JINGLE,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import kvui
|
||||
|
||||
|
||||
# !!! IMPORTANT !!!
|
||||
# The client implementation is *not* meant for teaching.
|
||||
# Obviously, it is written to the best of its author's abilities,
|
||||
# but it is not to the same standard as the rest of the apworld.
|
||||
# Copy things from here at your own risk.
|
||||
|
||||
|
||||
class ConnectionStatus(Enum):
|
||||
NOT_CONNECTED = 0
|
||||
SCOUTS_NOT_SENT = 1
|
||||
SCOUTS_SENT = 2
|
||||
GAME_RUNNING = 3
|
||||
|
||||
|
||||
class APQuestClientCommandProcessor(ClientCommandProcessor):
|
||||
ctx: "APQuestContext"
|
||||
|
||||
def default(self, raw: str) -> None:
|
||||
if self.ctx.external_math_trap_input(raw):
|
||||
return
|
||||
|
||||
super().default(raw)
|
||||
|
||||
|
||||
class APQuestContext(CommonContext):
|
||||
game = "APQuest"
|
||||
items_handling = 0b111 # full remote
|
||||
|
||||
client_loop: asyncio.Task[None]
|
||||
|
||||
last_connected_slot: int | None = None
|
||||
|
||||
slot_data: dict[str, Any]
|
||||
|
||||
ap_quest_game: Game | None = None
|
||||
hard_mode: bool = False
|
||||
hammer: bool = False
|
||||
extra_starting_chest: bool = False
|
||||
player_sprite: PlayerSprite = PlayerSprite.HUMAN
|
||||
|
||||
connection_status: ConnectionStatus = ConnectionStatus.NOT_CONNECTED
|
||||
|
||||
highest_processed_item_index: int = 0
|
||||
queued_locations: list[int]
|
||||
|
||||
delay_intro_song: bool
|
||||
|
||||
ui: APQuestManager
|
||||
command_processor = APQuestClientCommandProcessor
|
||||
|
||||
def __init__(
|
||||
self, server_address: str | None = None, password: str | None = None, delay_intro_song: bool = False
|
||||
) -> None:
|
||||
super().__init__(server_address, password)
|
||||
|
||||
self.queued_locations = []
|
||||
self.slot_data = {}
|
||||
self.delay_intro_song = delay_intro_song
|
||||
|
||||
async def server_auth(self, password_requested: bool = False) -> None:
|
||||
if password_requested and not self.password:
|
||||
self.ui.allow_intro_song()
|
||||
await super().server_auth(password_requested)
|
||||
await self.get_username()
|
||||
await self.send_connect(game=self.game)
|
||||
|
||||
def handle_connection_loss(self, msg: str) -> None:
|
||||
self.ui.allow_intro_song()
|
||||
super().handle_connection_loss(msg)
|
||||
|
||||
async def connect(self, address: str | None = None) -> None:
|
||||
self.ui.switch_to_regular_tab()
|
||||
await super().connect(address)
|
||||
|
||||
async def apquest_loop(self) -> None:
|
||||
while not self.exit_event.is_set():
|
||||
if self.connection_status != ConnectionStatus.GAME_RUNNING:
|
||||
if self.connection_status == ConnectionStatus.SCOUTS_NOT_SENT:
|
||||
await self.send_msgs([{"cmd": "LocationScouts", "locations": self.server_locations}])
|
||||
self.connection_status = ConnectionStatus.SCOUTS_SENT
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
|
||||
if not self.ap_quest_game or not self.ap_quest_game.gameboard or not self.ap_quest_game.gameboard.ready:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
|
||||
try:
|
||||
while self.queued_locations:
|
||||
location = self.queued_locations.pop(0)
|
||||
self.location_checked_side_effects(location)
|
||||
self.locations_checked.add(location)
|
||||
await self.check_locations({location})
|
||||
|
||||
rerender = False
|
||||
|
||||
new_items = self.items_received[self.highest_processed_item_index :]
|
||||
for item in new_items:
|
||||
self.highest_processed_item_index += 1
|
||||
self.ap_quest_game.receive_item(item.item, item.location, item.player)
|
||||
rerender = True
|
||||
|
||||
for new_remotely_cleared_location in self.checked_locations - self.locations_checked:
|
||||
self.ap_quest_game.force_clear_location(new_remotely_cleared_location)
|
||||
rerender = True
|
||||
|
||||
if rerender:
|
||||
self.render()
|
||||
|
||||
if self.ap_quest_game.player.has_won and not self.finished_game:
|
||||
await self.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
self.finished_game = True
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
def on_package(self, cmd: str, args: dict[str, Any]) -> None:
|
||||
if cmd == "ConnectionRefused":
|
||||
self.ui.allow_intro_song()
|
||||
|
||||
if cmd == "Connected":
|
||||
if self.connection_status == ConnectionStatus.GAME_RUNNING:
|
||||
# In a connection loss -> auto reconnect scenario, we can seamlessly keep going
|
||||
return
|
||||
|
||||
self.last_connected_slot = self.slot
|
||||
|
||||
self.connection_status = ConnectionStatus.NOT_CONNECTED # for safety, it will get set again later
|
||||
|
||||
self.slot_data = args["slot_data"]
|
||||
self.hard_mode = self.slot_data["hard_mode"]
|
||||
self.hammer = self.slot_data["hammer"]
|
||||
self.extra_starting_chest = self.slot_data["extra_starting_chest"]
|
||||
try:
|
||||
self.player_sprite = PlayerSprite(self.slot_data["player_sprite"])
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
self.player_sprite = PlayerSprite.UNKNOWN
|
||||
|
||||
self.ap_quest_game = Game(self.hard_mode, self.hammer, self.extra_starting_chest)
|
||||
self.highest_processed_item_index = 0
|
||||
self.render()
|
||||
|
||||
self.connection_status = ConnectionStatus.SCOUTS_NOT_SENT
|
||||
if cmd == "LocationInfo":
|
||||
remote_item_graphic_overrides = {
|
||||
Location(location): Item(network_item.item)
|
||||
for location, network_item in self.locations_info.items()
|
||||
if self.slot_info[network_item.player].game == self.game
|
||||
}
|
||||
|
||||
assert self.ap_quest_game is not None
|
||||
self.ap_quest_game.gameboard.fill_remote_location_content(remote_item_graphic_overrides)
|
||||
self.render()
|
||||
|
||||
self.connection_status = ConnectionStatus.GAME_RUNNING
|
||||
self.ui.game_started()
|
||||
|
||||
async def disconnect(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.finished_game = False
|
||||
self.locations_checked = set()
|
||||
self.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
await super().disconnect(*args, **kwargs)
|
||||
|
||||
def render(self) -> None:
|
||||
if self.ap_quest_game is None:
|
||||
raise RuntimeError("Tried to render before self.ap_quest_game was initialized.")
|
||||
|
||||
self.ui.render(self.ap_quest_game, self.player_sprite, self.hard_mode)
|
||||
self.handle_game_events()
|
||||
|
||||
def location_checked_side_effects(self, location: int) -> None:
|
||||
network_item = self.locations_info[location]
|
||||
|
||||
if network_item.player == self.slot and network_item.item == Item.MATH_TRAP.value:
|
||||
# In case of a local math trap, we only play the math trap trigger jingle
|
||||
return
|
||||
|
||||
item_quality = get_quality_for_network_item(network_item)
|
||||
self.play_jingle(ITEM_JINGLES[item_quality])
|
||||
|
||||
def play_jingle(self, audio_filename: str) -> None:
|
||||
self.ui.play_jingle(audio_filename)
|
||||
|
||||
def handle_game_events(self) -> None:
|
||||
if self.ap_quest_game is None:
|
||||
return
|
||||
|
||||
while self.ap_quest_game.queued_events:
|
||||
event = self.ap_quest_game.queued_events.pop(0)
|
||||
|
||||
if isinstance(event, LocationClearedEvent):
|
||||
self.queued_locations.append(event.location_id)
|
||||
continue
|
||||
|
||||
if isinstance(event, VictoryEvent):
|
||||
self.play_jingle(VICTORY_JINGLE)
|
||||
continue
|
||||
|
||||
if isinstance(event, ConfettiFired):
|
||||
gameboard_x, gameboard_y = self.ap_quest_game.gameboard.size
|
||||
gameboard_x += 1 # vertical item column
|
||||
x = (event.x + 0.5) / gameboard_x
|
||||
y = 1 - (event.y + 0.5) / gameboard_y # Kivy's y is bottom to top (ew)
|
||||
|
||||
self.ui.play_jingle(CONFETTI_CANNON)
|
||||
self.ui.add_confetti((x, y), (self.slot_data["confetti_explosiveness"] + 1) * 5)
|
||||
continue
|
||||
|
||||
if isinstance(event, MathProblemStarted):
|
||||
self.play_jingle(MATH_PROBLEM_STARTED_JINGLE)
|
||||
continue
|
||||
|
||||
if isinstance(event, MathProblemSolved):
|
||||
self.play_jingle(MATH_PROBLEM_SOLVED_JINGLE)
|
||||
continue
|
||||
|
||||
def input_and_rerender(self, input_key: Input) -> None:
|
||||
if self.ap_quest_game is None:
|
||||
return
|
||||
if not self.ap_quest_game.gameboard.ready:
|
||||
return
|
||||
self.ap_quest_game.input(input_key)
|
||||
self.render()
|
||||
|
||||
def queue_auto_move(self, target_x: int, target_y: int) -> None:
|
||||
if self.ap_quest_game is None:
|
||||
return
|
||||
if not self.ap_quest_game.gameboard.ready:
|
||||
return
|
||||
if not self.ui.game_view.focused > 1: # Must already be in focus
|
||||
return
|
||||
self.ap_quest_game.queue_auto_move(target_x, target_y)
|
||||
self.ui.start_auto_move()
|
||||
|
||||
def do_auto_move_and_rerender(self) -> None:
|
||||
if self.ap_quest_game is None:
|
||||
return
|
||||
if not self.ap_quest_game.gameboard.ready:
|
||||
return
|
||||
changed = self.ap_quest_game.do_auto_move()
|
||||
if changed:
|
||||
self.render()
|
||||
|
||||
def confetti_and_rerender(self) -> None:
|
||||
# Used by tap mode
|
||||
if self.ap_quest_game is None:
|
||||
return
|
||||
if not self.ap_quest_game.gameboard.ready:
|
||||
return
|
||||
|
||||
if self.ap_quest_game.attempt_fire_confetti_cannon():
|
||||
self.render()
|
||||
|
||||
def external_math_trap_input(self, raw: str) -> bool:
|
||||
if self.ap_quest_game is None:
|
||||
return False
|
||||
if not self.ap_quest_game.gameboard.ready:
|
||||
return False
|
||||
if not self.ap_quest_game.active_math_problem:
|
||||
return False
|
||||
|
||||
raw = raw.strip()
|
||||
|
||||
if not raw:
|
||||
return False
|
||||
if not raw.isnumeric():
|
||||
return False
|
||||
|
||||
self.ap_quest_game.math_problem_replace([int(digit) for digit in raw])
|
||||
|
||||
if not self.ap_quest_game.active_math_problem:
|
||||
self.ui.game_view.force_focus()
|
||||
|
||||
self.render()
|
||||
|
||||
return True
|
||||
|
||||
def make_gui(self) -> "type[kvui.GameManager]":
|
||||
self.load_kv()
|
||||
return APQuestManager
|
||||
|
||||
def load_kv(self) -> None:
|
||||
import pkgutil
|
||||
|
||||
from kivy.lang import Builder
|
||||
|
||||
data = pkgutil.get_data(__name__, "ap_quest_client.kv")
|
||||
if data is None:
|
||||
raise RuntimeError("ap_quest_client.kv could not be loaded.")
|
||||
|
||||
Builder.load_string(data.decode())
|
||||
|
||||
|
||||
async def main(args: Namespace) -> None:
|
||||
if not gui_enabled:
|
||||
raise RuntimeError("APQuest cannot be played without gui.")
|
||||
|
||||
# Assume we shouldn't play the intro song in the auto-connect scenario, because the game will instantly start.
|
||||
delay_intro_song = args.connect and args.name
|
||||
|
||||
ctx = APQuestContext(args.connect, args.password, delay_intro_song=delay_intro_song)
|
||||
ctx.auth = args.name
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
|
||||
|
||||
ctx.run_gui()
|
||||
ctx.run_cli()
|
||||
|
||||
ctx.client_loop = asyncio.create_task(ctx.apquest_loop(), name="Client Loop")
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
await ctx.shutdown()
|
||||
|
||||
|
||||
def launch(*args: str) -> None:
|
||||
from .launch import launch_ap_quest_client
|
||||
|
||||
launch_ap_quest_client(*args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
launch(*sys.argv[1:])
|
||||
@@ -0,0 +1,306 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from math import sqrt
|
||||
from random import choice, random
|
||||
from typing import Any
|
||||
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics import Color, Triangle
|
||||
from kivy.graphics.instructions import Canvas
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.image import Image
|
||||
from kivy.uix.widget import Widget
|
||||
from kivymd.uix.recycleview import MDRecycleView
|
||||
|
||||
from CommonClient import logger
|
||||
|
||||
from ..game.inputs import Input
|
||||
|
||||
INPUT_MAP_STR = {
|
||||
"w": Input.UP,
|
||||
"s": Input.DOWN,
|
||||
"d": Input.RIGHT,
|
||||
"a": Input.LEFT,
|
||||
" ": Input.ACTION,
|
||||
"c": Input.CONFETTI,
|
||||
"0": Input.ZERO,
|
||||
"1": Input.ONE,
|
||||
"2": Input.TWO,
|
||||
"3": Input.THREE,
|
||||
"4": Input.FOUR,
|
||||
"5": Input.FIVE,
|
||||
"6": Input.SIX,
|
||||
"7": Input.SEVEN,
|
||||
"8": Input.EIGHT,
|
||||
"9": Input.NINE,
|
||||
}
|
||||
|
||||
INPUT_MAP_SPECIAL_INT = {
|
||||
# Arrow Keys and Backspace
|
||||
273: Input.UP,
|
||||
274: Input.DOWN,
|
||||
275: Input.RIGHT,
|
||||
276: Input.LEFT,
|
||||
8: Input.BACKSPACE,
|
||||
}
|
||||
|
||||
|
||||
class APQuestGameView(MDRecycleView):
|
||||
focused: int = 1
|
||||
input_function: Callable[[Input], None]
|
||||
|
||||
def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.input_function = input_function
|
||||
Window.bind(on_key_down=self._on_keyboard_down)
|
||||
Window.bind(on_touch_down=self.check_focus)
|
||||
self.opacity = 0.5
|
||||
|
||||
def check_focus(self, _, touch, *args, **kwargs) -> None:
|
||||
if self.parent.collide_point(*touch.pos):
|
||||
self.focused += 1
|
||||
self.opacity = 1
|
||||
return
|
||||
|
||||
self.focused = 0
|
||||
self.opacity = 0.5
|
||||
|
||||
def force_focus(self) -> None:
|
||||
Window.release_keyboard()
|
||||
self.focused = 1
|
||||
self.opacity = 1
|
||||
|
||||
def _on_keyboard_down(self, _: Any, keycode_int: int, _2: Any, keycode: str, _4: Any) -> bool:
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
if keycode in INPUT_MAP_STR:
|
||||
self.input_function(INPUT_MAP_STR[keycode])
|
||||
elif keycode_int in INPUT_MAP_SPECIAL_INT:
|
||||
self.input_function(INPUT_MAP_SPECIAL_INT[keycode_int])
|
||||
return False
|
||||
|
||||
|
||||
class APQuestGrid(GridLayout):
|
||||
def check_resize(self, _: int, _1: int) -> None:
|
||||
parent_width, parent_height = self.parent.size
|
||||
|
||||
self_width_according_to_parent_height = parent_height * 12 / 11
|
||||
self_height_according_to_parent_width = parent_width * 11 / 12
|
||||
|
||||
if self_width_according_to_parent_height > parent_width:
|
||||
self.size = parent_width, self_height_according_to_parent_width
|
||||
else:
|
||||
self.size = self_width_according_to_parent_height, parent_height
|
||||
|
||||
|
||||
CONFETTI_COLORS = [
|
||||
(220 / 255, 0, 212 / 255), # PINK
|
||||
(0, 0, 252 / 255), # BLUE
|
||||
(252 / 255, 220 / 255, 0), # YELLOW
|
||||
(0, 184 / 255, 0), # GREEN
|
||||
(252 / 255, 56 / 255, 0), # ORANGE
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Confetti:
|
||||
x_pos: float
|
||||
y_pos: float
|
||||
x_speed: float
|
||||
y_speed: float
|
||||
color: tuple[float, float, float]
|
||||
life: float = 3
|
||||
|
||||
triangle1: Triangle | None = None
|
||||
triangle2: Triangle | None = None
|
||||
color_instruction: Color | None = None
|
||||
|
||||
def update_speed(self, dt: float) -> None:
|
||||
if self.x_speed > 0:
|
||||
self.x_speed -= 2.7 * dt
|
||||
if self.x_speed < 0:
|
||||
self.x_speed = 0
|
||||
else:
|
||||
self.x_speed += 2.7 * dt
|
||||
if self.x_speed > 0:
|
||||
self.x_speed = 0
|
||||
|
||||
if self.y_speed > -0.03:
|
||||
self.y_speed -= 2.7 * dt
|
||||
if self.y_speed < -0.03:
|
||||
self.y_speed = -0.03
|
||||
else:
|
||||
self.y_speed += 2.7 * dt
|
||||
if self.y_speed > -0.03:
|
||||
self.y_speed = -0.03
|
||||
|
||||
def move(self, dt: float) -> None:
|
||||
self.update_speed(dt)
|
||||
|
||||
if self.y_pos > 1:
|
||||
self.y_pos = 1
|
||||
self.y_speed = 0
|
||||
if self.x_pos < 0.01:
|
||||
self.x_pos = 0.01
|
||||
self.x_speed = 0
|
||||
if self.x_pos > 0.99:
|
||||
self.x_pos = 0.99
|
||||
self.x_speed = 0
|
||||
|
||||
self.x_pos += self.x_speed * dt
|
||||
self.y_pos += self.y_speed * dt
|
||||
|
||||
def render(self, offset_x: float, offset_y: float, max_x: int, max_y: int) -> None:
|
||||
if self.x_speed == 0 and self.y_speed == 0:
|
||||
x_normalized, y_normalized = 0.0, 1.0
|
||||
else:
|
||||
speed_magnitude = sqrt(self.x_speed**2 + self.y_speed**2)
|
||||
x_normalized, y_normalized = self.x_speed / speed_magnitude, self.y_speed / speed_magnitude
|
||||
|
||||
half_top_to_bottom = 0.006
|
||||
half_left_to_right = 0.018
|
||||
|
||||
upwards_delta_x = x_normalized * half_top_to_bottom
|
||||
upwards_delta_y = y_normalized * half_top_to_bottom
|
||||
sideways_delta_x = y_normalized * half_left_to_right
|
||||
sideways_delta_y = x_normalized * half_left_to_right
|
||||
|
||||
top_left_x, top_left_y = upwards_delta_x - sideways_delta_x, upwards_delta_y + sideways_delta_y
|
||||
bottom_left_x, bottom_left_y = -upwards_delta_x - sideways_delta_x, -upwards_delta_y + sideways_delta_y
|
||||
top_right_x, top_right_y = -bottom_left_x, -bottom_left_y
|
||||
bottom_right_x, bottom_right_y = -top_left_x, -top_left_y
|
||||
|
||||
top_left_x, top_left_y = top_left_x + self.x_pos, top_left_y + self.y_pos
|
||||
bottom_left_x, bottom_left_y = bottom_left_x + self.x_pos, bottom_left_y + self.y_pos
|
||||
top_right_x, top_right_y = top_right_x + self.x_pos, top_right_y + self.y_pos
|
||||
bottom_right_x, bottom_right_y = bottom_right_x + self.x_pos, bottom_right_y + self.y_pos
|
||||
|
||||
top_left_x, top_left_y = top_left_x * max_x + offset_x, top_left_y * max_y + offset_y
|
||||
bottom_left_x, bottom_left_y = bottom_left_x * max_x + offset_x, bottom_left_y * max_y + offset_y
|
||||
top_right_x, top_right_y = top_right_x * max_x + offset_x, top_right_y * max_y + offset_y
|
||||
bottom_right_x, bottom_right_y = bottom_right_x * max_x + offset_x, bottom_right_y * max_y + offset_y
|
||||
|
||||
points1 = (top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
|
||||
points2 = (bottom_right_x, bottom_right_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y)
|
||||
|
||||
if self.color_instruction is None:
|
||||
self.color_instruction = Color(*self.color)
|
||||
|
||||
if self.triangle1 is None:
|
||||
self.triangle1 = Triangle(points=points1)
|
||||
else:
|
||||
self.triangle1.points = points1
|
||||
|
||||
if self.triangle2 is None:
|
||||
self.triangle2 = Triangle(points=points2)
|
||||
else:
|
||||
self.triangle2.points = points2
|
||||
|
||||
def reduce_life(self, dt: float, canvas: Canvas) -> bool:
|
||||
self.life -= dt
|
||||
|
||||
if self.life <= 0:
|
||||
if self.color_instruction is not None:
|
||||
canvas.remove(self.color_instruction)
|
||||
if self.triangle1 is not None:
|
||||
canvas.remove(self.triangle1)
|
||||
if self.triangle2 is not None:
|
||||
canvas.remove(self.triangle2)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class ConfettiView(Widget):
|
||||
confetti: list[Confetti]
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.confetti = []
|
||||
|
||||
# Don't eat tap events for the game grid under the confetti view
|
||||
def on_touch_down(self, touch) -> bool:
|
||||
return False
|
||||
|
||||
def on_touch_move(self, touch) -> bool:
|
||||
return False
|
||||
|
||||
def on_touch_up(self, touch) -> bool:
|
||||
return False
|
||||
|
||||
def check_resize(self, _: int, _1: int) -> None:
|
||||
parent_width, parent_height = self.parent.size
|
||||
|
||||
self_width_according_to_parent_height = parent_height * 12 / 11
|
||||
self_height_according_to_parent_width = parent_height * 11 / 12
|
||||
|
||||
if self_width_according_to_parent_height > parent_width:
|
||||
self.size = parent_width, self_height_according_to_parent_width
|
||||
else:
|
||||
self.size = self_width_according_to_parent_height, parent_height
|
||||
|
||||
def redraw_confetti(self, dt: float) -> None:
|
||||
try:
|
||||
with self.canvas:
|
||||
for confetti in self.confetti:
|
||||
confetti.move(dt)
|
||||
|
||||
self.confetti = [confetti for confetti in self.confetti if confetti.reduce_life(dt, self.canvas)]
|
||||
|
||||
for confetti in self.confetti:
|
||||
confetti.render(self.pos[0], self.pos[1], self.size[0], self.size[1])
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
def add_confetti(self, initial_position: tuple[float, float], amount: int) -> None:
|
||||
for i in range(amount):
|
||||
self.confetti.append(
|
||||
Confetti(
|
||||
initial_position[0],
|
||||
initial_position[1],
|
||||
random() * 3.2 - 1.6 - (initial_position[0] - 0.5) * 1.2,
|
||||
random() * 3.2 - 1.3 - (initial_position[1] - 0.5) * 1.2,
|
||||
choice(CONFETTI_COLORS),
|
||||
3 + i * 0.05,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VolumeSliderView(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class APQuestControlsView(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class TapImage(ButtonBehavior, Image):
|
||||
callback: Callable[[], None]
|
||||
|
||||
def __init__(self, callback: Callable[[], None], **kwargs) -> None:
|
||||
self.callback = callback
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def on_release(self) -> bool:
|
||||
self.callback()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class TapIfConfettiCannonImage(ButtonBehavior, Image):
|
||||
callback: Callable[[], None]
|
||||
|
||||
is_confetti_cannon: bool = False
|
||||
|
||||
def __init__(self, callback: Callable[[], None], **kwargs: dict[str, Any]) -> None:
|
||||
self.callback = callback
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def on_release(self) -> bool:
|
||||
if self.is_confetti_cannon:
|
||||
self.callback()
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,227 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# isort: off
|
||||
from kvui import GameManager, MDNavigationItemBase
|
||||
|
||||
# isort: on
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from kivy._clock import ClockEvent
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.image import Image
|
||||
from kivy.uix.layout import Layout
|
||||
from kivymd.uix.recycleview import MDRecycleView
|
||||
|
||||
from ..game.game import Game
|
||||
from ..game.graphics import Graphic
|
||||
from .custom_views import (
|
||||
APQuestControlsView,
|
||||
APQuestGameView,
|
||||
APQuestGrid,
|
||||
ConfettiView,
|
||||
TapIfConfettiCannonImage,
|
||||
TapImage,
|
||||
VolumeSliderView,
|
||||
)
|
||||
from .graphics import PlayerSprite, get_texture
|
||||
from .sounds import SoundManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .ap_quest_client import APQuestContext
|
||||
|
||||
|
||||
class APQuestManager(GameManager):
|
||||
base_title = "APQuest for AP version"
|
||||
ctx: APQuestContext
|
||||
|
||||
lower_game_grid: GridLayout
|
||||
upper_game_grid: GridLayout
|
||||
|
||||
game_view: MDRecycleView | None = None
|
||||
game_view_tab: MDNavigationItemBase
|
||||
|
||||
sound_manager: SoundManager
|
||||
|
||||
bottom_image_grid: list[list[Image]]
|
||||
top_image_grid: list[list[TapImage]]
|
||||
confetti_view: ConfettiView
|
||||
|
||||
move_event: ClockEvent | None
|
||||
|
||||
bottom_grid_is_grass: bool
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.sound_manager = SoundManager()
|
||||
self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song
|
||||
self.top_image_grid = []
|
||||
self.bottom_image_grid = []
|
||||
self.move_event = None
|
||||
self.bottom_grid_is_grass = False
|
||||
|
||||
def allow_intro_song(self) -> None:
|
||||
self.sound_manager.allow_intro_to_play = True
|
||||
|
||||
def add_confetti(self, position: tuple[float, float], amount: int) -> None:
|
||||
self.confetti_view.add_confetti(position, amount)
|
||||
|
||||
def play_jingle(self, audio_filename: str) -> None:
|
||||
self.sound_manager.play_jingle(audio_filename)
|
||||
|
||||
def switch_to_tab(self, desired_tab: MDNavigationItemBase) -> None:
|
||||
if self.screens.current_tab == desired_tab:
|
||||
return
|
||||
self.screens.current_tab.active = False
|
||||
self.screens.switch_screens(desired_tab)
|
||||
desired_tab.active = True
|
||||
|
||||
def switch_to_game_tab(self) -> None:
|
||||
self.switch_to_tab(self.game_view_tab)
|
||||
|
||||
def switch_to_regular_tab(self) -> None:
|
||||
self.switch_to_tab(self.tabs.children[-1])
|
||||
|
||||
def game_started(self) -> None:
|
||||
self.switch_to_game_tab()
|
||||
if self.game_view is not None:
|
||||
self.game_view.force_focus()
|
||||
self.sound_manager.game_started = True
|
||||
|
||||
def render(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> None:
|
||||
self.setup_game_grid_if_not_setup(game)
|
||||
|
||||
# This calls game.render(), which needs to happen to update the state of math traps
|
||||
self.render_gameboard(game, player_sprite, hard_mode)
|
||||
# Only now can we check whether a math problem is active
|
||||
self.render_background_game_grid(game.gameboard.size, game.active_math_problem is None)
|
||||
self.sound_manager.math_trap_active = game.active_math_problem is not None
|
||||
|
||||
self.render_item_column(game)
|
||||
|
||||
def render_gameboard(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> None:
|
||||
rendered_gameboard = game.render()
|
||||
|
||||
for gameboard_row, image_row in zip(rendered_gameboard, self.top_image_grid, strict=False):
|
||||
for graphic, image in zip(gameboard_row, image_row[:11], strict=False):
|
||||
texture = get_texture(graphic, player_sprite, hard_mode)
|
||||
|
||||
if texture is None:
|
||||
image.opacity = 0
|
||||
image.texture = None
|
||||
continue
|
||||
|
||||
image.texture = texture
|
||||
image.opacity = 1
|
||||
|
||||
def render_item_column(self, game: Game) -> None:
|
||||
rendered_item_column = game.render_health_and_inventory(vertical=True)
|
||||
for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False):
|
||||
image = image_row[-1]
|
||||
|
||||
image.is_confetti_cannon = item_graphic == Graphic.CONFETTI_CANNON
|
||||
|
||||
texture = get_texture(item_graphic)
|
||||
if texture is None:
|
||||
image.opacity = 0
|
||||
image.texture = None
|
||||
continue
|
||||
|
||||
image.texture = texture
|
||||
image.opacity = 1
|
||||
|
||||
def render_background_game_grid(self, size: tuple[int, int], grass: bool) -> None:
|
||||
if grass == self.bottom_grid_is_grass:
|
||||
return
|
||||
|
||||
for row in range(size[1]):
|
||||
for column in range(size[0]):
|
||||
image = self.bottom_image_grid[row][column]
|
||||
|
||||
if not grass:
|
||||
image.color = (0.3, 0.3, 0.3)
|
||||
image.texture = None
|
||||
continue
|
||||
|
||||
boss_room = (row in (0, 1, 2) and (size[1] - column) in (1, 2, 3)) or (row, column) == (3, size[1] - 2)
|
||||
if boss_room:
|
||||
image.color = (0.45, 0.35, 0.1)
|
||||
image.texture = None
|
||||
continue
|
||||
image.texture = get_texture("Grass")
|
||||
image.color = (1.0, 1.0, 1.0)
|
||||
|
||||
self.bottom_grid_is_grass = grass
|
||||
|
||||
def setup_game_grid_if_not_setup(self, game: Game) -> None:
|
||||
if self.upper_game_grid.children:
|
||||
return
|
||||
|
||||
self.top_image_grid = []
|
||||
self.bottom_image_grid = []
|
||||
|
||||
size = game.gameboard.size
|
||||
|
||||
for row in range(size[1]):
|
||||
self.top_image_grid.append([])
|
||||
self.bottom_image_grid.append([])
|
||||
|
||||
for column in range(size[0]):
|
||||
bottom_image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
|
||||
self.lower_game_grid.add_widget(bottom_image)
|
||||
self.bottom_image_grid[-1].append(bottom_image)
|
||||
|
||||
top_image = TapImage(lambda y=row, x=column: self.ctx.queue_auto_move(x, y), fit_mode="fill")
|
||||
self.upper_game_grid.add_widget(top_image)
|
||||
self.top_image_grid[-1].append(top_image)
|
||||
|
||||
# Right side: Inventory
|
||||
image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
|
||||
self.lower_game_grid.add_widget(image)
|
||||
|
||||
image2 = TapIfConfettiCannonImage(lambda: self.ctx.confetti_and_rerender(), fit_mode="fill", opacity=0)
|
||||
self.upper_game_grid.add_widget(image2)
|
||||
|
||||
self.top_image_grid[-1].append(image2)
|
||||
|
||||
def start_auto_move(self) -> None:
|
||||
if self.move_event is not None:
|
||||
self.move_event.cancel()
|
||||
|
||||
self.ctx.do_auto_move_and_rerender()
|
||||
|
||||
self.move_event = Clock.schedule_interval(lambda _: self.ctx.do_auto_move_and_rerender(), 0.10)
|
||||
|
||||
def build(self) -> Layout:
|
||||
container = super().build()
|
||||
|
||||
self.game_view = APQuestGameView(self.ctx.input_and_rerender)
|
||||
|
||||
self.game_view_tab = self.add_client_tab("APQuest", self.game_view)
|
||||
|
||||
controls = APQuestControlsView()
|
||||
|
||||
self.add_client_tab("Controls", controls)
|
||||
|
||||
game_container = self.game_view.ids["game_container"]
|
||||
self.lower_game_grid = APQuestGrid()
|
||||
self.upper_game_grid = APQuestGrid()
|
||||
self.confetti_view = ConfettiView()
|
||||
game_container.add_widget(self.lower_game_grid)
|
||||
game_container.add_widget(self.upper_game_grid)
|
||||
game_container.add_widget(self.confetti_view)
|
||||
|
||||
game_container.bind(size=self.lower_game_grid.check_resize)
|
||||
game_container.bind(size=self.upper_game_grid.check_resize)
|
||||
game_container.bind(size=self.confetti_view.check_resize)
|
||||
|
||||
volume_slider_container = VolumeSliderView()
|
||||
volume_slider = volume_slider_container.ids["volume_slider"]
|
||||
volume_slider.value = self.sound_manager.volume_percentage
|
||||
volume_slider.bind(value=lambda _, new_volume: self.sound_manager.set_volume_percentage(new_volume))
|
||||
|
||||
self.grid.add_widget(volume_slider_container, index=3)
|
||||
|
||||
Clock.schedule_interval(lambda dt: self.confetti_view.redraw_confetti(dt), 1 / 60)
|
||||
|
||||
return container
|
||||
@@ -0,0 +1,185 @@
|
||||
import pkgutil
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import Literal, NamedTuple, Protocol, cast
|
||||
|
||||
from kivy.uix.image import CoreImage
|
||||
from typing_extensions import Buffer
|
||||
|
||||
from CommonClient import logger
|
||||
|
||||
from .. import game
|
||||
from ..game.graphics import Graphic
|
||||
|
||||
|
||||
# The import "from kivy.graphics.texture import Texture" does not work correctly.
|
||||
# We never need the class directly, so we need to use a protocol.
|
||||
class Texture(Protocol):
|
||||
mag_filter: Literal["nearest"]
|
||||
|
||||
def get_region(self, x: int, y: int, w: int, h: int) -> "Texture": ...
|
||||
|
||||
|
||||
class RelatedTexture(NamedTuple):
|
||||
base_texture_file: str
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
# Inanimates
|
||||
Graphic.WALL: RelatedTexture("inanimates.png", 16, 32, 16, 16),
|
||||
Graphic.BREAKABLE_BLOCK: RelatedTexture("inanimates.png", 32, 32, 16, 16),
|
||||
Graphic.CHEST: RelatedTexture("inanimates.png", 0, 16, 16, 16),
|
||||
Graphic.BUSH: RelatedTexture("inanimates.png", 16, 16, 16, 16),
|
||||
Graphic.KEY_DOOR: RelatedTexture("inanimates.png", 32, 16, 16, 16),
|
||||
Graphic.BUTTON_NOT_ACTIVATED: RelatedTexture("inanimates.png", 0, 0, 16, 16),
|
||||
Graphic.BUTTON_ACTIVATED: RelatedTexture("inanimates.png", 16, 0, 16, 16),
|
||||
Graphic.BUTTON_DOOR: RelatedTexture("inanimates.png", 32, 0, 16, 16),
|
||||
# Enemies
|
||||
Graphic.NORMAL_ENEMY_1_HEALTH: RelatedTexture("normal_enemy.png", 0, 0, 16, 16),
|
||||
Graphic.NORMAL_ENEMY_2_HEALTH: RelatedTexture("normal_enemy.png", 16, 0, 16, 16),
|
||||
Graphic.BOSS_5_HEALTH: RelatedTexture("boss.png", 16, 16, 16, 16),
|
||||
Graphic.BOSS_4_HEALTH: RelatedTexture("boss.png", 0, 16, 16, 16),
|
||||
Graphic.BOSS_3_HEALTH: RelatedTexture("boss.png", 32, 32, 16, 16),
|
||||
Graphic.BOSS_2_HEALTH: RelatedTexture("boss.png", 16, 32, 16, 16),
|
||||
Graphic.BOSS_1_HEALTH: RelatedTexture("boss.png", 0, 32, 16, 16),
|
||||
# Items
|
||||
Graphic.EMPTY_HEART: RelatedTexture("hearts.png", 0, 0, 16, 16),
|
||||
Graphic.HEART: RelatedTexture("hearts.png", 16, 0, 16, 16),
|
||||
Graphic.HALF_HEART: RelatedTexture("hearts.png", 32, 0, 16, 16),
|
||||
Graphic.REMOTE_ITEM: RelatedTexture("items.png", 0, 16, 16, 16),
|
||||
Graphic.CONFETTI_CANNON: RelatedTexture("items.png", 16, 16, 16, 16),
|
||||
Graphic.HAMMER: RelatedTexture("items.png", 32, 16, 16, 16),
|
||||
Graphic.KEY: RelatedTexture("items.png", 0, 0, 16, 16),
|
||||
Graphic.SHIELD: RelatedTexture("items.png", 16, 0, 16, 16),
|
||||
Graphic.SWORD: RelatedTexture("items.png", 32, 0, 16, 16),
|
||||
# Numbers
|
||||
Graphic.ZERO: RelatedTexture("numbers.png", 0, 16, 16, 16),
|
||||
Graphic.ONE: RelatedTexture("numbers.png", 16, 16, 16, 16),
|
||||
Graphic.TWO: RelatedTexture("numbers.png", 32, 16, 16, 16),
|
||||
Graphic.THREE: RelatedTexture("numbers.png", 48, 16, 16, 16),
|
||||
Graphic.FOUR: RelatedTexture("numbers.png", 64, 16, 16, 16),
|
||||
Graphic.FIVE: RelatedTexture("numbers.png", 0, 0, 16, 16),
|
||||
Graphic.SIX: RelatedTexture("numbers.png", 16, 0, 16, 16),
|
||||
Graphic.SEVEN: RelatedTexture("numbers.png", 32, 0, 16, 16),
|
||||
Graphic.EIGHT: RelatedTexture("numbers.png", 48, 0, 16, 16),
|
||||
Graphic.NINE: RelatedTexture("numbers.png", 64, 0, 16, 16),
|
||||
# Letters
|
||||
Graphic.LETTER_A: RelatedTexture("letters.png", 0, 16, 16, 16),
|
||||
Graphic.LETTER_E: RelatedTexture("letters.png", 16, 16, 16, 16),
|
||||
Graphic.LETTER_H: RelatedTexture("letters.png", 32, 16, 16, 16),
|
||||
Graphic.LETTER_I: RelatedTexture("letters.png", 0, 0, 16, 16),
|
||||
Graphic.LETTER_M: RelatedTexture("letters.png", 16, 0, 16, 16),
|
||||
Graphic.LETTER_T: RelatedTexture("letters.png", 32, 0, 16, 16),
|
||||
# Mathematical symbols
|
||||
Graphic.DIVIDE: RelatedTexture("symbols.png", 0, 16, 16, 16),
|
||||
Graphic.EQUALS: RelatedTexture("symbols.png", 16, 16, 16, 16),
|
||||
Graphic.MINUS: RelatedTexture("symbols.png", 32, 16, 16, 16),
|
||||
Graphic.PLUS: RelatedTexture("symbols.png", 0, 0, 16, 16),
|
||||
Graphic.TIMES: RelatedTexture("symbols.png", 16, 0, 16, 16),
|
||||
# Other visual-only elements
|
||||
Graphic.ITEMS_TEXT: "items_text.png",
|
||||
Graphic.NO: RelatedTexture("symbols.png", 32, 0, 16, 16),
|
||||
Graphic.UNKNOWN: RelatedTexture("symbols.png", 32, 0, 16, 16), # Same as "No"
|
||||
}
|
||||
|
||||
BACKGROUND_TILE = RelatedTexture("inanimates.png", 0, 32, 16, 16)
|
||||
|
||||
EASY_MODE_BOSS_2_HEALTH = RelatedTexture("boss.png", 16, 0, 16, 16)
|
||||
|
||||
|
||||
class PlayerSprite(Enum):
|
||||
HUMAN = 0
|
||||
DUCK = 1
|
||||
HORSE = 2
|
||||
CAT = 3
|
||||
UNKNOWN = -1
|
||||
|
||||
|
||||
PLAYER_GRAPHICS = {
|
||||
Graphic.PLAYER_DOWN: {
|
||||
PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 16, 16, 16),
|
||||
PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 16, 16, 16),
|
||||
PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 16, 16, 16),
|
||||
PlayerSprite.CAT: RelatedTexture("cat.png", 0, 16, 16, 16),
|
||||
},
|
||||
Graphic.PLAYER_UP: {
|
||||
PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 0, 16, 16),
|
||||
PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 0, 16, 16),
|
||||
PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 0, 16, 16),
|
||||
PlayerSprite.CAT: RelatedTexture("cat.png", 16, 0, 16, 16),
|
||||
},
|
||||
Graphic.PLAYER_LEFT: {
|
||||
PlayerSprite.HUMAN: RelatedTexture("human.png", 16, 16, 16, 16),
|
||||
PlayerSprite.DUCK: RelatedTexture("duck.png", 16, 16, 16, 16),
|
||||
PlayerSprite.HORSE: RelatedTexture("horse.png", 16, 16, 16, 16),
|
||||
PlayerSprite.CAT: RelatedTexture("cat.png", 16, 16, 16, 16),
|
||||
},
|
||||
Graphic.PLAYER_RIGHT: {
|
||||
PlayerSprite.HUMAN: RelatedTexture("human.png", 0, 0, 16, 16),
|
||||
PlayerSprite.DUCK: RelatedTexture("duck.png", 0, 0, 16, 16),
|
||||
PlayerSprite.HORSE: RelatedTexture("horse.png", 0, 0, 16, 16),
|
||||
PlayerSprite.CAT: RelatedTexture("cat.png", 0, 0, 16, 16),
|
||||
},
|
||||
}
|
||||
|
||||
ALL_GRAPHICS = [
|
||||
BACKGROUND_TILE,
|
||||
*IMAGE_GRAPHICS.values(),
|
||||
*[graphic for sub_dict in PLAYER_GRAPHICS.values() for graphic in sub_dict.values()],
|
||||
]
|
||||
|
||||
_textures: dict[str | RelatedTexture, Texture] = {}
|
||||
|
||||
|
||||
def get_texture_by_identifier(texture_identifier: str | RelatedTexture) -> Texture:
|
||||
if texture_identifier in _textures:
|
||||
return _textures[texture_identifier]
|
||||
|
||||
if isinstance(texture_identifier, str):
|
||||
image_data = pkgutil.get_data(game.__name__, f"graphics/{texture_identifier}")
|
||||
if image_data is None:
|
||||
raise RuntimeError(f'Could not find file "graphics/{texture_identifier}" for texture {texture_identifier}')
|
||||
|
||||
image_bytes = BytesIO(cast(Buffer, image_data))
|
||||
texture = cast(Texture, CoreImage(image_bytes, ext="png").texture)
|
||||
texture.mag_filter = "nearest"
|
||||
_textures[texture_identifier] = texture
|
||||
return texture
|
||||
|
||||
base_texture_filename, x, y, w, h = texture_identifier
|
||||
|
||||
base_texture = get_texture_by_identifier(base_texture_filename)
|
||||
|
||||
sub_texture = base_texture.get_region(x, y, w, h)
|
||||
sub_texture.mag_filter = "nearest"
|
||||
_textures[texture_identifier] = sub_texture
|
||||
return sub_texture
|
||||
|
||||
|
||||
def get_texture(
|
||||
graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None, hard_mode: bool = False
|
||||
) -> Texture | None:
|
||||
if graphic == Graphic.EMPTY:
|
||||
return None
|
||||
|
||||
if graphic == "Grass":
|
||||
return get_texture_by_identifier(BACKGROUND_TILE)
|
||||
|
||||
if graphic == Graphic.BOSS_2_HEALTH and not hard_mode:
|
||||
return get_texture_by_identifier(EASY_MODE_BOSS_2_HEALTH)
|
||||
|
||||
if graphic in IMAGE_GRAPHICS:
|
||||
return get_texture_by_identifier(IMAGE_GRAPHICS[graphic])
|
||||
|
||||
if graphic in PLAYER_GRAPHICS:
|
||||
if player_sprite is None:
|
||||
raise ValueError("Tried to load a player graphic without specifying a player_sprite")
|
||||
|
||||
return get_texture_by_identifier(PLAYER_GRAPHICS[graphic][player_sprite])
|
||||
|
||||
logger.exception(f"Tried to load unknown graphic {graphic}.")
|
||||
return get_texture(Graphic.UNKNOWN)
|
||||
@@ -0,0 +1,25 @@
|
||||
from enum import Enum
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from NetUtils import NetworkItem
|
||||
|
||||
|
||||
class ItemQuality(Enum):
|
||||
FILLER = 0
|
||||
TRAP = 1
|
||||
USEFUL = 2
|
||||
PROGRESSION = 3
|
||||
PROGUSEFUL = 4
|
||||
|
||||
|
||||
def get_quality_for_network_item(network_item: NetworkItem) -> ItemQuality:
|
||||
flags = ItemClassification(network_item.flags)
|
||||
if ItemClassification.progression in flags:
|
||||
if ItemClassification.useful in flags:
|
||||
return ItemQuality.PROGUSEFUL
|
||||
return ItemQuality.PROGRESSION
|
||||
if ItemClassification.useful in flags:
|
||||
return ItemQuality.USEFUL
|
||||
if ItemClassification.trap in flags:
|
||||
return ItemQuality.TRAP
|
||||
return ItemQuality.FILLER
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user