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
This commit is contained in:
NewSoupVi 2023-06-21 00:45:26 +02:00 committed by GitHub
parent afe9e12ef4
commit 845502ad39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 84 additions and 60 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -62,11 +62,11 @@ class WitnessWorld(World):
'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 @@ class WitnessWorld(World):
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 @@ class WitnessWorld(World):
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)

View File

@ -96,31 +96,30 @@ joke_hints = [
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)
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 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
if next_random_hint_is_item:
if not prog_items_in_this_world:
next_random_hint_is_item = not next_random_hint_is_item

View File

@ -143,11 +143,11 @@ class WitnessPlayerItems:
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 @@ class WitnessPlayerItems:
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 @@ class WitnessPlayerItems:
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

View File

@ -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",

View File

@ -296,21 +296,21 @@ class WitnessPlayerLogic:
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
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]
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 @@ class WitnessPlayerLogic:
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 = {

View File

@ -141,14 +141,19 @@ class WitnessLogic(LogicMixin):
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_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):
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)
)
if not (direct_access or theater_from_town and tunnels_from_town):
valid_option = False
break

View File

@ -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)

View File

@ -57,7 +57,7 @@ class StaticWitnessLogicObj:
"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 @@ class StaticWitnessLogicObj:
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

View File

@ -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