from typing import Dict, List, Optional from BaseClasses import Item, ItemClassification, Location, Region, Tutorial from Fill import fill_restrictive from worlds.AutoWorld import WebWorld, World from . import Constants, Rules from .Items import ItemType, SHIVERS_ITEM_ID_OFFSET, ShiversItem, item_table from .Options import ShiversOptions, shivers_option_groups from .Rules import set_rules class ShiversWeb(WebWorld): tutorials = [Tutorial( "Shivers Setup Guide", "A guide to setting up Shivers for Multiworld.", "English", "setup_en.md", "setup/en", ["GodlFire", "Cynbel_Terreus"] )] option_groups = shivers_option_groups class ShiversWorld(World): """ Shivers is a horror themed point and click adventure. Explore the mysteries of Windlenot's Museum of the Strange and Unusual. """ game = "Shivers" topology_present = False web = ShiversWeb() options_dataclass = ShiversOptions options: ShiversOptions set_rules = set_rules item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = Constants.location_name_to_id storage_placements = [] pot_completed_list: List[int] def generate_early(self): self.pot_completed_list = [] # Pot piece shuffle location: if self.options.location_pot_pieces == "own_world": self.options.local_items.value |= {name for name, data in item_table.items() if data.type in [ItemType.POT, ItemType.POT_COMPLETE]} elif self.options.location_pot_pieces == "different_world": self.options.non_local_items.value |= {name for name, data in item_table.items() if data.type in [ItemType.POT, ItemType.POT_COMPLETE]} # Ixupi captures priority locations: if self.options.ixupi_captures_priority: self.options.priority_locations.value |= ( {name for name in self.location_names if name.startswith('Ixupi Captured')} ) def create_item(self, name: str) -> Item: data = item_table[name] return ShiversItem(name, data.classification, data.code, self.player) def create_event_location(self, region_name: str, location_name: str, event_name: Optional[str] = None) -> None: region = self.get_region(region_name) loc = ShiversLocation(self.player, location_name, None, region) if event_name is not None: loc.place_locked_item(ShiversItem(event_name, ItemClassification.progression, None, self.player)) else: loc.place_locked_item(ShiversItem(location_name, ItemClassification.progression, None, self.player)) loc.show_in_spoiler = False region.locations.append(loc) def create_regions(self) -> None: # Create regions for region_name, exits in Constants.region_info["regions"]: r = Region(region_name, self.player, self.multiworld) self.multiworld.regions.append(r) for exit_name in exits: r.create_exit(exit_name) # Bind mandatory connections for entr_name, region_name in Constants.region_info["mandatory_connections"]: e = self.get_entrance(entr_name) r = self.get_region(region_name) e.connect(r) # Locations # Build exclusion list removed_locations = set() if not self.options.include_information_plaques: removed_locations.update(Constants.exclusion_info["plaques"]) if not self.options.elevators_stay_solved: removed_locations.update(Constants.exclusion_info["elevators"]) if not self.options.early_lightning: removed_locations.update(Constants.exclusion_info["lightning"]) # Add locations for region_name, locations in Constants.location_info["locations_by_region"].items(): region = self.get_region(region_name) for loc_name in locations: if loc_name not in removed_locations: loc = ShiversLocation(self.player, loc_name, self.location_name_to_id.get(loc_name, None), region) region.locations.append(loc) self.create_event_location("Prehistoric", "Set Skull Dial: Prehistoric") self.create_event_location("Tar River", "Set Skull Dial: Tar River") self.create_event_location("Egypt", "Set Skull Dial: Egypt") self.create_event_location("Burial", "Set Skull Dial: Burial") self.create_event_location("Gods Room", "Set Skull Dial: Gods Room") self.create_event_location("Werewolf", "Set Skull Dial: Werewolf") self.create_event_location("Projector Room", "Viewed Theater Movie") self.create_event_location("Clock Chains", "Clock Chains", "Set Time") self.create_event_location("Clock Tower", "Jukebox", "Set Song") self.create_event_location("Fortune Teller", "Viewed Fortune") self.create_event_location("Orrery", "Orrery", "Aligned Planets") self.create_event_location("Norse Stone", "Norse Stone", "Viewed Norse Stone") self.create_event_location("Beth's Body", "Beth's Body", "Viewed Page 17") self.create_event_location("Windlenot's Body", "Windlenot's Body", "Viewed Egyptian Hieroglyphics Explained") self.create_event_location("Guillotine", "Guillotine", "Lost Your Head") def create_items(self) -> None: # Add items to item pool item_pool = [] for name, data in item_table.items(): if data.type in [ItemType.KEY, ItemType.ABILITY, ItemType.IXUPI_AVAILABILITY]: item_pool.append(self.create_item(name)) # Pot pieces/Completed/Mixed: if self.options.full_pots == "pieces": item_pool += [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT] elif self.options.full_pots == "complete": item_pool += [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_COMPLETE] else: # Roll for if pieces or a complete pot will be used. # Pot Pieces pieces = [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT] complete = [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_COMPLETE] for i in range(10): if self.random.randint(0, 1) == 0: self.pot_completed_list.append(0) item_pool.append(pieces[i]) item_pool.append(pieces[i + 10]) # Completed Pot else: self.pot_completed_list.append(1) item_pool.append(complete[i]) # Add Easier Lyre item_pool += [self.create_item("Easier Lyre") for _ in range(9)] # Place library escape items. Choose a location to place the escape item library_region = self.get_region("Library") library_location = self.random.choice( [loc for loc in library_region.locations if not loc.name.startswith("Storage: ")] ) # Roll for which escape items will be placed in the Library library_random = self.random.randint(1, 3) if library_random == 1: library_location.place_locked_item(self.create_item("Crawling")) item_pool = [item for item in item_pool if item.name != "Crawling"] elif library_random == 2: library_location.place_locked_item(self.create_item("Key for Library")) item_pool = [item for item in item_pool if item.name != "Key for Library"] elif library_random == 3: library_location.place_locked_item(self.create_item("Key for Three Floor Elevator")) library_location_2 = self.random.choice( [loc for loc in library_region.locations if not loc.name.startswith("Storage: ") and loc != library_location] ) library_location_2.place_locked_item(self.create_item("Key for Egypt Room")) item_pool = [item for item in item_pool if item.name not in ["Key for Three Floor Elevator", "Key for Egypt Room"]] # If front door option is on, determine which set of keys will # be used for lobby access and add front door key to item pool lobby_access_keys = 0 if self.options.front_door_usable: lobby_access_keys = self.random.randint(0, 1) item_pool.append(self.create_item("Key for Front Door")) else: item_pool.append(self.create_item("Heal")) def set_lobby_access_keys(items: Dict[str, int]): if lobby_access_keys == 0: items["Key for Underground Lake"] = 1 items["Key for Office Elevator"] = 1 items["Key for Office"] = 1 else: items["Key for Front Door"] = 1 # Lobby access: if self.options.lobby_access == "early": set_lobby_access_keys(self.multiworld.early_items[self.player]) elif self.options.lobby_access == "local": set_lobby_access_keys(self.multiworld.local_early_items[self.player]) goal_item_code = SHIVERS_ITEM_ID_OFFSET + 100 + Constants.years_since_sep_30_1980 for name, data in item_table.items(): if data.type == ItemType.GOAL and data.code == goal_item_code: goal = self.create_item(name) self.get_location("Mystery Solved").place_locked_item(goal) # Extra filler is random between Heals and Easier Lyre. Heals weighted 95%. filler_needed = len(self.multiworld.get_unfilled_locations(self.player)) - len(item_pool) - 23 item_pool += map(self.create_item, self.random.choices( ["Heal", "Easier Lyre"], weights=[95, 5], k=filler_needed )) self.multiworld.itempool += item_pool def pre_fill(self) -> None: # Prefills event storage locations with duplicate pots storage_locs = [] storage_items = [] for locations in Constants.location_info["locations_by_region"].values(): for loc_name in locations: if loc_name.startswith("Storage: "): storage_locs.append(self.get_location(loc_name)) # Pot pieces/Completed/Mixed: if self.options.full_pots == "pieces": storage_items += [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_DUPLICATE] elif self.options.full_pots == "complete": storage_items += [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_COMPELTE_DUPLICATE] storage_items += [self.create_item("Empty") for _ in range(10)] else: pieces = [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_DUPLICATE] complete = [self.create_item(name) for name, data in item_table.items() if data.type == ItemType.POT_COMPELTE_DUPLICATE] for i in range(10): # Pieces if self.pot_completed_list[i] == 0: storage_items.append(pieces[i]) storage_items.append(pieces[i + 10]) # Complete else: storage_items.append(complete[i]) storage_items.append(self.create_item("Empty")) storage_items += [self.create_item("Empty") for _ in range(3)] state = self.multiworld.get_all_state(True) self.random.shuffle(storage_locs) self.random.shuffle(storage_items) fill_restrictive(self.multiworld, state, storage_locs.copy(), storage_items, True, True) self.storage_placements = {location.name.replace("Storage: ", ""): location.item.name.replace(" DUPE", "") for location in storage_locs} def fill_slot_data(self) -> dict: return { "StoragePlacements": self.storage_placements, "ExcludedLocations": list(self.options.exclude_locations.value), "IxupiCapturesNeeded": self.options.ixupi_captures_needed.value, "ElevatorsStaySolved": self.options.elevators_stay_solved.value, "EarlyBeth": self.options.early_beth.value, "EarlyLightning": self.options.early_lightning.value, "FrontDoorUsable": self.options.front_door_usable.value, "PuzzleCollectBehavior": self.options.puzzle_collect_behavior.value, } class ShiversLocation(Location): game = "Shivers"