def create_dungeons(world):
    for dungeon_info in dungeon_table:
        name = dungeon_info['name']
        hint = dungeon_info['hint'] if 'hint' in dungeon_info else name
        font_color = dungeon_info['font_color'] if 'font_color' in dungeon_info else 'White'
        
        if world.settings.logic_rules == 'glitched':
            if not world.dungeon_mq[name]:
                dungeon_json = os.path.join(data_path('Glitched World'), name + '.json')
            else:
                dungeon_json = os.path.join(data_path('Glitched World'), name + ' MQ.json')
        else:
            if not world.dungeon_mq[name]:
                dungeon_json = os.path.join(data_path('World'), name + '.json')
            else:
                dungeon_json = os.path.join(data_path('World'), name + ' MQ.json')

        
        world.load_regions_from_json(dungeon_json)

        boss_keys = ItemFactory(['Boss Key (%s)' % name] * dungeon_info['boss_key'])
        if not world.dungeon_mq[dungeon_info['name']]:
            small_keys = ItemFactory(['Small Key (%s)' % name] * dungeon_info['small_key'])
        else:
            small_keys = ItemFactory(['Small Key (%s)' % name] * dungeon_info['small_key_mq'])           
        dungeon_items = ItemFactory(['Map (%s)' % name, 
                                     'Compass (%s)' % name] * dungeon_info['dungeon_item'])
        if world.settings.shuffle_mapcompass in ['any_dungeon', 'overworld']:
            for item in dungeon_items:
                item.priority = True

        world.dungeons.append(Dungeon(world, name, hint, font_color, boss_keys, small_keys, dungeon_items))
Esempio n. 2
0
def generate_tunic_icon(color):
    with open(data_path('icons/grey.tiff'),
              'rb') as grey_fil, open(data_path('icons/belt.tiff'),
                                      'rb') as belt_fil:
        grey = list(grey_fil.read())
        belt = list(belt_fil.read())
        return add_belt(add_hue(grey, color, True), belt, True)[154:]
Esempio n. 3
0
def create_dungeons(world):
    for dungeon_info in dungeon_table:
        name = dungeon_info['name']
        hint = dungeon_info['hint'] if 'hint' in dungeon_info else name

        if world.settings.logic_rules == 'glitched':
            if not world.dungeon_mq[name]:
                dungeon_json = os.path.join(data_path('Glitched World'),
                                            name + '.json')
            else:
                dungeon_json = os.path.join(data_path('Glitched World'),
                                            name + ' MQ.json')
        else:
            if not world.dungeon_mq[name]:
                dungeon_json = os.path.join(data_path('World'), name + '.json')
            else:
                dungeon_json = os.path.join(data_path('World'),
                                            name + ' MQ.json')

        world.load_regions_from_json(dungeon_json)

        boss_keys = ItemFactory(['Boss Key (%s)' % name] *
                                dungeon_info['boss_key'])
        if not world.dungeon_mq[dungeon_info['name']]:
            small_keys = ItemFactory(['Small Key (%s)' % name] *
                                     dungeon_info['small_key'])
        else:
            small_keys = ItemFactory(['Small Key (%s)' % name] *
                                     dungeon_info['small_key_mq'])
        dungeon_items = ItemFactory(
            ['Map (%s)' % name, 'Compass (%s)' % name] *
            dungeon_info['dungeon_item'])

        world.dungeons.append(
            Dungeon(world, name, hint, boss_keys, small_keys, dungeon_items))
Esempio n. 4
0
def generate(input_data):
    settings = getSettings(input_data)

    for trick in SettingsList.logic_tricks.values():
        settings.__dict__[trick['name']] = trick['name'] in settings.allowed_tricks

    worlds = []
    for i in range(0, settings.world_count):
        worlds.append(World(i, settings))
        worlds[-1].ensure_tod_access = False

    for id, world in enumerate(worlds):
        if settings.logic_rules == 'glitched':
            overworld_data = os.path.join(data_path('Glitched World'), 'Overworld.json')
        else:
            overworld_data = os.path.join(data_path('World'), 'Overworld.json')

        # Compile the json rules based on settings
        world.load_regions_from_json(overworld_data)
        create_dungeons(world)
        world.create_internal_locations()

        # Populate drop items
        drop_locations = list(filter(lambda loc: loc.type == 'Drop', world.get_locations()))
        for drop_location in drop_locations:
            item = ItemPool.droplocations[drop_location.name]
            world.push_item(drop_location, ItemFactory(item, world))
            drop_location.locked = True

        return world
Esempio n. 5
0
    def __init__(self, settings, patch=True):
        self.__last_address = None

        file = settings.rom
        decomp_file = 'ZOOTDEC.z64'

        os.chdir(local_path())

        with open(data_path('generated/symbols.json'), 'r') as stream:
            symbols = json.load(stream)
            self.symbols = {
                name: int(addr, 16)
                for name, addr in symbols.items()
            }

        if file == '':
            # if not specified, try to read from the previously decompressed rom
            file = decomp_file
            try:
                self.read_rom(file)
            except FileNotFoundError:
                # could not find the decompressed rom either
                raise FileNotFoundError('Must specify path to base ROM')
        else:
            self.read_rom(file)

        # decompress rom, or check if it's already decompressed
        self.decompress_rom_file(file, decomp_file)

        # Add file to maximum size
        self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
        self.original = copy.copy(self.buffer)
        self.changed_address = {}
        self.changed_dma = {}
        self.force_patch = []
Esempio n. 6
0
    def __init__(self, settings, patch=True):
        self.last_address = None

        file = settings.rom
        decomp_file = 'ZOOTDEC.z64'

        os.chdir(local_path())

        with open(data_path('symbols.json'), 'r') as stream:
            symbols = json.load(stream)
            self.symbols = {
                name: int(addr, 16)
                for name, addr in symbols.items()
            }

        try:
            # Read decompressed file if it exists
            self.read_rom(decomp_file)
            # This is mainly for validation testing, but just in case...
            self.decompress_rom_file(decomp_file, decomp_file)
        except Exception as ex:
            # No decompressed file, instead read Input ROM
            self.read_rom(file)
            self.decompress_rom_file(file, decomp_file)

        # Add file to maximum size
        self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
Esempio n. 7
0
def build_world_graphs(settings, window=dummy_window()):
    logger = logging.getLogger('')
    worlds = []
    for i in range(0, settings.world_count):
        worlds.append(World(i, settings))

    window.update_status('Creating the Worlds')
    for id, world in enumerate(worlds):
        logger.info('Generating World %d.' % (id + 1))

        window.update_progress(0 + 1 * (id + 1) / settings.world_count)
        logger.info('Creating Overworld')

        if settings.logic_rules == 'glitched':
            overworld_data = os.path.join(data_path('Glitched World'),
                                          'Overworld.json')
        else:
            overworld_data = os.path.join(data_path('World'), 'Overworld.json')

        # Compile the json rules based on settings
        world.load_regions_from_json(overworld_data)
        create_dungeons(world)
        world.create_internal_locations()

        if settings.shopsanity != 'off':
            world.random_shop_prices()
        world.set_scrub_prices()

        window.update_progress(0 + 4 * (id + 1) / settings.world_count)
        logger.info('Calculating Access Rules.')
        set_rules(world)

        window.update_progress(0 + 5 * (id + 1) / settings.world_count)
        logger.info('Generating Item Pool.')
        generate_itempool(world)
        set_shop_rules(world)
        set_drop_location_names(world)
        world.fill_bosses()

    if settings.triforce_hunt:
        settings.distribution.configure_triforce_hunt(worlds)

    logger.info('Setting Entrances.')
    set_entrances(worlds)
    return worlds
Esempio n. 8
0
def patch_magic_colors(rom, settings, log, symbols):
    # patch magic colors
    magic = [
        ('Magic Meter Color', 'magic_color', symbols["CFG_MAGIC_COLOR"],
         ([0x154C654, 0x154CFB4], [0x154C65C,
                                   0x154CFBC])),  # GI Model DList colors
    ]
    magic_color_list = get_magic_colors()

    for magic_color, magic_setting, symbol, model_addresses in magic:
        magic_option = settings.__dict__[magic_setting]

        # Handle Plando
        if log.src_dict.get('ui_colors', {}).get(magic_color,
                                                 {}).get('color', ''):
            magic_option = log.src_dict['ui_colors'][magic_color]['color']

        if magic_option == 'Random Choice':
            magic_option = random.choice(magic_color_list)

        if magic_option == 'Completely Random':
            color = generate_random_color()
        elif magic_option in magic_colors:
            color = list(magic_colors[magic_option])
        else:
            color = hex_to_color(magic_option)
            magic_option = 'Custom'
        rom.write_int16s(symbol, color)
        if magic_option != 'Green' and settings.correct_model_colors:
            patch_model_colors(rom, color, model_addresses)
            icon.patch_overworld_icon(
                rom, color, 0xF45650,
                data_path('icons/magicSmallExtras.raw'))  # Overworld Small Pot
            icon.patch_overworld_icon(
                rom, color, 0xF47650,
                data_path('icons/magicLargeExtras.raw'))  # Overworld Big Pot
        else:
            patch_model_colors(rom, None, model_addresses)
            icon.patch_overworld_icon(rom, None, 0xF45650)
            icon.patch_overworld_icon(rom, None, 0xF47650)
        log.ui_colors[magic_color] = CollapseDict({
            ':option': magic_option,
            'color': color_to_hex(color),
        })
def load_aliases():
    j = read_json(data_path('LogicHelpers.json'))
    for s, repl in j.items():
        if '(' in s:
            rule, args = s[:-1].split('(', 1)
            args = [re.compile(r'\b%s\b' % a.strip()) for a in args.split(',')]
        else:
            rule = s
            args = ()
        rule_aliases[rule] = (args, repl)
    nonaliases = escaped_items.keys() - rule_aliases.keys()
Esempio n. 10
0
def buildBingoHintList(boardURL):
    try:
        if len(boardURL) > 256:
            raise URLError(f"URL too large {len(boardURL)}")
        with urllib.request.urlopen(boardURL + "/board") as board:
            if board.length and 0 < board.length < 4096:
                goalList = board.read()
            else:
                raise HTTPError(f"Board of invalid size {board.length}")
    except (URLError, HTTPError) as e:
        logger = logging.getLogger('')
        logger.info(
            f"Could not retrieve board info. Using default bingo hints instead: {e}"
        )
        genericBingo = read_json(data_path('Bingo/generic_bingo_hints.json'))
        return genericBingo['settings']['item_hints']

    # Goal list returned from Bingosync is a sequential list of all of the goals on the bingo board, starting at top-left and moving to the right.
    # Each goal is a dictionary with attributes for name, slot, and colours. The only one we use is the name
    goalList = [goal['name'] for goal in json.loads(goalList)]
    goalHintRequirements = read_json(data_path('Bingo/bingo_goals.json'))

    hintsToAdd = {}
    for goal in goalList:
        # Using 'get' here ensures some level of forward compatibility, where new goals added to randomiser bingo won't
        # cause the generator to crash (though those hints won't have item hints for them)
        requirements = goalHintRequirements.get(goal, {})
        if len(requirements) != 0:
            for item in requirements:
                hintsToAdd[item] = max(hintsToAdd.get(item, 0),
                                       requirements[item]['count'])

    # Items to be hinted need to be included in the item_hints list once for each instance you want hinted
    # (e.g. if you want all three strength upgrades to be hintes it needs to be in the list three times)
    hints = []
    for key, value in hintsToAdd.items():
        for _ in range(value):
            hints.append(key)
    return hints
Esempio n. 11
0
def guiMain():
    try:
        version_check("Node", "8.0.0", "https://nodejs.org/en/download/")
        version_check("NPM", "3.5.2", "https://nodejs.org/en/download/")
    except VersionError as ex:
        print(ex.args[0])
        webbrowser.open(ex.args[1])
        return

    web_version = '--web' in sys.argv
    if '--skip-settingslist' not in sys.argv:
        CreateJSON(data_path('generated/settings_list.json'), web_version)

    args = ["node", "run.js", "release", "python", sys.executable]
    subprocess.Popen(args,shell=False,cwd=local_path("GUI"))
Esempio n. 12
0
def patch_magic_colors(rom, settings, log, symbols):
    # patch magic colors
    magic = [
        ('Magic Meter Color', settings.magic_color, symbols["CFG_MAGIC_COLOR"],
         ([0x154C654, 0x154CFB4], [0x154C65C,
                                   0x154CFBC])),  # GI Model DList colors
    ]
    magic_color_list = get_magic_colors()

    for magic_color, magic_option, symbol, model_addresses in magic:
        if magic_option == 'Random Choice':
            magic_option = random.choice(magic_color_list)

        if magic_option == 'Completely Random':
            color = [
                random.getrandbits(8),
                random.getrandbits(8),
                random.getrandbits(8)
            ]
        elif magic_option in magic_colors:
            color = list(magic_colors[magic_option])
        else:
            color = list(int(magic_option[i:i + 2], 16) for i in (0, 2, 4))
            magic_option = 'Custom'
        rom.write_int16s(symbol, color)
        if settings.correct_model_colors and magic_option != 'Green':
            patch_model_colors(rom, color, model_addresses)
            icon.patch_overworld_icon(
                rom, color, 0xF45650,
                data_path('icons/magicSmallExtras.raw'))  # Overworld Small Pot
            icon.patch_overworld_icon(
                rom, color, 0xF47650,
                data_path('icons/magicLargeExtras.raw'))  # Overworld Big Pot
        log.magic_colors[magic_color] = dict(
            option=magic_option,
            color=''.join(['{:02X}'.format(c) for c in color]))
Esempio n. 13
0
    def __init__(self, file=None):
        super().__init__([])

        self.original = None
        self.changed_address = {}
        self.changed_dma = {}
        self.force_patch = []

        if file is None:
            return

        decomp_file = local_path('ZOOTDEC.z64')

        os.chdir(local_path())

        with open(data_path('generated/symbols.json'), 'r') as stream:
            symbols = json.load(stream)
            self.symbols = {
                name: int(addr, 16)
                for name, addr in symbols.items()
            }

        if file == '':
            # if not specified, try to read from the previously decompressed rom
            file = decomp_file
            try:
                self.read_rom(file)
            except FileNotFoundError:
                # could not find the decompressed rom either
                raise FileNotFoundError('Must specify path to base ROM')
        else:
            self.read_rom(file)

        # decompress rom, or check if it's already decompressed
        self.decompress_rom_file(file, decomp_file)

        # Add file to maximum size
        self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
        self.original = self.copy()

        # Add version number to header.
        self.write_bytes(0x35, get_version_bytes(__version__))
        self.force_patch.extend([0x35, 0x36, 0x37])
Esempio n. 14
0
    def __init__(self, settings, patch=True):
        self.__last_address = None

        file = settings.rom
        decomp_file = 'ZOOTDEC.z64'

        os.chdir(local_path())

        with open(data_path('generated/symbols.json'), 'r') as stream:
            symbols = json.load(stream)
            self.symbols = { name: int(addr, 16) for name, addr in symbols.items() }

        self.read_rom(file)
        self.decompress_rom_file(file, decomp_file)

        # Add file to maximum size
        self.buffer.extend(bytearray([0x00] * (0x4000000 - len(self.buffer))))
        self.original = copy.copy(self.buffer)
        self.changed_address = {}
        self.changed_dma = {}
Esempio n. 15
0
def get_json():
    with open(data_path('mqu.json'), 'r') as stream:
        data = json.load(stream)
    return data
Esempio n. 16
0
def process_sequences(rom,
                      sequences,
                      target_sequences,
                      disabled_source_sequences,
                      disabled_target_sequences,
                      ids,
                      seq_type='bgm'):
    # Process vanilla music data
    for bgm in ids:
        # Get sequence metadata
        name = bgm[0]
        cosmetic_name = name
        type = rom.read_int16(0xB89AE8 + (bgm[1] * 0x10))
        instrument_set = rom.read_byte(0xB89911 + 0xDD + (bgm[1] * 2))
        id = bgm[1]

        # Create new sequences
        seq = TableEntry(name,
                         cosmetic_name,
                         type,
                         instrument_set,
                         vanilla_id=id)
        target = TableEntry(name,
                            cosmetic_name,
                            type,
                            instrument_set,
                            replaces=id)

        # Special handling for file select/fairy fountain
        if seq.vanilla_id != 0x57 and cosmetic_name not in disabled_source_sequences:
            sequences.append(seq)
        if cosmetic_name not in disabled_target_sequences:
            target_sequences.append(target)

    # If present, load the file containing custom music to exclude
    try:
        with open(os.path.join(data_path(),
                               u'custom_music_exclusion.txt')) as excl_in:
            seq_exclusion_list = excl_in.readlines()
        seq_exclusion_list = [
            seq.rstrip() for seq in seq_exclusion_list if seq[0] != '#'
        ]
        seq_exclusion_list = [
            seq for seq in seq_exclusion_list if seq.endswith('.meta')
        ]
    except FileNotFoundError:
        seq_exclusion_list = []

    # Process music data in data/Music/
    # Each sequence requires a valid .seq sequence file and a .meta metadata file
    # Current .meta format: Cosmetic Name\nInstrument Set\nPool
    for dirpath, _, filenames in os.walk(u'./data/Music', followlinks=True):
        for fname in filenames:
            # Skip if included in exclusion file
            if fname in seq_exclusion_list:
                continue

            # Find meta file and check if corresponding seq file exists
            if fname.endswith('.meta') and os.path.isfile(
                    os.path.join(dirpath,
                                 fname.split('.')[0] + '.seq')):
                # Read meta info
                try:
                    with open(os.path.join(dirpath, fname), 'r') as stream:
                        lines = stream.readlines()
                    # Strip newline(s)
                    lines = [line.rstrip() for line in lines]
                except FileNotFoundError as ex:
                    raise FileNotFoundError('No meta file for: "' + fname +
                                            '". This should never happen')

                # Create new sequence, checking third line for correct type
                if (len(lines) > 2 and
                    (lines[2].lower() == seq_type.lower() or lines[2]
                     == '')) or (len(lines) <= 2 and seq_type == 'bgm'):
                    seq = TableEntry(os.path.join(dirpath,
                                                  fname.split('.')[0]),
                                     lines[0],
                                     instrument_set=int(lines[1], 16))

                    if seq.instrument_set < 0x00 or seq.instrument_set > 0x25:
                        raise Exception(
                            'Sequence instrument must be in range [0x00, 0x25]'
                        )

                    if seq.cosmetic_name not in disabled_source_sequences:
                        sequences.append(seq)

    return sequences, target_sequences
Esempio n. 17
0
def get_settings_from_command_line_args():
    parser = argparse.ArgumentParser(
        formatter_class=ArgumentDefaultsHelpFormatter)

    parser.add_argument('--gui', help='Launch the GUI', action='store_true')
    parser.add_argument('--loglevel',
                        default='info',
                        const='info',
                        nargs='?',
                        choices=['error', 'info', 'warning', 'debug'],
                        help='Select level of logging for output.')
    parser.add_argument(
        '--settings_string',
        help=
        'Provide sharable settings using a settings string. This will override all flags that it specifies.'
    )
    parser.add_argument(
        '--convert_settings',
        help=
        'Only convert the specified settings to a settings string. If a settings string is specified output the used settings instead.',
        action='store_true')
    parser.add_argument(
        '--settings',
        help='Use the specified settings file to use for generation')
    parser.add_argument(
        '--settings_preset',
        help=
        "Use the given preset for base settings. Anything defined in the --settings file or the --settings_string will override the preset."
    )
    parser.add_argument('--seed', help='Generate the specified seed.')
    parser.add_argument('--no_log',
                        help='Suppresses the generation of a log file.',
                        action='store_true')
    parser.add_argument(
        '--output_settings',
        help=
        'Always outputs a settings.json file even when spoiler is enabled.',
        action='store_true')

    args = parser.parse_args()
    settings_base = {}
    if args.settings_preset:
        presetsFiles = [data_path('presets_default.json')] + sorted(
            os.path.join(data_path('Presets'), fn)
            for fn in os.listdir(data_path('Presets')) if fn.endswith('.json'))
        for fn in presetsFiles:
            with open(fn, encoding='utf-8') as f:
                presets = json.load(f)
                if args.settings_preset in presets:
                    settings_base.update(presets[args.settings_preset])
                    break
        else:
            sys.stderr.write(
                f'ERROR:No preset found with name {args.settings_preset!r}\n')
            sys.exit(1)

    if args.settings == '-':
        settings_base.update(json.loads(sys.stdin.read()))
    elif args.settings or not settings_base:  # avoid implicitly using settings.sav with presets
        settingsFile = local_path(args.settings or 'settings.sav')

        try:
            with open(settingsFile, encoding='utf-8') as f:
                settings_base.update(json.load(f))
        except Exception as ex:
            if args.settings is not None:
                raise ex

    settings = Settings(settings_base)

    settings.output_settings = args.output_settings

    if args.settings_string is not None:
        settings.update_with_settings_string(args.settings_string)

    if args.seed is not None:
        settings.update_seed(args.seed)

    if args.convert_settings:
        if args.settings_string is not None:
            print(json.dumps(settings.to_json()))
        else:
            print(settings.get_settings_string())
        sys.exit(0)

    return settings, args.gui, args.loglevel, args.no_log
Esempio n. 18
0
def generate(settings, window):
    logger = logging.getLogger('')
    worlds = []
    for i in range(0, settings.world_count):
        worlds.append(World(i, settings))

    window.update_status('Creating the Worlds')
    for id, world in enumerate(worlds):
        logger.info('Generating World %d.' % (id + 1))

        window.update_progress(0 + 1 * (id + 1) / settings.world_count)
        logger.info('Creating Overworld')

        if settings.logic_rules == 'glitched':
            overworld_data = os.path.join(data_path('Glitched World'),
                                          'Overworld.json')
        else:
            overworld_data = os.path.join(data_path('World'), 'Overworld.json')

        # Compile the json rules based on settings
        world.load_regions_from_json(overworld_data)
        create_dungeons(world)
        world.create_internal_locations()

        if settings.shopsanity != 'off':
            world.random_shop_prices()
        world.set_scrub_prices()

        window.update_progress(0 + 4 * (id + 1) / settings.world_count)
        logger.info('Calculating Access Rules.')
        set_rules(world)

        window.update_progress(0 + 5 * (id + 1) / settings.world_count)
        logger.info('Generating Item Pool.')
        generate_itempool(world)
        set_shop_rules(world)
        set_drop_location_names(world)
        world.fill_bosses()

    if settings.triforce_hunt:
        settings.distribution.configure_triforce_hunt(worlds)

    logger.info('Setting Entrances.')
    set_entrances(worlds)

    window.update_status('Placing the Items')
    logger.info('Fill the world.')
    distribute_items_restrictive(window, worlds)
    window.update_progress(35)

    spoiler = Spoiler(worlds)
    if settings.create_spoiler:
        window.update_status('Calculating Spoiler Data')
        logger.info('Calculating playthrough.')
        create_playthrough(spoiler)
        window.update_progress(50)
    if settings.create_spoiler or settings.hints != 'none':
        window.update_status('Calculating Hint Data')
        logger.info('Calculating hint data.')
        State.update_required_items(spoiler)
        buildGossipHints(spoiler, worlds)
        window.update_progress(55)
    spoiler.build_file_hash()
    return spoiler
Esempio n. 19
0
def main(settings, window=dummy_window()):

    start = time.process_time()

    logger = logging.getLogger('')

    worlds = []

    allowed_tricks = {}
    for trick in logic_tricks.values():
        settings.__dict__[
            trick['name']] = trick['name'] in settings.allowed_tricks

    settings.load_distribution()

    # we load the rom before creating the seed so that error get caught early
    if settings.compress_rom == 'None' and not settings.create_spoiler:
        raise Exception(
            '`No Output` must have spoiler enabled to produce anything.')

    if settings.compress_rom != 'None':
        window.update_status('Loading ROM')
        rom = Rom(settings.rom)

    if not settings.world_count:
        settings.world_count = 1
    if settings.world_count < 1 or settings.world_count > 255:
        raise Exception('World Count must be between 1 and 255')
    if settings.player_num > settings.world_count or settings.player_num < 1:
        if settings.compress_rom not in ['None', 'Patch']:
            raise Exception('Player Num must be between 1 and %d' %
                            settings.world_count)
        else:
            settings.player_num = 1

    logger.info('OoT Randomizer Version %s  -  Seed: %s\n\n', __version__,
                settings.seed)
    settings.remove_disabled()
    random.seed(settings.numeric_seed)
    settings.resolve_random_settings()

    for i in range(0, settings.world_count):
        worlds.append(World(settings))

    window.update_status('Creating the Worlds')
    for id, world in enumerate(worlds):
        world.id = id
        world.distribution = settings.distribution.world_dists[id]
        logger.info('Generating World %d.' % id)

        window.update_progress(0 + 1 * (id + 1) / settings.world_count)
        logger.info('Creating Overworld')

        # Determine MQ Dungeons
        dungeon_pool = list(world.dungeon_mq)
        dist_num_mq = world.distribution.configure_dungeons(
            world, dungeon_pool)

        if world.mq_dungeons_random:
            for dungeon in dungeon_pool:
                world.dungeon_mq[dungeon] = random.choice([True, False])
            world.mq_dungeons = list(world.dungeon_mq.values()).count(True)
        else:
            mqd_picks = random.sample(dungeon_pool,
                                      world.mq_dungeons - dist_num_mq)
            for dung in mqd_picks:
                world.dungeon_mq[dung] = True

        if settings.logic_rules == 'glitched':
            overworld_data = os.path.join(data_path('Glitched World'),
                                          'Overworld.json')
        else:
            overworld_data = os.path.join(data_path('World'), 'Overworld.json')
        world.load_regions_from_json(overworld_data)

        create_dungeons(world)

        if settings.shopsanity != 'off':
            world.random_shop_prices()
        world.set_scrub_prices()

        window.update_progress(0 + 4 * (id + 1) / settings.world_count)
        logger.info('Calculating Access Rules.')
        set_rules(world)

        window.update_progress(0 + 5 * (id + 1) / settings.world_count)
        logger.info('Generating Item Pool.')
        generate_itempool(world)
        set_shop_rules(world)
        set_drop_location_names(world)

    logger.info('Setting Entrances.')
    set_entrances(worlds)

    window.update_status('Placing the Items')
    logger.info('Fill the world.')
    distribute_items_restrictive(window, worlds)
    window.update_progress(35)

    spoiler = Spoiler(worlds)
    cosmetics_log = None
    if settings.create_spoiler:
        window.update_status('Calculating Spoiler Data')
        logger.info('Calculating playthrough.')
        create_playthrough(spoiler)
        window.update_progress(50)
    if settings.create_spoiler or settings.hints != 'none':
        window.update_status('Calculating Hint Data')
        State.update_required_items(spoiler)
        for world in worlds:
            world.update_useless_areas(spoiler)
            buildGossipHints(spoiler, world)
        window.update_progress(55)
    spoiler.build_file_hash()

    logger.info('Patching ROM.')

    settings_string_hash = hashlib.sha1(
        settings.settings_string.encode('utf-8')).hexdigest().upper()[:5]
    if settings.output_file:
        outfilebase = settings.output_file
    elif settings.world_count > 1:
        outfilebase = 'OoT_%s_%s_W%d' % (settings_string_hash, settings.seed,
                                         settings.world_count)
    else:
        outfilebase = 'OoT_%s_%s' % (settings_string_hash, settings.seed)

    output_dir = default_output_path(settings.output_dir)

    if settings.compress_rom == 'Patch':
        rng_state = random.getstate()
        file_list = []
        window.update_progress(65)
        for world in worlds:
            if settings.world_count > 1:
                window.update_status('Patching ROM: Player %d' %
                                     (world.id + 1))
                patchfilename = '%sP%d.zpf' % (outfilebase, world.id + 1)
            else:
                window.update_status('Patching ROM')
                patchfilename = '%s.zpf' % outfilebase

            random.setstate(rng_state)
            patch_rom(spoiler, world, rom, outfilebase)
            cosmetics_log = patch_cosmetics(settings, rom)
            window.update_progress(65 + 20 *
                                   (world.id + 1) / settings.world_count)

            window.update_status('Creating Patch File')
            output_path = os.path.join(output_dir, patchfilename)
            file_list.append(patchfilename)
            create_patch_file(rom, output_path)
            rom.restore()
            window.update_progress(65 + 30 *
                                   (world.id + 1) / settings.world_count)

            if settings.create_cosmetics_log and cosmetics_log:
                window.update_status('Creating Cosmetics Log')
                if settings.world_count > 1:
                    cosmetics_log_filename = "%sP%d_Cosmetics.txt" % (
                        outfilebase, world.id + 1)
                else:
                    cosmetics_log_filename = '%s_Cosmetics.txt' % outfilebase
                cosmetics_log.to_file(
                    os.path.join(output_dir, cosmetics_log_filename))
                file_list.append(cosmetics_log_filename)
            cosmetics_log = None

        if settings.world_count > 1:
            window.update_status('Creating Patch Archive')
            output_path = os.path.join(output_dir, '%s.zpfz' % outfilebase)
            with zipfile.ZipFile(output_path, mode="w") as patch_archive:
                for file in file_list:
                    file_path = os.path.join(output_dir, file)
                    patch_archive.write(file_path,
                                        file.replace(outfilebase, ''),
                                        compress_type=zipfile.ZIP_DEFLATED)
            for file in file_list:
                os.remove(os.path.join(output_dir, file))
        logger.info("Created patchfile at: %s" % output_path)
        window.update_progress(95)

    elif settings.compress_rom != 'None':
        window.update_status('Patching ROM')
        patch_rom(spoiler, worlds[settings.player_num - 1], rom, outfilebase)
        cosmetics_log = patch_cosmetics(settings, rom)
        window.update_progress(65)

        window.update_status('Saving Uncompressed ROM')
        if settings.world_count > 1:
            filename = "%sP%d.z64" % (outfilebase, settings.player_num)
        else:
            filename = '%s.z64' % outfilebase
        output_path = os.path.join(output_dir, filename)
        rom.write_to_file(output_path)
        if settings.compress_rom == 'True':
            window.update_status('Compressing ROM')
            logger.info('Compressing ROM.')

            if is_bundled():
                compressor_path = "."
            else:
                compressor_path = "Compress"

            if platform.system() == 'Windows':
                if 8 * struct.calcsize("P") == 64:
                    compressor_path += "\\Compress.exe"
                else:
                    compressor_path += "\\Compress32.exe"
            elif platform.system() == 'Linux':
                if platform.uname()[4] == 'aarch64' or platform.uname(
                )[4] == 'arm64':
                    compressor_path += "/Compress_ARM64"
                else:
                    compressor_path += "/Compress"
            elif platform.system() == 'Darwin':
                compressor_path += "/Compress.out"
            else:
                compressor_path = ""
                logger.info('OS not supported for compression')

            output_compress_path = output_path[:output_path.
                                               rfind('.')] + '-comp.z64'
            if compressor_path != "":
                run_process(
                    window, logger,
                    [compressor_path, output_path, output_compress_path])
            os.remove(output_path)
            logger.info("Created compessed rom at: %s" % output_compress_path)
        else:
            logger.info("Created uncompessed rom at: %s" % output_path)
        window.update_progress(95)

    for world in worlds:
        for info in setting_infos:
            world.settings.__dict__[info.name] = world.__dict__[info.name]

    settings.distribution.update_spoiler(spoiler)
    if settings.create_spoiler:
        window.update_status('Creating Spoiler Log')
        spoiler_path = os.path.join(output_dir,
                                    '%s_Spoiler.json' % outfilebase)
        settings.distribution.to_file(spoiler_path)
        logger.info("Created spoiler log at: %s" %
                    ('%s_Spoiler.json' % outfilebase))
    else:
        window.update_status('Creating Settings Log')
        settings_path = os.path.join(output_dir,
                                     '%s_Settings.json' % outfilebase)
        settings.distribution.to_file(settings_path)
        logger.info("Created settings log at: %s" %
                    ('%s_Settings.json' % outfilebase))

    if settings.create_cosmetics_log and cosmetics_log:
        window.update_status('Creating Cosmetics Log')
        if settings.world_count > 1 and not settings.output_file:
            filename = "%sP%d_Cosmetics.txt" % (outfilebase,
                                                settings.player_num)
        else:
            filename = '%s_Cosmetics.txt' % outfilebase
        cosmetic_path = os.path.join(output_dir, filename)
        cosmetics_log.to_file(cosmetic_path)
        logger.info("Created cosmetic log at: %s" % cosmetic_path)

    window.update_progress(100)
    if cosmetics_log and cosmetics_log.error:
        window.update_status(
            'Success: Rom patched successfully. Some cosmetics could not be applied.'
        )
    else:
        window.update_status('Success: Rom patched successfully')
    logger.info('Done. Enjoy.')
    logger.debug('Total Time: %s', time.process_time() - start)

    return worlds[settings.player_num - 1]
Esempio n. 20
0
def HintDistFiles():
    return [os.path.join(data_path('Hints/'), d) for d in defaultHintDists] + [
        os.path.join(data_path('Hints/'), d)
        for d in sorted(os.listdir(data_path('Hints/')))
        if d.endswith('.json') and d not in defaultHintDists
    ]
Esempio n. 21
0
def buildWorldGossipHints(spoiler, world, checkedLocations=None):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = 0
    world.woth_dungeon = 0

    search = Search.max_explore([w.state for w in spoiler.worlds])
    for stone in gossipLocations.values():
        stone.reachable = (
            search.spot_access(world.get_location(stone.location))
            and search.state_list[world.id].guarantee_hint())

    if checkedLocations is None:
        checkedLocations = set()

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    if 'disabled' in world.hint_dist_user:
        for stone_name in world.hint_dist_user['disabled']:
            try:
                stone_id = gossipLocations_reversemap[stone_name]
            except KeyError:
                raise ValueError(f'Gossip stone location "{stone_name}" is not valid')
            stoneIDs.remove(stone_id)
            (gossip_text, _) = get_junk_hint(spoiler, world, checkedLocations)
            spoiler.hints[world.id][stone_id] = gossip_text

    stoneGroups = []
    if 'groups' in world.hint_dist_user:
        for group_names in world.hint_dist_user['groups']:
            group = []
            for stone_name in group_names:
                try:
                    stone_id = gossipLocations_reversemap[stone_name]
                except KeyError:
                    raise ValueError(f'Gossip stone location "{stone_name}" is not valid')

                stoneIDs.remove(stone_id)
                group.append(stone_id)
            stoneGroups.append(group)
    # put the remaining locations into singleton groups
    stoneGroups.extend([[id] for id in stoneIDs])

    random.shuffle(stoneGroups)

    # Create list of items for which we want hints. If Bingosync URL is supplied, include items specific to that bingo.
    # If not (or if the URL is invalid), use generic bingo hints
    if world.hint_dist == "bingo":
        bingoDefaults = read_json(data_path('Bingo/generic_bingo_hints.json'))
        if world.bingosync_url is not None and world.bingosync_url.startswith("https://bingosync.com/"): # Verify that user actually entered a bingosync URL
            logger = logging.getLogger('')
            logger.info("Got Bingosync URL. Building board-specific goals.")
            world.item_hints = buildBingoHintList(world.bingosync_url)
        else:
            world.item_hints = bingoDefaults['settings']['item_hints']

        if world.tokensanity in ("overworld", "all") and "Suns Song" not in world.item_hints:
            world.item_hints.append("Suns Song")

        if world.shopsanity != "off" and "Progressive Wallet" not in world.item_hints:
            world.item_hints.append("Progressive Wallet")


    # Load hint distro from distribution file or pre-defined settings
    #
    # 'fixed' key is used to mimic the tournament distribution, creating a list of fixed hint types to fill
    # Once the fixed hint type list is exhausted, weighted random choices are taken like all non-tournament sets
    # This diverges from the tournament distribution where leftover stones are filled with sometimes hints (or random if no sometimes locations remain to be hinted)
    sorted_dist = {}
    type_count = 1
    hint_dist = OrderedDict({})
    fixed_hint_types = []
    max_order = 0
    for hint_type in world.hint_dist_user['distribution']:
        if world.hint_dist_user['distribution'][hint_type]['order'] > 0:
            hint_order = int(world.hint_dist_user['distribution'][hint_type]['order'])
            sorted_dist[hint_order] = hint_type
            if max_order < hint_order:
                max_order = hint_order
            type_count = type_count + 1
    if (type_count - 1) < max_order:
        raise Exception("There are gaps in the custom hint orders. Please revise your plando file to remove them.")
    for i in range(1, type_count):
        hint_type = sorted_dist[i]
        if world.hint_dist_user['distribution'][hint_type]['copies'] > 0:
            fixed_num = world.hint_dist_user['distribution'][hint_type]['fixed']
            hint_weight = world.hint_dist_user['distribution'][hint_type]['weight']
        else:
            logging.getLogger('').warning("Hint copies is zero for type %s. Assuming this hint type should be disabled.", hint_type)
            fixed_num = 0
            hint_weight = 0
        hint_dist[hint_type] = (hint_weight, world.hint_dist_user['distribution'][hint_type]['copies'])
        hint_dist.move_to_end(hint_type)
        fixed_hint_types.extend([hint_type] * int(fixed_num))

    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints, only if hint copies > 0
    if hint_dist['always'][1] > 0:
        alwaysLocations = getHintGroup('always', world)
        for hint in alwaysLocations:
            location = world.get_location(hint.name)
            checkedLocations.add(hint.name)
            if location.item.name in bingoBottlesForHints and world.hint_dist == 'bingo':
                always_item = 'Bottle'
            else:
                always_item = location.item.name
            if always_item in world.item_hints:
                world.item_hints.remove(always_item)

            if location.name in world.hint_text_overrides:
                location_text = world.hint_text_overrides[location.name]
            else:
                location_text = getHint(location.name, world.clearer_hints).text
            if '#' not in location_text:
                location_text = '#%s#' % location_text
            item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
            add_hint(spoiler, world, stoneGroups, GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)
            logging.getLogger('').debug('Placed always hint for %s.', location.name)

    # Add trial hints, only if hint copies > 0
    if hint_dist['trial'][1] > 0:
        if world.trials_random and world.trials == 6:
            add_hint(spoiler, world, stoneGroups, GossipText("#Ganon's Tower# is protected by a powerful barrier.", ['Pink']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials_random and world.trials == 0:
            add_hint(spoiler, world, stoneGroups, GossipText("Sheik dispelled the barrier around #Ganon's Tower#.", ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials < 6 and world.trials > 3:
            for trial,skipped in world.skipped_trials.items():
                if skipped:
                    add_hint(spoiler, world, stoneGroups,GossipText("the #%s Trial# was dispelled by Sheik." % trial, ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials <= 3 and world.trials > 0:
            for trial,skipped in world.skipped_trials.items():
                if not skipped:
                    add_hint(spoiler, world, stoneGroups, GossipText("the #%s Trial# protects Ganon's Tower." % trial, ['Pink']), hint_dist['trial'][1], force_reachable=True)

    # Add user-specified hinted item locations if using a built-in hint distribution
    # Raise error if hint copies is zero
    if len(world.item_hints) > 0 and world.hint_dist_user['named_items_required']:
        if hint_dist['named-item'][1] == 0:
            raise Exception('User-provided item hints were requested, but copies per named-item hint is zero')
        else:
            for i in range(0, len(world.item_hints)):
                hint = get_specific_item_hint(spoiler, world, checkedLocations)
                if hint == None:
                    raise Exception('No valid hints for user-provided item')
                else:
                    gossip_text, location = hint
                    place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, hint_dist['named-item'][1], location)
                    if not place_ok:
                        raise Exception('Not enough gossip stones for user-provided item hints')

    hint_types = list(hint_types)
    hint_prob  = list(hint_prob)
    hint_counts = {}

    custom_fixed = True
    while stoneGroups:
        if fixed_hint_types:
            hint_type = fixed_hint_types.pop(0)
            copies = hint_dist[hint_type][1]
            if copies > len(stoneGroups):
                # Quiet to avoid leaking information.
                logging.getLogger('').debug(f'Not enough gossip stone locations ({len(stoneGroups)} groups) for fixed hint type {hint_type} with {copies} copies, proceeding with available stones.')
                copies = len(stoneGroups)
        else:
            custom_fixed = False
            # Make sure there are enough stones left for each hint type
            num_types = len(hint_types)
            hint_types = list(filter(lambda htype: hint_dist[htype][1] <= len(stoneGroups), hint_types))
            new_num_types = len(hint_types)
            if new_num_types == 0:
                raise Exception('Not enough gossip stone locations for remaining weighted hint types.')
            elif new_num_types < num_types:
                hint_prob = []
                for htype in hint_types:
                    hint_prob.append(hint_dist[htype][0])
            try:
                # Weight the probabilities such that hints that are over the expected proportion
                # will be drawn less, and hints that are under will be drawn more.
                # This tightens the variance quite a bit. The variance can be adjusted via the power
                weighted_hint_prob = []
                for w1_type, w1_prob in zip(hint_types, hint_prob):
                    p = w1_prob
                    if p != 0: # If the base prob is 0, then it's 0
                        for w2_type, w2_prob in zip(hint_types, hint_prob):
                            if w2_prob != 0: # If the other prob is 0, then it has no effect
                                # Raising this term to a power greater than 1 will decrease variance
                                # Conversely, a power less than 1 will increase variance
                                p = p * (((hint_counts.get(w2_type, 0) / w2_prob) + 1) / ((hint_counts.get(w1_type, 0) / w1_prob) + 1))
                    weighted_hint_prob.append(p)

                hint_type = random_choices(hint_types, weights=weighted_hint_prob)[0]
                copies = hint_dist[hint_type][1]
            except IndexError:
                raise Exception('Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
            # Zero out the probability in the base distribution in case the probability list is modified
            # to fit hint types in remaining gossip stones
            hint_dist[hint_type] = (0.0, copies)
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, copies, location)
            if place_ok:
                hint_counts[hint_type] = hint_counts.get(hint_type, 0) + 1
                if location is None:
                    logging.getLogger('').debug('Placed %s hint.', hint_type)
                else:
                    logging.getLogger('').debug('Placed %s hint for %s.', hint_type, location.name)
            if not place_ok and custom_fixed:
                logging.getLogger('').debug('Failed to place %s fixed hint for %s.', hint_type, location.name)
                fixed_hint_types.insert(0, hint_type)
Esempio n. 22
0
def HintDistFiles():
    return [os.path.join(data_path('Hints/'), d)
            for d in os.listdir(data_path('Hints/'))
            if d.endswith('.json')]
Esempio n. 23
0
def set_icon(window):
    er16 = tk.PhotoImage(file=data_path('ER16.gif'))
    er32 = tk.PhotoImage(file=data_path('ER32.gif'))
    er48 = tk.PhotoImage(file=data_path('ER48.gif'))
    window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access
Esempio n. 24
0
def settingToJsonMain():
    web_version = '--web' in sys.argv
    CreateJSON(data_path('generated/settings_list.json'), web_version)
Esempio n. 25
0
def guiMain(settings=None):
    frames = {}

    mainWindow = Tk()
    mainWindow.wm_title("OoT Randomizer %s" % ESVersion)
    mainWindow.resizable(False, False)

    set_icon(mainWindow)

    notebook = ttk.Notebook(mainWindow)
    frames['rom_tab'] = ttk.Frame(notebook)
    frames['rules_tab'] = ttk.Frame(notebook)
    frames['logic_tab'] = ttk.Frame(notebook)
    frames['other_tab'] = ttk.Frame(notebook)
    frames['cosmetic_tab'] = ttk.Frame(notebook)
    frames['SFX_tab'] = ttk.Frame(notebook)
    frames['cosmetic_tab_left'] = Frame(frames['cosmetic_tab'])
    frames['cosmetic_tab_right'] = Frame(frames['cosmetic_tab'])
    notebook.add(frames['rom_tab'], text='ROM Options')
    notebook.add(frames['rules_tab'], text='Main Rules')
    notebook.add(frames['logic_tab'], text='Detailed Logic')
    notebook.add(frames['other_tab'], text='Other')
    notebook.add(frames['cosmetic_tab'], text='Cosmetic')
    notebook.add(frames['SFX_tab'], text='SFX')

    #######################
    # Randomizer controls #
    #######################

    # Hold the results of the user's decisions here
    guivars = {}
    widgets = {}
    presets = {}

    # Hierarchy
    ############

    #Rules Tab
    frames['open']        = LabelFrame(frames['rules_tab'],          text='Open',              labelanchor=NW)
    frames['world']       = LabelFrame(frames['rules_tab'],          text='World',             labelanchor=NW)
    frames['shuffle']     = LabelFrame(frames['rules_tab'],          text='Shuffle',           labelanchor=NW)

    # Logic tab
    frames['checks']      = LabelFrame(frames['logic_tab'],          text='Adult Trade Sequence', labelanchor=NW)
    frames['tricks']      = LabelFrame(frames['logic_tab'],          text='Lens of Truth',     labelanchor=NW)

    #Other Tab
    frames['convenience'] = LabelFrame(frames['other_tab'],          text='Timesavers',        labelanchor=NW)
    frames['other']       = LabelFrame(frames['other_tab'],          text='Misc',              labelanchor=NW)

    #Cosmetic tab
    frames['cosmetic']    = LabelFrame(frames['cosmetic_tab_left'],  text='General',           labelanchor=NW)
    frames['sword_trails']= LabelFrame(frames['cosmetic_tab_left'],  text='Sword Trail Colors',labelanchor=NW)
    frames['ui_colors']=    LabelFrame(frames['cosmetic_tab_left'], text='UI Colors',         labelanchor=NW)
    frames['tunic_colors']= LabelFrame(frames['cosmetic_tab_right'], text='Tunic Colors',      labelanchor=NW)
    frames['navi_colors']=  LabelFrame(frames['cosmetic_tab_right'], text='Navi Colors',       labelanchor=NW)
    frames['gauntlet_colors']= LabelFrame(frames['cosmetic_tab_right'], text='Gauntlet Colors', labelanchor=NW)

    #Cosmetic tab
    frames['sfx']         = LabelFrame(frames['SFX_tab'],            text='General',           labelanchor=NW)
    frames['menu_sfx']    = LabelFrame(frames['SFX_tab'],            text='Menu',              labelanchor=NW)
    frames['npc_sfx']     = LabelFrame(frames['SFX_tab'],            text='NPC',               labelanchor=NW)


    # Shared
    def toggle_widget(widget, enabled):
        widget_type = widget.winfo_class()
        if widget_type == 'Frame' or widget_type == 'TFrame' or widget_type == 'Labelframe':
            if widget_type == 'Labelframe':
                widget.configure(fg='Black'if enabled else 'Grey')
            for child in widget.winfo_children():
                toggle_widget(child, enabled)
        else:
            if widget_type == 'TCombobox':
                widget.configure(state= 'readonly' if enabled else 'disabled')
            else:
                widget.configure(state= 'normal' if enabled else 'disabled')

            if widget_type == 'Scale':
                widget.configure(fg='Black'if enabled else 'Grey')

    def show_settings(*event):
        settings = guivars_to_settings(guivars)
        settings_string_var.set( settings.get_settings_string() )

        # Update any dependencies
        for info in setting_infos:
            dep_met = settings.check_dependency(info.name)

            if info.name in widgets:
                toggle_widget(widgets[info.name], dep_met)

            if info.type == list:
                widgets[info.name].delete(0, END)
                widgets[info.name].insert(0, *guivars[info.name])

            if info.type != list and info.name in guivars and guivars[info.name].get() == 'Custom Color':
                color = colorchooser.askcolor()
                if color == (None, None):
                    color = ((0,0,0),'#000000')
                guivars[info.name].set('Custom (' + color[1] + ')')

            if info.type != list and info.name in guivars and guivars[info.name].get() == 'Custom Navi Color':
                innerColor = colorchooser.askcolor(title='Pick an Inner Core color.')
                if innerColor == (None, None):
                    innerColor = ((0,0,0),'#000000')
                outerColor = colorchooser.askcolor(title='Pick an Outer Glow color.')
                if outerColor == (None, None):
                    outerColor = ((0,0,0),'#000000')
                guivars[info.name].set('Custom (%s %s)' % (innerColor[1], outerColor[1]))

        update_generation_type()


    versionCheckFrame = Frame(frames['rom_tab'])
    versionCheckFrame.pack(side=BOTTOM, anchor=NW, fill=X)

    fileDialogFrame = Frame(frames['rom_tab'])

    romDialogFrame = Frame(fileDialogFrame)
    baseRomLabel = Label(romDialogFrame, text='Base ROM')
    guivars['rom'] = StringVar(value='')
    romEntry = Entry(romDialogFrame, textvariable=guivars['rom'], width=50)

    def RomSelect():
        rom = filedialog.askopenfilename(filetypes=[("ROM Files", (".z64", ".n64")), ("All Files", "*")])
        if rom != '':
            guivars['rom'].set(rom)
    romSelectButton = Button(romDialogFrame, text='Browse', command=RomSelect, width=10)

    baseRomLabel.pack(side=LEFT, padx=(34,0))
    romEntry.pack(side=LEFT, padx=3)
    romSelectButton.pack(side=LEFT)

    romDialogFrame.pack()

    fileDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5,1))

    def output_dir_select():
        rom = filedialog.askdirectory(initialdir = default_output_path(guivars['output_dir'].get()))
        if rom != '':
            guivars['output_dir'].set(rom)

    outputDialogFrame = Frame(frames['rom_tab'])
    outputDirLabel = Label(outputDialogFrame, text='Output Directory')
    guivars['output_dir'] = StringVar(value='')
    outputDirEntry = Entry(outputDialogFrame, textvariable=guivars['output_dir'], width=50)
    outputDirButton = Button(outputDialogFrame, text='Browse', command=output_dir_select, width=10)
    outputDirLabel.pack(side=LEFT, padx=(3,0))
    outputDirEntry.pack(side=LEFT, padx=3)
    outputDirButton.pack(side=LEFT)
    outputDialogFrame.pack(side=TOP, anchor=W, pady=3)

    distFileDialogFrame = Frame(frames['rom_tab'])
    distFileLabel = Label(distFileDialogFrame, text='Distribution File')
    guivars['distribution_file'] = StringVar(value='')
    distFileEntry = Entry(distFileDialogFrame, textvariable=guivars['distribution_file'], width=50)

    def DistFileSelect():
        distFile = filedialog.askopenfilename(filetypes=[("JSON Files", (".json")), ("All Files", "*")])
        if distFile != '':
            guivars['distribution_file'].set(distFile)
    distFileSelectButton = Button(distFileDialogFrame, text='Browse', command=DistFileSelect, width=10)

    distFileLabel.pack(side=LEFT, padx=(9,0))
    distFileEntry.pack(side=LEFT, padx=3)
    distFileSelectButton.pack(side=LEFT)

    distFileDialogFrame.pack(side=TOP, anchor=W, pady=3)

    countDialogFrame = Frame(frames['rom_tab'])
    countLabel = Label(countDialogFrame, text='Generation Count')
    guivars['count'] = StringVar()
    widgets['count'] = Spinbox(countDialogFrame, from_=1, to=100, textvariable=guivars['count'], width=3)

    def open_readme():
        open_file('https://wiki.ootrandomizer.com/index.php?title=Main_Page')
    openReadmeButton = Button(countDialogFrame, text='Open Wiki Page', command=open_readme)
    openReadmeButton.pack(side=RIGHT, padx=5)

    def open_output():
        open_file(default_output_path(guivars['output_dir'].get()))
    openOutputButton = Button(countDialogFrame, text='Open Output Directory', command=open_output)
    openOutputButton.pack(side=RIGHT, padx=5)

    countLabel.pack(side=LEFT)
    widgets['count'].pack(side=LEFT, padx=2)
    countDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(1,1))

    # Build gui
    ############

    for info in setting_infos:
        if 'group' in info.gui_params:
            if info.gui_params['widget'] == 'Checkbutton':
                # Determine the initial value of the checkbox
                default_value = 1 if info.choices[info.default] == 'checked' else 0
                # Create a variable to access the box's state
                guivars[info.name] = IntVar(value=default_value)
                # Create the checkbox
                widgets[info.name] = Checkbutton(frames[info.gui_params['group']], text=info.gui_params['text'], variable=guivars[info.name], justify=LEFT, wraplength=220, command=show_settings)
                widgets[info.name].pack(expand=False, anchor=W)
            elif info.gui_params['widget'] == 'Combobox':
                # Create the variable to store the user's decision
                guivars[info.name] = StringVar(value=info.choices[info.default])
                # Create the option menu
                widgets[info.name] = Frame(frames[info.gui_params['group']])
                dropdown = ttk.Combobox(widgets[info.name], textvariable=guivars[info.name], values=list(map(lambda choice: info.choices[choice], info.choice_list)), state='readonly', width=36)
                dropdown.bind("<<ComboboxSelected>>", show_settings)
                dropdown.pack(side=BOTTOM, anchor=W)
                # Label the option
                if 'text' in info.gui_params:
                    label = Label(widgets[info.name], text=info.gui_params['text'])
                    label.pack(side=LEFT, anchor=W, padx=5)
                # Pack the frame
                widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)
            elif info.gui_params['widget'] == 'Radiobutton':
                # Create the variable to store the user's decision
                guivars[info.name] = StringVar(value=info.choices[info.default])
                # Create the option menu
                widgets[info.name] = LabelFrame(frames[info.gui_params['group']], text=info.gui_params.get('text', info.name), labelanchor=NW)
                # Setup orientation
                side = TOP
                anchor = W
                if "horizontal" in info.gui_params and info.gui_params["horizontal"]:
                    side = LEFT
                    anchor = N
                # Add the radio buttons
                for option in map(lambda choice: info.choices[choice], info.choice_list):
                    radio_button = Radiobutton(widgets[info.name], text=option, value=option, variable=guivars[info.name], justify=LEFT, wraplength=220, indicatoron=False, command=show_settings)
                    radio_button.pack(expand=True, side=side, anchor=anchor)
                # Pack the frame
                widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)
            elif info.gui_params['widget'] == 'Scale':
                # Create the variable to store the user's decision
                guivars[info.name] = IntVar(value=info.choices[info.default])
                # Create the option menu
                widgets[info.name] = Frame(frames[info.gui_params['group']])
                minval = info.gui_params.get('min', 0)
                maxval = info.gui_params.get('max', 100)
                stepval = info.gui_params.get('step', 1)
                scale = Scale(widgets[info.name], variable=guivars[info.name], from_=minval, to=maxval, tickinterval=stepval, resolution=stepval, showvalue=0, orient=HORIZONTAL, sliderlength=15, length=235, command=show_settings)
                scale.pack(side=BOTTOM, anchor=W)
                # Label the option
                if 'text' in info.gui_params:
                    label = Label(widgets[info.name], text=info.gui_params['text'])
                    label.pack(side=LEFT, anchor=W, padx=5)
                # Pack the frame
                widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)
            elif info.gui_params['widget'] == 'Entry':
                # Create the variable to store the user's decision
                guivars[info.name] = StringVar(value=info.default)
                # Create the option menu
                widgets[info.name] = Frame(frames[info.gui_params['group']])

                if 'validate' in info.gui_params:
                    entry = ValidatingEntry(widgets[info.name], command=show_settings, validate=info.gui_params['validate'], textvariable=guivars[info.name], width=35)
                else:
                    entry = Entry(widgets[info.name], textvariable=guivars[info.name], width=36)
                entry.pack(side=BOTTOM, anchor=W)
                # Label the option
                if 'text' in info.gui_params:
                    label = Label(widgets[info.name], text=info.gui_params['text'])
                    label.pack(side=LEFT, anchor=W, padx=5)
                # Pack the frame
                widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)
            elif info.gui_params['widget'] == 'SearchBox' or info.gui_params['widget'] == 'FilteredSearchBox':
                filtered = (info.gui_params['widget'] == 'FilteredSearchBox')
                search_frame = LabelFrame(frames[info.gui_params['group']], text=info.gui_params.get('text', info.name), labelanchor=NW)

                if filtered:
                    filter_frame = Frame(search_frame)
                    widgets[info.name + '_filterlabel'] = Label(filter_frame, text="Filter: ")
                    widgets[info.name + '_filterlabel'].pack(side=LEFT, anchor=W)
                    widgets[info.name + '_entry'] = SearchBox(search_frame, list(map(lambda choice: info.choices[choice], info.choice_list)), width=78)
                    widgets[info.name + '_filter'] = SearchBoxFilterControl(filter_frame, widgets[info.name + '_entry'], info.gui_params['filterdata'], width=50)
                    widgets[info.name + '_filter'].pack(expand=False, side=LEFT, anchor=W)
                    filter_frame.pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)
                    widgets[info.name + '_entry'].pack(expand=False, side=TOP, anchor=W)
                else:
                    widgets[info.name + '_entry'] = SearchBox(search_frame, list(map(lambda choice: info.choices[choice], info.choice_list)), width=78)
                    widgets[info.name + '_entry'].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)

                list_frame = Frame(search_frame)
                scrollbar = Scrollbar(list_frame, orient=VERTICAL)
                widgets[info.name] = Listbox(list_frame, width=78, height=7, yscrollcommand=scrollbar.set)
                guivars[info.name] = list(info.default)
                scrollbar.config(command=widgets[info.name].yview)
                scrollbar.pack(side=RIGHT, fill=Y)
                widgets[info.name].pack(side=LEFT)
                list_frame.pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)

                if 'entry_tooltip' in info.gui_params:
                    ToolTips.register(widgets[info.name + '_entry'], info.gui_params['entry_tooltip'])
                    if filtered:
                        ToolTips.register(widgets[info.name + '_filter'], info.gui_params['entry_tooltip'])
                if 'list_tooltip' in info.gui_params:
                    ToolTips.register(widgets[info.name], info.gui_params['list_tooltip'])

                def get_lambda(function, *args):
                    return lambda: function(*args)

                def add_list_selected(name):
                    new_location = widgets[name +'_entry'].get()
                    if new_location in widgets[name +'_entry'].options and new_location not in widgets[name].get(0, END):
                        widgets[name].insert(END, new_location)
                        guivars[name].append(new_location)
                    show_settings()

                def remove_list_selected(name):
                    location = widgets[name].get(ACTIVE)
                    widgets[name].delete(ACTIVE)
                    guivars[name].remove(location)
                    show_settings()

                def add_list_all(name):
                    for new_location in widgets[name + '_entry'].options:
                        if new_location not in widgets[name].get(0, END):
                            widgets[name].insert(END, new_location)
                            guivars[name].append(new_location)
                    show_settings()

                def remove_list_all(name):
                    items = list(widgets[name].get(0, END))
                    widgets[name].delete(0, END)
                    guivars[name] = []
                    for item in (x for x in items if x not in widgets[name + '_entry'].options):
                        widgets[name].insert(END, item)
                        guivars[name].append(item)
                    show_settings()
                    
                def clear_list_all(name):
                    widgets[name].delete(0, END)
                    guivars[name] = []
                    show_settings()

                list_button_frame = Frame(search_frame)
                list_add = Button(list_button_frame, width=10, text='Add', command=get_lambda(add_list_selected, info.name))
                list_add.pack(side=LEFT, anchor=N, padx=3, pady=3)
                list_remove = Button(list_button_frame, width=10, text='Remove', command=get_lambda(remove_list_selected, info.name))
                list_remove.pack(side=LEFT, anchor=N, padx=3, pady=3)

                list_add = Button(list_button_frame, width=10, text='All', command=get_lambda(add_list_all, info.name))
                list_add.pack(side=LEFT, anchor=N, padx=3, pady=3)
                list_remove = Button(list_button_frame, width=10, text='None', command=get_lambda(remove_list_all, info.name))
                list_remove.pack(side=LEFT, anchor=N, padx=3, pady=3)
                if filtered:
                    list_clear = Button(list_button_frame, width=10, text='Clear', command=get_lambda(clear_list_all, info.name))
                    list_clear.pack(side=LEFT, anchor=N, padx=3, pady=3)

                list_button_frame.pack(expand=False, side=TOP, padx=3, pady=3)

                # pack the frame
                search_frame.pack(expand=False, side=TOP, anchor=W, padx=3, pady=3)


            if 'tooltip' in info.gui_params:
                ToolTips.register(widgets[info.name], info.gui_params['tooltip'])


    # Pack the hierarchy
    frames['shuffle'].pack(fill=BOTH,  expand=True, anchor=N, side=RIGHT,  pady=(5,1))
    frames['open'].pack(   fill=BOTH,  expand=True, anchor=W, side=TOP,    pady=(5,1))
    frames['world'].pack(  fill=BOTH,  expand=True, anchor=W, side=BOTTOM, pady=(5,1))

    # Logic tab
    frames['checks'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1))
    frames['tricks'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1))

    # Other Tab
    frames['convenience'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1))
    frames['other'].pack(      fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1))

    # Cosmetics tab
    frames['cosmetic'].pack(          fill=BOTH, expand=True, anchor=W, side=TOP)
    frames['cosmetic_tab_left'].pack( fill=BOTH, expand=True, anchor=W, side=LEFT)
    frames['cosmetic_tab_right'].pack(fill=BOTH, expand=True, anchor=W, side=RIGHT)

    # Cosmetics tab - Left Side
    frames['sword_trails'].pack(   fill=BOTH, expand=True, anchor=W, side=TOP)
    frames['ui_colors'].pack(      fill=BOTH, expand=True, anchor=W, side=BOTTOM)

    # Cosmetics tab - Right Side
    frames['tunic_colors'].pack(fill=BOTH, expand=True, anchor=N, side=TOP)
    frames['navi_colors'].pack( fill=BOTH, expand=True, anchor=W, side=TOP)
    frames['gauntlet_colors'].pack(fill=BOTH, expand=True, anchor=W, side=BOTTOM)


    #SFX tab
    frames['sfx'].pack(          fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1))
    frames['menu_sfx'].pack( fill=BOTH, expand=False, anchor=W, side=TOP, pady=(5,1))
    frames['npc_sfx'].pack(fill=BOTH, expand=True, anchor=W, side=BOTTOM, pady=(5,1))

    notebook.pack(fill=BOTH, expand=True, padx=5, pady=5)


    #Multi-World
    widgets['multiworld'] = LabelFrame(frames['rom_tab'], text='Multi-World Generation')
    countLabel = Label(
            widgets['multiworld'],
            wraplength=250,
            justify=LEFT,
            text='''This is used for co-op generations. \
                    Increasing Player Count will drastically \
                    increase the generation time. \
                    \nFor more information, see: \
                 '''
            )
    hyperLabel = Label(widgets['multiworld'], wraplength=250, justify=LEFT, text='https://github.com/TestRunnerSRL/\nbizhawk-co-op\n', fg='blue', cursor='hand2')
    hyperLabel.bind("<Button-1>", lambda event: webbrowser.open_new(r"https://github.com/TestRunnerSRL/bizhawk-co-op"))
    countLabel.pack(side=TOP, anchor=W, padx=5, pady=0)
    hyperLabel.pack(side=TOP, anchor=W, padx=5, pady=0)

    worldCountFrame = Frame(widgets['multiworld'])
    countLabel = Label(worldCountFrame, text='Player Count')
    guivars['world_count'] = StringVar()
    widgets['world_count'] = Spinbox(worldCountFrame, from_=1, to=255, textvariable=guivars['world_count'], width=3)
    guivars['world_count'].trace('w', show_settings)
    countLabel.pack(side=LEFT)
    widgets['world_count'].pack(side=LEFT, padx=2)
    worldCountFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1,5))

    playerNumFrame = Frame(widgets['multiworld'])
    countLabel = Label(playerNumFrame, text='Player ID')
    guivars['player_num'] = StringVar()
    widgets['player_num'] = Spinbox(playerNumFrame, from_=1, to=255, textvariable=guivars['player_num'], width=3)
    countLabel.pack(side=LEFT)
    widgets['player_num'].pack(side=LEFT, padx=2)
    ToolTips.register(widgets['player_num'], 'Generate for specific Player.')
    playerNumFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1,5))

    widgets['multiworld'].pack(side=LEFT, fill=BOTH, anchor=NW, padx=5, pady=5)


    # Settings Presets Functions
    def import_setting_preset():
        if guivars['settings_preset'].get() == '[New Preset]':
            messagebox.showerror("Invalid Preset", "You must select an existing preset!")
            return

        # Get cosmetic settings
        old_settings = guivars_to_settings(guivars)
        new_settings = {setting.name: old_settings.__dict__[setting.name] for setting in
                            filter(lambda s: not (s.shared and s.bitwidth > 0), setting_infos)}

        preset = presets[guivars['settings_preset'].get()]
        new_settings.update(preset)

        settings = Settings(new_settings)
        settings.seed = guivars['seed'].get()

        settings_to_guivars(settings, guivars)
        show_settings()


    def add_settings_preset():
        preset_name = guivars['settings_preset'].get()
        if preset_name == '[New Preset]':
            preset_name = simpledialog.askstring("New Preset", "Enter a new preset name:")
            if not preset_name or preset_name in presets or preset_name == '[New Preset]':
                messagebox.showerror("Invalid Preset", "You must enter a new preset name!")
                return
        elif presets[preset_name].get('locked', False):
            messagebox.showerror("Invalid Preset", "You cannot modify a locked preset!")
            return
        else:
            if messagebox.askquestion("Overwrite Preset", 'Are you sure you want to overwrite the "%s" preset?' % preset_name) != 'yes':
                return

        settings = guivars_to_settings(guivars)
        preset = {setting.name: settings.__dict__[setting.name] for setting in
            filter(lambda s: s.shared and s.bitwidth > 0, setting_infos)}

        presets[preset_name] = preset
        guivars['settings_preset'].set(preset_name)
        update_preset_dropdown()
        save_presets()


    def remove_setting_preset():
        preset_name = guivars['settings_preset'].get()
        if preset_name == '[New Preset]':
            messagebox.showerror("Invalid Preset", "You must select an existing preset!")
            return
        elif presets[preset_name].get('locked', False):
            messagebox.showerror("Invalid Preset", "You cannot modify a locked preset!")
            return

        confirm = messagebox.askquestion('Remove Setting Preset', 'Are you sure you want to remove the setting preset "%s"?' % preset_name)
        if confirm != 'yes':
            return

        del presets[preset_name]
        guivars['settings_preset'].set('[New Preset]')
        update_preset_dropdown()
        save_presets()


    def update_preset_dropdown():
        widgets['settings_preset']['values'] = ['[New Preset]'] + list(presets.keys())


    def save_presets():
        presets_file = local_path('presets.sav')
        with open(presets_file, 'w') as outfile:
            preset_json = {name: preset for name,preset in presets.items() if not preset.get('locked')}
            json.dump(preset_json, outfile, indent=4)


    # Settings Presets
    widgets['settings_presets'] = LabelFrame(frames['rom_tab'], text='Settings Presets')
    countLabel = Label(
            widgets['settings_presets'],
            wraplength=250,
            justify=LEFT,
            text='''Presets are settings that can be saved\
                    and loaded from. Loading a preset\
                    will overwrite all settings that affect\
                    the seed.\
                    \n\
                 '''
            )
    countLabel.pack(side=TOP, anchor=W, padx=5, pady=0)

    selectPresetFrame = Frame(widgets['settings_presets'])
    guivars['settings_preset'] = StringVar(value='[New Preset]')
    widgets['settings_preset'] = ttk.Combobox(selectPresetFrame, textvariable=guivars['settings_preset'], values=['[New Preset]'], state='readonly', width=33)
    widgets['settings_preset'].pack(side=BOTTOM, anchor=W)
    ToolTips.register(widgets['settings_preset'], 'Select a setting preset to apply.')
    widgets['settings_preset'].pack(side=LEFT, padx=(5, 0))
    selectPresetFrame.pack(side=TOP, anchor=W, padx=5, pady=(1,5))

    buttonPresetFrame = Frame(widgets['settings_presets'])
    importPresetButton = Button(buttonPresetFrame, text='Load', width=9, command=import_setting_preset)
    addPresetButton = Button(buttonPresetFrame, text='Save', width=9, command=add_settings_preset)
    removePresetButton = Button(buttonPresetFrame, text='Remove', width=9, command=remove_setting_preset)
    importPresetButton.pack(side=LEFT, anchor=W, padx=2)
    addPresetButton.pack(side=LEFT, anchor=W, padx=2)
    removePresetButton.pack(side=LEFT, anchor=W, padx=2)
    buttonPresetFrame.pack(side=TOP, anchor=W, padx=5, pady=(1,5))

    widgets['settings_presets'].pack(side=RIGHT, fill=BOTH, anchor=NW, padx=5, pady=5)


    # Create the generation menu
    def update_generation_type(event=None):
        settings = guivars_to_settings(guivars)
        if generation_notebook.tab(generation_notebook.select())['text'] == 'Generate From Seed':
            notebook.tab(1, state="normal")
            if guivars['logic_rules'].get() == 'Glitchless':
                notebook.tab(2, state="normal")
            else:
                notebook.tab(2, state="disabled")
            notebook.tab(3, state="normal")
            notebook.tab(4, state="normal")
            notebook.tab(5, state="normal")
            toggle_widget(widgets['world_count'], settings.check_dependency('world_count'))
            toggle_widget(widgets['create_spoiler'], settings.check_dependency('create_spoiler'))
            toggle_widget(widgets['count'], settings.check_dependency('count'))
            toggle_widget(widgets['settings_presets'], True)
        else:
            notebook.tab(1, state="disabled")
            notebook.tab(2, state="disabled")
            notebook.tab(3, state="disabled")
            if guivars['repatch_cosmetics'].get():
                notebook.tab(4, state="normal")
                notebook.tab(5, state="normal")
                toggle_widget(widgets['create_cosmetics_log'], settings.check_dependency('create_cosmetics_log'))
            else:
                notebook.tab(4, state="disabled")
                notebook.tab(5, state="disabled")
                toggle_widget(widgets['create_cosmetics_log'], False)
            toggle_widget(widgets['world_count'], False)
            toggle_widget(widgets['create_spoiler'], False)
            toggle_widget(widgets['count'], False)
            toggle_widget(widgets['settings_presets'], False)



    generation_notebook = ttk.Notebook(mainWindow)
    frames['gen_from_seed'] = ttk.Frame(generation_notebook)
    frames['gen_from_file'] = ttk.Frame(generation_notebook)
    generation_notebook.add(frames['gen_from_seed'], text='Generate From Seed')
    generation_notebook.add(frames['gen_from_file'], text='Generate From File')
    generation_notebook.bind("<<NotebookTabChanged>>", show_settings)

    # From seed tab
    def import_settings(event=None):
        try:
            settings = guivars_to_settings(guivars)
            text = settings_string_var.get().upper()
            settings.seed = guivars['seed'].get()
            settings.update_with_settings_string(text)
            settings_to_guivars(settings, guivars)
            show_settings()
        except Exception as e:
            messagebox.showerror(title="Error", message="Invalid settings string")

    def copy_settings(event=None):
        mainWindow.clipboard_clear()
        new_clip = settings_string_var.get().upper()
        mainWindow.clipboard_append(new_clip)
        mainWindow.update()

    settingsFrame = Frame(frames['gen_from_seed'])
    settings_string_var = StringVar()
    widgets['setting_string'] = Entry(settingsFrame, textvariable=settings_string_var, width=44)

    label = Label(settingsFrame, text="Settings String", width=13, anchor=E)
    widgets['copy_settings'] = Button(settingsFrame, text='Copy', width=5, command=copy_settings)
    widgets['import_settings'] = Button(settingsFrame, text='Import', width=7, command=import_settings)
    label.pack(side=LEFT, anchor=W, padx=5)
    widgets['setting_string'].pack(side=LEFT, anchor=W)
    widgets['copy_settings'].pack(side=LEFT, anchor=W, padx=(5, 0))
    widgets['import_settings'].pack(side=LEFT, anchor=W)

    settingsFrame.pack(fill=BOTH, anchor=W, padx=5, pady=(10,0))

    def multiple_run(settings, window):
        orig_seed = settings.seed
        for i in range(settings.count):
            settings.update_seed(orig_seed + '-' + str(i))
            window.update_title("Generating Seed %s...%d/%d" % (settings.seed, i+1, settings.count))
            main(settings, window)

    def generateRom():
        settings = guivars_to_settings(guivars)
        if settings.count:
            BackgroundTaskProgress(mainWindow, "Generating Seed %s..." % settings.seed, multiple_run, settings)
        else:
            BackgroundTaskProgress(mainWindow, "Generating Seed %s..." % settings.seed, main, settings)

    generateSeedFrame = Frame(frames['gen_from_seed'])
    seedLabel = Label(generateSeedFrame, text='Seed', width=13, anchor=E)
    generateButton = Button(generateSeedFrame, text='Generate!', width=14, command=generateRom)
    guivars['seed'] = StringVar()
    widgets['seed'] = Entry(generateSeedFrame, textvariable=guivars['seed'], width=44)
    seedLabel.pack(side=LEFT, padx=5)
    widgets['seed'].pack(side=LEFT)
    generateButton.pack(side=LEFT, padx=(5, 0))

    generateSeedFrame.pack(side=BOTTOM, anchor=W, padx=5, pady=10)

    # From file tab
    patchDialogFrame = Frame(frames['gen_from_file'])

    patchFileLabel = Label(patchDialogFrame, text='Patch File')
    guivars['patch_file'] = StringVar(value='')
    patchEntry = Entry(patchDialogFrame, textvariable=guivars['patch_file'], width=52)

    def PatchSelect():
        patch_file = filedialog.askopenfilename(filetypes=[("Patch File Archive", "*.zpfz *.zpf"), ("All Files", "*")])
        if patch_file != '':
            guivars['patch_file'].set(patch_file)
    patchSelectButton = Button(patchDialogFrame, text='Select File', command=PatchSelect, width=14)

    patchFileLabel.pack(side=LEFT, padx=(5,0))
    patchEntry.pack(side=LEFT, padx=3)
    patchSelectButton.pack(side=LEFT)

    patchDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(10,5))

    def generateFromFile():
        settings = guivars_to_settings(guivars)
        BackgroundTaskProgress(mainWindow, "Generating From File %s..." % os.path.basename(settings.patch_file), from_patch_file, settings)

    patchCosmeticsAndGenerateFrame = Frame(frames['gen_from_file'])
    guivars['repatch_cosmetics'] = IntVar()
    widgets['repatch_cosmetics'] = Checkbutton(patchCosmeticsAndGenerateFrame, text='Update Cosmetics', variable=guivars['repatch_cosmetics'], justify=LEFT, wraplength=220, command=show_settings)
    widgets['repatch_cosmetics'].pack(side=LEFT, padx=5, anchor=W)

    generateFileButton = Button(patchCosmeticsAndGenerateFrame, text='Generate!', width=14, command=generateFromFile)
    generateFileButton.pack(side=RIGHT, anchor=E)
    patchCosmeticsAndGenerateFrame.pack(side=BOTTOM, fill=BOTH, expand=True, pady=(0,10), padx=(0, 10))

    generation_notebook.pack(fill=BOTH, expand=True, padx=5, pady=5)


    guivars['checked_version'] = StringVar()
    guivars['cosmetics_only'] = IntVar()

    if settings is not None:
        # Load values from commandline args
        settings_to_guivars(settings, guivars)
    else:
        # Try to load saved settings
        settingsFile = local_path('settings.sav')
        try:
            with open(settingsFile) as f:
                settings = Settings( json.load(f) )
        except:
            settings = Settings({})
        settings_to_guivars(settings, guivars)
        guivars['seed'].set("")

        presets = {}
        for file in [data_path('presets_default.json')] \
                  + [local_path(f) for f in os.listdir(local_path()) if f.startswith('presets_') and f.endswith('.sav')] \
                  + [local_path('presets.sav')]:
            try:
                with open(file) as f:
                    presets_temp = json.load(f)
                    if file != local_path('presets.sav'):
                        for preset in presets_temp.values():
                            preset['locked'] = True
                    presets.update(presets_temp)
            except:
                pass
        update_preset_dropdown()

    show_settings()

    def gui_check_version():
        task = BackgroundTask(mainWindow, check_version, guivars['checked_version'].get())
        while task.running:
            mainWindow.update()

        if task.status:
            versionCheckLabel = LabelFrame(versionCheckFrame, text="New Version Available!")
            versionCheckText = Label(versionCheckLabel, justify=LEFT, text=task.status[(task.status.find(' ')+1):])
            versionCheckLink = Label(versionCheckLabel, justify=LEFT, text='Click here and download the current version.', fg='blue', cursor='hand2')
            versionCheckLink.bind("<Button-1>", lambda event: webbrowser.open_new(r"https://github.com/TestRunnerSRL/OoT-Randomizer/tree/Dev"))
            versionCheckText.pack(anchor=NW, padx=5, pady=0)
            versionCheckLink.pack(anchor=NW, padx=5, pady=0)
            versionCheckLabel.pack(anchor=NW, fill=X, expand="yes", padx=5, pady=5)

    mainWindow.after(1000, gui_check_version)
    mainWindow.mainloop()

    # Save settings on close
    settings_file = local_path('settings.sav')
    with open(settings_file, 'w') as outfile:
        settings = guivars_to_settings(guivars)
        del settings.__dict__["distribution"]
        del settings.__dict__["seed"]
        del settings.__dict__["numeric_seed"]
        del settings.__dict__["check_version"]
        if "locked" in settings.__dict__:
            del settings.__dict__["locked"]
        json.dump(settings.__dict__, outfile, indent=4)
    
    save_presets()
Esempio n. 26
0
def get_preset_files():
    return [data_path('presets_default.json')] + sorted(
        os.path.join(data_path('Presets'), fn)
        for fn in os.listdir(data_path('Presets')) if fn.endswith('.json'))