| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | Defines progression, junk and event items for The Witness | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | import copy | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | from collections import defaultdict | 
					
						
							| 
									
										
										
										
											2022-07-23 12:42:14 +02:00
										 |  |  | from typing import Dict, NamedTuple, Optional, Set | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | from BaseClasses import Item, MultiWorld | 
					
						
							|  |  |  | from . import StaticWitnessLogic, WitnessPlayerLocations, WitnessPlayerLogic | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | from .Options import get_option_value, is_option_enabled, the_witness_options | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | from fractions import Fraction | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ItemData(NamedTuple): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     ItemData for an item in The Witness | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     code: Optional[int] | 
					
						
							|  |  |  |     progression: bool | 
					
						
							|  |  |  |     event: bool = False | 
					
						
							|  |  |  |     trap: bool = False | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |     never_exclude: bool = False | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class WitnessItem(Item): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Item from the game The Witness | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     game: str = "The Witness" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StaticWitnessItems: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Class that handles Witness items independent of world settings | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ALL_ITEM_TABLE: Dict[str, ItemData] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-23 12:42:14 +02:00
										 |  |  |     ITEM_NAME_GROUPS: Dict[str, Set[str]] = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  |     # These should always add up to 1!!! | 
					
						
							|  |  |  |     BONUS_WEIGHTS = { | 
					
						
							|  |  |  |         "Speed Boost": Fraction(1, 1), | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  |     # These should always add up to 1!!! | 
					
						
							|  |  |  |     TRAP_WEIGHTS = {    | 
					
						
							|  |  |  |         "Slowness": Fraction(8, 10), | 
					
						
							|  |  |  |         "Power Surge": Fraction(2, 10), | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ALL_JUNK_ITEMS = set(BONUS_WEIGHTS.keys()) | set(TRAP_WEIGHTS.keys()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     ITEM_ID_TO_DOOR_HEX_ALL = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     def __init__(self): | 
					
						
							|  |  |  |         item_tab = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |         for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             if item[0] == "11 Lasers" or item == "7 Lasers": | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             item_tab[item[0]] = ItemData(158000 + item[1], True, False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-23 12:42:14 +02:00
										 |  |  |             self.ITEM_NAME_GROUPS.setdefault("Symbols", set()).add(item[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         for progressive, item_list in StaticWitnessLogic.PROGRESSIVE_TO_ITEMS.items(): | 
					
						
							|  |  |  |             if not item_list: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if item_list[0] in self.ITEM_NAME_GROUPS.setdefault("Symbols", set()): | 
					
						
							|  |  |  |                 self.ITEM_NAME_GROUPS.setdefault("Symbols", set()).add(progressive) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |         for item in StaticWitnessLogic.ALL_DOOR_ITEMS: | 
					
						
							|  |  |  |             item_tab[item[0]] = ItemData(158000 + item[1], True, False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-23 12:42:14 +02:00
										 |  |  |             # 1500 - 1510 are the laser items, which are handled like doors but should be their own separate group. | 
					
						
							|  |  |  |             if item[1] in range(1500, 1511): | 
					
						
							|  |  |  |                 self.ITEM_NAME_GROUPS.setdefault("Lasers", set()).add(item[0]) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.ITEM_NAME_GROUPS.setdefault("Doors", set()).add(item[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         for item in StaticWitnessLogic.ALL_TRAPS: | 
					
						
							|  |  |  |             item_tab[item[0]] = ItemData( | 
					
						
							|  |  |  |                 158000 + item[1], False, False, True | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for item in StaticWitnessLogic.ALL_BOOSTS: | 
					
						
							|  |  |  |             item_tab[item[0]] = ItemData(158000 + item[1], False, False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |         for item in StaticWitnessLogic.ALL_USEFULS: | 
					
						
							| 
									
										
										
										
											2022-06-24 19:25:23 +02:00
										 |  |  |             item_tab[item[0]] = ItemData(158000 + item[1], False, False, False, item[2]) | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         item_tab = dict(sorted( | 
					
						
							|  |  |  |             item_tab.items(), | 
					
						
							|  |  |  |             key=lambda single_item: single_item[1].code | 
					
						
							|  |  |  |             if isinstance(single_item[1].code, int) else 0) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for key, item in item_tab.items(): | 
					
						
							|  |  |  |             self.ALL_ITEM_TABLE[key] = item | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         for door in StaticWitnessLogic.ALL_DOOR_ITEMS: | 
					
						
							|  |  |  |             self.ITEM_ID_TO_DOOR_HEX_ALL[door[1] + 158000] = {int(door_hex, 16) for door_hex in door[2]} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class WitnessPlayerItems: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Class that defines Items for a single world | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def code(item_name: str): | 
					
						
							|  |  |  |         return StaticWitnessItems.ALL_ITEM_TABLE[item_name].code | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def is_progression(item_name: str, multiworld: MultiWorld, player: int): | 
					
						
							|  |  |  |         useless_doors = { | 
					
						
							|  |  |  |             "River Monastery Shortcut (Door)", | 
					
						
							|  |  |  |             "Jungle & River Shortcuts", | 
					
						
							|  |  |  |             "Monastery Shortcut (Door)", | 
					
						
							|  |  |  |             "Orchard Second Gate (Door)", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if item_name in useless_doors: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ep_doors = { | 
					
						
							|  |  |  |             "Monastery Garden Entry (Door)", | 
					
						
							|  |  |  |             "Monastery Shortcuts", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if item_name in ep_doors: | 
					
						
							|  |  |  |             return get_option_value(multiworld, player, "shuffle_EPs") != 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, locat: WitnessPlayerLocations, multiworld: MultiWorld, player: int, logic: WitnessPlayerLogic): | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         """Adds event items after logic changes due to options""" | 
					
						
							|  |  |  |         self.EVENT_ITEM_TABLE = dict() | 
					
						
							|  |  |  |         self.ITEM_TABLE = copy.copy(StaticWitnessItems.ALL_ITEM_TABLE) | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |         self.PROGRESSION_TABLE = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |         self.ITEM_ID_TO_DOOR_HEX = dict() | 
					
						
							|  |  |  |         self.DOORS = set() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |         self.PROG_ITEM_AMOUNTS = defaultdict(lambda: 1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |         self.SYMBOLS_NOT_IN_THE_GAME = set() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |         self.EXTRA_AMOUNTS = { | 
					
						
							|  |  |  |             "Functioning Brain": 1, | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |             "Puzzle Skip": get_option_value(multiworld, player, "puzzle_skip_amount") | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         for k, v in self.ITEM_TABLE.items(): | 
					
						
							|  |  |  |             if v.progression and not self.is_progression(k, multiworld, player): | 
					
						
							|  |  |  |                 self.ITEM_TABLE[k] = ItemData(v.code, False, False, never_exclude=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |         for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS.union(StaticWitnessLogic.ALL_DOOR_ITEMS): | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |             if item[0] not in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME: | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |                 del self.ITEM_TABLE[item[0]] | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |                 if item in StaticWitnessLogic.ALL_SYMBOL_ITEMS: | 
					
						
							|  |  |  |                     self.SYMBOLS_NOT_IN_THE_GAME.add(StaticWitnessItems.ALL_ITEM_TABLE[item[0]].code) | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |                 if item[0] in StaticWitnessLogic.PROGRESSIVE_TO_ITEMS: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |                     self.PROG_ITEM_AMOUNTS[item[0]] = len(logic.MULTI_LISTS[item[0]]) | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |                 self.PROGRESSION_TABLE[item[0]] = self.ITEM_TABLE[item[0]] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |         self.MULTI_LISTS_BY_CODE = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for item in self.PROG_ITEM_AMOUNTS: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |             multi_list = logic.MULTI_LISTS[item] | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |             self.MULTI_LISTS_BY_CODE[self.code(item)] = [self.code(single_item) for single_item in multi_list] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         for entity_hex, items in logic.DOOR_ITEMS_BY_ID.items(): | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |             entity_hex_int = int(entity_hex, 16) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.DOORS.add(entity_hex_int) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for item in items: | 
					
						
							|  |  |  |                 item_id = StaticWitnessItems.ALL_ITEM_TABLE[item].code | 
					
						
							|  |  |  |                 self.ITEM_ID_TO_DOOR_HEX.setdefault(item_id, set()).add(entity_hex_int) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         symbols = is_option_enabled(multiworld, player, "shuffle_symbols") | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if "shuffle_symbols" not in the_witness_options.keys(): | 
					
						
							|  |  |  |             symbols = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         doors = get_option_value(multiworld, player, "shuffle_doors") | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         self.GOOD_ITEMS = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if symbols: | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |             self.GOOD_ITEMS = [ | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |                 "Dots", "Black/White Squares", "Stars", | 
					
						
							| 
									
										
										
										
											2022-06-16 03:04:45 +02:00
										 |  |  |                 "Shapers", "Symmetry" | 
					
						
							|  |  |  |             ] | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |             if doors: | 
					
						
							|  |  |  |                 self.GOOD_ITEMS = [ | 
					
						
							|  |  |  |                     "Dots", "Black/White Squares", "Symmetry" | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if is_option_enabled(multiworld, player, "shuffle_discarded_panels"): | 
					
						
							| 
									
										
										
										
											2023-03-03 00:08:24 +01:00
										 |  |  |                 if get_option_value(multiworld, player, "puzzle_randomization") == 1: | 
					
						
							|  |  |  |                     self.GOOD_ITEMS.append("Arrows") | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     self.GOOD_ITEMS.append("Triangles") | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  |             self.GOOD_ITEMS = [ | 
					
						
							|  |  |  |                 StaticWitnessLogic.ITEMS_TO_PROGRESSIVE.get(item, item) for item in self.GOOD_ITEMS | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         for event_location in locat.EVENT_LOCATION_TABLE: | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |             location = logic.EVENT_ITEM_PAIRS[event_location] | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             self.EVENT_ITEM_TABLE[location] = ItemData(None, True, True) | 
					
						
							|  |  |  |             self.ITEM_TABLE[location] = ItemData(None, True, True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  |         trap_percentage = get_option_value(multiworld, player, "trap_percentage") | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.JUNK_WEIGHTS = dict() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if trap_percentage != 0: | 
					
						
							|  |  |  |             # I'm sure there must be some super "pythonic" way of doing this :D | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for trap_name, trap_weight in StaticWitnessItems.TRAP_WEIGHTS.items(): | 
					
						
							|  |  |  |                 self.JUNK_WEIGHTS[trap_name] = (trap_weight * trap_percentage) / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if trap_percentage != 100: | 
					
						
							|  |  |  |             for bonus_name, bonus_weight in StaticWitnessItems.BONUS_WEIGHTS.items(): | 
					
						
							|  |  |  |                 self.JUNK_WEIGHTS[bonus_name] = (bonus_weight * (100 - trap_percentage)) / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         self.JUNK_WEIGHTS = { | 
					
						
							|  |  |  |             key: value for (key, value) | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  |             in self.JUNK_WEIGHTS.items() | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             if key in self.ITEM_TABLE.keys() | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-05-09 07:20:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # JUNK_WEIGHTS will add up to 1 if the boosts weights and the trap weights each add up to 1 respectively. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for junk_item in StaticWitnessItems.ALL_JUNK_ITEMS: | 
					
						
							|  |  |  |             if junk_item not in self.JUNK_WEIGHTS.keys(): | 
					
						
							|  |  |  |                 del self.ITEM_TABLE[junk_item] |