Core: move PlandoConnections and PlandoTexts to the options system (#2904)
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> Co-authored-by: Scipio Wright <scipiowright@gmail.com> Co-authored-by: beauxq <beauxq@yahoo.com> Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>
This commit is contained in:
parent
f40b10dc97
commit
4e5b6bb3d2
29
Generate.py
29
Generate.py
|
@ -23,9 +23,7 @@ from Main import main as ERmain
|
|||
from settings import get_settings
|
||||
from Utils import parse_yamls, version_tuple, __version__, tuplize_version
|
||||
from worlds.alttp.EntranceRandomizer import parse_arguments
|
||||
from worlds.alttp.Text import TextTable
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
from worlds.generic import PlandoConnection
|
||||
from worlds import failed_world_loads
|
||||
|
||||
|
||||
|
@ -506,35 +504,12 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
|
|||
if PlandoOptions.items in plando_options:
|
||||
ret.plando_items = game_weights.get("plando_items", [])
|
||||
if ret.game == "A Link to the Past":
|
||||
roll_alttp_settings(ret, game_weights, plando_options)
|
||||
if PlandoOptions.connections in plando_options:
|
||||
ret.plando_connections = []
|
||||
options = game_weights.get("plando_connections", [])
|
||||
for placement in options:
|
||||
if roll_percentage(get_choice("percentage", placement, 100)):
|
||||
ret.plando_connections.append(PlandoConnection(
|
||||
get_choice("entrance", placement),
|
||||
get_choice("exit", placement),
|
||||
get_choice("direction", placement, "both")
|
||||
))
|
||||
roll_alttp_settings(ret, game_weights)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
|
||||
|
||||
ret.plando_texts = {}
|
||||
if PlandoOptions.texts in plando_options:
|
||||
tt = TextTable()
|
||||
tt.removeUnwantedText()
|
||||
options = weights.get("plando_texts", [])
|
||||
for placement in options:
|
||||
if roll_percentage(get_choice_legacy("percentage", placement, 100)):
|
||||
at = str(get_choice_legacy("at", placement))
|
||||
if at not in tt:
|
||||
raise Exception(f"No text target \"{at}\" found.")
|
||||
ret.plando_texts[at] = str(get_choice_legacy("text", placement))
|
||||
|
||||
def roll_alttp_settings(ret: argparse.Namespace, weights):
|
||||
ret.sprite_pool = weights.get('sprite_pool', [])
|
||||
ret.sprite = get_choice_legacy('sprite', weights, "Link")
|
||||
if 'random_sprite_on_event' in weights:
|
||||
|
|
226
Options.py
226
Options.py
|
@ -12,6 +12,7 @@ from copy import deepcopy
|
|||
from dataclasses import dataclass
|
||||
|
||||
from schema import And, Optional, Or, Schema
|
||||
from typing_extensions import Self
|
||||
|
||||
from Utils import get_fuzzy_results, is_iterable_except_str
|
||||
|
||||
|
@ -896,6 +897,228 @@ class ItemSet(OptionSet):
|
|||
convert_name_groups = True
|
||||
|
||||
|
||||
class PlandoText(typing.NamedTuple):
|
||||
at: str
|
||||
text: typing.List[str]
|
||||
percentage: int = 100
|
||||
|
||||
|
||||
PlandoTextsFromAnyType = typing.Union[
|
||||
typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoText, typing.Any]], typing.Any
|
||||
]
|
||||
|
||||
|
||||
class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys):
|
||||
default = ()
|
||||
supports_weighting = False
|
||||
display_name = "Plando Texts"
|
||||
|
||||
def __init__(self, value: typing.Iterable[PlandoText]) -> None:
|
||||
self.value = list(deepcopy(value))
|
||||
super().__init__()
|
||||
|
||||
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
||||
from BaseClasses import PlandoOptions
|
||||
if self.value and not (PlandoOptions.texts & plando_options):
|
||||
# plando is disabled but plando options were given so overwrite the options
|
||||
self.value = []
|
||||
logging.warning(f"The plando texts module is turned off, "
|
||||
f"so text for {player_name} will be ignored.")
|
||||
|
||||
@classmethod
|
||||
def from_any(cls, data: PlandoTextsFromAnyType) -> Self:
|
||||
texts: typing.List[PlandoText] = []
|
||||
if isinstance(data, typing.Iterable):
|
||||
for text in data:
|
||||
if isinstance(text, typing.Mapping):
|
||||
if random.random() < float(text.get("percentage", 100)/100):
|
||||
at = text.get("at", None)
|
||||
if at is not None:
|
||||
given_text = text.get("text", [])
|
||||
if isinstance(given_text, str):
|
||||
given_text = [given_text]
|
||||
texts.append(PlandoText(
|
||||
at,
|
||||
given_text,
|
||||
text.get("percentage", 100)
|
||||
))
|
||||
elif isinstance(text, PlandoText):
|
||||
if random.random() < float(text.percentage/100):
|
||||
texts.append(text)
|
||||
else:
|
||||
raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}")
|
||||
cls.verify_keys([text.at for text in texts])
|
||||
return cls(texts)
|
||||
else:
|
||||
raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}")
|
||||
|
||||
@classmethod
|
||||
def get_option_name(cls, value: typing.List[PlandoText]) -> str:
|
||||
return str({text.at: " ".join(text.text) for text in value})
|
||||
|
||||
def __iter__(self) -> typing.Iterator[PlandoText]:
|
||||
yield from self.value
|
||||
|
||||
def __getitem__(self, index: typing.SupportsIndex) -> PlandoText:
|
||||
return self.value.__getitem__(index)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.value.__len__()
|
||||
|
||||
|
||||
class ConnectionsMeta(AssembleOptions):
|
||||
def __new__(mcs, name: str, bases: tuple[type, ...], attrs: dict[str, typing.Any]):
|
||||
if name != "PlandoConnections":
|
||||
assert "entrances" in attrs, f"Please define valid entrances for {name}"
|
||||
attrs["entrances"] = frozenset((connection.lower() for connection in attrs["entrances"]))
|
||||
assert "exits" in attrs, f"Please define valid exits for {name}"
|
||||
attrs["exits"] = frozenset((connection.lower() for connection in attrs["exits"]))
|
||||
if "__doc__" not in attrs:
|
||||
attrs["__doc__"] = PlandoConnections.__doc__
|
||||
cls = super().__new__(mcs, name, bases, attrs)
|
||||
return cls
|
||||
|
||||
|
||||
class PlandoConnection(typing.NamedTuple):
|
||||
class Direction:
|
||||
entrance = "entrance"
|
||||
exit = "exit"
|
||||
both = "both"
|
||||
|
||||
entrance: str
|
||||
exit: str
|
||||
direction: typing.Literal["entrance", "exit", "both"] # TODO: convert Direction to StrEnum once 3.8 is dropped
|
||||
percentage: int = 100
|
||||
|
||||
|
||||
PlandoConFromAnyType = typing.Union[
|
||||
typing.Iterable[typing.Union[typing.Mapping[str, typing.Any], PlandoConnection, typing.Any]], typing.Any
|
||||
]
|
||||
|
||||
|
||||
class PlandoConnections(Option[typing.List[PlandoConnection]], metaclass=ConnectionsMeta):
|
||||
"""Generic connections plando. Format is:
|
||||
- entrance: "Entrance Name"
|
||||
exit: "Exit Name"
|
||||
direction: "Direction"
|
||||
percentage: 100
|
||||
Direction must be one of 'entrance', 'exit', or 'both', and defaults to 'both' if omitted.
|
||||
Percentage is an integer from 1 to 100, and defaults to 100 when omitted."""
|
||||
|
||||
display_name = "Plando Connections"
|
||||
|
||||
default = ()
|
||||
supports_weighting = False
|
||||
|
||||
entrances: typing.ClassVar[typing.AbstractSet[str]]
|
||||
exits: typing.ClassVar[typing.AbstractSet[str]]
|
||||
|
||||
duplicate_exits: bool = False
|
||||
"""Whether or not exits should be allowed to be duplicate."""
|
||||
|
||||
def __init__(self, value: typing.Iterable[PlandoConnection]):
|
||||
self.value = list(deepcopy(value))
|
||||
super(PlandoConnections, self).__init__()
|
||||
|
||||
@classmethod
|
||||
def validate_entrance_name(cls, entrance: str) -> bool:
|
||||
return entrance.lower() in cls.entrances
|
||||
|
||||
@classmethod
|
||||
def validate_exit_name(cls, exit: str) -> bool:
|
||||
return exit.lower() in cls.exits
|
||||
|
||||
@classmethod
|
||||
def can_connect(cls, entrance: str, exit: str) -> bool:
|
||||
"""Checks that a given entrance can connect to a given exit.
|
||||
By default, this will always return true unless overridden."""
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def validate_plando_connections(cls, connections: typing.Iterable[PlandoConnection]) -> None:
|
||||
used_entrances: typing.List[str] = []
|
||||
used_exits: typing.List[str] = []
|
||||
for connection in connections:
|
||||
entrance = connection.entrance
|
||||
exit = connection.exit
|
||||
direction = connection.direction
|
||||
if direction not in (PlandoConnection.Direction.entrance,
|
||||
PlandoConnection.Direction.exit,
|
||||
PlandoConnection.Direction.both):
|
||||
raise ValueError(f"Unknown direction: {direction}")
|
||||
if entrance in used_entrances:
|
||||
raise ValueError(f"Duplicate Entrance {entrance} not allowed.")
|
||||
if not cls.duplicate_exits and exit in used_exits:
|
||||
raise ValueError(f"Duplicate Exit {exit} not allowed.")
|
||||
used_entrances.append(entrance)
|
||||
used_exits.append(exit)
|
||||
if not cls.validate_entrance_name(entrance):
|
||||
raise ValueError(f"{entrance.title()} is not a valid entrance.")
|
||||
if not cls.validate_exit_name(exit):
|
||||
raise ValueError(f"{exit.title()} is not a valid exit.")
|
||||
if not cls.can_connect(entrance, exit):
|
||||
raise ValueError(f"Connection between {entrance.title()} and {exit.title()} is invalid.")
|
||||
|
||||
@classmethod
|
||||
def from_any(cls, data: PlandoConFromAnyType) -> Self:
|
||||
if not isinstance(data, typing.Iterable):
|
||||
raise Exception(f"Cannot create plando connections from non-List value, got {type(data)}.")
|
||||
|
||||
value: typing.List[PlandoConnection] = []
|
||||
for connection in data:
|
||||
if isinstance(connection, typing.Mapping):
|
||||
percentage = connection.get("percentage", 100)
|
||||
if random.random() < float(percentage / 100):
|
||||
entrance = connection.get("entrance", None)
|
||||
if is_iterable_except_str(entrance):
|
||||
entrance = random.choice(sorted(entrance))
|
||||
exit = connection.get("exit", None)
|
||||
if is_iterable_except_str(exit):
|
||||
exit = random.choice(sorted(exit))
|
||||
direction = connection.get("direction", "both")
|
||||
|
||||
if not entrance or not exit:
|
||||
raise Exception("Plando connection must have an entrance and an exit.")
|
||||
value.append(PlandoConnection(
|
||||
entrance,
|
||||
exit,
|
||||
direction,
|
||||
percentage
|
||||
))
|
||||
elif isinstance(connection, PlandoConnection):
|
||||
if random.random() < float(connection.percentage / 100):
|
||||
value.append(connection)
|
||||
else:
|
||||
raise Exception(f"Cannot create connection from non-Dict type, got {type(connection)}.")
|
||||
cls.validate_plando_connections(value)
|
||||
return cls(value)
|
||||
|
||||
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
||||
from BaseClasses import PlandoOptions
|
||||
if self.value and not (PlandoOptions.connections & plando_options):
|
||||
# plando is disabled but plando options were given so overwrite the options
|
||||
self.value = []
|
||||
logging.warning(f"The plando connections module is turned off, "
|
||||
f"so connections for {player_name} will be ignored.")
|
||||
|
||||
@classmethod
|
||||
def get_option_name(cls, value: typing.List[PlandoConnection]) -> str:
|
||||
return ", ".join(["%s %s %s" % (connection.entrance,
|
||||
"<=>" if connection.direction == PlandoConnection.Direction.both else
|
||||
"<=" if connection.direction == PlandoConnection.Direction.exit else
|
||||
"=>",
|
||||
connection.exit) for connection in value])
|
||||
|
||||
def __getitem__(self, index: typing.SupportsIndex) -> PlandoConnection:
|
||||
return self.value.__getitem__(index)
|
||||
|
||||
def __iter__(self) -> typing.Iterator[PlandoConnection]:
|
||||
yield from self.value
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.value)
|
||||
|
||||
|
||||
class Accessibility(Choice):
|
||||
"""Set rules for reachability of your items/locations.
|
||||
Locations: ensure everything can be reached and acquired.
|
||||
|
@ -1049,7 +1272,8 @@ class ItemLinks(OptionList):
|
|||
])
|
||||
|
||||
@staticmethod
|
||||
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, allow_item_groups: bool = True) -> typing.Set:
|
||||
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world,
|
||||
allow_item_groups: bool = True) -> typing.Set:
|
||||
pool = set()
|
||||
for item_name in items:
|
||||
if item_name not in world.item_names and (not allow_item_groups or item_name not in world.item_name_groups):
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import typing
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses,\
|
||||
FreeText, Removed
|
||||
from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, \
|
||||
StartInventoryPool, PlandoBosses, PlandoConnections, PlandoTexts, FreeText, Removed
|
||||
from .EntranceShuffle import default_connections, default_dungeon_connections, \
|
||||
inverted_default_connections, inverted_default_dungeon_connections
|
||||
from .Text import TextTable
|
||||
|
||||
|
||||
class GlitchesRequired(Choice):
|
||||
|
@ -721,7 +724,27 @@ class AllowCollect(DefaultOnToggle):
|
|||
display_name = "Allow Collection of checks for other players"
|
||||
|
||||
|
||||
class ALttPPlandoConnections(PlandoConnections):
|
||||
entrances = set([connection[0] for connection in (
|
||||
*default_connections, *default_dungeon_connections, *inverted_default_connections,
|
||||
*inverted_default_dungeon_connections)])
|
||||
exits = set([connection[1] for connection in (
|
||||
*default_connections, *default_dungeon_connections, *inverted_default_connections,
|
||||
*inverted_default_dungeon_connections)])
|
||||
|
||||
|
||||
class ALttPPlandoTexts(PlandoTexts):
|
||||
"""Text plando. Format is:
|
||||
- text: 'This is your text'
|
||||
at: text_key
|
||||
percentage: 100
|
||||
Percentage is an integer from 1 to 100, and defaults to 100 when omitted."""
|
||||
valid_keys = TextTable.valid_keys
|
||||
|
||||
|
||||
alttp_options: typing.Dict[str, type(Option)] = {
|
||||
"plando_connections": ALttPPlandoConnections,
|
||||
"plando_texts": ALttPPlandoTexts,
|
||||
"start_inventory_from_pool": StartInventoryPool,
|
||||
"goal": Goal,
|
||||
"mode": Mode,
|
||||
|
|
|
@ -2538,12 +2538,12 @@ def write_strings(rom, world, player):
|
|||
tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}"
|
||||
tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}"
|
||||
|
||||
for at, text in world.plando_texts[player].items():
|
||||
for at, text, _ in world.plando_texts[player]:
|
||||
|
||||
if at not in tt:
|
||||
raise Exception(f"No text target \"{at}\" found.")
|
||||
else:
|
||||
tt[at] = text
|
||||
tt[at] = "\n".join(text)
|
||||
|
||||
rom.write_bytes(0xE0000, tt.getBytes())
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ from worlds.generic.Rules import add_rule
|
|||
|
||||
from BaseClasses import CollectionState
|
||||
from .SubClasses import ALttPLocation
|
||||
from .EntranceShuffle import door_addresses
|
||||
|
||||
from .Items import item_name_groups
|
||||
from .Options import small_key_shuffle, RandomizeShopInventories
|
||||
|
||||
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
|
||||
|
||||
logger = logging.getLogger("Shops")
|
||||
|
@ -66,6 +66,7 @@ class Shop:
|
|||
return 0
|
||||
|
||||
def get_bytes(self) -> List[int]:
|
||||
from .EntranceShuffle import door_addresses
|
||||
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
||||
entrances = self.region.entrances
|
||||
config = self.item_count
|
||||
|
@ -181,7 +182,7 @@ def push_shop_inventories(multiworld):
|
|||
|
||||
|
||||
def create_shops(multiworld, player: int):
|
||||
|
||||
from .Options import RandomizeShopInventories
|
||||
player_shop_table = shop_table.copy()
|
||||
if multiworld.include_witch_hut[player]:
|
||||
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
|
||||
|
@ -304,6 +305,7 @@ shop_generation_types = {
|
|||
|
||||
|
||||
def set_up_shops(multiworld, player: int):
|
||||
from .Options import small_key_shuffle
|
||||
# TODO: move hard+ mode changes for shields here, utilizing the new shops
|
||||
|
||||
if multiworld.retro_bow[player]:
|
||||
|
@ -426,7 +428,7 @@ def get_price_modifier(item):
|
|||
|
||||
def get_price(multiworld, item, player: int, price_type=None):
|
||||
"""Converts a raw Rupee price into a special price type"""
|
||||
|
||||
from .Options import small_key_shuffle
|
||||
if price_type:
|
||||
price_types = [price_type]
|
||||
else:
|
||||
|
|
|
@ -1289,6 +1289,415 @@ class LargeCreditBottomMapper(CharTextMapper):
|
|||
class TextTable(object):
|
||||
SIZE = 0x7355
|
||||
|
||||
valid_keys = [
|
||||
"set_cursor",
|
||||
"set_cursor2",
|
||||
"game_over_menu",
|
||||
"var_test",
|
||||
"follower_no_enter",
|
||||
"choice_1_3",
|
||||
"choice_2_3",
|
||||
"choice_3_3",
|
||||
"choice_1_2",
|
||||
"choice_2_2",
|
||||
"uncle_leaving_text",
|
||||
"uncle_dying_sewer",
|
||||
"tutorial_guard_1",
|
||||
"tutorial_guard_2",
|
||||
"tutorial_guard_3",
|
||||
"tutorial_guard_4",
|
||||
"tutorial_guard_5",
|
||||
"tutorial_guard_6",
|
||||
"tutorial_guard_7",
|
||||
"priest_sanctuary_before_leave",
|
||||
"sanctuary_enter",
|
||||
"zelda_sanctuary_story",
|
||||
"priest_sanctuary_before_pendants",
|
||||
"priest_sanctuary_after_pendants_before_master_sword",
|
||||
"priest_sanctuary_dying",
|
||||
"zelda_save_sewers",
|
||||
"priest_info",
|
||||
"zelda_sanctuary_before_leave",
|
||||
"telepathic_intro",
|
||||
"telepathic_reminder",
|
||||
"zelda_go_to_throne",
|
||||
"zelda_push_throne",
|
||||
"zelda_switch_room_pull",
|
||||
"zelda_save_lets_go",
|
||||
"zelda_save_repeat",
|
||||
"zelda_before_pendants",
|
||||
"zelda_after_pendants_before_master_sword",
|
||||
"telepathic_zelda_right_after_master_sword",
|
||||
"zelda_sewers",
|
||||
"zelda_switch_room",
|
||||
"kakariko_saharalasa_wife",
|
||||
"kakariko_saharalasa_wife_sword_story",
|
||||
"kakariko_saharalasa_wife_closing",
|
||||
"kakariko_saharalasa_after_master_sword",
|
||||
"kakariko_alert_guards",
|
||||
"sahasrahla_quest_have_pendants",
|
||||
"sahasrahla_quest_have_master_sword",
|
||||
"sahasrahla_quest_information",
|
||||
"sahasrahla_bring_courage",
|
||||
"sahasrahla_have_ice_rod",
|
||||
"telepathic_sahasrahla_beat_agahnim",
|
||||
"telepathic_sahasrahla_beat_agahnim_no_pearl",
|
||||
"sahasrahla_have_boots_no_icerod",
|
||||
"sahasrahla_have_courage",
|
||||
"sahasrahla_found",
|
||||
"sign_rain_north_of_links_house",
|
||||
"sign_north_of_links_house",
|
||||
"sign_path_to_death_mountain",
|
||||
"sign_lost_woods",
|
||||
"sign_zoras",
|
||||
"sign_outside_magic_shop",
|
||||
"sign_death_mountain_cave_back",
|
||||
"sign_east_of_links_house",
|
||||
"sign_south_of_lumberjacks",
|
||||
"sign_east_of_desert",
|
||||
"sign_east_of_sanctuary",
|
||||
"sign_east_of_castle",
|
||||
"sign_north_of_lake",
|
||||
"sign_desert_thief",
|
||||
"sign_lumberjacks_house",
|
||||
"sign_north_kakariko",
|
||||
"witch_bring_mushroom",
|
||||
"witch_brewing_the_item",
|
||||
"witch_assistant_no_bottle",
|
||||
"witch_assistant_no_empty_bottle",
|
||||
"witch_assistant_informational",
|
||||
"witch_assistant_no_bottle_buying",
|
||||
"potion_shop_no_empty_bottles",
|
||||
"item_get_lamp",
|
||||
"item_get_boomerang",
|
||||
"item_get_bow",
|
||||
"item_get_shovel",
|
||||
"item_get_magic_cape",
|
||||
"item_get_powder",
|
||||
"item_get_flippers",
|
||||
"item_get_power_gloves",
|
||||
"item_get_pendant_courage",
|
||||
"item_get_pendant_power",
|
||||
"item_get_pendant_wisdom",
|
||||
"item_get_mushroom",
|
||||
"item_get_book",
|
||||
"item_get_moonpearl",
|
||||
"item_get_compass",
|
||||
"item_get_map",
|
||||
"item_get_ice_rod",
|
||||
"item_get_fire_rod",
|
||||
"item_get_ether",
|
||||
"item_get_bombos",
|
||||
"item_get_quake",
|
||||
"item_get_hammer",
|
||||
"item_get_flute",
|
||||
"item_get_cane_of_somaria",
|
||||
"item_get_hookshot",
|
||||
"item_get_bombs",
|
||||
"item_get_bottle",
|
||||
"item_get_big_key",
|
||||
"item_get_titans_mitts",
|
||||
"item_get_magic_mirror",
|
||||
"item_get_fake_mastersword",
|
||||
"post_item_get_mastersword",
|
||||
"item_get_red_potion",
|
||||
"item_get_green_potion",
|
||||
"item_get_blue_potion",
|
||||
"item_get_bug_net",
|
||||
"item_get_blue_mail",
|
||||
"item_get_red_mail",
|
||||
"item_get_temperedsword",
|
||||
"item_get_mirror_shield",
|
||||
"item_get_cane_of_byrna",
|
||||
"missing_big_key",
|
||||
"missing_magic",
|
||||
"item_get_pegasus_boots",
|
||||
"talking_tree_info_start",
|
||||
"talking_tree_info_1",
|
||||
"talking_tree_info_2",
|
||||
"talking_tree_info_3",
|
||||
"talking_tree_info_4",
|
||||
"talking_tree_other",
|
||||
"item_get_pendant_power_alt",
|
||||
"item_get_pendant_wisdom_alt",
|
||||
"game_shooting_choice",
|
||||
"game_shooting_yes",
|
||||
"game_shooting_no",
|
||||
"game_shooting_continue",
|
||||
"pond_of_wishing",
|
||||
"pond_item_select",
|
||||
"pond_item_test",
|
||||
"pond_will_upgrade",
|
||||
"pond_item_test_no",
|
||||
"pond_item_test_no_no",
|
||||
"pond_item_boomerang",
|
||||
"pond_item_shield",
|
||||
"pond_item_silvers",
|
||||
"pond_item_bottle_filled",
|
||||
"pond_item_sword",
|
||||
"pond_of_wishing_happiness",
|
||||
"pond_of_wishing_choice",
|
||||
"pond_of_wishing_bombs",
|
||||
"pond_of_wishing_arrows",
|
||||
"pond_of_wishing_full_upgrades",
|
||||
"mountain_old_man_first",
|
||||
"mountain_old_man_deadend",
|
||||
"mountain_old_man_turn_right",
|
||||
"mountain_old_man_lost_and_alone",
|
||||
"mountain_old_man_drop_off",
|
||||
"mountain_old_man_in_his_cave_pre_agahnim",
|
||||
"mountain_old_man_in_his_cave",
|
||||
"mountain_old_man_in_his_cave_post_agahnim",
|
||||
"tavern_old_man_awake",
|
||||
"tavern_old_man_unactivated_flute",
|
||||
"tavern_old_man_know_tree_unactivated_flute",
|
||||
"tavern_old_man_have_flute",
|
||||
"chicken_hut_lady",
|
||||
"running_man",
|
||||
"game_race_sign",
|
||||
"sign_bumper_cave",
|
||||
"sign_catfish",
|
||||
"sign_north_village_of_outcasts",
|
||||
"sign_south_of_bumper_cave",
|
||||
"sign_east_of_pyramid",
|
||||
"sign_east_of_bomb_shop",
|
||||
"sign_east_of_mire",
|
||||
"sign_village_of_outcasts",
|
||||
"sign_before_wishing_pond",
|
||||
"sign_before_catfish_area",
|
||||
"castle_wall_guard",
|
||||
"gate_guard",
|
||||
"telepathic_tile_eastern_palace",
|
||||
"telepathic_tile_tower_of_hera_floor_4",
|
||||
"hylian_text_1",
|
||||
"mastersword_pedestal_translated",
|
||||
"telepathic_tile_spectacle_rock",
|
||||
"telepathic_tile_swamp_entrance",
|
||||
"telepathic_tile_thieves_town_upstairs",
|
||||
"telepathic_tile_misery_mire",
|
||||
"hylian_text_2",
|
||||
"desert_entry_translated",
|
||||
"telepathic_tile_under_ganon",
|
||||
"telepathic_tile_palace_of_darkness",
|
||||
"telepathic_tile_desert_bonk_torch_room",
|
||||
"telepathic_tile_castle_tower",
|
||||
"telepathic_tile_ice_large_room",
|
||||
"telepathic_tile_turtle_rock",
|
||||
"telepathic_tile_ice_entrance",
|
||||
"telepathic_tile_ice_stalfos_knights_room",
|
||||
"telepathic_tile_tower_of_hera_entrance",
|
||||
"houlihan_room",
|
||||
"caught_a_bee",
|
||||
"caught_a_fairy",
|
||||
"no_empty_bottles",
|
||||
"game_race_boy_time",
|
||||
"game_race_girl",
|
||||
"game_race_boy_success",
|
||||
"game_race_boy_failure",
|
||||
"game_race_boy_already_won",
|
||||
"game_race_boy_sneaky",
|
||||
"bottle_vendor_choice",
|
||||
"bottle_vendor_get",
|
||||
"bottle_vendor_no",
|
||||
"bottle_vendor_already_collected",
|
||||
"bottle_vendor_bee",
|
||||
"bottle_vendor_fish",
|
||||
"hobo_item_get_bottle",
|
||||
"blacksmiths_what_you_want",
|
||||
"blacksmiths_paywall",
|
||||
"blacksmiths_extra_okay",
|
||||
"blacksmiths_tempered_already",
|
||||
"blacksmiths_temper_no",
|
||||
"blacksmiths_bogart_sword",
|
||||
"blacksmiths_get_sword",
|
||||
"blacksmiths_shop_before_saving",
|
||||
"blacksmiths_shop_saving",
|
||||
"blacksmiths_collect_frog",
|
||||
"blacksmiths_still_working",
|
||||
"blacksmiths_saving_bows",
|
||||
"blacksmiths_hammer_anvil",
|
||||
"dark_flute_boy_storytime",
|
||||
"dark_flute_boy_get_shovel",
|
||||
"dark_flute_boy_no_get_shovel",
|
||||
"dark_flute_boy_flute_not_found",
|
||||
"dark_flute_boy_after_shovel_get",
|
||||
"shop_fortune_teller_lw_hint_0",
|
||||
"shop_fortune_teller_lw_hint_1",
|
||||
"shop_fortune_teller_lw_hint_2",
|
||||
"shop_fortune_teller_lw_hint_3",
|
||||
"shop_fortune_teller_lw_hint_4",
|
||||
"shop_fortune_teller_lw_hint_5",
|
||||
"shop_fortune_teller_lw_hint_6",
|
||||
"shop_fortune_teller_lw_hint_7",
|
||||
"shop_fortune_teller_lw_no_rupees",
|
||||
"shop_fortune_teller_lw",
|
||||
"shop_fortune_teller_lw_post_hint",
|
||||
"shop_fortune_teller_lw_no",
|
||||
"shop_fortune_teller_lw_hint_8",
|
||||
"shop_fortune_teller_lw_hint_9",
|
||||
"shop_fortune_teller_lw_hint_10",
|
||||
"shop_fortune_teller_lw_hint_11",
|
||||
"shop_fortune_teller_lw_hint_12",
|
||||
"shop_fortune_teller_lw_hint_13",
|
||||
"shop_fortune_teller_lw_hint_14",
|
||||
"shop_fortune_teller_lw_hint_15",
|
||||
"dark_sanctuary",
|
||||
"dark_sanctuary_hint_0",
|
||||
"dark_sanctuary_no",
|
||||
"dark_sanctuary_hint_1",
|
||||
"dark_sanctuary_yes",
|
||||
"dark_sanctuary_hint_2",
|
||||
"sick_kid_no_bottle",
|
||||
"sick_kid_trade",
|
||||
"sick_kid_post_trade",
|
||||
"desert_thief_sitting",
|
||||
"desert_thief_following",
|
||||
"desert_thief_question",
|
||||
"desert_thief_question_yes",
|
||||
"desert_thief_after_item_get",
|
||||
"desert_thief_reassure",
|
||||
"hylian_text_3",
|
||||
"tablet_ether_book",
|
||||
"tablet_bombos_book",
|
||||
"magic_bat_wake",
|
||||
"magic_bat_give_half_magic",
|
||||
"intro_main",
|
||||
"intro_throne_room",
|
||||
"intro_zelda_cell",
|
||||
"intro_agahnim",
|
||||
"pickup_purple_chest",
|
||||
"bomb_shop",
|
||||
"bomb_shop_big_bomb",
|
||||
"bomb_shop_big_bomb_buy",
|
||||
"item_get_big_bomb",
|
||||
"kiki_second_extortion",
|
||||
"kiki_second_extortion_no",
|
||||
"kiki_second_extortion_yes",
|
||||
"kiki_first_extortion",
|
||||
"kiki_first_extortion_yes",
|
||||
"kiki_first_extortion_no",
|
||||
"kiki_leaving_screen",
|
||||
"blind_in_the_cell",
|
||||
"blind_by_the_light",
|
||||
"blind_not_that_way",
|
||||
"aginah_l1sword_no_book",
|
||||
"aginah_l1sword_with_pendants",
|
||||
"aginah",
|
||||
"aginah_need_better_sword",
|
||||
"aginah_have_better_sword",
|
||||
"catfish",
|
||||
"catfish_after_item",
|
||||
"lumberjack_right",
|
||||
"lumberjack_left",
|
||||
"lumberjack_left_post_agahnim",
|
||||
"fighting_brothers_right",
|
||||
"fighting_brothers_right_opened",
|
||||
"fighting_brothers_left",
|
||||
"maiden_crystal_1",
|
||||
"maiden_crystal_2",
|
||||
"maiden_crystal_3",
|
||||
"maiden_crystal_4",
|
||||
"maiden_crystal_5",
|
||||
"maiden_crystal_6",
|
||||
"maiden_crystal_7",
|
||||
"maiden_ending",
|
||||
"maiden_confirm_understood",
|
||||
"barrier_breaking",
|
||||
"maiden_crystal_7_again",
|
||||
"agahnim_zelda_teleport",
|
||||
"agahnim_magic_running_away",
|
||||
"agahnim_hide_and_seek_found",
|
||||
"agahnim_defeated",
|
||||
"agahnim_final_meeting",
|
||||
"zora_meeting",
|
||||
"zora_tells_cost",
|
||||
"zora_get_flippers",
|
||||
"zora_no_cash",
|
||||
"zora_no_buy_item",
|
||||
"kakariko_saharalasa_grandson",
|
||||
"kakariko_saharalasa_grandson_next",
|
||||
"dark_palace_tree_dude",
|
||||
"fairy_wishing_ponds",
|
||||
"fairy_wishing_ponds_no",
|
||||
"pond_of_wishing_no",
|
||||
"pond_of_wishing_return_item",
|
||||
"pond_of_wishing_throw",
|
||||
"pond_pre_item_silvers",
|
||||
"pond_of_wishing_great_luck",
|
||||
"pond_of_wishing_good_luck",
|
||||
"pond_of_wishing_meh_luck",
|
||||
"pond_of_wishing_bad_luck",
|
||||
"pond_of_wishing_fortune",
|
||||
"item_get_14_heart",
|
||||
"item_get_24_heart",
|
||||
"item_get_34_heart",
|
||||
"item_get_whole_heart",
|
||||
"item_get_sanc_heart",
|
||||
"fairy_fountain_refill",
|
||||
"death_mountain_bullied_no_pearl",
|
||||
"death_mountain_bullied_with_pearl",
|
||||
"death_mountain_bully_no_pearl",
|
||||
"death_mountain_bully_with_pearl",
|
||||
"shop_darkworld_enter",
|
||||
"game_chest_village_of_outcasts",
|
||||
"game_chest_no_cash",
|
||||
"game_chest_not_played",
|
||||
"game_chest_played",
|
||||
"game_chest_village_of_outcasts_play",
|
||||
"shop_first_time",
|
||||
"shop_already_have",
|
||||
"shop_buy_shield",
|
||||
"shop_buy_red_potion",
|
||||
"shop_buy_arrows",
|
||||
"shop_buy_bombs",
|
||||
"shop_buy_bee",
|
||||
"shop_buy_heart",
|
||||
"shop_first_no_bottle_buy",
|
||||
"shop_buy_no_space",
|
||||
"ganon_fall_in",
|
||||
"ganon_phase_3",
|
||||
"lost_woods_thief",
|
||||
"blinds_hut_dude",
|
||||
"end_triforce",
|
||||
"toppi_fallen",
|
||||
"kakariko_tavern_fisherman",
|
||||
"thief_money",
|
||||
"thief_desert_rupee_cave",
|
||||
"thief_ice_rupee_cave",
|
||||
"telepathic_tile_south_east_darkworld_cave",
|
||||
"cukeman",
|
||||
"cukeman_2",
|
||||
"potion_shop_no_cash",
|
||||
"kakariko_powdered_chicken",
|
||||
"game_chest_south_of_kakariko",
|
||||
"game_chest_play_yes",
|
||||
"game_chest_play_no",
|
||||
"game_chest_lost_woods",
|
||||
"kakariko_flophouse_man_no_flippers",
|
||||
"kakariko_flophouse_man",
|
||||
"menu_start_2",
|
||||
"menu_start_3",
|
||||
"menu_pause",
|
||||
"game_digging_choice",
|
||||
"game_digging_start",
|
||||
"game_digging_no_cash",
|
||||
"game_digging_end_time",
|
||||
"game_digging_come_back_later",
|
||||
"game_digging_no_follower",
|
||||
"menu_start_4",
|
||||
"ganon_fall_in_alt",
|
||||
"ganon_phase_3_alt",
|
||||
"sign_east_death_mountain_bridge",
|
||||
"fish_money",
|
||||
"sign_ganons_tower",
|
||||
"sign_ganon",
|
||||
"ganon_phase_3_no_bow",
|
||||
"ganon_phase_3_no_silvers_alt",
|
||||
"ganon_phase_3_no_silvers",
|
||||
"ganon_phase_3_silvers",
|
||||
"murahdahla",
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._text = OrderedDict()
|
||||
self.setDefaultText()
|
||||
|
|
|
@ -68,9 +68,3 @@ class PlandoItem(NamedTuple):
|
|||
raise exception(warning)
|
||||
else:
|
||||
self.warn(warning)
|
||||
|
||||
|
||||
class PlandoConnection(NamedTuple):
|
||||
entrance: str
|
||||
exit: str
|
||||
direction: str # entrance, exit or both
|
||||
|
|
|
@ -2,10 +2,14 @@ import random
|
|||
from dataclasses import dataclass
|
||||
|
||||
from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \
|
||||
PerGameCommonOptions
|
||||
PerGameCommonOptions, PlandoConnections
|
||||
from .Names import LocationName
|
||||
|
||||
|
||||
class KDL3PlandoConnections(PlandoConnections):
|
||||
entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)}
|
||||
|
||||
|
||||
class Goal(Choice):
|
||||
"""
|
||||
Zero: collect the Heart Stars, and defeat Zero in the Hyper Zone.
|
||||
|
@ -400,6 +404,7 @@ class Gifting(Toggle):
|
|||
|
||||
@dataclass
|
||||
class KDL3Options(PerGameCommonOptions):
|
||||
plando_connections: KDL3PlandoConnections
|
||||
death_link: DeathLink
|
||||
game_language: GameLanguage
|
||||
goal: Goal
|
||||
|
|
|
@ -129,8 +129,8 @@ def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_patte
|
|||
}
|
||||
|
||||
possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)]
|
||||
if world.multiworld.plando_connections[world.player]:
|
||||
for connection in world.multiworld.plando_connections[world.player]:
|
||||
if world.options.plando_connections:
|
||||
for connection in world.options.plando_connections:
|
||||
try:
|
||||
entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1)
|
||||
stage_world, stage_stage = connection.exit.rsplit(" ", 1)
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing
|
|||
from argparse import Namespace
|
||||
|
||||
from BaseClasses import MultiWorld, PlandoOptions, CollectionState
|
||||
from test.TestBase import WorldTestBase
|
||||
from test.bases import WorldTestBase
|
||||
from test.general import gen_steps
|
||||
from worlds import AutoWorld
|
||||
from worlds.AutoWorld import call_all
|
||||
|
@ -32,6 +32,5 @@ class KDL3TestBase(WorldTestBase):
|
|||
})
|
||||
self.multiworld.set_options(args)
|
||||
self.multiworld.plando_options = PlandoOptions.connections
|
||||
self.multiworld.plando_connections = self.options["plando_connections"] if "plando_connections" in self.options.keys() else []
|
||||
for step in gen_steps:
|
||||
call_all(self.multiworld, step)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from . import KDL3TestBase
|
||||
from worlds.generic import PlandoConnection
|
||||
from Options import PlandoConnection
|
||||
from ..Names import LocationName
|
||||
import typing
|
||||
|
||||
|
@ -49,12 +49,10 @@ class TestShiro(KDL3TestBase):
|
|||
options = {
|
||||
"open_world": False,
|
||||
"plando_connections": [
|
||||
[],
|
||||
[
|
||||
PlandoConnection("Grass Land 1", "Iceberg 5", "both"),
|
||||
PlandoConnection("Grass Land 2", "Ripple Field 5", "both"),
|
||||
PlandoConnection("Grass Land 3", "Grass Land 1", "both")
|
||||
]],
|
||||
],
|
||||
"stage_shuffle": "shuffled",
|
||||
"plando_options": "connections"
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ from typing import Dict
|
|||
|
||||
from schema import And, Optional, Or, Schema
|
||||
|
||||
from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \
|
||||
StartInventoryPool, Toggle
|
||||
from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, \
|
||||
PlandoConnections, Range, StartInventoryPool, Toggle, Visibility
|
||||
from worlds.messenger.portals import CHECKPOINTS, PORTALS, SHOP_POINTS
|
||||
|
||||
|
||||
class MessengerAccessibility(Accessibility):
|
||||
|
@ -13,6 +14,36 @@ class MessengerAccessibility(Accessibility):
|
|||
__doc__ = Accessibility.__doc__.replace(f"default {Accessibility.default}", f"default {default}")
|
||||
|
||||
|
||||
class PortalPlando(PlandoConnections):
|
||||
"""
|
||||
Plando connections to be used with portal shuffle. Direction is ignored.
|
||||
List of valid connections can be found here: https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/messenger/portals.py#L12.
|
||||
The entering Portal should *not* have "Portal" appended.
|
||||
For the exits, those in checkpoints and shops should just be the name of the spot, while portals should have " Portal" at the end.
|
||||
Example:
|
||||
- entrance: Riviere Turquoise
|
||||
exit: Wingsuit
|
||||
- entrance: Sunken Shrine
|
||||
exit: Sunny Day
|
||||
- entrance: Searing Crags
|
||||
exit: Glacial Peak Portal
|
||||
"""
|
||||
portals = [f"{portal} Portal" for portal in PORTALS]
|
||||
shop_points = [point for points in SHOP_POINTS.values() for point in points]
|
||||
checkpoints = [point for points in CHECKPOINTS.values() for point in points]
|
||||
portal_entrances = PORTALS
|
||||
portal_exits = portals + shop_points + checkpoints
|
||||
entrances = portal_entrances
|
||||
exits = portal_exits
|
||||
|
||||
|
||||
# for back compatibility. To later be replaced with transition plando
|
||||
class HiddenPortalPlando(PortalPlando):
|
||||
visibility = Visibility.none
|
||||
entrances = PortalPlando.entrances
|
||||
exits = PortalPlando.exits
|
||||
|
||||
|
||||
class Logic(Choice):
|
||||
"""
|
||||
The level of logic to use when determining what locations in your world are accessible.
|
||||
|
@ -205,3 +236,5 @@ class MessengerOptions(DeathLinkMixin, PerGameCommonOptions):
|
|||
traps: Traps
|
||||
shop_price: ShopPrices
|
||||
shop_price_plan: PlannedShopPrices
|
||||
portal_plando: PortalPlando
|
||||
plando_connections: HiddenPortalPlando
|
||||
|
|
|
@ -2,8 +2,7 @@ from copy import deepcopy
|
|||
from typing import List, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState, PlandoOptions
|
||||
from worlds.generic import PlandoConnection
|
||||
from .options import ShufflePortals
|
||||
from Options import PlandoConnection
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MessengerWorld
|
||||
|
@ -207,6 +206,8 @@ REGION_ORDER = [
|
|||
|
||||
def shuffle_portals(world: "MessengerWorld") -> None:
|
||||
"""shuffles the output of the portals from the main hub"""
|
||||
from .options import ShufflePortals
|
||||
|
||||
def create_mapping(in_portal: str, warp: str) -> str:
|
||||
"""assigns the chosen output to the input"""
|
||||
parent = out_to_parent[warp]
|
||||
|
@ -247,7 +248,9 @@ def shuffle_portals(world: "MessengerWorld") -> None:
|
|||
available_portals = [val for zone in shop_points.values() for val in zone]
|
||||
world.random.shuffle(available_portals)
|
||||
|
||||
plando = world.multiworld.plando_connections[world.player]
|
||||
plando = world.options.portal_plando.value
|
||||
if not plando:
|
||||
plando = world.options.plando_connections.value
|
||||
if plando and world.multiworld.plando_options & PlandoOptions.connections:
|
||||
handle_planned_portals(plando)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import typing
|
||||
from Options import Choice, Option, Toggle, DefaultOnToggle, Range, OptionList, DeathLink
|
||||
from Options import Choice, Option, Toggle, DefaultOnToggle, Range, OptionList, DeathLink, PlandoConnections
|
||||
from .Constants import region_info
|
||||
|
||||
|
||||
class AdvancementGoal(Range):
|
||||
|
@ -97,7 +98,19 @@ class StartingItems(OptionList):
|
|||
display_name = "Starting Items"
|
||||
|
||||
|
||||
class MCPlandoConnections(PlandoConnections):
|
||||
entrances = set(connection[0] for connection in region_info["default_connections"])
|
||||
exits = set(connection[1] for connection in region_info["default_connections"])
|
||||
|
||||
@classmethod
|
||||
def can_connect(cls, entrance, exit):
|
||||
if exit in region_info["illegal_connections"] and entrance in region_info["illegal_connections"][exit]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
minecraft_options: typing.Dict[str, type(Option)] = {
|
||||
"plando_connections": MCPlandoConnections,
|
||||
"advancement_goal": AdvancementGoal,
|
||||
"egg_shards_required": EggShardsRequired,
|
||||
"egg_shards_available": EggShardsAvailable,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import typing
|
||||
import random
|
||||
from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink
|
||||
from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections
|
||||
from .EntranceShuffle import entrance_shuffle_table
|
||||
from .LogicTricks import normalized_name_tricks
|
||||
from .ColorSFXOptions import *
|
||||
|
||||
|
@ -29,6 +30,11 @@ class TrackRandomRange(Range):
|
|||
raise RuntimeError(f"All options specified in \"{cls.display_name}\" are weighted as zero.")
|
||||
|
||||
|
||||
class OoTPlandoConnections(PlandoConnections):
|
||||
entrances = set([connection[1][0] for connection in entrance_shuffle_table])
|
||||
exits = set([connection[2][0] for connection in entrance_shuffle_table if len(connection) > 2])
|
||||
|
||||
|
||||
class Logic(Choice):
|
||||
"""Set the logic used for the generator.
|
||||
Glitchless: Normal gameplay. Can enable more difficult logical paths using the Logic Tricks option.
|
||||
|
@ -1277,6 +1283,7 @@ class LogicTricks(OptionList):
|
|||
|
||||
# All options assembled into a single dict
|
||||
oot_options: typing.Dict[str, type(Option)] = {
|
||||
"plando_connections": OoTPlandoConnections,
|
||||
"logic_rules": Logic,
|
||||
"logic_no_night_tokens_without_suns_song": NightTokens,
|
||||
**open_options,
|
||||
|
|
|
@ -32,7 +32,7 @@ from .Cosmetics import patch_cosmetics
|
|||
|
||||
from Utils import get_options
|
||||
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
|
||||
from Options import Range, Toggle, VerifyKeys, Accessibility
|
||||
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections
|
||||
from Fill import fill_restrictive, fast_fill, FillError
|
||||
from worlds.generic.Rules import exclusion_rules, add_item_rule
|
||||
from ..AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
|
@ -201,6 +201,8 @@ class OOTWorld(World):
|
|||
option_value = bool(result)
|
||||
elif isinstance(result, VerifyKeys):
|
||||
option_value = result.value
|
||||
elif isinstance(result, PlandoConnections):
|
||||
option_value = result.value
|
||||
else:
|
||||
option_value = result.current_key
|
||||
setattr(self, option_name, option_value)
|
||||
|
|
|
@ -10,7 +10,7 @@ from .er_scripts import create_er_regions
|
|||
from .er_data import portal_mapping
|
||||
from .options import TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from worlds.generic import PlandoConnection
|
||||
from Options import PlandoConnection
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
|
||||
|
@ -70,17 +70,17 @@ class TunicWorld(World):
|
|||
seed_groups: Dict[str, SeedGroup] = {}
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if self.multiworld.plando_connections[self.player]:
|
||||
for index, cxn in enumerate(self.multiworld.plando_connections[self.player]):
|
||||
if self.options.plando_connections:
|
||||
for index, cxn in enumerate(self.options.plando_connections):
|
||||
# making shops second to simplify other things later
|
||||
if cxn.entrance.startswith("Shop"):
|
||||
replacement = PlandoConnection(cxn.exit, "Shop Portal", "both")
|
||||
self.multiworld.plando_connections[self.player].remove(cxn)
|
||||
self.multiworld.plando_connections[self.player].insert(index, replacement)
|
||||
self.options.plando_connections.value.remove(cxn)
|
||||
self.options.plando_connections.value.insert(index, replacement)
|
||||
elif cxn.exit.startswith("Shop"):
|
||||
replacement = PlandoConnection(cxn.entrance, "Shop Portal", "both")
|
||||
self.multiworld.plando_connections[self.player].remove(cxn)
|
||||
self.multiworld.plando_connections[self.player].insert(index, replacement)
|
||||
self.options.plando_connections.value.remove(cxn)
|
||||
self.options.plando_connections.value.insert(index, replacement)
|
||||
|
||||
# Universal tracker stuff, shouldn't do anything in standard gen
|
||||
if hasattr(self.multiworld, "re_gen_passthrough"):
|
||||
|
|
|
@ -3,8 +3,8 @@ from BaseClasses import Region, ItemClassification, Item, Location
|
|||
from .locations import location_table
|
||||
from .er_data import Portal, tunic_er_regions, portal_mapping, traversal_requirements, DeadEnd
|
||||
from .er_rules import set_er_region_rules
|
||||
from Options import PlandoConnection
|
||||
from .options import EntranceRando
|
||||
from worlds.generic import PlandoConnection
|
||||
from random import Random
|
||||
from copy import deepcopy
|
||||
|
||||
|
@ -194,7 +194,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_rules)
|
||||
|
||||
if world.options.entrance_rando.value in EntranceRando.options:
|
||||
plando_connections = world.multiworld.plando_connections[world.player]
|
||||
plando_connections = world.options.plando_connections.value
|
||||
else:
|
||||
plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"]
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
from Options import (DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PerGameCommonOptions,
|
||||
OptionGroup)
|
||||
from Options import (DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PlandoConnections,
|
||||
PerGameCommonOptions, OptionGroup)
|
||||
from .er_data import portal_mapping
|
||||
|
||||
|
||||
class SwordProgression(DefaultOnToggle):
|
||||
|
@ -170,6 +171,13 @@ class ShuffleLadders(Toggle):
|
|||
"""
|
||||
internal_name = "shuffle_ladders"
|
||||
display_name = "Shuffle Ladders"
|
||||
|
||||
|
||||
class TUNICPlandoConnections(PlandoConnections):
|
||||
entrances = {*(portal.name for portal in portal_mapping), "Shop", "Shop Portal"}
|
||||
exits = {*(portal.name for portal in portal_mapping), "Shop", "Shop Portal"}
|
||||
|
||||
duplicate_exits = True
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -190,7 +198,8 @@ class TunicOptions(PerGameCommonOptions):
|
|||
lanternless: Lanternless
|
||||
maskless: Maskless
|
||||
laurels_location: LaurelsLocation
|
||||
|
||||
plando_connections: TUNICPlandoConnections
|
||||
|
||||
|
||||
tunic_option_groups = [
|
||||
OptionGroup("Logic Options", [
|
||||
|
|
Loading…
Reference in New Issue