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:
parent
afe9e12ef4
commit
845502ad39
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
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]
|
||||
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]))
|
||||
continue
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -296,7 +296,6 @@ 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 = []
|
||||
|
||||
for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS:
|
||||
|
@ -305,11 +304,12 @@ class WitnessPlayerLogic:
|
|||
|
||||
loc_obj = StaticWitnessLogic.CHECKS_BY_NAME[yaml_disabled_location]
|
||||
|
||||
if loc_obj["panelType"] != "EP":
|
||||
continue
|
||||
|
||||
if loc_obj["panelType"] == "EP" and get_option_value(world, player, "shuffle_EPs") == 2:
|
||||
yaml_disabled_eps.append(loc_obj["checkHex"])
|
||||
|
||||
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:
|
||||
|
@ -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 = {
|
||||
|
|
|
@ -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 = (
|
||||
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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue