The Witness 0.3.4 features (#780)
New options:
Shuffle Doors: Many doors in the game will open on their own upon receiving an item ("key").
Variant - Shuffle Door/Control Panels: Many panels in the game that open doors or control devices in the world will be off until receiving their respective item ("key").
Shuffle Lasers: Lasers no longer activate by solving the laser panel, instead you will get an item that activates the laser.
Shuffle Symbols: Now that there is something else to shuffle (doors / door panels), you can turn off Symbol Rando.
Shuffle Postgame (replaces "Shuffle Hard"): The randomizer will now determine by your settings which panels are in the "postgame" - Meaning they can only be accessed after you can complete your win condition anyway.
This commit is contained in:
@@ -18,22 +18,15 @@ When the world has parsed its options, a second function is called to finalize t
|
||||
import copy
|
||||
from BaseClasses import MultiWorld
|
||||
from .static_logic import StaticWitnessLogic
|
||||
from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda, get_early_utm_list
|
||||
from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda, get_early_utm_list, \
|
||||
get_symbol_shuffle_list, get_door_panel_shuffle_list, get_doors_complex_list, get_doors_max_list, \
|
||||
get_doors_simple_list, get_laser_shuffle
|
||||
from .Options import is_option_enabled, get_option_value, the_witness_options
|
||||
|
||||
|
||||
class WitnessPlayerLogic:
|
||||
"""WITNESS LOGIC CLASS"""
|
||||
|
||||
def update_door_dict(self, panel_hex):
|
||||
item_id = StaticWitnessLogic.ALL_DOOR_ITEM_IDS_BY_HEX.get(panel_hex)
|
||||
|
||||
if item_id is None:
|
||||
return
|
||||
|
||||
self.DOOR_DICT_FOR_CLIENT[panel_hex] = item_id
|
||||
self.DOOR_CONNECTIONS_TO_SEVER.update(StaticWitnessLogic.CONNECTIONS_TO_SEVER_BY_DOOR_HEX[panel_hex])
|
||||
|
||||
def reduce_req_within_region(self, panel_hex):
|
||||
"""
|
||||
Panels in this game often only turn on when other panels are solved.
|
||||
@@ -43,35 +36,42 @@ class WitnessPlayerLogic:
|
||||
Panels outside of the same region will still be checked manually.
|
||||
"""
|
||||
|
||||
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"]
|
||||
check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
|
||||
|
||||
real_items = {item[0] for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME}
|
||||
these_items = frozenset({frozenset()})
|
||||
|
||||
if check_obj["id"]:
|
||||
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"]
|
||||
|
||||
these_items = frozenset({
|
||||
subset.intersection(real_items)
|
||||
subset.intersection(self.PROG_ITEMS_ACTUALLY_IN_THE_GAME)
|
||||
for subset in these_items
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
for items_option in these_items:
|
||||
for dependentItem in door_items:
|
||||
all_options.add(items_option.union(dependentItem))
|
||||
|
||||
return frozenset(all_options)
|
||||
|
||||
these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"]
|
||||
|
||||
if StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex) in real_items:
|
||||
self.update_door_dict(panel_hex)
|
||||
|
||||
these_panels = frozenset({frozenset()})
|
||||
|
||||
if these_panels == frozenset({frozenset()}):
|
||||
return these_items
|
||||
|
||||
all_options = set()
|
||||
|
||||
check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
|
||||
|
||||
for option in these_panels:
|
||||
dependent_items_for_option = frozenset({frozenset()})
|
||||
|
||||
for option_panel in option:
|
||||
new_items = set()
|
||||
dep_obj = StaticWitnessLogic.CHECKS_BY_HEX.get(option_panel)
|
||||
|
||||
if option_panel in {"7 Lasers", "11 Lasers"}:
|
||||
new_items = frozenset({frozenset([option_panel])})
|
||||
# If a panel turns on when a panel in a different region turns on,
|
||||
@@ -101,8 +101,34 @@ class WitnessPlayerLogic:
|
||||
return frozenset(all_options)
|
||||
|
||||
def make_single_adjustment(self, adj_type, line):
|
||||
from . import StaticWitnessItems
|
||||
"""Makes a single logic adjustment based on additional logic file"""
|
||||
|
||||
if adj_type == "Items":
|
||||
if line not in StaticWitnessItems.ALL_ITEM_TABLE:
|
||||
raise RuntimeError("Item \"" + line + "\" does not exit.")
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(line)
|
||||
|
||||
if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
|
||||
panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
|
||||
for panel_hex in panel_hexes:
|
||||
self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, set()).add(line)
|
||||
|
||||
return
|
||||
|
||||
if adj_type == "Remove Items":
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.discard(line)
|
||||
|
||||
if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
|
||||
panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
|
||||
for panel_hex in panel_hexes:
|
||||
if panel_hex in self.DOOR_ITEMS_BY_ID:
|
||||
self.DOOR_ITEMS_BY_ID[panel_hex].discard(line)
|
||||
|
||||
if adj_type == "Starting Inventory":
|
||||
self.STARTING_INVENTORY.add(line)
|
||||
|
||||
if adj_type == "Event Items":
|
||||
line_split = line.split(" - ")
|
||||
hex_set = line_split[1].split(",")
|
||||
@@ -130,18 +156,20 @@ class WitnessPlayerLogic:
|
||||
if adj_type == "Requirement Changes":
|
||||
line_split = line.split(" - ")
|
||||
|
||||
required_items = parse_lambda(line_split[2])
|
||||
items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS}
|
||||
required_items = frozenset(
|
||||
subset.intersection(items_actually_in_the_game)
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
requirement = {
|
||||
"panels": parse_lambda(line_split[1]),
|
||||
"items": required_items
|
||||
}
|
||||
|
||||
if len(line_split) > 2:
|
||||
required_items = parse_lambda(line_split[2])
|
||||
items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS}
|
||||
required_items = frozenset(
|
||||
subset.intersection(items_actually_in_the_game)
|
||||
for subset in required_items
|
||||
)
|
||||
|
||||
requirement["items"] = required_items
|
||||
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX[line_split[0]] = requirement
|
||||
|
||||
return
|
||||
@@ -151,11 +179,6 @@ class WitnessPlayerLogic:
|
||||
|
||||
self.COMPLETELY_DISABLED_CHECKS.add(panel_hex)
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = {
|
||||
item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME
|
||||
if item[0] != StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
if adj_type == "Region Changes":
|
||||
@@ -189,18 +212,25 @@ class WitnessPlayerLogic:
|
||||
adjustment_linesets_in_order.append(get_disable_unrandomized_list())
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_symbols") or "shuffle_symbols" not in the_witness_options.keys():
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_SYMBOL_ITEMS)
|
||||
adjustment_linesets_in_order.append(get_symbol_shuffle_list())
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_doors"):
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_DOOR_ITEMS)
|
||||
if get_option_value(world, player, "shuffle_doors") == 1:
|
||||
adjustment_linesets_in_order.append(get_door_panel_shuffle_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 2:
|
||||
adjustment_linesets_in_order.append(get_doors_simple_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 3:
|
||||
adjustment_linesets_in_order.append(get_doors_complex_list())
|
||||
|
||||
if get_option_value(world, player, "shuffle_doors") == 4:
|
||||
adjustment_linesets_in_order.append(get_doors_max_list())
|
||||
|
||||
if is_option_enabled(world, player, "early_secret_area"):
|
||||
adjustment_linesets_in_order.append(get_early_utm_list())
|
||||
else:
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = {
|
||||
item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME
|
||||
if item[0] != "Mountaintop River Shape Power On"
|
||||
}
|
||||
|
||||
if is_option_enabled(world, player, "shuffle_lasers"):
|
||||
adjustment_linesets_in_order.append(get_laser_shuffle())
|
||||
|
||||
for adjustment_lineset in adjustment_linesets_in_order:
|
||||
current_adjustment_type = None
|
||||
@@ -233,62 +263,32 @@ class WitnessPlayerLogic:
|
||||
pair = (name, self.EVENT_ITEM_NAMES[panel])
|
||||
return pair
|
||||
|
||||
def _regions_are_adjacent(self, region1, region2):
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region1]:
|
||||
if connection[0] == region2:
|
||||
return True
|
||||
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region2]:
|
||||
if connection[0] == region1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def make_event_panel_lists(self):
|
||||
"""
|
||||
Special event panel data structures
|
||||
"""
|
||||
|
||||
for region_conn in self.CONNECTIONS_BY_REGION_NAME.values():
|
||||
for region_and_option in region_conn:
|
||||
for panelset in region_and_option[1]:
|
||||
for panel in panelset:
|
||||
self.EVENT_PANELS_FROM_REGIONS.add(panel)
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
|
||||
|
||||
self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS)
|
||||
self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_REGIONS)
|
||||
for region_name, connections in self.CONNECTIONS_BY_REGION_NAME.items():
|
||||
for connection in connections:
|
||||
for panel_req in connection[1]:
|
||||
for panel in panel_req:
|
||||
if panel == "TrueOneWay":
|
||||
continue
|
||||
|
||||
for panel in self.EVENT_PANELS_FROM_REGIONS:
|
||||
for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items():
|
||||
for connection in self.CONNECTIONS_BY_REGION_NAME[region_name]:
|
||||
connected_r = connection[0]
|
||||
if connected_r not in StaticWitnessLogic.ALL_REGIONS_BY_NAME:
|
||||
continue
|
||||
if region_name == "Boat" or connected_r == "Boat":
|
||||
continue
|
||||
connected_r = StaticWitnessLogic.ALL_REGIONS_BY_NAME[connected_r]
|
||||
if not any([panel in option for option in connection[1]]):
|
||||
continue
|
||||
if panel not in region["panels"] | connected_r["panels"]:
|
||||
self.NECESSARY_EVENT_PANELS.add(panel)
|
||||
if StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"] != region_name:
|
||||
self.EVENT_PANELS_FROM_REGIONS.add(panel)
|
||||
|
||||
for event_panel in self.EVENT_PANELS_FROM_PANELS:
|
||||
for panel, panel_req in self.REQUIREMENTS_BY_HEX.items():
|
||||
if any([event_panel in item_set for item_set in panel_req]):
|
||||
region1 = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"]
|
||||
region2 = StaticWitnessLogic.CHECKS_BY_HEX[event_panel]["region"]["name"]
|
||||
|
||||
if not self._regions_are_adjacent(region1, region2):
|
||||
self.NECESSARY_EVENT_PANELS.add(event_panel)
|
||||
self.EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS)
|
||||
self.EVENT_PANELS.update(self.EVENT_PANELS_FROM_REGIONS)
|
||||
|
||||
for always_hex, always_item in self.ALWAYS_EVENT_NAMES_BY_HEX.items():
|
||||
self.ALWAYS_EVENT_HEX_CODES.add(always_hex)
|
||||
self.NECESSARY_EVENT_PANELS.add(always_hex)
|
||||
self.EVENT_PANELS.add(always_hex)
|
||||
self.EVENT_ITEM_NAMES[always_hex] = always_item
|
||||
|
||||
for panel in self.NECESSARY_EVENT_PANELS:
|
||||
for panel in self.EVENT_PANELS:
|
||||
pair = self.make_event_item_pair(panel)
|
||||
self.EVENT_ITEM_PAIRS[pair[0]] = pair[1]
|
||||
|
||||
@@ -297,8 +297,8 @@ class WitnessPlayerLogic:
|
||||
self.EVENT_PANELS_FROM_REGIONS = set()
|
||||
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set()
|
||||
self.DOOR_DICT_FOR_CLIENT = dict()
|
||||
self.DOOR_CONNECTIONS_TO_SEVER = set()
|
||||
self.DOOR_ITEMS_BY_ID = dict()
|
||||
self.STARTING_INVENTORY = set()
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME = copy.copy(StaticWitnessLogic.STATIC_CONNECTIONS_BY_REGION_NAME)
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(StaticWitnessLogic.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
|
||||
@@ -306,8 +306,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Determining which panels need to be events is a difficult process.
|
||||
# At the end, we will have EVENT_ITEM_PAIRS for all the necessary ones.
|
||||
self.ORIGINAL_EVENT_PANELS = set()
|
||||
self.NECESSARY_EVENT_PANELS = set()
|
||||
self.EVENT_PANELS = set()
|
||||
self.EVENT_ITEM_PAIRS = dict()
|
||||
self.ALWAYS_EVENT_HEX_CODES = set()
|
||||
self.COMPLETELY_DISABLED_CHECKS = set()
|
||||
@@ -320,42 +319,63 @@ class WitnessPlayerLogic:
|
||||
"0x00037": "Monastery Branch Panels Activate",
|
||||
"0x0A079": "Access to Bunker Laser",
|
||||
"0x0A3B5": "Door to Tutorial Discard Opens",
|
||||
"0x00139": "Keep Hedges 2 Turns On",
|
||||
"0x019DC": "Keep Hedges 3 Turns On",
|
||||
"0x019E7": "Keep Hedges 4 Turns On",
|
||||
"0x01D3F": "Keep Laser Panel (Pressure Plates) Activates",
|
||||
"0x09F7F": "Mountain Access",
|
||||
"0x0367C": "Quarry Laser Mill Requirement Met",
|
||||
"0x009A1": "Swamp Rotating Bridge Near Side",
|
||||
"0x009A1": "Swamp Rotated Shapers 1 Activates",
|
||||
"0x00006": "Swamp Cyan Water Drains",
|
||||
"0x00990": "Swamp Broken Shapers 1 Activates",
|
||||
"0x0A8DC": "Lower Avoid 6 Activates",
|
||||
"0x0000A": "Swamp More Rotated Shapers 1 Access",
|
||||
"0x09ED8": "Inside Mountain Second Layer Both Light Bridges Solved",
|
||||
"0x09E86": "Inside Mountain Second Layer Blue Bridge Access",
|
||||
"0x09ED8": "Inside Mountain Second Layer Yellow Bridge Access",
|
||||
"0x0A3D0": "Quarry Laser Boathouse Requirement Met",
|
||||
"0x00596": "Swamp Red Water Drains",
|
||||
"0x28B39": "Town Tower 4th Door Opens",
|
||||
"0x00E3A": "Swamp Purple Water Drains",
|
||||
"0x0343A": "Door to Symmetry Island Powers On",
|
||||
"0xFFF00": "Inside Mountain Bottom Layer Discard Turns On"
|
||||
"0xFFF00": "Inside Mountain Bottom Layer Discard Turns On",
|
||||
"0x17CA6": "All Boat Panels Turn On",
|
||||
"0x17CDF": "All Boat Panels Turn On",
|
||||
"0x09DB8": "All Boat Panels Turn On",
|
||||
"0x17C95": "All Boat Panels Turn On",
|
||||
"0x03BB0": "Town Church Lattice Vision From Outside",
|
||||
"0x28AC1": "Town Shapers & Dots & Eraser Turns On",
|
||||
"0x28A69": "Town Tower 1st Door Opens",
|
||||
"0x28ACC": "Town Tower 2nd Door Opens",
|
||||
"0x28AD9": "Town Tower 3rd Door Opens",
|
||||
"0x28B39": "Town Tower 4th Door Opens",
|
||||
"0x03675": "Quarry Mill Ramp Activation From Above",
|
||||
"0x03679": "Quarry Mill Lift Lowering While Standing On It",
|
||||
"0x2FAF6": "Tutorial Gate Secret Solution Knowledge",
|
||||
"0x079DF": "Town Hexagonal Reflection Turns On",
|
||||
"0x17DA2": "Right Orange Bridge Fully Extended",
|
||||
"0x19B24": "Shadows Lower Avoid Patterns Visible",
|
||||
"0x2700B": "Open Door to Treehouse Laser House",
|
||||
"0x00055": "Orchard Apple Trees 4 Turns On",
|
||||
}
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||
"0x0360D": "Symmetry Laser Activation",
|
||||
"0x03608": "Desert Laser Activation",
|
||||
"0x00509": "Symmetry Laser Activation",
|
||||
"0x012FB": "Desert Laser Activation",
|
||||
"0x09F98": "Desert Laser Redirection",
|
||||
"0x03612": "Quarry Laser Activation",
|
||||
"0x19650": "Shadows Laser Activation",
|
||||
"0x0360E": "Keep Laser Activation",
|
||||
"0x03317": "Keep Laser Activation",
|
||||
"0x17CA4": "Monastery Laser Activation",
|
||||
"0x032F5": "Town Laser Activation",
|
||||
"0x03616": "Jungle Laser Activation",
|
||||
"0x09DE0": "Bunker Laser Activation",
|
||||
"0x03615": "Swamp Laser Activation",
|
||||
"0x03613": "Treehouse Laser Activation",
|
||||
"0x01539": "Quarry Laser Activation",
|
||||
"0x181B3": "Shadows Laser Activation",
|
||||
"0x014BB": "Keep Laser Activation",
|
||||
"0x17C65": "Monastery Laser Activation",
|
||||
"0x032F9": "Town Laser Activation",
|
||||
"0x00274": "Jungle Laser Activation",
|
||||
"0x0C2B2": "Bunker Laser Activation",
|
||||
"0x00BF6": "Swamp Laser Activation",
|
||||
"0x028A4": "Treehouse Laser Activation",
|
||||
"0x03535": "Shipwreck Video Pattern Knowledge",
|
||||
"0x03542": "Mountain Video Pattern Knowledge",
|
||||
"0x0339E": "Desert Video Pattern Knowledge",
|
||||
"0x03481": "Tutorial Video Pattern Knowledge",
|
||||
"0x03702": "Jungle Video Pattern Knowledge",
|
||||
"0x2FAF6": "Theater Walkway Video Pattern Knowledge",
|
||||
"0x0356B": "Challenge Video Pattern Knowledge",
|
||||
"0x09F7F": "Mountaintop Trap Door Turns On",
|
||||
"0x17C34": "Mountain Access",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user