From 845502ad396f25e28402fabcd6fd0becf9b58bb3 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:45:26 +0200 Subject: [PATCH] The Witness: Hint distribution changes, added locations, misc fixes (#1785) Changes: * Hints should feel a lot less same-y now ("Priority hints" are no longer always hints in disguise) * Keep Hedge Mazes 1-3 and Pressure Plates 1-3 are added as locations in all settings * Desert Final Room Hexagonal & Desert Final Room Bent 3 are added as locations * Entries in exclude_locations that are referring to panels are now sent through slot data. This means they can be pre-skipped on the client side. Fixes: * Logic error in the Stoneworks that led to more restrictive seeds than necessary * Logic error for Theater Flowers EP that led to more restrictive seeds than necessary * Fixed crash in plando when "item" is a dict with weights * Spoiler log locations were in random order per region, now they are consistent --- worlds/witness/WitnessLogic.txt | 2 +- worlds/witness/WitnessLogicExpert.txt | 2 +- worlds/witness/WitnessLogicVanilla.txt | 4 +- worlds/witness/__init__.py | 21 +++++--- worlds/witness/hints.py | 53 +++++++++++-------- worlds/witness/items.py | 8 +-- worlds/witness/locations.py | 8 +++ worlds/witness/player_logic.py | 21 ++++---- worlds/witness/rules.py | 17 +++--- .../witness/settings/Disable_Unrandomized.txt | 2 - worlds/witness/static_logic.py | 4 +- worlds/witness/utils.py | 2 +- 12 files changed, 84 insertions(+), 60 deletions(-) diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt index 329cdf3ce8f3..dd6f9d31af2d 100644 --- a/worlds/witness/WitnessLogic.txt +++ b/worlds/witness/WitnessLogic.txt @@ -264,7 +264,7 @@ Quarry Stoneworks Upper Floor (Quarry Stoneworks) - Quarry Stoneworks Middle Flo 158141 - 0x014E9 (Upper Row 8) - 0x03686 - Colored Squares & Eraser 158142 - 0x03677 (Stair Control) - True - Colored Squares & Eraser Door - 0x0368A (Stairs) - 0x03677 -158143 - 0x3C125 (Control Room Left) - 0x0367C - Black/White Squares & Dots & Eraser +158143 - 0x3C125 (Control Room Left) - 0x014E9 - Black/White Squares & Dots & Eraser 158144 - 0x0367C (Control Room Right) - 0x014E9 - Colored Squares & Dots & Eraser 159411 - 0x0069D (Ramp EP) - 0x03676 & 0x275FF - True 159413 - 0x00614 (Lift EP) - 0x275FF & 0x03675 - True diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt index 148240b1ccf6..b0cd5d0fbb56 100644 --- a/worlds/witness/WitnessLogicExpert.txt +++ b/worlds/witness/WitnessLogicExpert.txt @@ -264,7 +264,7 @@ Quarry Stoneworks Upper Floor (Quarry Stoneworks) - Quarry Stoneworks Middle Flo 158141 - 0x014E9 (Upper Row 8) - 0x03686 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol 158142 - 0x03677 (Stair Control) - True - Squares & Colored Squares & Eraser Door - 0x0368A (Stairs) - 0x03677 -158143 - 0x3C125 (Control Room Left) - 0x0367C - Squares & Black/White Squares & Dots & Full Dots & Eraser +158143 - 0x3C125 (Control Room Left) - 0x014E9 - Squares & Black/White Squares & Dots & Full Dots & Eraser 158144 - 0x0367C (Control Room Right) - 0x014E9 - Squares & Colored Squares & Triangles & Eraser & Stars & Stars + Same Colored Symbol 159411 - 0x0069D (Ramp EP) - 0x03676 & 0x275FF - True 159413 - 0x00614 (Lift EP) - 0x275FF & 0x03675 - True diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt index e0d2cdee1cd6..18d3e01a7c57 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/WitnessLogicVanilla.txt @@ -15,7 +15,7 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629: 158006 - 0x0A3B2 (Back Right) - True - True 158007 - 0x03629 (Gate Open) - 0x002C2 & 0x0A3B5 & 0x0A3B2 - True 158008 - 0x03505 (Gate Close) - 0x2FAF6 & 0x03629 - True -158009 - 0x0C335 (Pillar) - True - Triangles - True +158009 - 0x0C335 (Pillar) - True - Triangles 158010 - 0x0C373 (Patio Floor) - 0x0C335 - Dots 159512 - 0x33530 (Cloud EP) - True - True 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True @@ -264,7 +264,7 @@ Quarry Stoneworks Upper Floor (Quarry Stoneworks) - Quarry Stoneworks Middle Flo 158141 - 0x014E9 (Upper Row 8) - 0x03686 - Colored Squares & Eraser 158142 - 0x03677 (Stair Control) - True - Colored Squares & Eraser Door - 0x0368A (Stairs) - 0x03677 -158143 - 0x3C125 (Control Room Left) - 0x0367C - Black/White Squares & Dots & Eraser +158143 - 0x3C125 (Control Room Left) - 0x014E9 - Black/White Squares & Dots & Eraser 158144 - 0x0367C (Control Room Right) - 0x014E9 - Colored Squares & Dots & Eraser 159411 - 0x0069D (Ramp EP) - 0x03676 & 0x275FF - True 159413 - 0x00614 (Lift EP) - 0x275FF & 0x03675 - True diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 358e0634038e..339130777fd0 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -62,11 +62,11 @@ def _get_slot_data(self): 'item_id_to_door_hexes': self.static_items.ITEM_ID_TO_DOOR_HEX_ALL, 'door_hexes_in_the_pool': self.items.DOORS, 'symbols_not_in_the_game': self.items.SYMBOLS_NOT_IN_THE_GAME, - 'disabled_panels': self.player_logic.COMPLETELY_DISABLED_CHECKS, + 'disabled_panels': list(self.player_logic.COMPLETELY_DISABLED_CHECKS), 'log_ids_to_hints': self.log_ids_to_hints, 'progressive_item_lists': self.items.MULTI_LISTS_BY_CODE, 'obelisk_side_id_to_EPs': self.static_logic.OBELISK_SIDE_ID_TO_EP_HEXES, - 'precompleted_puzzles': {int(h, 16) for h in self.player_logic.PRECOMPLETED_LOCATIONS}, + 'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], 'entity_to_name': self.static_logic.ENTITY_ID_TO_NAME, } @@ -143,14 +143,19 @@ def create_items(self): for v in self.multiworld.plando_items[self.player]: if v.get("from_pool", True): - plandoed_items.update({self.items_by_name[i] for i in v.get("items", dict()).keys() - if i in self.items_by_name}) - if "item" in v and v["item"] in self.items_by_name: - plandoed_items.add(self.items_by_name[v["item"]]) + for item_key in {"item", "items"}: + if item_key in v: + if type(v[item_key]) is str: + plandoed_items.add(v[item_key]) + elif type(v[item_key]) is dict: + plandoed_items.update(item for item, weight in v[item_key].items() if weight) + else: + # Other type of iterable + plandoed_items.update(v[item_key]) for symbol in self.items.GOOD_ITEMS: item = self.items_by_name[symbol] - if item in pool and item not in plandoed_items: + if item in pool and symbol not in plandoed_items: # for now, any item that is mentioned in any plando option, even if it's a list of items, is ineligible. # Hopefully, in the future, plando gets resolved before create_items. # I could also partially resolve lists myself, but this could introduce errors if not done carefully. @@ -255,7 +260,7 @@ def fill_slot_data(self) -> dict: self.multiworld.per_slot_randoms[self.player].shuffle(audio_logs) - duplicates = len(audio_logs) // hint_amount + duplicates = min(3, len(audio_logs) // hint_amount) for _ in range(0, hint_amount): hint = generated_hints.pop(0) diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 4c36c4826ba7..4336f8006510 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -96,31 +96,30 @@ def get_always_hint_items(multiworld: MultiWorld, player: int): - priority = [ + always = [ "Boat", - "Mountain Bottom Floor Final Room Entry (Door)", - "Caves Mountain Shortcut (Door)", - "Caves Swamp Shortcut (Door)", "Caves Exits to Main Island", "Progressive Dots", ] difficulty = get_option_value(multiworld, player, "puzzle_randomization") discards = is_option_enabled(multiworld, player, "shuffle_discarded_panels") + wincon = get_option_value(multiworld, player, "victory_condition") if discards: if difficulty == 1: - priority.append("Arrows") + always.append("Arrows") else: - priority.append("Triangles") + always.append("Triangles") - return priority + if wincon == 0: + always.append("Mountain Bottom Floor Final Room Entry (Door)") + + return always def get_always_hint_locations(multiworld: MultiWorld, player: int): return { - "Swamp Purple Underwater", - "Shipwreck Vault Box", "Challenge Vault Box", "Mountain Bottom Floor Discard", "Theater Eclipse EP", @@ -131,6 +130,8 @@ def get_always_hint_locations(multiworld: MultiWorld, player: int): def get_priority_hint_items(multiworld: MultiWorld, player: int): priority = { + "Caves Mountain Shortcut (Door)", + "Caves Swamp Shortcut (Door)", "Negative Shapers", "Sound Dots", "Colored Dots", @@ -157,16 +158,18 @@ def get_priority_hint_items(multiworld: MultiWorld, player: int): if get_option_value(multiworld, player, "doors") >= 2: priority.add("Desert Laser") lasers.remove("Desert Laser") - priority.update(multiworld.per_slot_randoms[player].sample(lasers, 2)) + priority.update(multiworld.per_slot_randoms[player].sample(lasers, 5)) else: - priority.update(multiworld.per_slot_randoms[player].sample(lasers, 3)) + priority.update(multiworld.per_slot_randoms[player].sample(lasers, 6)) return priority def get_priority_hint_locations(multiworld: MultiWorld, player: int): return { + "Swamp Purple Underwater", + "Shipwreck Vault Box", "Town RGB Room Left", "Town RGB Room Right", "Treehouse Green Bridge 7", @@ -264,7 +267,8 @@ def make_hints(multiworld: MultiWorld, player: int, hint_amount: int): multiworld.per_slot_randoms[player].shuffle(hints) # shuffle always hint order in case of low hint amount - next_random_hint_is_item = multiworld.per_slot_randoms[player].randint(0, 2) + remaining_hints = hint_amount - len(hints) + priority_hint_amount = int(max(0.0, min(len(priority_hint_pairs) / 2, remaining_hints / 2))) prog_items_in_this_world = sorted(list(prog_items_in_this_world)) locations_in_this_world = sorted(list(loc_in_this_world)) @@ -272,18 +276,21 @@ def make_hints(multiworld: MultiWorld, player: int, hint_amount: int): multiworld.per_slot_randoms[player].shuffle(prog_items_in_this_world) multiworld.per_slot_randoms[player].shuffle(locations_in_this_world) - while len(hints) < hint_amount: - if priority_hint_pairs: - loc = multiworld.per_slot_randoms[player].choice(list(priority_hint_pairs.keys())) - item = priority_hint_pairs[loc] - del priority_hint_pairs[loc] - - if item[1]: - hints.append((f"{item[0]} can be found at {loc}.", item[2])) - else: - hints.append((f"{loc} contains {item[0]}.", item[2])) - continue + priority_hint_list = list(priority_hint_pairs.items()) + multiworld.per_slot_randoms[player].shuffle(priority_hint_list) + for _ in range(0, priority_hint_amount): + next_priority_hint = priority_hint_list.pop() + loc = next_priority_hint[0] + item = next_priority_hint[1] + + if item[1]: + hints.append((f"{item[0]} can be found at {loc}.", item[2])) + else: + hints.append((f"{loc} contains {item[0]}.", item[2])) + next_random_hint_is_item = multiworld.per_slot_randoms[player].randint(0, 2) + + while len(hints) < hint_amount: if next_random_hint_is_item: if not prog_items_in_this_world: next_random_hint_is_item = not next_random_hint_is_item diff --git a/worlds/witness/items.py b/worlds/witness/items.py index 4beb3b0290c9..da4a396424fa 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/items.py @@ -143,11 +143,11 @@ def __init__(self, locat: WitnessPlayerLocations, multiworld: MultiWorld, player self.PROGRESSION_TABLE = dict() self.ITEM_ID_TO_DOOR_HEX = dict() - self.DOORS = set() + self.DOORS = list() self.PROG_ITEM_AMOUNTS = defaultdict(lambda: 1) - self.SYMBOLS_NOT_IN_THE_GAME = set() + self.SYMBOLS_NOT_IN_THE_GAME = list() self.EXTRA_AMOUNTS = { "Functioning Brain": 1, @@ -162,7 +162,7 @@ def __init__(self, locat: WitnessPlayerLocations, multiworld: MultiWorld, player if item[0] not in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME: del self.ITEM_TABLE[item[0]] if item in StaticWitnessLogic.ALL_SYMBOL_ITEMS: - self.SYMBOLS_NOT_IN_THE_GAME.add(StaticWitnessItems.ALL_ITEM_TABLE[item[0]].code) + self.SYMBOLS_NOT_IN_THE_GAME.append(StaticWitnessItems.ALL_ITEM_TABLE[item[0]].code) else: if item[0] in StaticWitnessLogic.PROGRESSIVE_TO_ITEMS: self.PROG_ITEM_AMOUNTS[item[0]] = len(logic.MULTI_LISTS[item[0]]) @@ -178,7 +178,7 @@ def __init__(self, locat: WitnessPlayerLocations, multiworld: MultiWorld, player for entity_hex, items in logic.DOOR_ITEMS_BY_ID.items(): entity_hex_int = int(entity_hex, 16) - self.DOORS.add(entity_hex_int) + self.DOORS.append(entity_hex_int) for item in items: item_id = StaticWitnessItems.ALL_ITEM_TABLE[item].code diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index f9d1012cb4f7..9618046ce3ce 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -43,6 +43,8 @@ class StaticWitnessLocations: "Desert Light Room 3", "Desert Pond Room 5", "Desert Flood Room 6", + "Desert Final Hexagonal", + "Desert Final Bent 3", "Desert Laser Panel", "Quarry Stoneworks Lower Row 6", @@ -61,7 +63,13 @@ class StaticWitnessLocations: "Shadows Near 5", "Shadows Laser Panel", + "Keep Hedge Maze 1", + "Keep Hedge Maze 2", + "Keep Hedge Maze 3", "Keep Hedge Maze 4", + "Keep Pressure Plates 1", + "Keep Pressure Plates 2", + "Keep Pressure Plates 3", "Keep Pressure Plates 4", "Keep Discard", "Keep Laser Panel Hedges", diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 3e81993dc9bc..3a38c5face48 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -296,21 +296,21 @@ def make_options_adjustments(self, world, player): elif get_option_value(world, player, "shuffle_EPs") == 1: # Individual EPs adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:]) - else: # Obelisk Sides - yaml_disabled_eps = [] + yaml_disabled_eps = [] - for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS: - if yaml_disabled_location not in StaticWitnessLogic.CHECKS_BY_NAME: - continue - - loc_obj = StaticWitnessLogic.CHECKS_BY_NAME[yaml_disabled_location] + for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS: + if yaml_disabled_location not in StaticWitnessLogic.CHECKS_BY_NAME: + continue - if loc_obj["panelType"] != "EP": - continue + loc_obj = StaticWitnessLogic.CHECKS_BY_NAME[yaml_disabled_location] + if loc_obj["panelType"] == "EP" and get_option_value(world, player, "shuffle_EPs") == 2: yaml_disabled_eps.append(loc_obj["checkHex"]) - adjustment_linesets_in_order.append(["Precompleted Locations:"] + yaml_disabled_eps) + if loc_obj["panelType"] in {"EP", "General"}: + self.EXCLUDED_LOCATIONS.add(loc_obj["checkHex"]) + + adjustment_linesets_in_order.append(["Precompleted Locations:"] + yaml_disabled_eps) for adjustment_lineset in adjustment_linesets_in_order: current_adjustment_type = None @@ -430,6 +430,7 @@ def __init__(self, world: MultiWorld, player: int, disabled_locations: Set[str], self.ALWAYS_EVENT_HEX_CODES = set() self.COMPLETELY_DISABLED_CHECKS = set() self.PRECOMPLETED_LOCATIONS = set() + self.EXCLUDED_LOCATIONS = set() self.ADDED_CHECKS = set() self.VICTORY_LOCATION = "0x0356B" self.EVENT_ITEM_NAMES = { diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py index ba9a1fb7a3ce..409a86ee43cd 100644 --- a/worlds/witness/rules.py +++ b/worlds/witness/rules.py @@ -141,14 +141,19 @@ def _witness_meets_item_requirements(self, panel, world, player, player_logic: W and self.can_reach("Windmill Interior to Theater", "Entrance", player) ) - exit_to_town = self.can_reach("Theater to Town", "Entrance", player) - entrance_to_town = ( - self.can_reach("Town to Windmill Interior", "Entrance", player) - and self.can_reach("Windmill Interior to Theater", "Entrance", player) + theater_from_town = ( + self.can_reach("Town to Windmill Interior", "Entrance", player) + and self.can_reach("Windmill Interior to Theater", "Entrance", player) + or self.can_reach("Theater to Town", "Entrance", player) + ) + + tunnels_from_town = ( + self.can_reach("Tunnels to Windmill Interior", "Entrance", player) + and self.can_reach("Town to Windmill Interior", "Entrance", player) + or self.can_reach("Tunnels to Town", "Entrance", player) ) - tunnels_to_town = self.can_reach("Tunnels to Town", "Entrance", player) - if not (direct_access or (exit_to_town or entrance_to_town) and tunnels_to_town): + if not (direct_access or theater_from_town and tunnels_from_town): valid_option = False break diff --git a/worlds/witness/settings/Disable_Unrandomized.txt b/worlds/witness/settings/Disable_Unrandomized.txt index dbe9caa5be81..f7a0fcb7cbd6 100644 --- a/worlds/witness/settings/Disable_Unrandomized.txt +++ b/worlds/witness/settings/Disable_Unrandomized.txt @@ -84,8 +84,6 @@ Disabled Locations: 0x0070F (Second Row 2) 0x0087D (Second Row 3) 0x002C7 (Second Row 4) -0x15ADD (River Outside Vault) -0x03702 (River Vault Box) 0x17CAA (Monastery Shortcut Panel) 0x0C2A4 (Bunker Entry) 0x17C79 (Tinted Glass Door) diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index f395613b9137..92a89c0351e5 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -57,7 +57,7 @@ def read_logic_file(self, file_path="WitnessLogic.txt"): "panels": parse_lambda(required_panel_lambda) } - current_region["panels"].add(check_hex) + current_region["panels"].append(check_hex) continue required_item_lambda = line_split.pop(0) @@ -117,7 +117,7 @@ def read_logic_file(self, file_path="WitnessLogic.txt"): self.CHECKS_BY_NAME[self.CHECKS_BY_HEX[check_hex]["checkName"]] = self.CHECKS_BY_HEX[check_hex] self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[check_hex] = requirement - current_region["panels"].add(check_hex) + current_region["panels"].append(check_hex) def __init__(self, file_path="WitnessLogic.txt"): # All regions with a list of panels in them and the connections to other regions, before logic adjustments diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py index 7182545cf59d..8148a15be763 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/utils.py @@ -106,7 +106,7 @@ def define_new_region(region_string): region_obj = { "name": region_name, "shortName": region_name_simple, - "panels": set() + "panels": list() } return region_obj, options