def __init__(self, settings): self.settings = settings self.equipment_colors = {} self.ui_colors = {} self.misc_colors = {} self.sfx = {} self.bgm = {} self.src_dict = {} self.errors = [] if self.settings.enable_cosmetic_file: if self.settings.cosmetic_file: try: if any( map(self.settings.cosmetic_file.endswith, ['.z64', '.n64', '.v64'])): raise InvalidFileException( "Your Ocarina of Time ROM doesn't belong in the cosmetics plandomizer setting. If you don't know what this is for, or don't plan to use it, disable cosmetic plandomizer and try again." ) with open(self.settings.cosmetic_file) as infile: self.src_dict = json.load(infile) except json.decoder.JSONDecodeError as e: raise InvalidFileException( f"Invalid Cosmetic Plandomizer File. Make sure the file is a valid JSON file. Failure reason: {str(e)}" ) from None except FileNotFoundError: message = "Cosmetic Plandomizer file not found at %s" % ( self.settings.cosmetic_file) logging.getLogger('').warning(message) self.errors.append(message) self.settings.enable_cosmetic_file = False except InvalidFileException as e: logging.getLogger('').warning(str(e)) self.errors.append(str(e)) self.settings.enable_cosmetic_file = False else: logging.getLogger('').warning( "Cosmetic Plandomizer enabled, but no file provided.") self.settings.enable_cosmetic_file = False if self.src_dict.get('settings', {}): valid_settings = [] for setting in setting_infos: if setting.name not in self.src_dict[ 'settings'] or not setting.cosmetic: continue self.settings.__dict__[ setting.name] = self.src_dict['settings'][setting.name] valid_settings.append(setting.name) for setting in list(self.src_dict['settings'].keys()): if setting not in valid_settings: del self.src_dict['settings'][setting] if self.src_dict['settings'].get('randomize_all_cosmetics', False): settings.resolve_random_settings( cosmetic=True, randomize_key='randomize_all_cosmetics') if self.src_dict['settings'].get('randomize_all_sfx', False): settings.resolve_random_settings( cosmetic=True, randomize_key='randomize_all_sfx')
def __init__(self, id, settings): self.id = id self.shuffle = 'vanilla' self.dungeons = [] self.regions = [] self.itempool = [] self._cached_locations = None self._entrance_cache = {} self._region_cache = {} self._location_cache = {} self.required_locations = [] self.shop_prices = {} self.scrub_prices = {} self.maximum_wallets = 0 self.light_arrow_location = None self.triforce_count = 0 self.bingosync_url = None self.parser = Rule_AST_Transformer(self) self.event_items = set() # dump settings directly into world's namespace # this gives the world an attribute for every setting listed in Settings.py self.settings = settings self.__dict__.update(settings.__dict__) self.distribution = settings.distribution.world_dists[id] # rename a few attributes... self.keysanity = self.shuffle_smallkeys in [ 'keysanity', 'remove', 'any_dungeon', 'overworld' ] self.check_beatable_only = not self.all_reachable self.shuffle_special_interior_entrances = self.shuffle_interior_entrances == 'all' self.shuffle_interior_entrances = self.shuffle_interior_entrances in [ 'simple', 'all' ] self.entrance_shuffle = self.shuffle_interior_entrances or self.shuffle_grotto_entrances or self.shuffle_dungeon_entrances or \ self.shuffle_overworld_entrances or self.owl_drops or self.warp_songs or self.spawn_positions self.ensure_tod_access = self.shuffle_interior_entrances or self.shuffle_overworld_entrances or self.spawn_positions self.disable_trade_revert = self.shuffle_interior_entrances or self.shuffle_overworld_entrances if self.open_forest == 'closed' and ( self.shuffle_special_interior_entrances or self.shuffle_overworld_entrances or self.warp_songs or self.spawn_positions or self.decouple_entrances or (self.mix_entrance_pools != 'off')): self.open_forest = 'closed_deku' self.triforce_goal = self.triforce_goal_per_world * settings.world_count if self.triforce_hunt: # Pin shuffle_ganon_bosskey to 'triforce' when triforce_hunt is enabled # (specifically, for randomize_settings) self.shuffle_ganon_bosskey = 'triforce' # Determine LACS Condition if self.shuffle_ganon_bosskey == 'lacs_medallions': self.lacs_condition = 'medallions' elif self.shuffle_ganon_bosskey == 'lacs_dungeons': self.lacs_condition = 'dungeons' elif self.shuffle_ganon_bosskey == 'lacs_stones': self.lacs_condition = 'stones' elif self.shuffle_ganon_bosskey == 'lacs_tokens': self.lacs_condition = 'tokens' else: self.lacs_condition = 'vanilla' # trials that can be skipped will be decided later self.skipped_trials = { 'Forest': False, 'Fire': False, 'Water': False, 'Spirit': False, 'Shadow': False, 'Light': False } # dungeon forms will be decided later self.dungeon_mq = { 'Deku Tree': False, 'Dodongos Cavern': False, 'Jabu Jabus Belly': False, 'Bottom of the Well': False, 'Ice Cavern': False, 'Gerudo Training Grounds': False, 'Forest Temple': False, 'Fire Temple': False, 'Water Temple': False, 'Spirit Temple': False, 'Shadow Temple': False, 'Ganons Castle': False } self.can_take_damage = True self.resolve_random_settings() if len(settings.hint_dist_user) == 0: for d in HintDistFiles(): dist = read_json(d) if dist['name'] == self.hint_dist: self.hint_dist_user = dist else: self.hint_dist = 'custom' # Validate hint distribution format # Originally built when I was just adding the type distributions # Location/Item Additions and Overrides are not validated hint_dist_valid = False if all(key in self.hint_dist_user['distribution'] for key in hint_dist_keys): hint_dist_valid = True sub_keys = {'order', 'weight', 'fixed', 'copies'} for key in self.hint_dist_user['distribution']: if not all(sub_key in sub_keys for sub_key in self.hint_dist_user['distribution'][key]): hint_dist_valid = False if not hint_dist_valid: raise InvalidFileException( """Hint distributions require all hint types be present in the distro (trial, always, woth, barren, item, song, overworld, dungeon, entrance, sometimes, random, junk, named-item). If a hint type should not be shuffled, set its order to 0. Hint type format is \"type\": { \"order\": 0, \"weight\": 0.0, \"fixed\": 0, \"copies\": 0 }""" ) self.added_hint_types = {} self.item_added_hint_types = {} self.hint_exclusions = set() if self.skip_child_zelda or settings.skip_child_zelda: self.hint_exclusions.add('Song from Impa') self.hint_type_overrides = {} self.item_hint_type_overrides = {} for dist in hint_dist_keys: self.added_hint_types[dist] = [] for loc in self.hint_dist_user['add_locations']: if 'types' in loc: if dist in loc['types']: self.added_hint_types[dist].append(loc['location']) self.item_added_hint_types[dist] = [] for i in self.hint_dist_user['add_items']: if dist in i['types']: self.item_added_hint_types[dist].append(i['item']) self.hint_type_overrides[dist] = [] for loc in self.hint_dist_user['remove_locations']: if dist in loc['types']: self.hint_type_overrides[dist].append(loc['location']) self.item_hint_type_overrides[dist] = [] for i in self.hint_dist_user['remove_items']: if dist in i['types']: self.item_hint_type_overrides[dist].append(i['item']) self.hint_text_overrides = {} for loc in self.hint_dist_user['add_locations']: if 'text' in loc: # Arbitrarily throw an error at 80 characters to prevent overfilling the text box. if len(loc['text']) > 80: raise Exception('Custom hint text too large for %s', loc['location']) self.hint_text_overrides.update({loc['location']: loc['text']}) self.always_hints = [hint.name for hint in getRequiredHints(self)] self.state = State(self) # Allows us to cut down on checking whether some items are required self.max_progressions = { item: value[3].get('progressive', 1) if value[3] else 1 for item, value in item_table.items() } max_tokens = 0 if self.bridge == 'tokens': max_tokens = max(max_tokens, self.bridge_tokens) if self.lacs_condition == 'tokens': max_tokens = max(max_tokens, self.lacs_tokens) tokens = [50, 40, 30, 20, 10] for t in tokens: if f'{t} Gold Skulltula Reward' not in self.disabled_locations: max_tokens = max(max_tokens, t) self.max_progressions['Gold Skulltula Token'] = max_tokens # Additional Ruto's Letter become Bottle, so we may have to collect two. self.max_progressions['Rutos Letter'] = 2