Beispiel #1
0
    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')
Beispiel #2
0
    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