Skip to content

Commit

Permalink
The Witness: Obelisk Keys (#2805)
Browse files Browse the repository at this point in the history
  • Loading branch information
NewSoupVi authored Mar 12, 2024
1 parent ae6c16b commit 4bf676e
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 31 deletions.
7 changes: 7 additions & 0 deletions worlds/witness/WitnessItems.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 42 additions & 19 deletions worlds/witness/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,46 @@ def _get_slot_data(self):
'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

Expand All @@ -102,26 +142,9 @@ def generate_early(self):
)
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"
)

has_progression = (
interacts_with_multiworld
or self.options.shuffle_lasers == "local"
or self.options.shuffle_boat
or self.options.early_caves == "add_to_pool"
)
self.log_ids_to_hints = dict()

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"]
Expand Down
9 changes: 9 additions & 0 deletions worlds/witness/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
31 changes: 19 additions & 12 deletions worlds/witness/player_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,30 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]:
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))

# 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.
if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels):
these_items = all_options
# 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'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

# Another dependency that is not power-based: The Symmetry Island Upper Panel latches
elif panel_hex == "0x1C349":
these_items = all_options
# Another dependency that is not power-based: The Symmetry Island Upper Panel latches
elif panel_hex == "0x1C349":
these_items = all_options

else:
return frozenset(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)
these_items = all_options

disabled_eps = {eHex for eHex in self.COMPLETELY_DISABLED_ENTITIES
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[eHex]["entityType"] == "EP"}
Expand Down Expand Up @@ -429,6 +433,9 @@ def make_options_adjustments(self, world: "WitnessWorld"):
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")
Expand All @@ -442,7 +449,7 @@ def make_options_adjustments(self, world: "WitnessWorld"):
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:
Expand Down
6 changes: 6 additions & 0 deletions worlds/witness/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"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,
Expand All @@ -35,6 +36,7 @@
"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.
Expand All @@ -48,6 +50,7 @@
"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,
Expand All @@ -69,6 +72,7 @@
"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).
Expand All @@ -82,6 +86,7 @@
"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,
Expand All @@ -103,5 +108,6 @@
"area_hint_percentage": AreaHintPercentage.default,
"laser_hints": LaserHints.default,
"death_link": DeathLink.default,
"death_link_amnesty": DeathLinkAmnesty.default,
},
}
7 changes: 7 additions & 0 deletions worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Items:
Desert Obelisk Key
Monastery Obelisk Key
Treehouse Obelisk Key
Mountainside Obelisk Key
Quarry Obelisk Key
Town Obelisk Key
4 changes: 4 additions & 0 deletions worlds/witness/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down

0 comments on commit 4bf676e

Please sign in to comment.