Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Witness: Event System & Item Classification System revamp #2652

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c86e5bf
New classification changes (Credit: Exempt-Medic)
NewSoupVi Dec 29, 2023
d8b4005
Don't need to pass world
NewSoupVi Dec 29, 2023
72a8ef8
Comments
NewSoupVi Dec 29, 2023
5f47508
Replace it with another magic system because why not at this point :D…
NewSoupVi Dec 29, 2023
5a15c04
oops
NewSoupVi Dec 29, 2023
c368cb7
Oops
NewSoupVi Dec 29, 2023
7e6cdd4
Make events conditions. Disable_Non_Randomized will no longer just 'h…
NewSoupVi Jan 1, 2024
1030bfa
What the fuck? Has this just always been broken?
NewSoupVi Jan 1, 2024
a1e1948
Merge branch 'item_classifications' into event_revamp_and_item_classi…
NewSoupVi Jan 1, 2024
ae6a5a1
Don't have boolean function with 'not' in the name
NewSoupVi Jan 1, 2024
8c466e2
Another useful classification
NewSoupVi Jan 2, 2024
a116d00
slight code refactor
NewSoupVi Jan 2, 2024
cb5df8f
Funny haha booleans
NewSoupVi Jan 2, 2024
8df2134
Fixed Long Bridge Usefulness
NewSoupVi Jan 4, 2024
dce7f83
Merge branch 'main' into event_revamp_and_item_classifications
NewSoupVi Jan 16, 2024
356df32
Merge branch 'main' into event_revamp_and_item_classifications
NewSoupVi Jan 16, 2024
76b5dd2
Bring back Jungle Popup Wall Control Panel event outside of doors: pa…
NewSoupVi Jan 17, 2024
f210069
Add some more useful classifications
NewSoupVi Feb 10, 2024
99b8786
Merge branch 'main' into event_revamp_and_item_classifications
Berserker66 Feb 11, 2024
42ed54f
Merge remote-tracking branch 'upstream/main' into event_revamp_and_it…
NewSoupVi Feb 11, 2024
46ada16
Merge branch 'event_revamp_and_item_classifications' of https://githu…
NewSoupVi Feb 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 5 additions & 23 deletions worlds/witness/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,30 +112,12 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn
or name in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME
}

# Adjust item classifications based on game settings.
eps_shuffled = self._world.options.shuffle_EPs
come_to_you = self._world.options.elevators_come_to_you
difficulty = self._world.options.puzzle_randomization
# Downgrade door items
for item_name, item_data in self.item_data.items():
if not eps_shuffled and item_name in {"Monastery Garden Entry (Door)",
"Monastery Shortcuts",
"Quarry Boathouse Hook Control (Panel)",
"Windmill Turn Control (Panel)"}:
# Downgrade doors that only gate progress in EP shuffle.
item_data.classification = ItemClassification.useful
elif not come_to_you and not eps_shuffled and item_name in {"Quarry Elevator Control (Panel)",
"Swamp Long Bridge (Panel)"}:
# These Bridges/Elevators are not logical access because they may leave you stuck.
item_data.classification = ItemClassification.useful
elif item_name in {"River Monastery Garden Shortcut (Door)",
"Monastery Laser Shortcut (Door)",
"Orchard Second Gate (Door)",
"Jungle Bamboo Laser Shortcut (Door)",
"Caves Elevator Controls (Panel)"}:
# Downgrade doors that don't gate progress.
item_data.classification = ItemClassification.useful
elif item_name == "Keep Pressure Plates 2 Exit (Door)" and not (difficulty == "none" and eps_shuffled):
# PP2EP requires the door in vanilla puzzles, otherwise it's unnecessary
if not isinstance(item_data.definition, DoorItemDefinition):
continue

if all(not self._logic.solvability_guaranteed(e_hex) for e_hex in item_data.definition.panel_id_hexes):
item_data.classification = ItemClassification.useful

# Build the mandatory item list.
Expand Down
2 changes: 1 addition & 1 deletion worlds/witness/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic):
)

event_locations = {
p for p in player_logic.EVENT_PANELS
p for p in player_logic.USED_EVENT_NAMES_BY_HEX
}

self.EVENT_LOCATION_TABLE = {
Expand Down
119 changes: 96 additions & 23 deletions worlds/witness/player_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]:
for option_entity in option:
dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity)

if option_entity in self.EVENT_NAMES_BY_HEX:
if option_entity in self.ALWAYS_EVENT_NAMES_BY_HEX:
new_items = frozenset({frozenset([option_entity])})
elif (panel_hex, option_entity) in self.CONDITIONAL_EVENTS:
new_items = frozenset({frozenset([option_entity])})
self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[(panel_hex, option_entity)]
elif option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect",
"PP2 Weirdness", "Theater to Tunnels"}:
new_items = frozenset({frozenset([option_entity])})
Expand Down Expand Up @@ -170,14 +173,11 @@ def make_single_adjustment(self, adj_type: str, line: str):
if adj_type == "Event Items":
line_split = line.split(" - ")
new_event_name = line_split[0]
hex_set = line_split[1].split(",")

for entity, event_name in self.EVENT_NAMES_BY_HEX.items():
if event_name == new_event_name:
self.DONT_MAKE_EVENTS.add(entity)
entity_hex = line_split[1]
dependent_hex_set = line_split[2].split(",")

for hex_code in hex_set:
self.EVENT_NAMES_BY_HEX[hex_code] = new_event_name
for dependent_hex in dependent_hex_set:
self.CONDITIONAL_EVENTS[(entity_hex, dependent_hex)] = new_event_name

return

Expand Down Expand Up @@ -437,7 +437,7 @@ def make_options_adjustments(self, world: "WitnessWorld"):
obelisk = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[self.REFERENCE_LOGIC.EP_TO_OBELISK_SIDE[ep_hex]]
obelisk_name = obelisk["checkName"]
ep_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[ep_hex]["checkName"]
self.EVENT_NAMES_BY_HEX[ep_hex] = f"{obelisk_name} - {ep_name}"
self.ALWAYS_EVENT_NAMES_BY_HEX[ep_hex] = f"{obelisk_name} - {ep_name}"
else:
adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:])

Expand Down Expand Up @@ -505,7 +505,8 @@ def make_dependency_reduced_checklist(self):
for option in connection[1]:
individual_entity_requirements = []
for entity in option:
if entity in self.EVENT_NAMES_BY_HEX or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX:
if (entity in self.ALWAYS_EVENT_NAMES_BY_HEX
or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX):
individual_entity_requirements.append(frozenset({frozenset({entity})}))
else:
entity_req = self.reduce_req_within_region(entity)
Expand All @@ -522,28 +523,96 @@ def make_dependency_reduced_checklist(self):

self.CONNECTIONS_BY_REGION_NAME[region] = new_connections

def solvability_guaranteed(self, entity_hex: str):
return not (
entity_hex in self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY
or entity_hex in self.COMPLETELY_DISABLED_ENTITIES
or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES
)

def determine_unrequired_entities(self, world: "WitnessWorld"):
"""Figure out which major items are actually useless in this world's settings"""

# Gather quick references to relevant options
eps_shuffled = world.options.shuffle_EPs
come_to_you = world.options.elevators_come_to_you
difficulty = world.options.puzzle_randomization
discards_shuffled = world.options.shuffle_discarded_panels
boat_shuffled = world.options.shuffle_boat
symbols_shuffled = world.options.shuffle_symbols
disable_non_randomized = world.options.disable_non_randomized_puzzles
postgame_included = world.options.shuffle_postgame
goal = world.options.victory_condition
doors = world.options.shuffle_doors
shortbox_req = world.options.mountain_lasers
longbox_req = world.options.challenge_lasers

# Make some helper booleans so it is easier to follow what's going on
mountain_upper_is_in_postgame = (
goal == "mountain_box_short"
or goal == "mountain_box_long" and longbox_req <= shortbox_req
)
mountain_upper_included = postgame_included or not mountain_upper_is_in_postgame
remote_doors = doors >= 2
door_panels = doors == "panels" or doors == "mixed"

# It is easier to think about when these items *are* required, so we make that dict first
# If the entity is disabled anyway, we don't need to consider that case
is_item_required_dict = {
"0x03750": eps_shuffled, # Monastery Garden Entry Door
"0x275FA": eps_shuffled, # Boathouse Hook Control
"0x17D02": eps_shuffled, # Windmill Turn Control
"0x0368A": symbols_shuffled or door_panels, # Quarry Stoneworks Stairs Door
"0x3865F": symbols_shuffled or door_panels or eps_shuffled, # Quarry Boathouse 2nd Barrier
"0x17CC4": come_to_you or eps_shuffled, # Quarry Elevator Panel
"0x17E2B": come_to_you and boat_shuffled or eps_shuffled, # Swamp Long Bridge
"0x0CF2A": False, # Jungle Monastery Garden Shortcut
"0x17CAA": remote_doors, # Jungle Monastery Garden Shortcut Panel
"0x0364E": False, # Monastery Laser Shortcut Door
"0x03713": remote_doors, # Monastery Laser Shortcut Panel
"0x03313": False, # Orchard Second Gate
"0x337FA": remote_doors, # Jungle Bamboo Laser Shortcut Panel
"0x3873B": False, # Jungle Bamboo Laser Shortcut Door
"0x335AB": False, # Caves Elevator Controls
"0x335AC": False, # Caves Elevator Controls
"0x3369D": False, # Caves Elevator Controls
"0x01BEA": difficulty == "none" and eps_shuffled, # Keep PP2
"0x0A0C9": eps_shuffled or discards_shuffled or disable_non_randomized, # Cargo Box Entry Door
"0x09EEB": discards_shuffled or mountain_upper_included, # Mountain Floor 2 Elevator Control Panel
"0x09EDD": mountain_upper_included, # Mountain Floor 2 Exit Door
"0x17CAB": symbols_shuffled or not disable_non_randomized or "0x17CAB" not in self.DOOR_ITEMS_BY_ID,
# Jungle Popup Wall Panel
}

# Now, return the keys of the dict entries where the result is False to get unrequired major items
self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY |= {
item_name for item_name, is_required in is_item_required_dict.items() if not is_required
}

def make_event_item_pair(self, panel: str):
"""
Makes a pair of an event panel and its event item
"""
action = " Opened" if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["entityType"] == "Door" else " Solved"

name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["checkName"] + action
if panel not in self.EVENT_NAMES_BY_HEX:
if panel not in self.USED_EVENT_NAMES_BY_HEX:
warning("Panel \"" + name + "\" does not have an associated event name.")
self.EVENT_NAMES_BY_HEX[panel] = name + " Event"
pair = (name, self.EVENT_NAMES_BY_HEX[panel])
self.USED_EVENT_NAMES_BY_HEX[panel] = name + " Event"
pair = (name, self.USED_EVENT_NAMES_BY_HEX[panel])
return pair

def make_event_panel_lists(self):
self.EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"

for event_hex, event_name in self.EVENT_NAMES_BY_HEX.items():
if event_hex in self.COMPLETELY_DISABLED_ENTITIES or event_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES:
continue
self.EVENT_PANELS.add(event_hex)
self.USED_EVENT_NAMES_BY_HEX.update(self.ALWAYS_EVENT_NAMES_BY_HEX)

self.USED_EVENT_NAMES_BY_HEX = {
event_hex: event_name for event_hex, event_name in self.USED_EVENT_NAMES_BY_HEX.items()
if self.solvability_guaranteed(event_hex)
}

for panel in self.EVENT_PANELS:
for panel in self.USED_EVENT_NAMES_BY_HEX:
pair = self.make_event_item_pair(panel)
self.EVENT_ITEM_PAIRS[pair[0]] = pair[1]

Expand All @@ -556,6 +625,8 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in

self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES = set()

self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY = set()

self.THEORETICAL_ITEMS = set()
self.THEORETICAL_ITEMS_NO_MULTI = set()
self.MULTI_AMOUNTS = defaultdict(lambda: 1)
Expand All @@ -580,16 +651,14 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in

# 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.EVENT_PANELS = set()
self.EVENT_ITEM_PAIRS = dict()
self.DONT_MAKE_EVENTS = set()
self.COMPLETELY_DISABLED_ENTITIES = set()
self.PRECOMPLETED_LOCATIONS = set()
self.EXCLUDED_LOCATIONS = set()
self.ADDED_CHECKS = set()
self.VICTORY_LOCATION = "0x0356B"

self.EVENT_NAMES_BY_HEX = {
self.ALWAYS_EVENT_NAMES_BY_HEX = {
"0x00509": "+1 Laser (Symmetry Laser)",
"0x012FB": "+1 Laser (Desert Laser)",
"0x09F98": "Desert Laser Redirection",
Expand All @@ -602,10 +671,14 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
"0x0C2B2": "+1 Laser (Bunker Laser)",
"0x00BF6": "+1 Laser (Swamp Laser)",
"0x028A4": "+1 Laser (Treehouse Laser)",
"0x09F7F": "Mountain Entry",
"0x17C34": "Mountain Entry",
"0xFFF00": "Bottom Floor Discard Turns On",
}

self.USED_EVENT_NAMES_BY_HEX = {}
self.CONDITIONAL_EVENTS = {}

self.make_options_adjustments(world)
self.determine_unrequired_entities(world)
self.make_dependency_reduced_checklist()
self.make_event_panel_lists()
2 changes: 1 addition & 1 deletion worlds/witness/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def _has_item(item: str, world: "WitnessWorld", player: int,
return lambda state: _can_do_expert_pp2(state, world)
elif item == "Theater to Tunnels":
return lambda state: _can_do_theater_to_tunnels(state, world)
if item in player_logic.EVENT_PANELS:
if item in player_logic.USED_EVENT_NAMES_BY_HEX:
return _can_solve_panel(item, world, player, player_logic, locat)

prog_item = StaticWitnessLogic.get_parent_progressive_item(item)
Expand Down
10 changes: 5 additions & 5 deletions worlds/witness/settings/Exclusions/Disable_Unrandomized.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Event Items:
Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9
Bunker Laser Activation - 0x00061,0x17D01,0x17C42
Shadows Laser Activation - 0x00021,0x17D28,0x17C71
Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x17CF7
Jungle Popup Wall Lifts - 0x17FA0,0x17D27,0x17F9B,0x17CAB
Monastery Laser Activation - 0x17C65 - 0x00A5B,0x17CE7,0x17FA9
Bunker Laser Activation - 0x0C2B2 - 0x00061,0x17D01,0x17C42
Shadows Laser Activation - 0x181B3 - 0x00021,0x17D28,0x17C71
Town Tower 4th Door Opens - 0x2779A - 0x17CFB,0x3C12B,0x17CF7
Jungle Popup Wall Lifts - 0x1475B - 0x17FA0,0x17D27,0x17F9B,0x17CAB

Requirement Changes:
0x17C65 - 0x00A5B | 0x17CE7 | 0x17FA9
Expand Down
Loading