The Witness: Obelisk Keys (#2805)
This commit is contained in:
parent
ae6c16bde1
commit
4bf676e588
|
@ -265,6 +265,13 @@ Doors:
|
|||
2165 - Caves Panels - 0x3369D,0x00FF8,0x0A16E,0x335AB,0x335AC
|
||||
2170 - Tunnels Panels - 0x09E85,0x039B4
|
||||
|
||||
2200 - Desert Obelisk Key - 0x0332B,0x03367,0x28B8A,0x037B6,0x037B2,0x000F7,0x3351D,0x0053C,0x00771,0x335C8,0x335C9,0x337F8,0x037BB,0x220E4,0x220E5,0x334B9,0x334BC,0x22106,0x0A14C,0x0A14D,0x00359
|
||||
2201 - Monastery Obelisk Key - 0x03ABC,0x03ABE,0x03AC0,0x03AC4,0x03AC5,0x03BE2,0x03BE3,0x0A409,0x006E5,0x006E6,0x006E7,0x034A7,0x034AD,0x034AF,0x03DAB,0x03DAC,0x03DAD,0x03E01,0x289F4,0x289F5,0x00263
|
||||
2202 - Treehouse Obelisk Key - 0x0053D,0x0053E,0x00769,0x33721,0x220A7,0x220BD,0x03B22,0x03B23,0x03B24,0x03B25,0x03A79,0x28ABD,0x28ABE,0x3388F,0x28B29,0x28B2A,0x018B6,0x033BE,0x033BF,0x033DD,0x033E5,0x28AE9,0x3348F,0x00097
|
||||
2203 - Mountainside Obelisk Key - 0x001A3,0x335AE,0x000D3,0x035F5,0x09D5D,0x09D5E,0x09D63,0x3370E,0x035DE,0x03601,0x03603,0x03D0D,0x3369A,0x336C8,0x33505,0x03A9E,0x016B2,0x3365F,0x03731,0x036CE,0x03C07,0x03A93,0x03AA6,0x3397C,0x0105D,0x0A304,0x035CB,0x035CF,0x00367
|
||||
2204 - Quarry Obelisk Key - 0x28A7B,0x005F6,0x00859,0x17CB9,0x28A4A,0x334B6,0x00614,0x0069D,0x28A4C,0x289CF,0x289D1,0x33692,0x03E77,0x03E7C,0x22073
|
||||
2205 - Town Obelisk Key - 0x035C7,0x01848,0x03D06,0x33530,0x33600,0x28A2F,0x28A37,0x334A3,0x3352F,0x33857,0x33879,0x03C19,0x28B30,0x035C9,0x03335,0x03412,0x038A6,0x038AA,0x03E3F,0x03E40,0x28B8E,0x28B91,0x03BCE,0x03BCF,0x03BD1,0x339B6,0x33A20,0x33A29,0x33A2A,0x33B06,0x0A16C
|
||||
|
||||
Lasers:
|
||||
1500 - Symmetry Laser - 0x00509
|
||||
1501 - Desert Laser - 0x012FB
|
||||
|
|
|
@ -89,6 +89,46 @@ class WitnessWorld(World):
|
|||
'entity_to_name': StaticWitnessLogic.ENTITY_ID_TO_NAME,
|
||||
}
|
||||
|
||||
def determine_sufficient_progression(self):
|
||||
"""
|
||||
Determine whether there are enough progression items in this world to consider it "interactive".
|
||||
In the case of singleplayer, this just outputs a warning.
|
||||
In the case of multiplayer, the requirements are a bit stricter and an Exception is raised.
|
||||
"""
|
||||
|
||||
# A note on Obelisk Keys:
|
||||
# Obelisk Keys are never relevant in singleplayer, because the locations they lock are irrelevant to in-game
|
||||
# progress and irrelevant to all victory conditions. Thus, I consider them "fake progression" for singleplayer.
|
||||
# However, those locations could obviously contain big items needed for other players, so I consider
|
||||
# "Obelisk Keys only" valid for multiworld.
|
||||
|
||||
# A note on Laser Shuffle:
|
||||
# In singleplayer, I don't mind "Ice Rod Hunt" type gameplay, so "laser shuffle only" is valid.
|
||||
# However, I do not want to allow "Ice Rod Hunt" style gameplay in multiworld, so "laser shuffle only" is
|
||||
# not considered interactive enough for multiworld.
|
||||
|
||||
interacts_sufficiently_with_multiworld = (
|
||||
self.options.shuffle_symbols
|
||||
or self.options.shuffle_doors
|
||||
or self.options.obelisk_keys and self.options.shuffle_EPs
|
||||
)
|
||||
|
||||
has_locally_relevant_progression = (
|
||||
self.options.shuffle_symbols
|
||||
or self.options.shuffle_doors
|
||||
or self.options.shuffle_lasers
|
||||
or self.options.shuffle_boat
|
||||
or self.options.early_caves == "add_to_pool" and self.options.victory_condition == "challenge"
|
||||
)
|
||||
|
||||
if not has_locally_relevant_progression and self.multiworld.players == 1:
|
||||
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression"
|
||||
f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.")
|
||||
elif not interacts_sufficiently_with_multiworld and self.multiworld.players > 1:
|
||||
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough"
|
||||
f" progression items that can be placed in other players' worlds. Please turn on Symbol"
|
||||
f" Shuffle, Door Shuffle or Obelisk Keys.")
|
||||
|
||||
def generate_early(self):
|
||||
disabled_locations = self.options.exclude_locations.value
|
||||
|
||||
|
@ -102,26 +142,9 @@ class WitnessWorld(World):
|
|||
)
|
||||
self.regio: WitnessRegions = WitnessRegions(self.locat, self)
|
||||
|
||||
interacts_with_multiworld = (
|
||||
self.options.shuffle_symbols or
|
||||
self.options.shuffle_doors or
|
||||
self.options.shuffle_lasers == "anywhere"
|
||||
)
|
||||
self.log_ids_to_hints = dict()
|
||||
|
||||
has_progression = (
|
||||
interacts_with_multiworld
|
||||
or self.options.shuffle_lasers == "local"
|
||||
or self.options.shuffle_boat
|
||||
or self.options.early_caves == "add_to_pool"
|
||||
)
|
||||
|
||||
if not has_progression and self.multiworld.players == 1:
|
||||
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression"
|
||||
f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.")
|
||||
elif not interacts_with_multiworld and self.multiworld.players > 1:
|
||||
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough"
|
||||
f" progression items that can be placed in other players' worlds. Please turn on Symbol"
|
||||
f" Shuffle, Door Shuffle or non-local Laser Shuffle.")
|
||||
self.determine_sufficient_progression()
|
||||
|
||||
if self.options.shuffle_lasers == "local":
|
||||
self.options.local_items.value |= self.item_name_groups["Lasers"]
|
||||
|
|
|
@ -120,6 +120,14 @@ class EnvironmentalPuzzlesDifficulty(Choice):
|
|||
option_eclipse = 2
|
||||
|
||||
|
||||
class ObeliskKeys(DefaultOnToggle):
|
||||
"""
|
||||
Add one Obelisk Key item per Obelisk, locking you out of solving any of the associated Environmental Puzzles.
|
||||
Does nothing if "Shuffle Environmental Puzzles" is set to "off".
|
||||
"""
|
||||
display_name = "Obelisk Keys"
|
||||
|
||||
|
||||
class ShufflePostgame(Toggle):
|
||||
"""Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal.
|
||||
Use this if you don't play with release on victory. IMPORTANT NOTE: The possibility of your second
|
||||
|
@ -263,6 +271,7 @@ class TheWitnessOptions(PerGameCommonOptions):
|
|||
disable_non_randomized_puzzles: DisableNonRandomizedPuzzles
|
||||
shuffle_discarded_panels: ShuffleDiscardedPanels
|
||||
shuffle_vault_boxes: ShuffleVaultBoxes
|
||||
obelisk_keys: ObeliskKeys
|
||||
shuffle_EPs: ShuffleEnvironmentalPuzzles
|
||||
EP_difficulty: EnvironmentalPuzzlesDifficulty
|
||||
shuffle_postgame: ShufflePostgame
|
||||
|
|
|
@ -63,16 +63,18 @@ class WitnessPlayerLogic:
|
|||
if panel_hex in self.DOOR_ITEMS_BY_ID:
|
||||
door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]})
|
||||
|
||||
all_options = set()
|
||||
all_options: Set[FrozenSet[str]] = set()
|
||||
|
||||
for dependentItem in door_items:
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependentItem)
|
||||
for items_option in these_items:
|
||||
all_options.add(items_option.union(dependentItem))
|
||||
|
||||
# If this entity is not an EP, and it has an associated door item, ignore the original power dependencies
|
||||
if StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["entityType"] != "EP":
|
||||
# 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved,
|
||||
# except in Expert, where that dependency doesn't exist, but now there *is* a power dependency.
|
||||
# In the future, it would be wise to make a distinction between "power dependencies" and other dependencies.
|
||||
# In the future, it'd be wise to make a distinction between "power dependencies" and other dependencies.
|
||||
if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels):
|
||||
these_items = all_options
|
||||
|
||||
|
@ -80,10 +82,12 @@ class WitnessPlayerLogic:
|
|||
elif panel_hex == "0x1C349":
|
||||
these_items = all_options
|
||||
|
||||
# For any other door entity, we just return a set with the item that opens it & disregard power dependencies
|
||||
else:
|
||||
return frozenset(all_options)
|
||||
|
||||
else:
|
||||
these_items = all_options
|
||||
|
||||
disabled_eps = {eHex for eHex in self.COMPLETELY_DISABLED_ENTITIES
|
||||
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[eHex]["entityType"] == "EP"}
|
||||
|
||||
|
@ -429,6 +433,9 @@ class WitnessPlayerLogic:
|
|||
if lasers:
|
||||
adjustment_linesets_in_order.append(get_laser_shuffle())
|
||||
|
||||
if world.options.shuffle_EPs and world.options.obelisk_keys:
|
||||
adjustment_linesets_in_order.append(get_obelisk_keys())
|
||||
|
||||
if world.options.shuffle_EPs == "obelisk_sides":
|
||||
ep_gen = ((ep_hex, ep_obj) for (ep_hex, ep_obj) in self.REFERENCE_LOGIC.ENTITIES_BY_HEX.items()
|
||||
if ep_obj["entityType"] == "EP")
|
||||
|
@ -442,7 +449,7 @@ class WitnessPlayerLogic:
|
|||
adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:])
|
||||
|
||||
if not world.options.shuffle_EPs:
|
||||
adjustment_linesets_in_order.append(["Irrelevant Locations:"] + get_ep_all_individual()[1:])
|
||||
adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_all_individual()[1:])
|
||||
|
||||
for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS:
|
||||
if yaml_disabled_location not in self.REFERENCE_LOGIC.ENTITIES_BY_NAME:
|
||||
|
|
|
@ -14,6 +14,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"door_groupings": DoorGroupings.option_off,
|
||||
"shuffle_boat": True,
|
||||
"shuffle_lasers": ShuffleLasers.option_local,
|
||||
"obelisk_keys": ObeliskKeys.option_false,
|
||||
|
||||
"disable_non_randomized_puzzles": True,
|
||||
"shuffle_discarded_panels": False,
|
||||
|
@ -35,6 +36,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
"death_link_amnesty": DeathLinkAmnesty.default,
|
||||
},
|
||||
|
||||
# For relative beginners who want to move to the next step.
|
||||
|
@ -48,6 +50,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"door_groupings": DoorGroupings.option_regional,
|
||||
"shuffle_boat": True,
|
||||
"shuffle_lasers": ShuffleLasers.option_off,
|
||||
"obelisk_keys": ObeliskKeys.option_false,
|
||||
|
||||
"disable_non_randomized_puzzles": False,
|
||||
"shuffle_discarded_panels": True,
|
||||
|
@ -69,6 +72,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
"death_link_amnesty": DeathLinkAmnesty.default,
|
||||
},
|
||||
|
||||
# Allsanity but without the BS (no expert, no tedious EPs).
|
||||
|
@ -82,6 +86,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"door_groupings": DoorGroupings.option_off,
|
||||
"shuffle_boat": True,
|
||||
"shuffle_lasers": ShuffleLasers.option_anywhere,
|
||||
"obelisk_keys": ObeliskKeys.option_true,
|
||||
|
||||
"disable_non_randomized_puzzles": False,
|
||||
"shuffle_discarded_panels": True,
|
||||
|
@ -103,5 +108,6 @@ witness_option_presets: Dict[str, Dict[str, Any]] = {
|
|||
"area_hint_percentage": AreaHintPercentage.default,
|
||||
"laser_hints": LaserHints.default,
|
||||
"death_link": DeathLink.default,
|
||||
"death_link_amnesty": DeathLinkAmnesty.default,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Items:
|
||||
Desert Obelisk Key
|
||||
Monastery Obelisk Key
|
||||
Treehouse Obelisk Key
|
||||
Mountainside Obelisk Key
|
||||
Quarry Obelisk Key
|
||||
Town Obelisk Key
|
|
@ -177,6 +177,10 @@ def get_ep_obelisks() -> List[str]:
|
|||
return get_adjustment_file("settings/EP_Shuffle/EP_Sides.txt")
|
||||
|
||||
|
||||
def get_obelisk_keys() -> List[str]:
|
||||
return get_adjustment_file("settings/Door_Shuffle/Obelisk_Keys.txt")
|
||||
|
||||
|
||||
def get_ep_easy() -> List[str]:
|
||||
return get_adjustment_file("settings/EP_Shuffle/EP_Easy.txt")
|
||||
|
||||
|
|
Loading…
Reference in New Issue