diff --git a/.gitignore b/.gitignore index 8c5d8669..91b22ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,13 @@ README.html EnemizerCLI/ .mypy_cache/ RaceRom.py +upx/ weights/ +settings.json +working_dirs.json + +*.exe + venv test diff --git a/AdjusterMain.py b/AdjusterMain.py index 30092b15..b1d5cadf 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -21,6 +21,9 @@ def adjust(args): else: raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') + if not hasattr(args,"sprite"): + args.sprite = None + apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes) rom.write_to_file(output_path('%s.sfc' % outfilebase)) diff --git a/BaseClasses.py b/BaseClasses.py index 65fff7e5..895e02c3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -769,6 +769,16 @@ class CollectionState(object): else: self.prog_items.add(('Bow', item.player)) changed = True + elif 'Armor' in item.name: + if self.has('Red Mail', item.player): + pass + elif self.has('Blue Mail', item.player): + self.prog_items.add(('Red Mail', item.player)) + changed = True + else: + self.prog_items.add(('Blue Mail', item.player)) + changed = True + elif item.name.startswith('Bottle'): if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit: self.prog_items.add((item.name, item.player)) diff --git a/Bosses.py b/Bosses.py index 4c748391..ea02b8ba 100644 --- a/Bosses.py +++ b/Bosses.py @@ -183,6 +183,10 @@ def place_bosses(world, player): raise FillError('Could not place boss for location %s' % loc_text) bosses.remove(boss) + # GT Bosses can move dungeon - find the real dungeon to place them in + if level: + loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] + loc_text = loc + ' (' + level + ')' logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random @@ -193,5 +197,9 @@ def place_bosses(world, player): except IndexError: raise FillError('Could not place boss for location %s' % loc_text) + # GT Bosses can move dungeon - find the real dungeon to place them in + if level: + loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0] + loc_text = loc + ' (' + level + ')' logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) diff --git a/CLI.py b/CLI.py new file mode 100644 index 00000000..b3bbe78b --- /dev/null +++ b/CLI.py @@ -0,0 +1,508 @@ +import argparse +import copy +import json +import os +import logging +import random +import textwrap +import shlex +import sys + +from Main import main +from Utils import is_bundled, close_console +from Fill import FillError + +import classes.constants as CONST + + +class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): + + def _get_help_string(self, action): + return textwrap.dedent(action.help) + +def parse_arguments(argv, no_defaults=False): + def defval(value): + return value if not no_defaults else None + + # get settings + settings = get_settings() + + # we need to know how many players we have first + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) + multiargs, _ = parser.parse_known_args(argv) + + parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) + parser.add_argument('--create_spoiler', default=defval(settings["create_spoiler"] != 0), help='Output a Spoiler File', action='store_true') + parser.add_argument('--logic', default=defval(settings["logic"]), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'], + help='''\ + Select Enforcement of Item Requirements. (default: %(default)s) + No Glitches: + Minor Glitches: May require Fake Flippers, Bunny Revival + and Dark Room Navigation. + No Logic: Distribute items without regard for + item requirements. + ''') + parser.add_argument('--mode', default=defval(settings["mode"]), const='open', nargs='?', choices=['standard', 'open', 'inverted'], + help='''\ + Select game mode. (default: %(default)s) + Open: World starts with Zelda rescued. + Standard: Fixes Hyrule Castle Secret Entrance and Front Door + but may lead to weird rain state issues if you exit + through the Hyrule Castle side exits before rescuing + Zelda in a full shuffle. + Inverted: Starting locations are Dark Sanctuary in West Dark + World or at Link's House, which is shuffled freely. + Requires the moon pearl to be Link in the Light World + instead of a bunny. + ''') + parser.add_argument('--swords', default=defval(settings["swords"]), const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'], + help='''\ + Select sword placement. (default: %(default)s) + Random: All swords placed randomly. + Assured: Start game with a sword already. + Swordless: No swords. Curtains in Skull Woods and Agahnim\'s + Tower are removed, Agahnim\'s Tower barrier can be + destroyed with hammer. Misery Mire and Turtle Rock + can be opened without a sword. Hammer damages Ganon. + Ether and Bombos Tablet can be activated with Hammer + (and Book). Bombos pads have been added in Ice + Palace, to allow for an alternative to firerod. + Vanilla: Swords are in vanilla locations. + ''') + parser.add_argument('--goal', default=defval(settings["goal"]), const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], + help='''\ + Select completion goal. (default: %(default)s) + Ganon: Collect all crystals, beat Agahnim 2 then + defeat Ganon. + Crystals: Collect all crystals then defeat Ganon. + Pedestal: Places the Triforce at the Master Sword Pedestal. + All Dungeons: Collect all crystals, pendants, beat both + Agahnim fights and then defeat Ganon. + Triforce Hunt: Places 30 Triforce Pieces in the world, collect + 20 of them to beat the game. + ''') + parser.add_argument('--difficulty', default=defval(settings["difficulty"]), const='normal', nargs='?', choices=['normal', 'hard', 'expert'], + help='''\ + Select game difficulty. Affects available itempool. (default: %(default)s) + Normal: Normal difficulty. + Hard: A harder setting with less equipment and reduced health. + Expert: A harder yet setting with minimum equipment and health. + ''') + parser.add_argument('--item_functionality', default=defval(settings["item_functionality"]), const='normal', nargs='?', choices=['normal', 'hard', 'expert'], + help='''\ + Select limits on item functionality to increase difficulty. (default: %(default)s) + Normal: Normal functionality. + Hard: Reduced functionality. + Expert: Greatly reduced functionality. + ''') + parser.add_argument('--timer', default=defval(settings["timer"]), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], + help='''\ + Select game timer setting. Affects available itempool. (default: %(default)s) + None: No timer. + Display: Displays a timer but does not affect + the itempool. + Timed: Starts with clock at zero. Green Clocks + subtract 4 minutes (Total: 20), Blue Clocks + subtract 2 minutes (Total: 10), Red Clocks add + 2 minutes (Total: 10). Winner is player with + lowest time at the end. + Timed OHKO: Starts clock at 10 minutes. Green Clocks add + 5 minutes (Total: 25). As long as clock is at 0, + Link will die in one hit. + OHKO: Like Timed OHKO, but no clock items are present + and the clock is permenantly at zero. + Timed Countdown: Starts with clock at 40 minutes. Same clocks as + Timed mode. If time runs out, you lose (but can + still keep playing). + ''') + parser.add_argument('--progressive', default=defval(settings["progressive"]), const='normal', nargs='?', choices=['on', 'off', 'random'], + help='''\ + Select progressive equipment setting. Affects available itempool. (default: %(default)s) + On: Swords, Shields, Armor, and Gloves will + all be progressive equipment. Each subsequent + item of the same type the player finds will + upgrade that piece of equipment by one stage. + Off: Swords, Shields, Armor, and Gloves will not + be progressive equipment. Higher level items may + be found at any time. Downgrades are not possible. + Random: Swords, Shields, Armor, and Gloves will, per + category, be randomly progressive or not. + Link will die in one hit. + ''') + parser.add_argument('--algorithm', default=defval(settings["algorithm"]), const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], + help='''\ + Select item filling algorithm. (default: %(default)s + balanced: vt26 derivative that aims to strike a balance between + the overworld heavy vt25 and the dungeon heavy vt26 + algorithm. + vt26: Shuffle items and place them in a random location + that it is not impossible to be in. This includes + dungeon keys and items. + vt25: Shuffle items and place them in a random location + that it is not impossible to be in. + vt21: Unbiased in its selection, but has tendency to put + Ice Rod in Turtle Rock. + vt22: Drops off stale locations after 1/3 of progress + items were placed to try to circumvent vt21\'s + shortcomings. + Freshness: Keep track of stale locations (ones that cannot be + reached yet) and decrease likeliness of selecting + them the more often they were found unreachable. + Flood: Push out items starting from Link\'s House and + slightly biased to placing progression items with + less restrictions. + ''') + parser.add_argument('--shuffle', default=defval(settings["shuffle"]), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], + help='''\ + Select Entrance Shuffling Algorithm. (default: %(default)s) + Full: Mix cave and dungeon entrances freely while limiting + multi-entrance caves to one world. + Simple: Shuffle Dungeon Entrances/Exits between each other + and keep all 4-entrance dungeons confined to one + location. All caves outside of death mountain are + shuffled in pairs and matched by original type. + Restricted: Use Dungeons shuffling from Simple but freely + connect remaining entrances. + Crossed: Mix cave and dungeon entrances freely while allowing + caves to cross between worlds. + Insanity: Decouple entrances and exits from each other and + shuffle them freely. Caves that used to be single + entrance will still exit to the same location from + which they are entered. + Vanilla: All entrances are in the same locations they were + in the base game. + Legacy shuffles preserve behavior from older versions of the + entrance randomizer including significant technical limitations. + The dungeon variants only mix up dungeons and keep the rest of + the overworld vanilla. + ''') + parser.add_argument('--door_shuffle', default=defval(settings["door_shuffle"]), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed'], + help='''\ + Select Door Shuffling Algorithm. (default: %(default)s) + Basic: Doors are mixed within a single dungeon. + (Not yet implemented) + Crossed: Doors are mixed between all dungeons. + (Not yet implemented) + Vanilla: All doors are connected the same way they were in the + base game. + ''') + parser.add_argument('--experimental', default=defval(settings["experimental"] != 0), help='Enable experimental features', action='store_true') + parser.add_argument('--dungeon_counters', default=defval(settings["dungeon_counters"]), help='Enable dungeon chest counters', const='off', nargs='?', choices=['off', 'on', 'pickup']) + parser.add_argument('--crystals_ganon', default=defval(settings["crystals_ganon"]), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], + help='''\ + How many crystals are needed to defeat ganon. Any other + requirements for ganon for the selected goal still apply. + This setting does not apply when the all dungeons goal is + selected. (default: %(default)s) + Random: Picks a random value between 0 and 7 (inclusive). + 0-7: Number of crystals needed + ''') + parser.add_argument('--crystals_gt', default=defval(settings["crystals_gt"]), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], + help='''\ + How many crystals are needed to open GT. For inverted mode + this applies to the castle tower door instead. (default: %(default)s) + Random: Picks a random value between 0 and 7 (inclusive). + 0-7: Number of crystals needed + ''') + parser.add_argument('--openpyramid', default=defval(settings["openpyramid"] != 0), help='''\ + Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it + ''', action='store_true') + parser.add_argument('--rom', default=defval(settings["rom"]), help='Path to an ALttP JAP(1.0) rom to use as a base.') + parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') + parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help='Define seed number to generate.', type=int) + parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else None), help='''\ + Use to batch generate multiple seeds with same settings. + If --seed is provided, it will be used for the first seed, then + used to derive the next seed (i.e. generating 10 seeds with + --seed given will produce the same 10 (different) roms each + time). + ''', type=int) + parser.add_argument('--fastmenu', default=defval(settings["fastmenu"]), const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'], + help='''\ + Select the rate at which the menu opens and closes. + (default: %(default)s) + ''') + parser.add_argument('--quickswap', default=defval(settings["quickswap"] != 0), help='Enable quick item swapping with L and R.', action='store_true') + parser.add_argument('--disablemusic', default=defval(settings["disablemusic"] != 0), help='Disables game music.', action='store_true') + parser.add_argument('--mapshuffle', default=defval(settings["mapshuffle"] != 0), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true') + parser.add_argument('--compassshuffle', default=defval(settings["compassshuffle"] != 0), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true') + parser.add_argument('--keyshuffle', default=defval(settings["keyshuffle"] != 0), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') + parser.add_argument('--bigkeyshuffle', default=defval(settings["bigkeyshuffle"] != 0), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') + parser.add_argument('--keysanity', default=defval(settings["keysanity"] != 0), help=argparse.SUPPRESS, action='store_true') + parser.add_argument('--retro', default=defval(settings["retro"] != 0), help='''\ + Keys are universal, shooting arrows costs rupees, + and a few other little things make this more like Zelda-1. + ''', action='store_true') + parser.add_argument('--startinventory', default=defval(settings["startinventory"]), help='Specifies a list of items that will be in your starting inventory (separated by commas)') + parser.add_argument('--usestartinventory', default=defval(settings["usestartinventory"] != 0), help='Not supported.') + parser.add_argument('--custom', default=defval(settings["custom"] != 0), help='Not supported.') + parser.add_argument('--customitemarray', default={}, help='Not supported.') + parser.add_argument('--accessibility', default=defval(settings["accessibility"]), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ + Select Item/Location Accessibility. (default: %(default)s) + Items: You can reach all unique inventory items. No guarantees about + reaching all locations or all keys. + Locations: You will be able to reach every location in the game. + None: You will be able to reach enough locations to beat the game. + ''') + parser.add_argument('--hints', default=defval(settings["hints"] != 0), help='''\ + Make telepathic tiles and storytellers give helpful hints. + ''', action='store_true') + # included for backwards compatibility + parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(settings["shuffleganon"] != 0)) + parser.add_argument('--no-shuffleganon', help='''\ + If set, the Pyramid Hole and Ganon's Tower are not + included entrance shuffle pool. + ''', action='store_false', dest='shuffleganon') + parser.add_argument('--heartbeep', default=defval(settings["heartbeep"]), const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'], + help='''\ + Select the rate at which the heart beep sound is played at + low health. (default: %(default)s) + ''') + parser.add_argument('--heartcolor', default=defval(settings["heartcolor"]), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'], + help='Select the color of Link\'s heart meter. (default: %(default)s)') + parser.add_argument('--ow_palettes', default=defval(settings["ow_palettes"]), choices=['default', 'random', 'blackout']) + parser.add_argument('--uw_palettes', default=defval(settings["uw_palettes"]), choices=['default', 'random', 'blackout']) + parser.add_argument('--sprite', default=defval(settings["sprite"]), help='''\ + Path to a sprite sheet to use for Link. Needs to be in + binary format and have a length of 0x7000 (28672) bytes, + or 0x7078 (28792) bytes including palette data. + Alternatively, can be a ALttP Rom patched with a Link + sprite that will be extracted. + ''') + parser.add_argument('--suppress_rom', default=defval(settings["suppress_rom"] != 0), help='Do not create an output rom file.', action='store_true') + parser.add_argument('--gui', help='Launch the GUI', action='store_true') + parser.add_argument('--jsonout', action='store_true', help='''\ + Output .json patch to stdout instead of a patched rom. Used + for VT site integration, do not use otherwise. + ''') + parser.add_argument('--skip_playthrough', action='store_true', default=defval(settings["skip_playthrough"] != 0)) + parser.add_argument('--enemizercli', default=defval(settings["enemizercli"])) + parser.add_argument('--shufflebosses', default=defval(settings["shufflebosses"]), choices=['none', 'basic', 'normal', 'chaos']) + parser.add_argument('--shuffleenemies', default=defval(settings["shuffleenemies"]), choices=['none', 'shuffled', 'chaos']) + parser.add_argument('--enemy_health', default=defval(settings["enemy_health"]), choices=['default', 'easy', 'normal', 'hard', 'expert']) + parser.add_argument('--enemy_damage', default=defval(settings["enemy_damage"]), choices=['default', 'shuffled', 'chaos']) + parser.add_argument('--shufflepots', default=defval(settings["shufflepots"] != 0), action='store_true') + parser.add_argument('--beemizer', default=defval(settings["beemizer"]), type=lambda value: min(max(int(value), 0), 4)) + parser.add_argument('--remote_items', default=defval(settings["remote_items"] != 0), action='store_true') + parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255)) + parser.add_argument('--names', default=defval(settings["names"])) + parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) + parser.add_argument('--outputpath', default=defval(settings["outputpath"])) + parser.add_argument('--race', default=defval(settings["race"] != 0), action='store_true') + parser.add_argument('--saveonexit', default=defval(settings["saveonexit"]), choices=['never', 'ask', 'always']) + parser.add_argument('--outputname') + + if multiargs.multi: + for player in range(1, multiargs.multi + 1): + parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS) + + ret = parser.parse_args(argv) + + if ret.keysanity: + ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4 + + if multiargs.multi: + defaults = copy.deepcopy(ret) + for player in range(1, multiargs.multi + 1): + playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True) + + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', + 'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', + 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', + 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', + 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', + 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', + 'remote_items']: + value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) + if player == 1: + setattr(ret, name, {1: value}) + else: + getattr(ret, name)[player] = value + + return ret + + +def get_settings(): + # set default settings + settings = { + "retro": False, + "mode": "open", + "logic": "noglitches", + "goal": "ganon", + "crystals_gt": "7", + "crystals_ganon": "7", + "swords": "random", + "difficulty": "normal", + "item_functionality": "normal", + "timer": "none", + "progressive": "on", + "accessibility": "items", + "algorithm": "balanced", + + "openpyramid": False, + "shuffleganon": False, + "shuffle": "vanilla", + + "shufflepots": False, + "shuffleenemies": "none", + "shufflebosses": "none", + "enemy_damage": "default", + "enemy_health": "default", + "enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"), + + "mapshuffle": False, + "compassshuffle": False, + "keyshuffle": False, + "bigkeyshuffle": False, + "keysanity": False, + "door_shuffle": "basic", + "experimental": 0, + "dungeon_counters": "off", + + "multi": 1, + "names": "", + + "hints": True, + "disablemusic": False, + "quickswap": False, + "heartcolor": "red", + "heartbeep": "normal", + "sprite": None, + "fastmenu": "normal", + "ow_palettes": "default", + "uw_palettes": "default", + + "create_spoiler": False, + "skip_playthrough": False, + "suppress_rom": False, + "usestartinventory": False, + "custom": False, + "rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), + + "seed": None, + "count": None, + "startinventory": "", + "beemizer": 0, + "remote_items": False, + "race": False, + "customitemarray": { + "bow": 0, + "progressivebow": 2, + "boomerang": 1, + "redmerang": 1, + "hookshot": 1, + "mushroom": 1, + "powder": 1, + "firerod": 1, + "icerod": 1, + "bombos": 1, + "ether": 1, + "quake": 1, + "lamp": 1, + "hammer": 1, + "shovel": 1, + "flute": 1, + "bugnet": 1, + "book": 1, + "bottle": 4, + "somaria": 1, + "byrna": 1, + "cape": 1, + "mirror": 1, + "boots": 1, + "powerglove": 0, + "titansmitt": 0, + "progressiveglove": 2, + "flippers": 1, + "pearl": 1, + "heartpiece": 24, + "heartcontainer": 10, + "sancheart": 1, + "sword1": 0, + "sword2": 0, + "sword3": 0, + "sword4": 0, + "progressivesword": 4, + "shield1": 0, + "shield2": 0, + "shield3": 0, + "progressiveshield": 3, + "mail2": 0, + "mail3": 0, + "progressivemail": 2, + "halfmagic": 1, + "quartermagic": 0, + "bombsplus5": 0, + "bombsplus10": 0, + "arrowsplus5": 0, + "arrowsplus10": 0, + "arrow1": 1, + "arrow10": 12, + "bomb1": 0, + "bomb3": 16, + "bomb10": 1, + "rupee1": 2, + "rupee5": 4, + "rupee20": 28, + "rupee50": 7, + "rupee100": 1, + "rupee300": 5, + "blueclock": 0, + "greenclock": 0, + "redclock": 0, + "silversupgrade": 0, + "generickeys": 0, + "triforcepieces": 0, + "triforcepiecesgoal": 0, + "triforce": 0, + "rupoor": 0, + "rupoorcost": 10 + }, + "randomSprite": False, + "outputpath": os.path.join("."), + "saveonexit": "ask", + "startinventoryarray": {} + } + + if sys.platform.lower().find("windows"): + settings["enemizercli"] += ".exe" + + # read saved settings file if it exists and set these + settings_path = os.path.join(".", "resources", "user", "settings.json") + if os.path.exists(settings_path): + with(open(settings_path)) as json_file: + data = json.load(json_file) + for k, v in data.items(): + settings[k] = v + return settings + + +def get_args_priority(settings_args, gui_args, cli_args): + args = {} + args["settings"] = get_settings() if settings_args is None else settings_args + args["gui"] = {} if gui_args is None else gui_args + args["cli"] = cli_args + + args["load"] = args["settings"] + if args["gui"] is not None: + for k in args["gui"]: + if k not in args["load"] or args["load"][k] != args["gui"]: + args["load"][k] = args["gui"][k] + + if args["cli"] is None: + args["cli"] = {} + cli = vars(parse_arguments(None)) + for k, v in cli.items(): + if isinstance(v, dict) and 1 in v: + args["cli"][k] = v[1] + else: + args["cli"][k] = v + load_doesnt_have_key = k not in args["load"] + different_val = (k in args["load"] and k in args["cli"]) and (args["load"][k] != args["cli"][k]) + cli_has_empty_dict = k in args["cli"] and isinstance(args["cli"][k], dict) and len(args["cli"][k]) == 0 + if load_doesnt_have_key or different_val: + if not cli_has_empty_dict: + args["load"][k] = args["cli"][k] + + return args diff --git a/DoorShuffle.py b/DoorShuffle.py index b856a7d6..b46e3a4b 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -165,7 +165,7 @@ def vanilla_key_logic(world, player): world.key_logic[player][builder.name] = key_layout.key_logic log_key_logic(builder.name, key_layout.key_logic) last_key = None - if world.shuffle[player] == 'vanilla': + if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items': validate_vanilla_key_logic(world, player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index ec693e8a..5dd74be4 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1,5 +1,6 @@ import random import collections +import itertools from collections import defaultdict, deque from enum import Enum, unique import logging @@ -1493,20 +1494,32 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger problem_builders = identify_branching_issues_2(problem_builders) # step 5: assign randomly until gone - must maintain connectedness, neutral polarity, branching, lack, etc. + comb_w_replace = len(dungeon_map) ** len(neutral_choices) + combinations = None + if comb_w_replace <= 1000: + combinations = list(itertools.product(dungeon_map.keys(), repeat=len(neutral_choices))) + random.shuffle(combinations) tries = 0 while len(polarized_sectors) > 0: - if tries > 100: + if tries > 1000 or (combinations and tries >= len(combinations)): raise Exception('No valid assignment found. Ref: %s' % next(iter(dungeon_map.keys()))) - choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices)) - valid = [] + if combinations: + choices = combinations[tries] + else: + choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices)) + chosen_sectors = defaultdict(list) for i, choice in enumerate(choices): - builder = dungeon_map[choice] - if valid_assignment(builder, neutral_choices[i]): + chosen_sectors[choice].extend(neutral_choices[i]) + all_valid = True + for name, sector_list in chosen_sectors.items(): + if not valid_assignment(dungeon_map[name], sector_list): + all_valid = False + break + if all_valid: + for i, choice in enumerate(choices): + builder = dungeon_map[choice] for sector in neutral_choices[i]: assign_sector(sector, builder, polarized_sectors, global_pole) - valid.append(neutral_choices[i]) - for c in valid: - neutral_choices.remove(c) tries += 1 @@ -1957,6 +1970,7 @@ def resolve_equations(builder, sector_list): # negative benefit transforms (dead end) def find_priority_equation(equations, current_access): flex = calc_flex(equations, current_access) + required = calc_required(equations, current_access) best_profit = None triplet_candidates = [] local_profit_map = {} @@ -1977,14 +1991,17 @@ def find_priority_equation(equations, current_access): else: triplet_candidates.append((eq, eq_list, sector)) local_profit_map[sector] = best_local_profit - if len(triplet_candidates) == 0: + filtered_candidates = filter_requirements(triplet_candidates, equations, required, current_access) + if len(filtered_candidates) == 0: + filtered_candidates = triplet_candidates + if len(filtered_candidates) == 0: return None, None, None # can't pay for anything - if len(triplet_candidates) == 1: - return triplet_candidates[0] + if len(filtered_candidates) == 1: + return filtered_candidates[0] - required_candidates = [x for x in triplet_candidates if x[0].required] + required_candidates = [x for x in filtered_candidates if x[0].required] if len(required_candidates) == 0: - required_candidates = triplet_candidates + required_candidates = filtered_candidates if len(required_candidates) == 1: return required_candidates[0] @@ -2000,6 +2017,46 @@ def find_priority_equation(equations, current_access): return good_local_candidates[0] # just pick one I guess +def calc_required(equations, current_access): + ttl = 0 + for num in current_access.values(): + ttl += num + local_profit_map = {} + for sector, eq_list in equations.items(): + best_local_profit = None + for eq in eq_list: + profit = eq.profit() + if best_local_profit is None or profit > best_local_profit: + best_local_profit = profit + local_profit_map[sector] = best_local_profit + ttl += best_local_profit + if ttl == 0: + new_lists = {} + for sector, eq_list in equations.items(): + if len(eq_list) > 1: + rem_list = [] + for eq in eq_list: + if eq.profit() < local_profit_map[sector]: + rem_list.append(eq) + if len(rem_list) > 0: + new_lists[sector] = [x for x in eq_list if x not in rem_list] + for sector, eq_list in new_lists.items(): + if len(eq_list) <= 1: + for eq in eq_list: + eq.required = True + equations[sector] = eq_list + required_costs = defaultdict(int) + required_benefits = defaultdict(int) + for sector, eq_list in equations.items(): + for eq in eq_list: + if eq.required: + for key, door_list in eq.cost.items(): + required_costs[key] += len(door_list) + for key, door_list in eq.benefit.items(): + required_benefits[key] += len(door_list) + return required_costs, required_benefits + + def calc_flex(equations, current_access): flex_spending = defaultdict(int) required_costs = defaultdict(int) @@ -2013,6 +2070,45 @@ def calc_flex(equations, current_access): return flex_spending +def filter_requirements(triplet_candidates, equations, required, current_access): + r_costs, r_exits = required + valid_candidates = [] + for cand, cand_list, cand_sector in triplet_candidates: + valid = True + if not cand.required: + potential_benefit = defaultdict(int) + potential_costs = defaultdict(int) + for h_type, benefit in current_access.items(): + cur_cost = len(cand.cost[h_type]) + if benefit - cur_cost > 0: + potential_benefit[h_type] += benefit - cur_cost + for h_type, benefit_list in cand.benefit.items(): + potential_benefit[h_type] += len(benefit_list) + for sector, eq_list in equations.items(): + if sector == cand_sector: + affected_doors = [d for x in cand.benefit.values() for d in x] + [d for x in cand.cost.values() for d in x] + adj_list = [x for x in eq_list if x.door not in affected_doors] + else: + adj_list = eq_list + for eq in adj_list: + for h_type, benefit_list in eq.benefit.items(): + potential_benefit[h_type] += len(benefit_list) + for h_type, cost_list in eq.cost.items(): + potential_costs[h_type] += len(cost_list) + for h_type, requirement in r_costs.items(): + if requirement > 0 and potential_benefit[h_type] < requirement: + valid = False + break + if valid: + for h_type, requirement in r_exits.items(): + if requirement > 0 and potential_costs[h_type] < requirement: + valid = False + break + if valid: + valid_candidates.append((cand, cand_list, cand_sector)) + return valid_candidates + + def resolve_equation(equation, eq_list, sector, current_access, reached_doors, equations): for key, door_list in equation.cost.items(): if current_access[key] - len(door_list) < 0: diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index 5153443e..64a0e2d7 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -8,313 +8,12 @@ import textwrap import shlex import sys +from CLI import parse_arguments from Main import main from Rom import get_sprite_from_name from Utils import is_bundled, close_console from Fill import FillError - -class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): - - def _get_help_string(self, action): - return textwrap.dedent(action.help) - -def parse_arguments(argv, no_defaults=False): - def defval(value): - return value if not no_defaults else None - - # we need to know how many players we have first - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255)) - multiargs, _ = parser.parse_known_args(argv) - - parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') - parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'], - help='''\ - Select Enforcement of Item Requirements. (default: %(default)s) - No Glitches: - Minor Glitches: May require Fake Flippers, Bunny Revival - and Dark Room Navigation. - No Logic: Distribute items without regard for - item requirements. - ''') - parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'], - help='''\ - Select game mode. (default: %(default)s) - Open: World starts with Zelda rescued. - Standard: Fixes Hyrule Castle Secret Entrance and Front Door - but may lead to weird rain state issues if you exit - through the Hyrule Castle side exits before rescuing - Zelda in a full shuffle. - Inverted: Starting locations are Dark Sanctuary in West Dark - World or at Link's House, which is shuffled freely. - Requires the moon pearl to be Link in the Light World - instead of a bunny. - ''') - parser.add_argument('--swords', default=defval('random'), const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'], - help='''\ - Select sword placement. (default: %(default)s) - Random: All swords placed randomly. - Assured: Start game with a sword already. - Swordless: No swords. Curtains in Skull Woods and Agahnim\'s - Tower are removed, Agahnim\'s Tower barrier can be - destroyed with hammer. Misery Mire and Turtle Rock - can be opened without a sword. Hammer damages Ganon. - Ether and Bombos Tablet can be activated with Hammer - (and Book). Bombos pads have been added in Ice - Palace, to allow for an alternative to firerod. - Vanilla: Swords are in vanilla locations. - ''') - parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], - help='''\ - Select completion goal. (default: %(default)s) - Ganon: Collect all crystals, beat Agahnim 2 then - defeat Ganon. - Crystals: Collect all crystals then defeat Ganon. - Pedestal: Places the Triforce at the Master Sword Pedestal. - All Dungeons: Collect all crystals, pendants, beat both - Agahnim fights and then defeat Ganon. - Triforce Hunt: Places 30 Triforce Pieces in the world, collect - 20 of them to beat the game. - ''') - parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'], - help='''\ - Select game difficulty. Affects available itempool. (default: %(default)s) - Normal: Normal difficulty. - Hard: A harder setting with less equipment and reduced health. - Expert: A harder yet setting with minimum equipment and health. - ''') - parser.add_argument('--item_functionality', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'], - help='''\ - Select limits on item functionality to increase difficulty. (default: %(default)s) - Normal: Normal functionality. - Hard: Reduced functionality. - Expert: Greatly reduced functionality. - ''') - parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], - help='''\ - Select game timer setting. Affects available itempool. (default: %(default)s) - None: No timer. - Display: Displays a timer but does not affect - the itempool. - Timed: Starts with clock at zero. Green Clocks - subtract 4 minutes (Total: 20), Blue Clocks - subtract 2 minutes (Total: 10), Red Clocks add - 2 minutes (Total: 10). Winner is player with - lowest time at the end. - Timed OHKO: Starts clock at 10 minutes. Green Clocks add - 5 minutes (Total: 25). As long as clock is at 0, - Link will die in one hit. - OHKO: Like Timed OHKO, but no clock items are present - and the clock is permenantly at zero. - Timed Countdown: Starts with clock at 40 minutes. Same clocks as - Timed mode. If time runs out, you lose (but can - still keep playing). - ''') - parser.add_argument('--progressive', default=defval('on'), const='normal', nargs='?', choices=['on', 'off', 'random'], - help='''\ - Select progressive equipment setting. Affects available itempool. (default: %(default)s) - On: Swords, Shields, Armor, and Gloves will - all be progressive equipment. Each subsequent - item of the same type the player finds will - upgrade that piece of equipment by one stage. - Off: Swords, Shields, Armor, and Gloves will not - be progressive equipment. Higher level items may - be found at any time. Downgrades are not possible. - Random: Swords, Shields, Armor, and Gloves will, per - category, be randomly progressive or not. - Link will die in one hit. - ''') - parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], - help='''\ - Select item filling algorithm. (default: %(default)s - balanced: vt26 derivative that aims to strike a balance between - the overworld heavy vt25 and the dungeon heavy vt26 - algorithm. - vt26: Shuffle items and place them in a random location - that it is not impossible to be in. This includes - dungeon keys and items. - vt25: Shuffle items and place them in a random location - that it is not impossible to be in. - vt21: Unbiased in its selection, but has tendency to put - Ice Rod in Turtle Rock. - vt22: Drops off stale locations after 1/3 of progress - items were placed to try to circumvent vt21\'s - shortcomings. - Freshness: Keep track of stale locations (ones that cannot be - reached yet) and decrease likeliness of selecting - them the more often they were found unreachable. - Flood: Push out items starting from Link\'s House and - slightly biased to placing progression items with - less restrictions. - ''') - parser.add_argument('--shuffle', default=defval('vanilla'), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], - help='''\ - Select Entrance Shuffling Algorithm. (default: %(default)s) - Full: Mix cave and dungeon entrances freely while limiting - multi-entrance caves to one world. - Simple: Shuffle Dungeon Entrances/Exits between each other - and keep all 4-entrance dungeons confined to one - location. All caves outside of death mountain are - shuffled in pairs and matched by original type. - Restricted: Use Dungeons shuffling from Simple but freely - connect remaining entrances. - Crossed: Mix cave and dungeon entrances freely while allowing - caves to cross between worlds. - Insanity: Decouple entrances and exits from each other and - shuffle them freely. Caves that used to be single - entrance will still exit to the same location from - which they are entered. - Vanilla: All entrances are in the same locations they were - in the base game. - Legacy shuffles preserve behavior from older versions of the - entrance randomizer including significant technical limitations. - The dungeon variants only mix up dungeons and keep the rest of - the overworld vanilla. - ''') - parser.add_argument('--door_shuffle', default=defval('basic'), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed'], - help='''\ - Select Door Shuffling Algorithm. (default: %(default)s) - Basic: Doors are mixed within a single dungeon. - (Not yet implemented) - Crossed: Doors are mixed between all dungeons. - (Not yet implemented) - Vanilla: All doors are connected the same way they were in the - base game. - ''') - parser.add_argument('--experimental', default=defval(False), help='Enable experimental features', action='store_true') - parser.add_argument('--dungeon_counters', default=defval(False), help='Enable dungeon chest counters', action='store_true') - parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], - help='''\ - How many crystals are needed to defeat ganon. Any other - requirements for ganon for the selected goal still apply. - This setting does not apply when the all dungeons goal is - selected. (default: %(default)s) - Random: Picks a random value between 0 and 7 (inclusive). - 0-7: Number of crystals needed - ''') - parser.add_argument('--crystals_gt', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'], - help='''\ - How many crystals are needed to open GT. For inverted mode - this applies to the castle tower door instead. (default: %(default)s) - Random: Picks a random value between 0 and 7 (inclusive). - 0-7: Number of crystals needed - ''') - parser.add_argument('--openpyramid', default=defval(False), help='''\ - Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it - ''', action='store_true') - parser.add_argument('--rom', default=defval('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'), help='Path to an ALttP JAP(1.0) rom to use as a base.') - parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') - parser.add_argument('--seed', help='Define seed number to generate.', type=int) - parser.add_argument('--count', help='''\ - Use to batch generate multiple seeds with same settings. - If --seed is provided, it will be used for the first seed, then - used to derive the next seed (i.e. generating 10 seeds with - --seed given will produce the same 10 (different) roms each - time). - ''', type=int) - parser.add_argument('--fastmenu', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'], - help='''\ - Select the rate at which the menu opens and closes. - (default: %(default)s) - ''') - parser.add_argument('--quickswap', default=defval(False), help='Enable quick item swapping with L and R.', action='store_true') - parser.add_argument('--disablemusic', default=defval(False), help='Disables game music.', action='store_true') - parser.add_argument('--mapshuffle', default=defval(False), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true') - parser.add_argument('--compassshuffle', default=defval(False), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true') - parser.add_argument('--keyshuffle', default=defval(False), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') - parser.add_argument('--bigkeyshuffle', default=defval(False), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true') - parser.add_argument('--keysanity', default=defval(False), help=argparse.SUPPRESS, action='store_true') - parser.add_argument('--retro', default=defval(False), help='''\ - Keys are universal, shooting arrows costs rupees, - and a few other little things make this more like Zelda-1. - ''', action='store_true') - parser.add_argument('--startinventory', default=defval(''), help='Specifies a list of items that will be in your starting inventory (separated by commas)') - parser.add_argument('--custom', default=defval(False), help='Not supported.') - parser.add_argument('--customitemarray', default=defval(False), help='Not supported.') - parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ - Select Item/Location Accessibility. (default: %(default)s) - Items: You can reach all unique inventory items. No guarantees about - reaching all locations or all keys. - Locations: You will be able to reach every location in the game. - None: You will be able to reach enough locations to beat the game. - ''') - parser.add_argument('--hints', default=defval(False), help='''\ - Make telepathic tiles and storytellers give helpful hints. - ''', action='store_true') - # included for backwards compatibility - parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True)) - parser.add_argument('--no-shuffleganon', help='''\ - If set, the Pyramid Hole and Ganon's Tower are not - included entrance shuffle pool. - ''', action='store_false', dest='shuffleganon') - parser.add_argument('--heartbeep', default=defval('normal'), const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'], - help='''\ - Select the rate at which the heart beep sound is played at - low health. (default: %(default)s) - ''') - parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'], - help='Select the color of Link\'s heart meter. (default: %(default)s)') - parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout']) - parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout']) - parser.add_argument('--sprite', help='''\ - Path to a sprite sheet to use for Link. Needs to be in - binary format and have a length of 0x7000 (28672) bytes, - or 0x7078 (28792) bytes including palette data. - Alternatively, can be a ALttP Rom patched with a Link - sprite that will be extracted. - ''') - parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') - parser.add_argument('--gui', help='Launch the GUI', action='store_true') - parser.add_argument('--jsonout', action='store_true', help='''\ - Output .json patch to stdout instead of a patched rom. Used - for VT site integration, do not use otherwise. - ''') - parser.add_argument('--skip_playthrough', action='store_true', default=defval(False)) - parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core')) - parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos']) - parser.add_argument('--shuffleenemies', default=defval('none'), choices=['none', 'shuffled', 'chaos']) - parser.add_argument('--enemy_health', default=defval('default'), choices=['default', 'easy', 'normal', 'hard', 'expert']) - parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos']) - parser.add_argument('--shufflepots', default=defval(False), action='store_true') - parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4)) - parser.add_argument('--remote_items', default=defval(False), action='store_true') - parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255)) - parser.add_argument('--names', default=defval('')) - parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1)) - parser.add_argument('--outputpath') - parser.add_argument('--race', default=defval(False), action='store_true') - parser.add_argument('--outputname') - - if multiargs.multi: - for player in range(1, multiargs.multi + 1): - parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS) - - ret = parser.parse_args(argv) - if ret.keysanity: - ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4 - - if multiargs.multi: - defaults = copy.deepcopy(ret) - for player in range(1, multiargs.multi + 1): - playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True) - - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', - 'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', - 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', - 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', - 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', - 'remote_items']: - value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) - if player == 1: - setattr(ret, name, {1: value}) - else: - getattr(ret, name)[player] = value - - return ret - def start(): args = parse_arguments(None) @@ -360,7 +59,12 @@ def start(): seed = random.randint(0, 999999999) for fail in failures: logger.info('%s seed failed with: %s', fail[1], fail[0]) - logger.info('Generation fail rate: %f%%', 100*len(failures)/args.count) + fail_rate = 100 * len(failures) / args.count + success_rate = 100 * (args.count - len(failures)) / args.count + fail_rate = str(fail_rate).split('.') + success_rate = str(success_rate).split('.') + logger.info('Generation fail rate: ' + str(fail_rate[0] ).rjust(3, " ") + '.' + str(fail_rate[1] ).ljust(6, '0') + '%') + logger.info('Generation success rate: ' + str(success_rate[0]).rjust(3, " ") + '.' + str(success_rate[1]).ljust(6, '0') + '%') else: main(seed=args.seed, args=args) diff --git a/DungeonRandomizer.spec b/DungeonRandomizer.spec new file mode 100644 index 00000000..7b8de387 --- /dev/null +++ b/DungeonRandomizer.spec @@ -0,0 +1,59 @@ +# -*- mode: python -*- + +block_cipher = None +console = True + +def recurse_for_py_files(names_so_far): + returnvalue = [] + for name in os.listdir(os.path.join(*names_so_far)): + if name != "__pycache__": + subdir_name = os.path.join(*names_so_far, name) + if os.path.isdir(subdir_name): + new_name_list = names_so_far + [name] + for filename in os.listdir(os.path.join(*new_name_list)): + base_file,file_extension = os.path.splitext(filename) + if file_extension == ".py": + new_name = ".".join(new_name_list+[base_file]) + if not new_name in returnvalue: + returnvalue.append(new_name) + returnvalue.extend(recurse_for_py_files(new_name_list)) + returnvalue.append("PIL._tkinter_finder") #Linux needs this + return returnvalue + +hiddenimports = [] + +a = Analysis(['DungeonRandomizer.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=hiddenimports, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller +excluded_binaries = [ + 'VCRUNTIME140.dll', + 'msvcp140.dll', + 'mfc140u.dll'] +a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='DungeonRandomizer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=console ) # <--- change this to True to enable command prompt when the app runs diff --git a/EntranceShuffle.py b/EntranceShuffle.py index e1813011..c4747374 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2298,8 +2298,6 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', 'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (West)', 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', diff --git a/Fill.py b/Fill.py index 39a4d24e..54b9581f 100644 --- a/Fill.py +++ b/Fill.py @@ -233,7 +233,7 @@ def valid_key_placement(item, location, itempool, world): if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True key_logic = world.key_logic[item.player][dungeon.name] - unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name]) + unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) return key_logic.check_placement(unplaced_keys) else: inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) diff --git a/Gui.py b/Gui.py index 6936a678..632b9a43 100755 --- a/Gui.py +++ b/Gui.py @@ -1,1594 +1,186 @@ #!/usr/bin/env python3 -from argparse import Namespace -from glob import glob import json -import logging -import random import os -import shutil -from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, BOTH, Entry, Spinbox, Button, filedialog, messagebox, ttk -from urllib.parse import urlparse -from urllib.request import urlopen +import sys +from tkinter import Tk, Button, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, RIGHT, ttk, messagebox -from AdjusterMain import adjust +from argparse import Namespace +from CLI import get_settings, get_args_priority from DungeonRandomizer import parse_arguments -from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress -from Main import main, __version__ as ESVersion -from Rom import Sprite -from Utils import is_bundled, local_path, output_path, open_file +from gui.adjust.overview import adjust_page +from gui.startinventory.overview import startinventory_page +from gui.custom.overview import custom_page +from gui.loadcliargs import loadcliargs, loadadjustargs +from gui.randomize.item import item_page +from gui.randomize.entrando import entrando_page +from gui.randomize.enemizer import enemizer_page +from gui.randomize.dungeon import dungeon_page +from gui.randomize.multiworld import multiworld_page +from gui.randomize.gameoptions import gameoptions_page +from gui.randomize.generation import generation_page +from gui.bottom import bottom_frame, create_guiargs +from GuiUtils import set_icon +from Main import __version__ as ESVersion def guiMain(args=None): - mainWindow = Tk() - mainWindow.wm_title("Door Shuffle %s" % ESVersion) + def save_settings(args): + user_resources_path = os.path.join(".", "resources", "user") + settings_path = os.path.join(user_resources_path) + if not os.path.exists(settings_path): + os.makedirs(settings_path) + for widget in self.pages["adjust"].content.widgets: + args["adjust." + widget] = self.pages["adjust"].content.widgets[widget].storageVar.get() + with open(os.path.join(settings_path, "settings.json"), "w+") as f: + f.write(json.dumps(args, indent=2)) + os.chmod(os.path.join(settings_path, "settings.json"),0o755) + def save_settings_from_gui(confirm): + gui_args = vars(create_guiargs(self)) + if self.randomSprite.get(): + gui_args['sprite'] = 'random' + elif gui_args['sprite']: + gui_args['sprite'] = gui_args['sprite'].name + save_settings(gui_args) + if confirm: + messagebox.showinfo("Door Shuffle " + ESVersion, "Settings saved from GUI.") + + # routine for exiting the app + def guiExit(): + skip_exit = False + if self.settings['saveonexit'] == 'ask': + dosave = messagebox.askyesnocancel("Door Shuffle " + ESVersion, "Save settings before exit?") + if dosave: + save_settings_from_gui(True) + if dosave is None: + skip_exit = True + elif self.settings['saveonexit'] == 'always': + save_settings_from_gui(False) + if not skip_exit: + sys.exit(0) + + # make main window + # add program title & version number + mainWindow = Tk() + self = mainWindow + + mainWindow.wm_title("Door Shuffle %s" % ESVersion) + mainWindow.protocol("WM_DELETE_WINDOW", guiExit) # intercept when user clicks the X + + # set program icon set_icon(mainWindow) - notebook = ttk.Notebook(mainWindow) - randomizerWindow = ttk.Frame(notebook) - adjustWindow = ttk.Frame(notebook) - customWindow = ttk.Frame(notebook) - notebook.add(randomizerWindow, text='Randomize') - notebook.add(adjustWindow, text='Adjust') - notebook.add(customWindow, text='Custom') - notebook.pack() + # get args + # getting Settings & CLI (no GUI built yet) + self.args = get_args_priority(None, None, None) - # Shared Controls + # get saved settings + self.settings = self.args["settings"] - farBottomFrame = Frame(mainWindow) + # make array for pages + self.pages = {} - def open_output(): - if args and args.outputpath: - open_file(output_path(args.outputpath)) - else: - open_file(output_path('')) + # make array for frames + self.frames = {} - openOutputButton = Button(farBottomFrame, text='Open Output Directory', command=open_output) - - if os.path.exists(local_path('README.html')): - def open_readme(): - open_file(local_path('README.html')) - openReadmeButton = Button(farBottomFrame, text='Open Documentation', command=open_readme) - openReadmeButton.pack(side=LEFT) - - farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5) + self.notebook = ttk.Notebook(self) + self.pages["randomizer"] = ttk.Frame(self.notebook) + self.pages["adjust"] = ttk.Frame(self.notebook) + self.pages["startinventory"] = ttk.Frame(self.notebook) + self.pages["custom"] = ttk.Frame(self.notebook) + self.notebook.add(self.pages["randomizer"], text='Randomize') + self.notebook.add(self.pages["adjust"], text='Adjust') + self.notebook.add(self.pages["startinventory"], text='Starting Inventory') + self.notebook.add(self.pages["custom"], text='Custom Item Pool') + self.notebook.pack() # randomizer controls - topFrame = Frame(randomizerWindow) - rightHalfFrame = Frame(topFrame) - checkBoxFrame = Frame(rightHalfFrame) + # Randomize notebook page: + # make notebook pages: Item, Entrances, Enemizer, Dungeon Shuffle, Multiworld, Game Options, Generation Setup + # Item: Item Randomizer settings + # Entrances: Entrance Randomizer settings + # Enemizer: Enemy Randomizer settings + # Dungeon Shuffle: Dungeon Door Randomizer settings + # Multiworld: Multiworld settings + # Game Options: Cosmetic settings that don't affect logic/placement + # Generation Setup: Primarily one&done settings + self.pages["randomizer"].notebook = ttk.Notebook(self.pages["randomizer"]) - createSpoilerVar = IntVar() - createSpoilerCheckbutton = Checkbutton(checkBoxFrame, text="Create Spoiler Log", variable=createSpoilerVar) - suppressRomVar = IntVar() - suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar) - openpyramidVar = IntVar() - openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar) - mcsbshuffleFrame = Frame(checkBoxFrame) - mcsbLabel = Label(mcsbshuffleFrame, text="Shuffle: ") - mapshuffleVar = IntVar() - mapshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Maps", variable=mapshuffleVar) - compassshuffleVar = IntVar() - compassshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Compasses", variable=compassshuffleVar) - keyshuffleVar = IntVar() - keyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Keys", variable=keyshuffleVar) - bigkeyshuffleVar = IntVar() - bigkeyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="BigKeys", variable=bigkeyshuffleVar) - retroVar = IntVar() - retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar) - shuffleGanonVar = IntVar() - shuffleGanonVar.set(1) #set default - shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar) - hintsVar = IntVar() - hintsVar.set(1) #set default - hintsCheckbutton = Checkbutton(checkBoxFrame, text="Include Helpful Hints", variable=hintsVar) - experimentVar = IntVar() - experimentCheckbutton = Checkbutton(checkBoxFrame, text="Enable Experimental Features", variable=experimentVar) - customVar = IntVar() - customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar) - dungeonCounterVar = IntVar() - dungeonCounterbutton = Checkbutton(checkBoxFrame, text="Enable dungeon chest counters", variable=dungeonCounterVar) + # make array for pages + self.pages["randomizer"].pages = {} - createSpoilerCheckbutton.pack(expand=True, anchor=W) - suppressRomCheckbutton.pack(expand=True, anchor=W) - openpyramidCheckbutton.pack(expand=True, anchor=W) - mcsbshuffleFrame.pack(expand=True, anchor=W) - mcsbLabel.grid(row=0, column=0) - mapshuffleCheckbutton.grid(row=0, column=1) - compassshuffleCheckbutton.grid(row=0, column=2) - keyshuffleCheckbutton.grid(row=0, column=3) - bigkeyshuffleCheckbutton.grid(row=0, column=4) - retroCheckbutton.pack(expand=True, anchor=W) - shuffleGanonCheckbutton.pack(expand=True, anchor=W) - hintsCheckbutton.pack(expand=True, anchor=W) - experimentCheckbutton.pack(expand=True, anchor=W) - customCheckbutton.pack(expand=True, anchor=W) - dungeonCounterbutton.pack(expand=True, anchor=W) + # Item Randomizer + self.pages["randomizer"].pages["item"] = item_page(self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["item"], text="Items") - romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options") - romOptionsFrame.columnconfigure(0, weight=1) - romOptionsFrame.columnconfigure(1, weight=1) - for i in range(5): - romOptionsFrame.rowconfigure(i, weight=1) + # Entrance Randomizer + self.pages["randomizer"].pages["entrance"] = entrando_page(self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["entrance"], text="Entrances") - disableMusicVar = IntVar() - disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=disableMusicVar) - disableMusicCheckbutton.grid(row=0, column=0, sticky=E) + # Enemizer + self.pages["randomizer"].pages["enemizer"],self.settings = enemizer_page(self.pages["randomizer"].notebook,self.settings) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["enemizer"], text="Enemizer") - spriteDialogFrame = Frame(romOptionsFrame) - spriteDialogFrame.grid(row=0, column=1) - baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') + # Dungeon Shuffle + self.pages["randomizer"].pages["dungeon"] = dungeon_page(self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeon Shuffle") - spriteNameVar = StringVar() - sprite = None - def set_sprite(sprite_param): - nonlocal sprite - if sprite_param is None or not sprite_param.valid: - sprite = None - spriteNameVar.set('(unchanged)') - else: - sprite = sprite_param - spriteNameVar.set(sprite.name) + # Multiworld + self.pages["randomizer"].pages["multiworld"],self.settings = multiworld_page(self.pages["randomizer"].notebook,self.settings) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["multiworld"], text="Multiworld") - set_sprite(None) - spriteNameVar.set('(unchanged)') - spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar) + # Game Options + self.pages["randomizer"].pages["gameoptions"] = gameoptions_page(self, self.pages["randomizer"].notebook) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["gameoptions"], text="Game Options") - def SpriteSelect(): - SpriteSelector(mainWindow, set_sprite) + # Generation Setup + self.pages["randomizer"].pages["generation"],self.settings = generation_page(self.pages["randomizer"].notebook,self.settings) + self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["generation"], text="Generation Setup") - spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect) + # add randomizer notebook to main window + self.pages["randomizer"].notebook.pack() - baseSpriteLabel.pack(side=LEFT) - spriteEntry.pack(side=LEFT) - spriteSelectButton.pack(side=LEFT) + # bottom of window: Open Output Directory, Open Documentation (if exists) + self.frames["bottom"] = bottom_frame(self, self, None) + ## Save Settings Button + savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=lambda: save_settings_from_gui(True)) + savesettingsButton.pack(side=RIGHT) - quickSwapVar = IntVar() - quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=quickSwapVar) - quickSwapCheckbutton.grid(row=1, column=0, sticky=E) + # set bottom frame to main window + self.frames["bottom"].pack(side=BOTTOM, fill=X, padx=5, pady=5) - fastMenuFrame = Frame(romOptionsFrame) - fastMenuFrame.grid(row=1, column=1, sticky=E) - fastMenuLabel = Label(fastMenuFrame, text='Menu speed') - fastMenuLabel.pack(side=LEFT) - fastMenuVar = StringVar() - fastMenuVar.set('normal') - fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half') - fastMenuOptionMenu.pack(side=LEFT) - - heartcolorFrame = Frame(romOptionsFrame) - heartcolorFrame.grid(row=2, column=0, sticky=E) - heartcolorLabel = Label(heartcolorFrame, text='Heart color') - heartcolorLabel.pack(side=LEFT) - heartcolorVar = StringVar() - heartcolorVar.set('red') - heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random') - heartcolorOptionMenu.pack(side=LEFT) - - heartbeepFrame = Frame(romOptionsFrame) - heartbeepFrame.grid(row=2, column=1, sticky=E) - heartbeepLabel = Label(heartbeepFrame, text='Heartbeep') - heartbeepLabel.pack(side=LEFT) - heartbeepVar = StringVar() - heartbeepVar.set('normal') - heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off') - heartbeepOptionMenu.pack(side=LEFT) - - owPalettesFrame = Frame(romOptionsFrame) - owPalettesFrame.grid(row=3, column=0, sticky=E) - owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes') - owPalettesLabel.pack(side=LEFT) - owPalettesVar = StringVar() - owPalettesVar.set('default') - owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout') - owPalettesOptionMenu.pack(side=LEFT) - - uwPalettesFrame = Frame(romOptionsFrame) - uwPalettesFrame.grid(row=3, column=1, sticky=E) - uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes') - uwPalettesLabel.pack(side=LEFT) - uwPalettesVar = StringVar() - uwPalettesVar.set('default') - uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, uwPalettesVar, 'default', 'random', 'blackout') - uwPalettesOptionMenu.pack(side=LEFT) - - romDialogFrame = Frame(romOptionsFrame) - romDialogFrame.grid(row=4, column=0, columnspan=2, sticky=W+E) - - baseRomLabel = Label(romDialogFrame, text='Base Rom: ') - romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc") - romEntry = Entry(romDialogFrame, textvariable=romVar) - - def RomSelect(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")]) - romVar.set(rom) - romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect) - - baseRomLabel.pack(side=LEFT) - romEntry.pack(side=LEFT, expand=True, fill=X) - romSelectButton.pack(side=LEFT) - - checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10) - romOptionsFrame.pack(expand=True, fill=BOTH, padx=3) - - drowDownFrame = Frame(topFrame) - - modeFrame = Frame(drowDownFrame) - modeVar = StringVar() - modeVar.set('open') - modeOptionMenu = OptionMenu(modeFrame, modeVar, 'standard', 'open', 'inverted') - modeOptionMenu.pack(side=RIGHT) - modeLabel = Label(modeFrame, text='Game mode') - modeLabel.pack(side=LEFT) - - logicFrame = Frame(drowDownFrame) - logicVar = StringVar() - logicVar.set('noglitches') - logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic') - logicOptionMenu.pack(side=RIGHT) - logicLabel = Label(logicFrame, text='Game logic') - logicLabel.pack(side=LEFT) - - goalFrame = Frame(drowDownFrame) - goalVar = StringVar() - goalVar.set('ganon') - goalOptionMenu = OptionMenu(goalFrame, goalVar, 'ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals') - goalOptionMenu.pack(side=RIGHT) - goalLabel = Label(goalFrame, text='Game goal') - goalLabel.pack(side=LEFT) - - crystalsGTFrame = Frame(drowDownFrame) - crystalsGTVar = StringVar() - crystalsGTVar.set('7') - crystalsGTOptionMenu = OptionMenu(crystalsGTFrame, crystalsGTVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random') - crystalsGTOptionMenu.pack(side=RIGHT) - crystalsGTLabel = Label(crystalsGTFrame, text='Crystals to open Ganon\'s Tower') - crystalsGTLabel.pack(side=LEFT) - - crystalsGanonFrame = Frame(drowDownFrame) - crystalsGanonVar = StringVar() - crystalsGanonVar.set('7') - crystalsGanonOptionMenu = OptionMenu(crystalsGanonFrame, crystalsGanonVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random') - crystalsGanonOptionMenu.pack(side=RIGHT) - crystalsGanonLabel = Label(crystalsGanonFrame, text='Crystals to fight Ganon') - crystalsGanonLabel.pack(side=LEFT) - - swordFrame = Frame(drowDownFrame) - swordVar = StringVar() - swordVar.set('random') - swordOptionMenu = OptionMenu(swordFrame, swordVar, 'random', 'assured', 'swordless', 'vanilla') - swordOptionMenu.pack(side=RIGHT) - swordLabel = Label(swordFrame, text='Sword availability') - swordLabel.pack(side=LEFT) - - difficultyFrame = Frame(drowDownFrame) - difficultyVar = StringVar() - difficultyVar.set('normal') - difficultyOptionMenu = OptionMenu(difficultyFrame, difficultyVar, 'normal', 'hard', 'expert') - difficultyOptionMenu.pack(side=RIGHT) - difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool') - difficultyLabel.pack(side=LEFT) - - itemfunctionFrame = Frame(drowDownFrame) - itemfunctionVar = StringVar() - itemfunctionVar.set('normal') - itemfunctionOptionMenu = OptionMenu(itemfunctionFrame, itemfunctionVar, 'normal', 'hard', 'expert') - itemfunctionOptionMenu.pack(side=RIGHT) - itemfunctionLabel = Label(itemfunctionFrame, text='Difficulty: item functionality') - itemfunctionLabel.pack(side=LEFT) - - timerFrame = Frame(drowDownFrame) - timerVar = StringVar() - timerVar.set('none') - timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown') - timerOptionMenu.pack(side=RIGHT) - timerLabel = Label(timerFrame, text='Timer setting') - timerLabel.pack(side=LEFT) - - progressiveFrame = Frame(drowDownFrame) - progressiveVar = StringVar() - progressiveVar.set('on') - progressiveOptionMenu = OptionMenu(progressiveFrame, progressiveVar, 'on', 'off', 'random') - progressiveOptionMenu.pack(side=RIGHT) - progressiveLabel = Label(progressiveFrame, text='Progressive equipment') - progressiveLabel.pack(side=LEFT) - - accessibilityFrame = Frame(drowDownFrame) - accessibilityVar = StringVar() - accessibilityVar.set('items') - accessibilityOptionMenu = OptionMenu(accessibilityFrame, accessibilityVar, 'items', 'locations', 'none') - accessibilityOptionMenu.pack(side=RIGHT) - accessibilityLabel = Label(accessibilityFrame, text='Item accessibility') - accessibilityLabel.pack(side=LEFT) - - algorithmFrame = Frame(drowDownFrame) - algorithmVar = StringVar() - algorithmVar.set('balanced') - algorithmOptionMenu = OptionMenu(algorithmFrame, algorithmVar, 'freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced') - algorithmOptionMenu.pack(side=RIGHT) - algorithmLabel = Label(algorithmFrame, text='Item distribution algorithm') - algorithmLabel.pack(side=LEFT) - - shuffleFrame = Frame(drowDownFrame) - shuffleVar = StringVar() - shuffleVar.set('vanilla') - shuffleOptionMenu = OptionMenu(shuffleFrame, shuffleVar, 'vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple') - shuffleOptionMenu.pack(side=RIGHT) - shuffleLabel = Label(shuffleFrame, text='Entrance shuffle algorithm') - shuffleLabel.pack(side=LEFT) - - doorShuffleFrame = Frame(drowDownFrame) - doorShuffleVar = StringVar() - doorShuffleVar.set('basic') - doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crossed') - doorShuffleOptionMenu.pack(side=RIGHT) - doorShuffleLabel = Label(doorShuffleFrame, text='Door shuffle algorithm') - doorShuffleLabel.pack(side=LEFT) - - logicFrame.pack(expand=True, anchor=E) - accessibilityFrame.pack(expand=True, anchor=E) - - goalFrame.pack(expand=True, anchor=E) - crystalsGTFrame.pack(expand=True, anchor=E) - crystalsGanonFrame.pack(expand=True, anchor=E) - - modeFrame.pack(expand=True, anchor=E) - shuffleFrame.pack(expand=True, anchor=E) - doorShuffleFrame.pack(expand=True, anchor=E) - - difficultyFrame.pack(expand=True, anchor=E) - itemfunctionFrame.pack(expand=True, anchor=E) - timerFrame.pack(expand=True, anchor=E) - progressiveFrame.pack(expand=True, anchor=E) - accessibilityFrame.pack(expand=True, anchor=E) - algorithmFrame.pack(expand=True, anchor=E) - - enemizerFrame = LabelFrame(randomizerWindow, text="Enemizer", padx=5, pady=2) - enemizerFrame.columnconfigure(0, weight=1) - enemizerFrame.columnconfigure(1, weight=1) - enemizerFrame.columnconfigure(2, weight=1) - enemizerFrame.columnconfigure(3, weight=1) - - enemizerPathFrame = Frame(enemizerFrame) - enemizerPathFrame.grid(row=0, column=0, columnspan=3, sticky=W+E, padx=3) - enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ") - enemizerCLIlabel.pack(side=LEFT) - enemizerCLIpathVar = StringVar(value="EnemizerCLI/EnemizerCLI.Core") - enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=enemizerCLIpathVar) - enemizerCLIpathEntry.pack(side=LEFT, expand=True, fill=X) - def EnemizerSelectPath(): - path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")]) - if path: - enemizerCLIpathVar.set(path) - enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath) - enemizerCLIbrowseButton.pack(side=LEFT) - - potShuffleVar = IntVar() - potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar) - potShuffleButton.grid(row=0, column=3) - - enemizerEnemyFrame = Frame(enemizerFrame) - enemizerEnemyFrame.grid(row=1, column=0, pady=5) - enemizerEnemyLabel = Label(enemizerEnemyFrame, text='Enemy shuffle') - enemizerEnemyLabel.pack(side=LEFT) - enemyShuffleVar = StringVar() - enemyShuffleVar.set('none') - enemizerEnemyOption = OptionMenu(enemizerEnemyFrame, enemyShuffleVar, 'none', 'shuffled', 'chaos') - enemizerEnemyOption.pack(side=LEFT) - - enemizerBossFrame = Frame(enemizerFrame) - enemizerBossFrame.grid(row=1, column=1) - enemizerBossLabel = Label(enemizerBossFrame, text='Boss shuffle') - enemizerBossLabel.pack(side=LEFT) - enemizerBossVar = StringVar() - enemizerBossVar.set('none') - enemizerBossOption = OptionMenu(enemizerBossFrame, enemizerBossVar, 'none', 'basic', 'normal', 'chaos') - enemizerBossOption.pack(side=LEFT) - - enemizerDamageFrame = Frame(enemizerFrame) - enemizerDamageFrame.grid(row=1, column=2) - enemizerDamageLabel = Label(enemizerDamageFrame, text='Enemy damage') - enemizerDamageLabel.pack(side=LEFT) - enemizerDamageVar = StringVar() - enemizerDamageVar.set('default') - enemizerDamageOption = OptionMenu(enemizerDamageFrame, enemizerDamageVar, 'default', 'shuffled', 'chaos') - enemizerDamageOption.pack(side=LEFT) - - enemizerHealthFrame = Frame(enemizerFrame) - enemizerHealthFrame.grid(row=1, column=3) - enemizerHealthLabel = Label(enemizerHealthFrame, text='Enemy health') - enemizerHealthLabel.pack(side=LEFT) - enemizerHealthVar = StringVar() - enemizerHealthVar.set('default') - enemizerHealthOption = OptionMenu(enemizerHealthFrame, enemizerHealthVar, 'default', 'easy', 'normal', 'hard', 'expert') - enemizerHealthOption.pack(side=LEFT) - - bottomFrame = Frame(randomizerWindow, pady=5) - - worldLabel = Label(bottomFrame, text='Worlds') - worldVar = StringVar() - worldSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=worldVar) - namesLabel = Label(bottomFrame, text='Player names') - namesVar = StringVar() - namesEntry = Entry(bottomFrame, textvariable=namesVar) - seedLabel = Label(bottomFrame, text='Seed #') - seedVar = StringVar() - seedEntry = Entry(bottomFrame, width=15, textvariable=seedVar) - countLabel = Label(bottomFrame, text='Count') - countVar = StringVar() - countSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=countVar) - - def generateRom(): - guiargs = Namespace() - guiargs.multi = int(worldVar.get()) - guiargs.names = namesVar.get() - guiargs.seed = int(seedVar.get()) if seedVar.get() else None - guiargs.count = int(countVar.get()) if countVar.get() != '1' else None - guiargs.mode = modeVar.get() - guiargs.logic = logicVar.get() - - guiargs.goal = goalVar.get() - guiargs.crystals_gt = crystalsGTVar.get() - guiargs.crystals_ganon = crystalsGanonVar.get() - guiargs.swords = swordVar.get() - guiargs.difficulty = difficultyVar.get() - guiargs.item_functionality = itemfunctionVar.get() - guiargs.timer = timerVar.get() - guiargs.progressive = progressiveVar.get() - guiargs.accessibility = accessibilityVar.get() - guiargs.algorithm = algorithmVar.get() - guiargs.shuffle = shuffleVar.get() - guiargs.door_shuffle = doorShuffleVar.get() - guiargs.heartbeep = heartbeepVar.get() - guiargs.heartcolor = heartcolorVar.get() - guiargs.fastmenu = fastMenuVar.get() - guiargs.create_spoiler = bool(createSpoilerVar.get()) - guiargs.skip_playthrough = not bool(createSpoilerVar.get()) - guiargs.suppress_rom = bool(suppressRomVar.get()) - guiargs.openpyramid = bool(openpyramidVar.get()) - guiargs.mapshuffle = bool(mapshuffleVar.get()) - guiargs.compassshuffle = bool(compassshuffleVar.get()) - guiargs.keyshuffle = bool(keyshuffleVar.get()) - guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get()) - guiargs.retro = bool(retroVar.get()) - guiargs.quickswap = bool(quickSwapVar.get()) - guiargs.disablemusic = bool(disableMusicVar.get()) - guiargs.ow_palettes = owPalettesVar.get() - guiargs.uw_palettes = uwPalettesVar.get() - guiargs.shuffleganon = bool(shuffleGanonVar.get()) - guiargs.hints = bool(hintsVar.get()) - guiargs.experimental = bool(experimentVar.get()) - guiargs.enemizercli = enemizerCLIpathVar.get() - guiargs.shufflebosses = enemizerBossVar.get() - guiargs.shuffleenemies = enemyShuffleVar.get() - guiargs.enemy_health = enemizerHealthVar.get() - guiargs.enemy_damage = enemizerDamageVar.get() - guiargs.shufflepots = bool(potShuffleVar.get()) - guiargs.custom = bool(customVar.get()) - guiargs.customitemarray = [int(bowVar.get()), int(silverarrowVar.get()), int(boomerangVar.get()), int(magicboomerangVar.get()), int(hookshotVar.get()), int(mushroomVar.get()), int(magicpowderVar.get()), int(firerodVar.get()), - int(icerodVar.get()), int(bombosVar.get()), int(etherVar.get()), int(quakeVar.get()), int(lampVar.get()), int(hammerVar.get()), int(shovelVar.get()), int(fluteVar.get()), int(bugnetVar.get()), - int(bookVar.get()), int(bottleVar.get()), int(somariaVar.get()), int(byrnaVar.get()), int(capeVar.get()), int(mirrorVar.get()), int(bootsVar.get()), int(powergloveVar.get()), int(titansmittVar.get()), - int(proggloveVar.get()), int(flippersVar.get()), int(pearlVar.get()), int(heartpieceVar.get()), int(fullheartVar.get()), int(sancheartVar.get()), int(sword1Var.get()), int(sword2Var.get()), - int(sword3Var.get()), int(sword4Var.get()), int(progswordVar.get()), int(shield1Var.get()), int(shield2Var.get()), int(shield3Var.get()), int(progshieldVar.get()), int(bluemailVar.get()), - int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()), - int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()), - int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(progbowVar.get()), int(bomb10Var.get()), int(triforcepieceVar.get()), - int(triforcecountVar.get()), int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())] - guiargs.dungeon_counters = bool(dungeonCounterVar.get()) - guiargs.rom = romVar.get() - guiargs.sprite = sprite - guiargs.outputpath = args.outputpath if args else None - # get default values for missing parameters - for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items(): - if k not in vars(guiargs): - setattr(guiargs, k, v) - elif type(v) is dict: # use same settings for every player - setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) - try: - if guiargs.count is not None: - seed = guiargs.seed - for _ in range(guiargs.count): - main(seed=seed, args=guiargs) - seed = random.randint(0, 999999999) - else: - main(seed=guiargs.seed, args=guiargs) - except Exception as e: - logging.exception(e) - messagebox.showerror(title="Error while creating seed", message=str(e)) - else: - messagebox.showinfo(title="Success", message="Rom patched successfully") - - generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom) - - worldLabel.pack(side=LEFT) - worldSpinbox.pack(side=LEFT) - namesLabel.pack(side=LEFT) - namesEntry.pack(side=LEFT) - seedLabel.pack(side=LEFT, padx=(5, 0)) - seedEntry.pack(side=LEFT) - countLabel.pack(side=LEFT, padx=(5, 0)) - countSpinbox.pack(side=LEFT) - generateButton.pack(side=LEFT, padx=(5, 0)) - - openOutputButton.pack(side=RIGHT) - - drowDownFrame.pack(side=LEFT) - rightHalfFrame.pack(side=RIGHT) - topFrame.pack(side=TOP) - bottomFrame.pack(side=BOTTOM) - enemizerFrame.pack(side=BOTTOM, fill=BOTH) + self.outputPath = StringVar() + self.randomSprite = BooleanVar() # Adjuster Controls + self.pages["adjust"].content,self.settings = adjust_page(self, self.pages["adjust"], self.settings) + self.pages["adjust"].content.pack(side=TOP, fill=BOTH, expand=True) - topFrame2 = Frame(adjustWindow) - rightHalfFrame2 = Frame(topFrame2) - checkBoxFrame2 = Frame(rightHalfFrame2) - - quickSwapCheckbutton2 = Checkbutton(checkBoxFrame2, text="Enabled L/R Item quickswapping", variable=quickSwapVar) - disableMusicCheckbutton2 = Checkbutton(checkBoxFrame2, text="Disable game music", variable=disableMusicVar) - - quickSwapCheckbutton2.pack(expand=True, anchor=W) - disableMusicCheckbutton2.pack(expand=True, anchor=W) - - fileDialogFrame2 = Frame(rightHalfFrame2) - - romDialogFrame2 = Frame(fileDialogFrame2) - baseRomLabel2 = Label(romDialogFrame2, text='Rom to adjust') - romVar2 = StringVar() - romEntry2 = Entry(romDialogFrame2, textvariable=romVar2) - - def RomSelect2(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")]) - romVar2.set(rom) - romSelectButton2 = Button(romDialogFrame2, text='Select Rom', command=RomSelect2) - - baseRomLabel2.pack(side=LEFT) - romEntry2.pack(side=LEFT) - romSelectButton2.pack(side=LEFT) - - spriteDialogFrame2 = Frame(fileDialogFrame2) - baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite') - spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar) - - def SpriteSelectAdjuster(): - SpriteSelector(mainWindow, set_sprite, adjuster=True) - - spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelectAdjuster) - - baseSpriteLabel2.pack(side=LEFT) - spriteEntry2.pack(side=LEFT) - spriteSelectButton2.pack(side=LEFT) - - romDialogFrame2.pack() - spriteDialogFrame2.pack() - - checkBoxFrame2.pack() - fileDialogFrame2.pack() - - drowDownFrame2 = Frame(topFrame2) - heartbeepFrame2 = Frame(drowDownFrame2) - heartbeepOptionMenu2 = OptionMenu(heartbeepFrame2, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off') - heartbeepOptionMenu2.pack(side=RIGHT) - heartbeepLabel2 = Label(heartbeepFrame2, text='Heartbeep sound rate') - heartbeepLabel2.pack(side=LEFT) - - heartcolorFrame2 = Frame(drowDownFrame2) - heartcolorOptionMenu2 = OptionMenu(heartcolorFrame2, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random') - heartcolorOptionMenu2.pack(side=RIGHT) - heartcolorLabel2 = Label(heartcolorFrame2, text='Heart color') - heartcolorLabel2.pack(side=LEFT) - - fastMenuFrame2 = Frame(drowDownFrame2) - fastMenuOptionMenu2 = OptionMenu(fastMenuFrame2, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half') - fastMenuOptionMenu2.pack(side=RIGHT) - fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed') - fastMenuLabel2.pack(side=LEFT) - - owPalettesFrame2 = Frame(drowDownFrame2) - owPalettesOptionMenu2 = OptionMenu(owPalettesFrame2, owPalettesVar, 'default', 'random', 'blackout') - owPalettesOptionMenu2.pack(side=RIGHT) - owPalettesLabel2 = Label(owPalettesFrame2, text='Overworld palettes') - owPalettesLabel2.pack(side=LEFT) - - uwPalettesFrame2 = Frame(drowDownFrame2) - uwPalettesOptionMenu2 = OptionMenu(uwPalettesFrame2, uwPalettesVar, 'default', 'random', 'blackout') - uwPalettesOptionMenu2.pack(side=RIGHT) - uwPalettesLabel2 = Label(uwPalettesFrame2, text='Dungeon palettes') - uwPalettesLabel2.pack(side=LEFT) - - heartbeepFrame2.pack(expand=True, anchor=E) - heartcolorFrame2.pack(expand=True, anchor=E) - fastMenuFrame2.pack(expand=True, anchor=E) - owPalettesFrame2.pack(expand=True, anchor=E) - uwPalettesFrame2.pack(expand=True, anchor=E) - - bottomFrame2 = Frame(topFrame2) - - def adjustRom(): - guiargs = Namespace() - guiargs.heartbeep = heartbeepVar.get() - guiargs.heartcolor = heartcolorVar.get() - guiargs.fastmenu = fastMenuVar.get() - guiargs.ow_palettes = owPalettesVar.get() - guiargs.uw_palettes = uwPalettesVar.get() - guiargs.quickswap = bool(quickSwapVar.get()) - guiargs.disablemusic = bool(disableMusicVar.get()) - guiargs.rom = romVar2.get() - guiargs.baserom = romVar.get() - guiargs.sprite = sprite - try: - adjust(args=guiargs) - except Exception as e: - logging.exception(e) - messagebox.showerror(title="Error while creating seed", message=str(e)) - else: - messagebox.showinfo(title="Success", message="Rom patched successfully") - - adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom) - - adjustButton.pack(side=LEFT, padx=(5, 0)) - - drowDownFrame2.pack(side=LEFT, pady=(0, 40)) - rightHalfFrame2.pack(side=RIGHT) - topFrame2.pack(side=TOP, pady=70) - bottomFrame2.pack(side=BOTTOM, pady=(180, 0)) + # Starting Inventory Controls + self.pages["startinventory"].content = startinventory_page(self, self.pages["startinventory"]) + self.pages["startinventory"].content.pack(side=TOP, fill=BOTH, expand=True) # Custom Controls - - topFrame3 = Frame(customWindow) + self.pages["custom"].content = custom_page(self,self.pages["custom"]) + self.pages["custom"].content.pack(side=TOP, fill=BOTH, expand=True) def validation(P): if str.isdigit(P) or P == "": return True else: return False - vcmd=(topFrame3.register(validation), '%P') + vcmd=(self.pages["custom"].content.register(validation), '%P') - itemList1 = Frame(topFrame3) - itemList2 = Frame(topFrame3) - itemList3 = Frame(topFrame3) - itemList4 = Frame(topFrame3) - itemList5 = Frame(topFrame3) + # load args + loadcliargs(self, self.args["load"]) - bowFrame = Frame(itemList1) - bowLabel = Label(bowFrame, text='Bow') - bowVar = StringVar(value='0') - bowEntry = Entry(bowFrame, textvariable=bowVar, width=3, validate='all', vcmd=vcmd) - bowFrame.pack() - bowLabel.pack(anchor=W, side=LEFT, padx=(0,53)) - bowEntry.pack(anchor=E) - - progbowFrame = Frame(itemList1) - progbowLabel = Label(progbowFrame, text='Prog.Bow') - progbowVar = StringVar(value='2') - progbowEntry = Entry(progbowFrame, textvariable=progbowVar, width=3, validate='all', vcmd=vcmd) - progbowFrame.pack() - progbowLabel.pack(anchor=W, side=LEFT, padx=(0,25)) - progbowEntry.pack(anchor=E) - - boomerangFrame = Frame(itemList1) - boomerangLabel = Label(boomerangFrame, text='Boomerang') - boomerangVar = StringVar(value='1') - boomerangEntry = Entry(boomerangFrame, textvariable=boomerangVar, width=3, validate='all', vcmd=vcmd) - boomerangFrame.pack() - boomerangLabel.pack(anchor=W, side=LEFT, padx=(0,14)) - boomerangEntry.pack(anchor=E) - - magicboomerangFrame = Frame(itemList1) - magicboomerangLabel = Label(magicboomerangFrame, text='M.Boomerang') - magicboomerangVar = StringVar(value='1') - magicboomerangEntry = Entry(magicboomerangFrame, textvariable=magicboomerangVar, width=3, validate='all', vcmd=vcmd) - magicboomerangFrame.pack() - magicboomerangLabel.pack(anchor=W, side=LEFT) - magicboomerangEntry.pack(anchor=E) - - hookshotFrame = Frame(itemList1) - hookshotLabel = Label(hookshotFrame, text='Hookshot') - hookshotVar = StringVar(value='1') - hookshotEntry = Entry(hookshotFrame, textvariable=hookshotVar, width=3, validate='all', vcmd=vcmd) - hookshotFrame.pack() - hookshotLabel.pack(anchor=W, side=LEFT, padx=(0,24)) - hookshotEntry.pack(anchor=E) - - mushroomFrame = Frame(itemList1) - mushroomLabel = Label(mushroomFrame, text='Mushroom') - mushroomVar = StringVar(value='1') - mushroomEntry = Entry(mushroomFrame, textvariable=mushroomVar, width=3, validate='all', vcmd=vcmd) - mushroomFrame.pack() - mushroomLabel.pack(anchor=W, side=LEFT, padx=(0,17)) - mushroomEntry.pack(anchor=E) - - magicpowderFrame = Frame(itemList1) - magicpowderLabel = Label(magicpowderFrame, text='Magic Powder') - magicpowderVar = StringVar(value='1') - magicpowderEntry = Entry(magicpowderFrame, textvariable=magicpowderVar, width=3, validate='all', vcmd=vcmd) - magicpowderFrame.pack() - magicpowderLabel.pack(anchor=W, side=LEFT) - magicpowderEntry.pack(anchor=E) - - firerodFrame = Frame(itemList1) - firerodLabel = Label(firerodFrame, text='Fire Rod') - firerodVar = StringVar(value='1') - firerodEntry = Entry(firerodFrame, textvariable=firerodVar, width=3, validate='all', vcmd=vcmd) - firerodFrame.pack() - firerodLabel.pack(anchor=W, side=LEFT, padx=(0,33)) - firerodEntry.pack(anchor=E) - - icerodFrame = Frame(itemList1) - icerodLabel = Label(icerodFrame, text='Ice Rod') - icerodVar = StringVar(value='1') - icerodEntry = Entry(icerodFrame, textvariable=icerodVar, width=3, validate='all', vcmd=vcmd) - icerodFrame.pack() - icerodLabel.pack(anchor=W, side=LEFT, padx=(0,37)) - icerodEntry.pack(anchor=E) - - bombosFrame = Frame(itemList1) - bombosLabel = Label(bombosFrame, text='Bombos') - bombosVar = StringVar(value='1') - bombosEntry = Entry(bombosFrame, textvariable=bombosVar, width=3, validate='all', vcmd=vcmd) - bombosFrame.pack() - bombosLabel.pack(anchor=W, side=LEFT, padx=(0,32)) - bombosEntry.pack(anchor=E) - - etherFrame = Frame(itemList1) - etherLabel = Label(etherFrame, text='Ether') - etherVar = StringVar(value='1') - etherEntry = Entry(etherFrame, textvariable=etherVar, width=3, validate='all', vcmd=vcmd) - etherFrame.pack() - etherLabel.pack(anchor=W, side=LEFT, padx=(0,49)) - etherEntry.pack(anchor=E) - - quakeFrame = Frame(itemList1) - quakeLabel = Label(quakeFrame, text='Quake') - quakeVar = StringVar(value='1') - quakeEntry = Entry(quakeFrame, textvariable=quakeVar, width=3, validate='all', vcmd=vcmd) - quakeFrame.pack() - quakeLabel.pack(anchor=W, side=LEFT, padx=(0,42)) - quakeEntry.pack(anchor=E) - - lampFrame = Frame(itemList1) - lampLabel = Label(lampFrame, text='Lamp') - lampVar = StringVar(value='1') - lampEntry = Entry(lampFrame, textvariable=lampVar, width=3, validate='all', vcmd=vcmd) - lampFrame.pack() - lampLabel.pack(anchor=W, side=LEFT, padx=(0,46)) - lampEntry.pack(anchor=E) - - hammerFrame = Frame(itemList1) - hammerLabel = Label(hammerFrame, text='Hammer') - hammerVar = StringVar(value='1') - hammerEntry = Entry(hammerFrame, textvariable=hammerVar, width=3, validate='all', vcmd=vcmd) - hammerFrame.pack() - hammerLabel.pack(anchor=W, side=LEFT, padx=(0,29)) - hammerEntry.pack(anchor=E) - - shovelFrame = Frame(itemList1) - shovelLabel = Label(shovelFrame, text='Shovel') - shovelVar = StringVar(value='1') - shovelEntry = Entry(shovelFrame, textvariable=shovelVar, width=3, validate='all', vcmd=vcmd) - shovelFrame.pack() - shovelLabel.pack(anchor=W, side=LEFT, padx=(0,41)) - shovelEntry.pack(anchor=E) - - fluteFrame = Frame(itemList1) - fluteLabel = Label(fluteFrame, text='Flute') - fluteVar = StringVar(value='1') - fluteEntry = Entry(fluteFrame, textvariable=fluteVar, width=3, validate='all', vcmd=vcmd) - fluteFrame.pack() - fluteLabel.pack(anchor=W, side=LEFT, padx=(0,50)) - fluteEntry.pack(anchor=E) - - bugnetFrame = Frame(itemList2) - bugnetLabel = Label(bugnetFrame, text='Bug Net') - bugnetVar = StringVar(value='1') - bugnetEntry = Entry(bugnetFrame, textvariable=bugnetVar, width=3, validate='all', vcmd=vcmd) - bugnetFrame.pack() - bugnetLabel.pack(anchor=W, side=LEFT, padx=(0,41)) - bugnetEntry.pack(anchor=E) - - bookFrame = Frame(itemList2) - bookLabel = Label(bookFrame, text='Book') - bookVar = StringVar(value='1') - bookEntry = Entry(bookFrame, textvariable=bookVar, width=3, validate='all', vcmd=vcmd) - bookFrame.pack() - bookLabel.pack(anchor=W, side=LEFT, padx=(0,57)) - bookEntry.pack(anchor=E) - - bottleFrame = Frame(itemList2) - bottleLabel = Label(bottleFrame, text='Bottle') - bottleVar = StringVar(value='4') - bottleEntry = Entry(bottleFrame, textvariable=bottleVar, width=3, validate='all', vcmd=vcmd) - bottleFrame.pack() - bottleLabel.pack(anchor=W, side=LEFT, padx=(0,53)) - bottleEntry.pack(anchor=E) - - somariaFrame = Frame(itemList2) - somariaLabel = Label(somariaFrame, text='C.Somaria') - somariaVar = StringVar(value='1') - somariaEntry = Entry(somariaFrame, textvariable=somariaVar, width=3, validate='all', vcmd=vcmd) - somariaFrame.pack() - somariaLabel.pack(anchor=W, side=LEFT, padx=(0,30)) - somariaEntry.pack(anchor=E) - - byrnaFrame = Frame(itemList2) - byrnaLabel = Label(byrnaFrame, text='C.Byrna') - byrnaVar = StringVar(value='1') - byrnaEntry = Entry(byrnaFrame, textvariable=byrnaVar, width=3, validate='all', vcmd=vcmd) - byrnaFrame.pack() - byrnaLabel.pack(anchor=W, side=LEFT, padx=(0,43)) - byrnaEntry.pack(anchor=E) - - capeFrame = Frame(itemList2) - capeLabel = Label(capeFrame, text='Magic Cape') - capeVar = StringVar(value='1') - capeEntry = Entry(capeFrame, textvariable=capeVar, width=3, validate='all', vcmd=vcmd) - capeFrame.pack() - capeLabel.pack(anchor=W, side=LEFT, padx=(0,21)) - capeEntry.pack(anchor=E) - - mirrorFrame = Frame(itemList2) - mirrorLabel = Label(mirrorFrame, text='Magic Mirror') - mirrorVar = StringVar(value='1') - mirrorEntry = Entry(mirrorFrame, textvariable=mirrorVar, width=3, validate='all', vcmd=vcmd) - mirrorFrame.pack() - mirrorLabel.pack(anchor=W, side=LEFT, padx=(0,15)) - mirrorEntry.pack(anchor=E) - - bootsFrame = Frame(itemList2) - bootsLabel = Label(bootsFrame, text='Pegasus Boots') - bootsVar = StringVar(value='1') - bootsEntry = Entry(bootsFrame, textvariable=bootsVar, width=3, validate='all', vcmd=vcmd) - bootsFrame.pack() - bootsLabel.pack(anchor=W, side=LEFT, padx=(0,8)) - bootsEntry.pack(anchor=E) - - powergloveFrame = Frame(itemList2) - powergloveLabel = Label(powergloveFrame, text='Power Glove') - powergloveVar = StringVar(value='0') - powergloveEntry = Entry(powergloveFrame, textvariable=powergloveVar, width=3, validate='all', vcmd=vcmd) - powergloveFrame.pack() - powergloveLabel.pack(anchor=W, side=LEFT, padx=(0,18)) - powergloveEntry.pack(anchor=E) - - titansmittFrame = Frame(itemList2) - titansmittLabel = Label(titansmittFrame, text='Titan\'s Mitt') - titansmittVar = StringVar(value='0') - titansmittEntry = Entry(titansmittFrame, textvariable=titansmittVar, width=3, validate='all', vcmd=vcmd) - titansmittFrame.pack() - titansmittLabel.pack(anchor=W, side=LEFT, padx=(0,24)) - titansmittEntry.pack(anchor=E) - - proggloveFrame = Frame(itemList2) - proggloveLabel = Label(proggloveFrame, text='Prog.Glove') - proggloveVar = StringVar(value='2') - proggloveEntry = Entry(proggloveFrame, textvariable=proggloveVar, width=3, validate='all', vcmd=vcmd) - proggloveFrame.pack() - proggloveLabel.pack(anchor=W, side=LEFT, padx=(0,26)) - proggloveEntry.pack(anchor=E) - - flippersFrame = Frame(itemList2) - flippersLabel = Label(flippersFrame, text='Flippers') - flippersVar = StringVar(value='1') - flippersEntry = Entry(flippersFrame, textvariable=flippersVar, width=3, validate='all', vcmd=vcmd) - flippersFrame.pack() - flippersLabel.pack(anchor=W, side=LEFT, padx=(0,43)) - flippersEntry.pack(anchor=E) - - pearlFrame = Frame(itemList2) - pearlLabel = Label(pearlFrame, text='Moon Pearl') - pearlVar = StringVar(value='1') - pearlEntry = Entry(pearlFrame, textvariable=pearlVar, width=3, validate='all', vcmd=vcmd) - pearlFrame.pack() - pearlLabel.pack(anchor=W, side=LEFT, padx=(0,23)) - pearlEntry.pack(anchor=E) - - heartpieceFrame = Frame(itemList2) - heartpieceLabel = Label(heartpieceFrame, text='Piece of Heart') - heartpieceVar = StringVar(value='24') - heartpieceEntry = Entry(heartpieceFrame, textvariable=heartpieceVar, width=3, validate='all', vcmd=vcmd) - heartpieceFrame.pack() - heartpieceLabel.pack(anchor=W, side=LEFT, padx=(0,10)) - heartpieceEntry.pack(anchor=E) - - fullheartFrame = Frame(itemList2) - fullheartLabel = Label(fullheartFrame, text='Heart Container') - fullheartVar = StringVar(value='10') - fullheartEntry = Entry(fullheartFrame, textvariable=fullheartVar, width=3, validate='all', vcmd=vcmd) - fullheartFrame.pack() - fullheartLabel.pack(anchor=W, side=LEFT) - fullheartEntry.pack(anchor=E) - - sancheartFrame = Frame(itemList2) - sancheartLabel = Label(sancheartFrame, text='Sanctuary Heart') - sancheartVar = StringVar(value='1') - sancheartEntry = Entry(sancheartFrame, textvariable=sancheartVar, width=3, validate='all', vcmd=vcmd) - sancheartFrame.pack() - sancheartLabel.pack(anchor=W, side=LEFT) - sancheartEntry.pack(anchor=E) - - sword1Frame = Frame(itemList3) - sword1Label = Label(sword1Frame, text='Sword 1') - sword1Var = StringVar(value='0') - sword1Entry = Entry(sword1Frame, textvariable=sword1Var, width=3, validate='all', vcmd=vcmd) - sword1Frame.pack() - sword1Label.pack(anchor=W, side=LEFT, padx=(0,34)) - sword1Entry.pack(anchor=E) - - sword2Frame = Frame(itemList3) - sword2Label = Label(sword2Frame, text='Sword 2') - sword2Var = StringVar(value='0') - sword2Entry = Entry(sword2Frame, textvariable=sword2Var, width=3, validate='all', vcmd=vcmd) - sword2Frame.pack() - sword2Label.pack(anchor=W, side=LEFT, padx=(0,34)) - sword2Entry.pack(anchor=E) - - sword3Frame = Frame(itemList3) - sword3Label = Label(sword3Frame, text='Sword 3') - sword3Var = StringVar(value='0') - sword3Entry = Entry(sword3Frame, textvariable=sword3Var, width=3, validate='all', vcmd=vcmd) - sword3Frame.pack() - sword3Label.pack(anchor=W, side=LEFT, padx=(0,34)) - sword3Entry.pack(anchor=E) - - sword4Frame = Frame(itemList3) - sword4Label = Label(sword4Frame, text='Sword 4') - sword4Var = StringVar(value='0') - sword4Entry = Entry(sword4Frame, textvariable=sword4Var, width=3, validate='all', vcmd=vcmd) - sword4Frame.pack() - sword4Label.pack(anchor=W, side=LEFT, padx=(0,34)) - sword4Entry.pack(anchor=E) - - progswordFrame = Frame(itemList3) - progswordLabel = Label(progswordFrame, text='Prog.Sword') - progswordVar = StringVar(value='4') - progswordEntry = Entry(progswordFrame, textvariable=progswordVar, width=3, validate='all', vcmd=vcmd) - progswordFrame.pack() - progswordLabel.pack(anchor=W, side=LEFT, padx=(0,15)) - progswordEntry.pack(anchor=E) - - shield1Frame = Frame(itemList3) - shield1Label = Label(shield1Frame, text='Shield 1') - shield1Var = StringVar(value='0') - shield1Entry = Entry(shield1Frame, textvariable=shield1Var, width=3, validate='all', vcmd=vcmd) - shield1Frame.pack() - shield1Label.pack(anchor=W, side=LEFT, padx=(0,35)) - shield1Entry.pack(anchor=E) - - shield2Frame = Frame(itemList3) - shield2Label = Label(shield2Frame, text='Shield 2') - shield2Var = StringVar(value='0') - shield2Entry = Entry(shield2Frame, textvariable=shield2Var, width=3, validate='all', vcmd=vcmd) - shield2Frame.pack() - shield2Label.pack(anchor=W, side=LEFT, padx=(0,35)) - shield2Entry.pack(anchor=E) - - shield3Frame = Frame(itemList3) - shield3Label = Label(shield3Frame, text='Shield 3') - shield3Var = StringVar(value='0') - shield3Entry = Entry(shield3Frame, textvariable=shield3Var, width=3, validate='all', vcmd=vcmd) - shield3Frame.pack() - shield3Label.pack(anchor=W, side=LEFT, padx=(0,35)) - shield3Entry.pack(anchor=E) - - progshieldFrame = Frame(itemList3) - progshieldLabel = Label(progshieldFrame, text='Prog.Shield') - progshieldVar = StringVar(value='3') - progshieldEntry = Entry(progshieldFrame, textvariable=progshieldVar, width=3, validate='all', vcmd=vcmd) - progshieldFrame.pack() - progshieldLabel.pack(anchor=W, side=LEFT, padx=(0,16)) - progshieldEntry.pack(anchor=E) - - bluemailFrame = Frame(itemList3) - bluemailLabel = Label(bluemailFrame, text='Blue Mail') - bluemailVar = StringVar(value='0') - bluemailEntry = Entry(bluemailFrame, textvariable=bluemailVar, width=3, validate='all', vcmd=vcmd) - bluemailFrame.pack() - bluemailLabel.pack(anchor=W, side=LEFT, padx=(0,27)) - bluemailEntry.pack(anchor=E) - - redmailFrame = Frame(itemList3) - redmailLabel = Label(redmailFrame, text='Red Mail') - redmailVar = StringVar(value='0') - redmailEntry = Entry(redmailFrame, textvariable=redmailVar, width=3, validate='all', vcmd=vcmd) - redmailFrame.pack() - redmailLabel.pack(anchor=W, side=LEFT, padx=(0,30)) - redmailEntry.pack(anchor=E) - - progmailFrame = Frame(itemList3) - progmailLabel = Label(progmailFrame, text='Prog.Mail') - progmailVar = StringVar(value='2') - progmailEntry = Entry(progmailFrame, textvariable=progmailVar, width=3, validate='all', vcmd=vcmd) - progmailFrame.pack() - progmailLabel.pack(anchor=W, side=LEFT, padx=(0,25)) - progmailEntry.pack(anchor=E) - - halfmagicFrame = Frame(itemList3) - halfmagicLabel = Label(halfmagicFrame, text='Half Magic') - halfmagicVar = StringVar(value='1') - halfmagicEntry = Entry(halfmagicFrame, textvariable=halfmagicVar, width=3, validate='all', vcmd=vcmd) - halfmagicFrame.pack() - halfmagicLabel.pack(anchor=W, side=LEFT, padx=(0,18)) - halfmagicEntry.pack(anchor=E) - - quartermagicFrame = Frame(itemList3) - quartermagicLabel = Label(quartermagicFrame, text='Quarter Magic') - quartermagicVar = StringVar(value='0') - quartermagicEntry = Entry(quartermagicFrame, textvariable=quartermagicVar, width=3, validate='all', vcmd=vcmd) - quartermagicFrame.pack() - quartermagicLabel.pack(anchor=W, side=LEFT) - quartermagicEntry.pack(anchor=E) - - bcap5Frame = Frame(itemList3) - bcap5Label = Label(bcap5Frame, text='Bomb C.+5') - bcap5Var = StringVar(value='0') - bcap5Entry = Entry(bcap5Frame, textvariable=bcap5Var, width=3, validate='all', vcmd=vcmd) - bcap5Frame.pack() - bcap5Label.pack(anchor=W, side=LEFT, padx=(0,16)) - bcap5Entry.pack(anchor=E) - - bcap10Frame = Frame(itemList3) - bcap10Label = Label(bcap10Frame, text='Bomb C.+10') - bcap10Var = StringVar(value='0') - bcap10Entry = Entry(bcap10Frame, textvariable=bcap10Var, width=3, validate='all', vcmd=vcmd) - bcap10Frame.pack() - bcap10Label.pack(anchor=W, side=LEFT, padx=(0,10)) - bcap10Entry.pack(anchor=E) - - acap5Frame = Frame(itemList4) - acap5Label = Label(acap5Frame, text='Arrow C.+5') - acap5Var = StringVar(value='0') - acap5Entry = Entry(acap5Frame, textvariable=acap5Var, width=3, validate='all', vcmd=vcmd) - acap5Frame.pack() - acap5Label.pack(anchor=W, side=LEFT, padx=(0,7)) - acap5Entry.pack(anchor=E) - - acap10Frame = Frame(itemList4) - acap10Label = Label(acap10Frame, text='Arrow C.+10') - acap10Var = StringVar(value='0') - acap10Entry = Entry(acap10Frame, textvariable=acap10Var, width=3, validate='all', vcmd=vcmd) - acap10Frame.pack() - acap10Label.pack(anchor=W, side=LEFT, padx=(0,1)) - acap10Entry.pack(anchor=E) - - arrow1Frame = Frame(itemList4) - arrow1Label = Label(arrow1Frame, text='Arrow (1)') - arrow1Var = StringVar(value='1') - arrow1Entry = Entry(arrow1Frame, textvariable=arrow1Var, width=3, validate='all', vcmd=vcmd) - arrow1Frame.pack() - arrow1Label.pack(anchor=W, side=LEFT, padx=(0,18)) - arrow1Entry.pack(anchor=E) - - arrow10Frame = Frame(itemList4) - arrow10Label = Label(arrow10Frame, text='Arrows (10)') - arrow10Var = StringVar(value='12') - arrow10Entry = Entry(arrow10Frame, textvariable=arrow10Var, width=3, validate='all', vcmd=vcmd) - arrow10Frame.pack() - arrow10Label.pack(anchor=W, side=LEFT, padx=(0,7)) - arrow10Entry.pack(anchor=E) - - bomb1Frame = Frame(itemList4) - bomb1Label = Label(bomb1Frame, text='Bomb (1)') - bomb1Var = StringVar(value='0') - bomb1Entry = Entry(bomb1Frame, textvariable=bomb1Var, width=3, validate='all', vcmd=vcmd) - bomb1Frame.pack() - bomb1Label.pack(anchor=W, side=LEFT, padx=(0,18)) - bomb1Entry.pack(anchor=E) - - bomb3Frame = Frame(itemList4) - bomb3Label = Label(bomb3Frame, text='Bombs (3)') - bomb3Var = StringVar(value='16') - bomb3Entry = Entry(bomb3Frame, textvariable=bomb3Var, width=3, validate='all', vcmd=vcmd) - bomb3Frame.pack() - bomb3Label.pack(anchor=W, side=LEFT, padx=(0,13)) - bomb3Entry.pack(anchor=E) - - bomb10Frame = Frame(itemList4) - bomb10Label = Label(bomb10Frame, text='Bombs (10)') - bomb10Var = StringVar(value='1') - bomb10Entry = Entry(bomb10Frame, textvariable=bomb10Var, width=3, validate='all', vcmd=vcmd) - bomb10Frame.pack() - bomb10Label.pack(anchor=W, side=LEFT, padx=(0,7)) - bomb10Entry.pack(anchor=E) - - rupee1Frame = Frame(itemList4) - rupee1Label = Label(rupee1Frame, text='Rupee (1)') - rupee1Var = StringVar(value='2') - rupee1Entry = Entry(rupee1Frame, textvariable=rupee1Var, width=3, validate='all', vcmd=vcmd) - rupee1Frame.pack() - rupee1Label.pack(anchor=W, side=LEFT, padx=(0,17)) - rupee1Entry.pack(anchor=E) - - rupee5Frame = Frame(itemList4) - rupee5Label = Label(rupee5Frame, text='Rupees (5)') - rupee5Var = StringVar(value='4') - rupee5Entry = Entry(rupee5Frame, textvariable=rupee5Var, width=3, validate='all', vcmd=vcmd) - rupee5Frame.pack() - rupee5Label.pack(anchor=W, side=LEFT, padx=(0,12)) - rupee5Entry.pack(anchor=E) - - rupee20Frame = Frame(itemList4) - rupee20Label = Label(rupee20Frame, text='Rupees (20)') - rupee20Var = StringVar(value='28') - rupee20Entry = Entry(rupee20Frame, textvariable=rupee20Var, width=3, validate='all', vcmd=vcmd) - rupee20Frame.pack() - rupee20Label.pack(anchor=W, side=LEFT, padx=(0,6)) - rupee20Entry.pack(anchor=E) - - rupee50Frame = Frame(itemList4) - rupee50Label = Label(rupee50Frame, text='Rupees (50)') - rupee50Var = StringVar(value='7') - rupee50Entry = Entry(rupee50Frame, textvariable=rupee50Var, width=3, validate='all', vcmd=vcmd) - rupee50Frame.pack() - rupee50Label.pack(anchor=W, side=LEFT, padx=(0,6)) - rupee50Entry.pack(anchor=E) - - rupee100Frame = Frame(itemList4) - rupee100Label = Label(rupee100Frame, text='Rupees (100)') - rupee100Var = StringVar(value='1') - rupee100Entry = Entry(rupee100Frame, textvariable=rupee100Var, width=3, validate='all', vcmd=vcmd) - rupee100Frame.pack() - rupee100Label.pack(anchor=W, side=LEFT, padx=(0,0)) - rupee100Entry.pack(anchor=E) - - rupee300Frame = Frame(itemList4) - rupee300Label = Label(rupee300Frame, text='Rupees (300)') - rupee300Var = StringVar(value='5') - rupee300Entry = Entry(rupee300Frame, textvariable=rupee300Var, width=3, validate='all', vcmd=vcmd) - rupee300Frame.pack() - rupee300Label.pack(anchor=W, side=LEFT, padx=(0,0)) - rupee300Entry.pack(anchor=E) - - blueclockFrame = Frame(itemList4) - blueclockLabel = Label(blueclockFrame, text='Blue Clock') - blueclockVar = StringVar(value='0') - blueclockEntry = Entry(blueclockFrame, textvariable=blueclockVar, width=3, validate='all', vcmd=vcmd) - blueclockFrame.pack() - blueclockLabel.pack(anchor=W, side=LEFT, padx=(0,11)) - blueclockEntry.pack(anchor=E) - - greenclockFrame = Frame(itemList4) - greenclockLabel = Label(greenclockFrame, text='Green Clock') - greenclockVar = StringVar(value='0') - greenclockEntry = Entry(greenclockFrame, textvariable=greenclockVar, width=3, validate='all', vcmd=vcmd) - greenclockFrame.pack() - greenclockLabel.pack(anchor=W, side=LEFT, padx=(0,3)) - greenclockEntry.pack(anchor=E) - - redclockFrame = Frame(itemList4) - redclockLabel = Label(redclockFrame, text='Red Clock') - redclockVar = StringVar(value='0') - redclockEntry = Entry(redclockFrame, textvariable=redclockVar, width=3, validate='all', vcmd=vcmd) - redclockFrame.pack() - redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14)) - redclockEntry.pack(anchor=E) - - silverarrowFrame = Frame(itemList5) - silverarrowLabel = Label(silverarrowFrame, text='Silver Arrow') - silverarrowVar = StringVar(value='0') - silverarrowEntry = Entry(silverarrowFrame, textvariable=silverarrowVar, width=3, validate='all', vcmd=vcmd) - silverarrowFrame.pack() - silverarrowLabel.pack(anchor=W, side=LEFT, padx=(0,64)) - silverarrowEntry.pack(anchor=E) - - universalkeyFrame = Frame(itemList5) - universalkeyLabel = Label(universalkeyFrame, text='Universal Key') - universalkeyVar = StringVar(value='0') - universalkeyEntry = Entry(universalkeyFrame, textvariable=universalkeyVar, width=3, validate='all', vcmd=vcmd) - universalkeyFrame.pack() - universalkeyLabel.pack(anchor=W, side=LEFT, padx=(0,57)) - universalkeyEntry.pack(anchor=E) - - triforcepieceFrame = Frame(itemList5) - triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece') - triforcepieceVar = StringVar(value='0') - triforcepieceEntry = Entry(triforcepieceFrame, textvariable=triforcepieceVar, width=3, validate='all', vcmd=vcmd) - triforcepieceFrame.pack() - triforcepieceLabel.pack(anchor=W, side=LEFT, padx=(0,55)) - triforcepieceEntry.pack(anchor=E) - - triforcecountFrame = Frame(itemList5) - triforcecountLabel = Label(triforcecountFrame, text='Triforce Pieces Required') - triforcecountVar = StringVar(value='0') - triforcecountEntry = Entry(triforcecountFrame, textvariable=triforcecountVar, width=3, validate='all', vcmd=vcmd) - triforcecountFrame.pack() - triforcecountLabel.pack(anchor=W, side=LEFT, padx=(0,0)) - triforcecountEntry.pack(anchor=E) - - triforceFrame = Frame(itemList5) - triforceLabel = Label(triforceFrame, text='Triforce (win game)') - triforceVar = StringVar(value='0') - triforceEntry = Entry(triforceFrame, textvariable=triforceVar, width=3, validate='all', vcmd=vcmd) - triforceFrame.pack() - triforceLabel.pack(anchor=W, side=LEFT, padx=(0,23)) - triforceEntry.pack(anchor=E) - - rupoorFrame = Frame(itemList5) - rupoorLabel = Label(rupoorFrame, text='Rupoor') - rupoorVar = StringVar(value='0') - rupoorEntry = Entry(rupoorFrame, textvariable=rupoorVar, width=3, validate='all', vcmd=vcmd) - rupoorFrame.pack() - rupoorLabel.pack(anchor=W, side=LEFT, padx=(0,87)) - rupoorEntry.pack(anchor=E) - - rupoorcostFrame = Frame(itemList5) - rupoorcostLabel = Label(rupoorcostFrame, text='Rupoor Cost') - rupoorcostVar = StringVar(value='10') - rupoorcostEntry = Entry(rupoorcostFrame, textvariable=rupoorcostVar, width=6, validate='all', vcmd=vcmd) - rupoorcostFrame.pack() - rupoorcostLabel.pack(anchor=W, side=LEFT, padx=(0,43)) - rupoorcostEntry.pack(anchor=E) - - itemList1.pack(side=LEFT, padx=(0,0)) - itemList2.pack(side=LEFT, padx=(0,0)) - itemList3.pack(side=LEFT, padx=(0,0)) - itemList4.pack(side=LEFT, padx=(0,0)) - itemList5.pack(side=LEFT, padx=(0,0)) - topFrame3.pack(side=TOP, pady=(17,0)) - - if args is not None: - for k,v in vars(args).items(): - if type(v) is dict: - setattr(args, k, v[1]) # only get values for player 1 for now - # load values from commandline args - createSpoilerVar.set(int(args.create_spoiler)) - suppressRomVar.set(int(args.suppress_rom)) - mapshuffleVar.set(args.mapshuffle) - compassshuffleVar.set(args.compassshuffle) - keyshuffleVar.set(args.keyshuffle) - bigkeyshuffleVar.set(args.bigkeyshuffle) - retroVar.set(args.retro) - openpyramidVar.set(args.openpyramid) - quickSwapVar.set(int(args.quickswap)) - disableMusicVar.set(int(args.disablemusic)) - if args.multi: - worldVar.set(str(args.multi)) - if args.count: - countVar.set(str(args.count)) - if args.seed: - seedVar.set(str(args.seed)) - modeVar.set(args.mode) - swordVar.set(args.swords) - difficultyVar.set(args.difficulty) - itemfunctionVar.set(args.item_functionality) - timerVar.set(args.timer) - progressiveVar.set(args.progressive) - accessibilityVar.set(args.accessibility) - goalVar.set(args.goal) - crystalsGTVar.set(args.crystals_gt) - crystalsGanonVar.set(args.crystals_ganon) - algorithmVar.set(args.algorithm) - shuffleVar.set(args.shuffle) - doorShuffleVar.set(args.door_shuffle) - heartcolorVar.set(args.heartcolor) - heartbeepVar.set(args.heartbeep) - fastMenuVar.set(args.fastmenu) - logicVar.set(args.logic) - romVar.set(args.rom) - shuffleGanonVar.set(args.shuffleganon) - hintsVar.set(args.hints) - experimentVar.set(args.experimental) - enemizerCLIpathVar.set(args.enemizercli) - potShuffleVar.set(args.shufflepots) - enemyShuffleVar.set(args.shuffleenemies) - enemizerBossVar.set(args.shufflebosses) - enemizerDamageVar.set(args.enemy_damage) - enemizerHealthVar.set(args.enemy_health) - owPalettesVar.set(args.ow_palettes) - uwPalettesVar.set(args.uw_palettes) - if args.sprite is not None: - set_sprite(Sprite(args.sprite)) + # load adjust settings into options + loadadjustargs(self, self.settings) mainWindow.mainloop() -class SpriteSelector(object): - def __init__(self, parent, callback, adjuster=False): - if is_bundled(): - self.deploy_icons() - self.parent = parent - self.window = Toplevel(parent) - self.callback = callback - self.adjuster = adjuster - - self.window.wm_title("TAKE ANY ONE YOU WANT") - self.window['padx'] = 5 - self.window['pady'] = 5 - self.all_sprites = [] - - def open_unofficial_sprite_dir(_evt): - open_file(self.unofficial_sprite_dir) - - official_frametitle = Label(self.window, text='Official Sprites') - - unofficial_frametitle = Frame(self.window) - title_text = Label(unofficial_frametitle, text="Unofficial Sprites") - title_link = Label(unofficial_frametitle, text="(open)", fg="blue", cursor="hand2") - title_text.pack(side=LEFT) - title_link.pack(side=LEFT) - title_link.bind("", open_unofficial_sprite_dir) - - self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official sprites not found. Click "Update official sprites" to download them.') - self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.') - - frame = Frame(self.window) - frame.pack(side=BOTTOM, fill=X, pady=5) - - button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) - button.pack(side=RIGHT, padx=(5, 0)) - - button = Button(frame, text="Update official sprites", command=self.update_official_sprites) - button.pack(side=RIGHT, padx=(5, 0)) - - button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite) - button.pack(side=LEFT, padx=(0, 5)) - - button = Button(frame, text="Random sprite", command=self.use_random_sprite) - button.pack(side=LEFT, padx=(0, 5)) - - if adjuster: - button = Button(frame, text="Current sprite from rom", command=self.use_default_sprite) - button.pack(side=LEFT, padx=(0, 5)) - - set_icon(self.window) - self.window.focus() - - def icon_section(self, frame_label, path, no_results_label): - frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5) - frame.pack(side=TOP, fill=X) - - sprites = [] - - for file in glob(output_path(path)): - sprites.append(Sprite(file)) - - sprites.sort(key=lambda s: str.lower(s.name or "").strip()) - - i = 0 - for sprite in sprites: - image = get_image_for_sprite(sprite) - if image is None: - continue - self.all_sprites.append(sprite) - button = Button(frame, image=image, command=lambda spr=sprite: self.select_sprite(spr)) - ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name else "")) - button.image = image - button.grid(row=i // 16, column=i % 16) - i += 1 - - if i == 0: - label = Label(frame, text=no_results_label) - label.pack() - - - def update_official_sprites(self): - # need to wrap in try catch. We don't want errors getting the json or downloading the files to break us. - self.window.destroy() - self.parent.update() - def work(task): - resultmessage = "" - successful = True - - def finished(): - task.close_window() - if successful: - messagebox.showinfo("Sprite Updater", resultmessage) - else: - messagebox.showerror("Sprite Updater", resultmessage) - SpriteSelector(self.parent, self.callback, self.adjuster) - - try: - task.update_status("Downloading official sprites list") - with urlopen('https://alttpr.com/sprites') as response: - sprites_arr = json.loads(response.read().decode("utf-8")) - except Exception as e: - resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - task.queue_event(finished) - return - - try: - task.update_status("Determining needed sprites") - current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')] - official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] - needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites] - bundled_sprites = [os.path.basename(file) for file in glob(self.local_official_sprite_dir+'/*')] - # todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle. - - official_filenames = [filename for (_, filename) in official_sprites] - obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames] - except Exception as e: - resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - task.queue_event(finished) - return - - updated = 0 - for (sprite_url, filename) in needed_sprites: - try: - task.update_status("Downloading needed sprite %g/%g" % (updated + 1, len(needed_sprites))) - target = os.path.join(self.official_sprite_dir, filename) - with urlopen(sprite_url) as response, open(target, 'wb') as out: - shutil.copyfileobj(response, out) - except Exception as e: - resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - updated += 1 - - deleted = 0 - for sprite in obsolete_sprites: - try: - task.update_status("Removing obsolete sprite %g/%g" % (deleted + 1, len(obsolete_sprites))) - os.remove(os.path.join(self.official_sprite_dir, sprite)) - except Exception as e: - resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e) - successful = False - deleted += 1 - - if successful: - resultmessage = "official sprites updated successfully" - - task.queue_event(finished) - - BackgroundTaskProgress(self.parent, work, "Updating Sprites") - - - def browse_for_sprite(self): - sprite = filedialog.askopenfilename( - filetypes=[("All Sprite Sources", (".zspr", ".spr", ".sfc", ".smc")), - ("ZSprite files", ".zspr"), - ("Sprite files", ".spr"), - ("Rom Files", (".sfc", ".smc")), - ("All Files", "*")]) - try: - self.callback(Sprite(sprite)) - except Exception: - self.callback(None) - self.window.destroy() - - - def use_default_sprite(self): - self.callback(None) - self.window.destroy() - - def use_default_link_sprite(self): - self.callback(Sprite.default_link_sprite()) - self.window.destroy() - - def use_random_sprite(self): - self.callback(random.choice(self.all_sprites) if self.all_sprites else None) - self.window.destroy() - - def select_sprite(self, spritename): - self.callback(spritename) - self.window.destroy() - - - def deploy_icons(self): - if not os.path.exists(self.unofficial_sprite_dir): - os.makedirs(self.unofficial_sprite_dir) - if not os.path.exists(self.official_sprite_dir): - shutil.copytree(self.local_official_sprite_dir, self.official_sprite_dir) - - @property - def official_sprite_dir(self): - if is_bundled(): - return output_path("sprites/official") - return self.local_official_sprite_dir - - @property - def local_official_sprite_dir(self): - return local_path("data/sprites/official") - - @property - def unofficial_sprite_dir(self): - if is_bundled(): - return output_path("sprites/unofficial") - return self.local_unofficial_sprite_dir - - @property - def local_unofficial_sprite_dir(self): - return local_path("data/sprites/unofficial") - - -def get_image_for_sprite(sprite): - if not sprite.valid: - return None - height = 24 - width = 16 - - def draw_sprite_into_gif(add_palette_color, set_pixel_color_index): - - def drawsprite(spr, pal_as_colors, offset): - for y, row in enumerate(spr): - for x, pal_index in enumerate(row): - if pal_index: - color = pal_as_colors[pal_index - 1] - set_pixel_color_index(x + offset[0], y + offset[1], color) - - add_palette_color(16, (40, 40, 40)) - shadow = [ - [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], - ] - - drawsprite(shadow, [16], (2, 17)) - - palettes = sprite.decode_palette() - for i in range(15): - add_palette_color(i + 1, palettes[0][i]) - - body = sprite.decode16(0x4C0) - drawsprite(body, list(range(1, 16)), (0, 8)) - head = sprite.decode16(0x40) - drawsprite(head, list(range(1, 16)), (0, 0)) - - def make_gif(callback): - gif_header = b'GIF89a' - - gif_lsd = bytearray(7) - gif_lsd[0] = width - gif_lsd[2] = height - gif_lsd[4] = 0xF4 # 32 color palette follows. transparant + 15 for sprite + 1 for shadow=17 which rounds up to 32 as nearest power of 2 - gif_lsd[5] = 0 # background color is zero - gif_lsd[6] = 0 # aspect raio not specified - gif_gct = bytearray(3 * 32) - - gif_gce = bytearray(8) - gif_gce[0] = 0x21 # start of extention blocked - gif_gce[1] = 0xF9 # identifies this as the Graphics Control extension - gif_gce[2] = 4 # we are suppling only the 4 four bytes - gif_gce[3] = 0x01 # this gif includes transparency - gif_gce[4] = gif_gce[5] = 0 # animation frrame delay (unused) - gif_gce[6] = 0 # transparent color is index 0 - gif_gce[7] = 0 # end of gif_gce - gif_id = bytearray(10) - gif_id[0] = 0x2c - # byte 1,2 are image left. 3,4 are image top both are left as zerosuitsamus - gif_id[5] = width - gif_id[7] = height - gif_id[9] = 0 # no local color table - - gif_img_minimum_code_size = bytes([7]) # we choose 7 bits, so that each pixel is represented by a byte, for conviennce. - - clear = 0x80 - stop = 0x81 - - unchunked_image_data = bytearray(height * (width + 1) + 1) - # we technically need a Clear code once every 125 bytes, but we do it at the start of every row for simplicity - for row in range(height): - unchunked_image_data[row * (width + 1)] = clear - unchunked_image_data[-1] = stop - - def add_palette_color(index, color): - gif_gct[3 * index] = color[0] - gif_gct[3 * index + 1] = color[1] - gif_gct[3 * index + 2] = color[2] - - def set_pixel_color_index(x, y, color): - unchunked_image_data[y * (width + 1) + x + 1] = color - - callback(add_palette_color, set_pixel_color_index) - - def chunk_image(img): - for i in range(0, len(img), 255): - chunk = img[i:i + 255] - yield bytes([len(chunk)]) - yield chunk - - gif_img = b''.join([gif_img_minimum_code_size] + list(chunk_image(unchunked_image_data)) + [b'\x00']) - - gif = b''.join([gif_header, gif_lsd, gif_gct, gif_gce, gif_id, gif_img, b'\x3b']) - - return gif - - gif_data = make_gif(draw_sprite_into_gif) - image = PhotoImage(data=gif_data) - - return image.zoom(2) if __name__ == '__main__': - guiMain() + args = parse_arguments(None) + guiMain(args) diff --git a/Gui.spec b/Gui.spec new file mode 100644 index 00000000..cd6de67d --- /dev/null +++ b/Gui.spec @@ -0,0 +1,59 @@ +# -*- mode: python -*- + +block_cipher = None +console = True + +def recurse_for_py_files(names_so_far): + returnvalue = [] + for name in os.listdir(os.path.join(*names_so_far)): + if name != "__pycache__": + subdir_name = os.path.join(*names_so_far, name) + if os.path.isdir(subdir_name): + new_name_list = names_so_far + [name] + for filename in os.listdir(os.path.join(*new_name_list)): + base_file,file_extension = os.path.splitext(filename) + if file_extension == ".py": + new_name = ".".join(new_name_list+[base_file]) + if not new_name in returnvalue: + returnvalue.append(new_name) + returnvalue.extend(recurse_for_py_files(new_name_list)) + returnvalue.append("PIL._tkinter_finder") #Linux needs this + return returnvalue + +hiddenimports = [] + +a = Analysis(['Gui.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=hiddenimports, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller +excluded_binaries = [ + 'VCRUNTIME140.dll', + 'msvcp140.dll', + 'mfc140u.dll'] +a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='Gui', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=console ) # <--- change this to True to enable command prompt when the app runs diff --git a/ItemList.py b/ItemList.py index fa10baeb..971424fc 100644 --- a/ItemList.py +++ b/ItemList.py @@ -9,6 +9,8 @@ from EntranceShuffle import connect_entrance from Fill import FillError, fill_restrictive from Items import ItemFactory +import classes.constants as CONST + #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. #Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. @@ -124,6 +126,57 @@ difficulties = { ), } +def get_custom_array_key(item): + label_switcher = { + "silverarrow": "silversupgrade", + "blueboomerang": "boomerang", + "redboomerang": "redmerang", + "ocarina": "flute", + "bugcatchingnet": "bugnet", + "bookofmudora": "book", + "pegasusboots": "boots", + "titansmitts": "titansmitt", + "pieceofheart": "heartpiece", + "bossheartcontainer": "heartcontainer", + "sanctuaryheartcontainer": "sancheart", + "mastersword": "sword2", + "temperedsword": "sword3", + "goldensword": "sword4", + "blueshield": "shield1", + "redshield": "shield2", + "mirrorshield": "shield3", + "bluemail": "mail2", + "redmail": "mail3", + "progressivearmor": "progressivemail", + "splus12": "halfmagic", + "splus14": "quartermagic", + "singlearrow": "arrow1", + "singlebomb": "bomb1", + "triforcepiece": "triforcepieces" + } + key = item.lower() + trans = { + " ": "", + '(': "", + '/': "", + ')': "", + '+': "", + "magic": "", + "caneof": "", + "upgrade": "splus", + "arrows": "arrow", + "arrowplus": "arrowsplus", + "bombs": "bomb", + "bombplus": "bombsplus", + "rupees": "rupee" + } + for check in trans: + repl = trans[check] + key = key.replace(check,repl) + if key in label_switcher: + key = label_switcher.get(key) + return key + def generate_itempool(world, player): if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): @@ -141,7 +194,7 @@ def generate_itempool(world, player): region = world.get_region('Light World',player) loc = Location(player, "Murahdahla", parent=region) - loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count[player] + loc.access_rule = lambda state: state.item_count(get_custom_array_key('Triforce Piece'), player) + state.item_count(get_custom_array_key('Power Star'), player) > state.world.treasure_hunt_count[player] region.locations.append(loc) world.dynamic_locations.append(loc) @@ -204,7 +257,7 @@ def generate_itempool(world, player): # set up item pool if world.custom: (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray) - world.rupoor_cost = min(world.customitemarray[69], 9999) + world.rupoor_cost = min(world.customitemarray["rupoorcost"], 9999) else: (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player]) @@ -212,10 +265,10 @@ def generate_itempool(world, player): amt = world.pool_adjustment[player] if amt < 0: for i in range(0, amt): - pool.remove('Rupees (20)') + pool.remove(get_custom_array_key('Rupees (20)')) elif amt > 0: for i in range(0, amt): - pool.append('Rupees (20)') + pool.append(get_custom_array_key('Rupees (20)')) for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -268,9 +321,9 @@ def generate_itempool(world, player): # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) - if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0): + if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray["heartcontainer"] == 0): [item for item in items if item.name == 'Boss Heart Container'][0].advancement = True - elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[29] < 4): + elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray["heartpiece"] < 4): adv_heart_pieces = [item for item in items if item.name == 'Piece of Heart'][0:4] for hp in adv_heart_pieces: hp.advancement = True @@ -558,80 +611,31 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s placed_items[loc] = item # Correct for insanely oversized item counts and take initial steps to handle undersized pools. - for x in range(0, 66): - if customitemarray[x] > total_items_to_place: - customitemarray[x] = total_items_to_place - if customitemarray[68] > total_items_to_place: - customitemarray[68] = total_items_to_place - itemtotal = 0 - for x in range(0, 66): - itemtotal = itemtotal + customitemarray[x] - itemtotal = itemtotal + customitemarray[68] - itemtotal = itemtotal + customitemarray[70] + # Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors + for x in [*range(0, 66 + 1), 68, 69]: + key = CONST.CUSTOMITEMS[x] + if customitemarray[key] > total_items_to_place: + customitemarray[key] = total_items_to_place - pool.extend(['Bow'] * customitemarray[0]) - pool.extend(['Silver Arrows']* customitemarray[1]) - pool.extend(['Blue Boomerang'] * customitemarray[2]) - pool.extend(['Red Boomerang'] * customitemarray[3]) - pool.extend(['Hookshot'] * customitemarray[4]) - pool.extend(['Mushroom'] * customitemarray[5]) - pool.extend(['Magic Powder'] * customitemarray[6]) - pool.extend(['Fire Rod'] * customitemarray[7]) - pool.extend(['Ice Rod'] * customitemarray[8]) - pool.extend(['Bombos'] * customitemarray[9]) - pool.extend(['Ether'] * customitemarray[10]) - pool.extend(['Quake'] * customitemarray[11]) - pool.extend(['Lamp'] * customitemarray[12]) - pool.extend(['Hammer'] * customitemarray[13]) - pool.extend(['Shovel'] * customitemarray[14]) - pool.extend(['Ocarina'] * customitemarray[15]) - pool.extend(['Bug Catching Net'] * customitemarray[16]) - pool.extend(['Book of Mudora'] * customitemarray[17]) - pool.extend(['Cane of Somaria'] * customitemarray[19]) - pool.extend(['Cane of Byrna'] * customitemarray[20]) - pool.extend(['Cape'] * customitemarray[21]) - pool.extend(['Pegasus Boots'] * customitemarray[23]) - pool.extend(['Power Glove'] * customitemarray[24]) - pool.extend(['Titans Mitts'] * customitemarray[25]) - pool.extend(['Progressive Glove'] * customitemarray[26]) - pool.extend(['Flippers'] * customitemarray[27]) - pool.extend(['Piece of Heart'] * customitemarray[29]) - pool.extend(['Boss Heart Container'] * customitemarray[30]) - pool.extend(['Sanctuary Heart Container'] * customitemarray[31]) - pool.extend(['Master Sword'] * customitemarray[33]) - pool.extend(['Tempered Sword'] * customitemarray[34]) - pool.extend(['Golden Sword'] * customitemarray[35]) - pool.extend(['Blue Shield'] * customitemarray[37]) - pool.extend(['Red Shield'] * customitemarray[38]) - pool.extend(['Mirror Shield'] * customitemarray[39]) - pool.extend(['Progressive Shield'] * customitemarray[40]) - pool.extend(['Blue Mail'] * customitemarray[41]) - pool.extend(['Red Mail'] * customitemarray[42]) - pool.extend(['Progressive Armor'] * customitemarray[43]) - pool.extend(['Magic Upgrade (1/2)'] * customitemarray[44]) - pool.extend(['Magic Upgrade (1/4)'] * customitemarray[45]) - pool.extend(['Bomb Upgrade (+5)'] * customitemarray[46]) - pool.extend(['Bomb Upgrade (+10)'] * customitemarray[47]) - pool.extend(['Arrow Upgrade (+5)'] * customitemarray[48]) - pool.extend(['Arrow Upgrade (+10)'] * customitemarray[49]) - pool.extend(['Single Arrow'] * customitemarray[50]) - pool.extend(['Arrows (10)'] * customitemarray[51]) - pool.extend(['Single Bomb'] * customitemarray[52]) - pool.extend(['Bombs (3)'] * customitemarray[53]) - pool.extend(['Rupee (1)'] * customitemarray[54]) - pool.extend(['Rupees (5)'] * customitemarray[55]) - pool.extend(['Rupees (20)'] * customitemarray[56]) - pool.extend(['Rupees (50)'] * customitemarray[57]) - pool.extend(['Rupees (100)'] * customitemarray[58]) - pool.extend(['Rupees (300)'] * customitemarray[59]) - pool.extend(['Rupoor'] * customitemarray[60]) - pool.extend(['Blue Clock'] * customitemarray[61]) - pool.extend(['Green Clock'] * customitemarray[62]) - pool.extend(['Red Clock'] * customitemarray[63]) - pool.extend(['Progressive Bow'] * customitemarray[64]) - pool.extend(['Bombs (10)'] * customitemarray[65]) - pool.extend(['Triforce Piece'] * customitemarray[66]) - pool.extend(['Triforce'] * customitemarray[68]) + # Triforce + if customitemarray["triforce"] > total_items_to_place: + customitemarray["triforce"] = total_items_to_place + + itemtotal = 0 + # Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors + for x in [*range(0, 66 + 1), 68, 69]: + key = CONST.CUSTOMITEMS[x] + itemtotal = itemtotal + customitemarray[key] + # Triforce + itemtotal = itemtotal + customitemarray["triforce"] + # Generic Keys + itemtotal = itemtotal + customitemarray["generickeys"] + + customitems = [ + "Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce" + ] + for customitem in customitems: + pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)]) diff = difficulties[difficulty] @@ -641,17 +645,17 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s # all bottles, since only one bottle is available if diff.same_bottle: thisbottle = random.choice(diff.bottles) - for _ in range(customitemarray[18]): + for _ in range(customitemarray["bottle"]): if not diff.same_bottle: thisbottle = random.choice(diff.bottles) pool.append(thisbottle) - if customitemarray[66] > 0 or customitemarray[67] > 0: - treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99. + if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0: + treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], 99), 1) #To display, count must be between 1 and 99. treasure_hunt_icon = 'Triforce Piece' # Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. - if (customitemarray[66] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray[68] == 0): - extrapieces = treasure_hunt_count - customitemarray[66] + if (customitemarray["triforcepieces"] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray["triforce"] == 0): + extrapieces = treasure_hunt_count - customitemarray["triforcepieces"] pool.extend(['Triforce Piece'] * extrapieces) itemtotal = itemtotal + extrapieces @@ -670,28 +674,30 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s if retro: key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) place_item(key_location, 'Small Key (Universal)') - pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0)) + pool.extend(['Small Key (Universal)'] * max((customitemarray["generickeys"] - 1), 0)) else: - pool.extend(['Small Key (Universal)'] * customitemarray[70]) + pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"]) else: - pool.extend(['Small Key (Universal)'] * customitemarray[70]) + pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"]) - pool.extend(['Fighter Sword'] * customitemarray[32]) - pool.extend(['Progressive Sword'] * customitemarray[36]) + pool.extend(['Fighter Sword'] * customitemarray["sword1"]) + pool.extend(['Progressive Sword'] * customitemarray["progressivesword"]) if shuffle == 'insanity_legacy': place_item('Link\'s House', 'Magic Mirror') place_item('Sanctuary', 'Moon Pearl') - pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0)) - pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0)) + pool.extend(['Magic Mirror'] * max((customitemarray["mirror"] -1 ), 0)) + pool.extend(['Moon Pearl'] * max((customitemarray["pearl"] - 1), 0)) else: - pool.extend(['Magic Mirror'] * customitemarray[22]) - pool.extend(['Moon Pearl'] * customitemarray[28]) + pool.extend(['Magic Mirror'] * customitemarray["mirror"]) + pool.extend(['Moon Pearl'] * customitemarray["pearl"]) if retro: itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode if itemtotal < total_items_to_place: - pool.extend(['Nothing'] * (total_items_to_place - itemtotal)) + nothings = total_items_to_place - itemtotal + print("Placing " + str(nothings) + " Nothings") + pool.extend(['Nothing'] * nothings) return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 24b7dbb7..9c3d07eb 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -147,7 +147,7 @@ def analyze_dungeon(key_layout, world, player): key_layout.key_counters = create_key_counters(key_layout, world, player) key_logic = key_layout.key_logic - find_bk_locked_sections(key_layout, world) + find_bk_locked_sections(key_layout, world, player) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) if world.retro[player] and world.mode[player] != 'standard': return @@ -304,14 +304,14 @@ def queue_sorter_2(queue_item): return 1 if door.bigKey else 0 -def find_bk_locked_sections(key_layout, world): +def find_bk_locked_sections(key_layout, world, player): if key_layout.big_key_special: return key_counters = key_layout.key_counters key_logic = key_layout.key_logic bk_key_not_required = set() - big_chest_allowed_big_key = world.accessibility != 'locations' + big_chest_allowed_big_key = world.accessibility[player] != 'locations' for counter in key_counters.values(): key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_locations.update(counter.free_locations) @@ -1450,8 +1450,8 @@ def validate_key_placement(key_layout, world, player): if not can_progress: missing_locations = set(max_counter.free_locations.keys()).difference(found_locations) missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name] - #missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations? - if len(missing_items) > 0: #world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0): + # missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations? + if len(missing_items) > 0: # world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0): logging.getLogger('').error("Keylock - can't open locations: ") for i in missing_locations: logging.getLogger('').error(i) diff --git a/Main.py b/Main.py index 179a02d4..978d3e7e 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names -__version__ = '0.0.17pre' +__version__ = '0.0.18dev' def main(args, seed=None): @@ -57,8 +57,8 @@ def main(args, seed=None): world.enemy_health = args.enemy_health.copy() world.enemy_damage = args.enemy_damage.copy() world.beemizer = args.beemizer.copy() - world.dungeon_counters = args.dungeon_counters.copy() world.experimental = args.experimental.copy() + world.dungeon_counters = args.dungeon_counters.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -185,9 +185,13 @@ def main(args, seed=None): patch_rom(world, rom, player, team, use_enemizer) if use_enemizer and (args.enemizercli or not args.jsonout): - patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit) - if not args.jsonout: - rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000) + if os.path.exists(args.enemizercli): + patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit) + if not args.jsonout: + rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000) + else: + logging.warning("EnemizerCLI not found at:" + args.enemizercli) + logging.warning("No Enemizer options will be applied until this is resolved.") if args.race: patch_race_rom(rom) diff --git a/Rom.py b/Rom.py index 30d70800..73bb6de8 100644 --- a/Rom.py +++ b/Rom.py @@ -981,8 +981,8 @@ def patch_rom(world, rom, player, team, enemized): startingstate = CollectionState(world) if startingstate.has('Bow', player): - equip[0x340] = 1 - equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases + equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1 + equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases if not world.retro[player]: equip[0x38E] |= 0x80 if startingstate.has('Silver Arrows', player): @@ -1157,11 +1157,11 @@ def patch_rom(world, rom, player, team, enemized): rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld # compasses showing dungeon count - if world.clock_mode != 'off': + if world.clock_mode != 'off' or world.dungeon_counters[player] == 'off': rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location - elif world.dungeon_counters[player]: - rom.write_byte(0x18003C, 0x02) # show always - elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla': + elif world.dungeon_counters[player] == 'on': + rom.write_byte(0x18003C, 0x02) # always on + elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup': rom.write_byte(0x18003C, 0x01) # show on pickup else: rom.write_byte(0x18003C, 0x00) diff --git a/Rules.py b/Rules.py index 813f3879..9cc8102d 100644 --- a/Rules.py +++ b/Rules.py @@ -1185,14 +1185,13 @@ def set_inverted_big_bomb_rules(world, player): 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower', 'Cave 45', - 'Checkerboard Cave'] + 'Checkerboard Cave', + 'Inverted Big Bomb Shop'] LW_DM_entrances = ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)', 'Tower of Hera', 'Death Mountain Return Cave (West)', 'Paradox Cave (Top)', @@ -1212,7 +1211,7 @@ def set_inverted_big_bomb_rules(world, player): 'Chest Game', 'Dark World Hammer Peg Cave', 'Red Shield Shop', - 'Dark Sanctuary Hint', + 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop', @@ -1222,7 +1221,7 @@ def set_inverted_big_bomb_rules(world, player): Southern_DW_entrances = ['Hype Cave', 'Bonk Fairy (Dark)', 'Archery Game', - 'Inverted Big Bomb Shop', + 'Inverted Links House', 'Dark Lake Hylia Shop', 'Swamp Palace'] Isolated_DW_entrances = ['Spike Cave', diff --git a/Utils.py b/Utils.py index a52e8616..d616ef22 100644 --- a/Utils.py +++ b/Utils.py @@ -34,6 +34,8 @@ def is_bundled(): return getattr(sys, 'frozen', False) def local_path(path): + return path + if local_path.cached_path is not None: return os.path.join(local_path.cached_path, path) diff --git a/build-dr.py b/build-dr.py new file mode 100644 index 00000000..120028d9 --- /dev/null +++ b/build-dr.py @@ -0,0 +1,21 @@ +import subprocess +import os +import shutil + +DEST_DIRECTORY = '.' + +if os.path.isdir("upx"): + upx_string = "--upx-dir=upx" +else: + upx_string = "" + +if os.path.isdir("build"): + shutil.rmtree("build") + +subprocess.run(" ".join(["pyinstaller DungeonRandomizer.spec ", + upx_string, + "-y ", + "--onefile ", + f"--distpath {DEST_DIRECTORY} ", + ]), + shell=True) diff --git a/build-gui.py b/build-gui.py new file mode 100644 index 00000000..3f63548d --- /dev/null +++ b/build-gui.py @@ -0,0 +1,21 @@ +import subprocess +import os +import shutil + +DEST_DIRECTORY = '.' + +if os.path.isdir("upx"): + upx_string = "--upx-dir=upx" +else: + upx_string = "" + +if os.path.isdir("build"): + shutil.rmtree("build") + +subprocess.run(" ".join(["pyinstaller Gui.spec ", + upx_string, + "-y ", + "--onefile ", + f"--distpath {DEST_DIRECTORY} ", + ]), + shell=True) diff --git a/classes/SpriteSelector.py b/classes/SpriteSelector.py new file mode 100644 index 00000000..7e527d05 --- /dev/null +++ b/classes/SpriteSelector.py @@ -0,0 +1,347 @@ +from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, NSEW, LEFT, BOTTOM, X, RIGHT, TOP, HORIZONTAL, EW, NS +from glob import glob +import json +import os +import random +import shutil +from urllib.parse import urlparse +from urllib.request import urlopen +import webbrowser +from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress +from Rom import Sprite +from Utils import is_bundled, local_path, output_path, open_file + + +class SpriteSelector(object): + def __init__(self, parent, callback, adjuster=False): + if is_bundled(): + self.deploy_icons() + self.parent = parent + self.window = Toplevel(parent) + self.window.geometry("800x650") + self.sections = [] + self.callback = callback + self.adjuster = adjuster + + self.window.wm_title("TAKE ANY ONE YOU WANT") + self.window['padx'] = 5 + self.window['pady'] = 5 + self.all_sprites = [] + + def open_official_sprite_listing(_evt): + webbrowser.open("http://alttpr.com/sprite_preview") + + def open_unofficial_sprite_dir(_evt): + open_file(self.unofficial_sprite_dir) + + def open_spritesomething_listing(_evt): + webbrowser.open("https://artheau.github.io/SpriteSomething/?mode=zelda3/link") + + official_frametitle = Frame(self.window) + official_title_text = Label(official_frametitle, text="Official Sprites") + official_title_link = Label(official_frametitle, text="(open)", fg="blue", cursor="hand2") + official_title_text.pack(side=LEFT) + official_title_link.pack(side=LEFT) + official_title_link.bind("", open_official_sprite_listing) + + unofficial_frametitle = Frame(self.window) + unofficial_title_text = Label(unofficial_frametitle, text="Unofficial Sprites") + unofficial_title_link = Label(unofficial_frametitle, text="(open)", fg="blue", cursor="hand2") + unofficial_title_text.pack(side=LEFT) + unofficial_title_link.pack(side=LEFT) + unofficial_title_link.bind("", open_unofficial_sprite_dir) + spritesomething_title_link = Label(unofficial_frametitle, text="(SpriteSomething)", fg="blue", cursor="hand2") + spritesomething_title_link.pack(side=LEFT) + spritesomething_title_link.bind("", open_spritesomething_listing) + + self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official sprites not found. Click "Update official sprites" to download them.') + self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.') + + frame = Frame(self.window) + frame.pack(side=BOTTOM, fill=X, pady=5) + + button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) + button.pack(side=RIGHT, padx=(5, 0)) + + button = Button(frame, text="Update official sprites", command=self.update_official_sprites) + button.pack(side=RIGHT, padx=(5, 0)) + + button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite) + button.pack(side=LEFT, padx=(0, 5)) + + button = Button(frame, text="Random sprite", command=self.use_random_sprite) + button.pack(side=LEFT, padx=(0, 5)) + + if adjuster: + button = Button(frame, text="Current sprite from rom", command=self.use_default_sprite) + button.pack(side=LEFT, padx=(0, 5)) + + set_icon(self.window) + self.window.focus() + + def icon_section(self, frame_label, path, no_results_label): + frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5) + canvas = Canvas(frame, borderwidth=0) + y_scrollbar = Scrollbar(frame, orient="vertical", command=canvas.yview) + y_scrollbar.pack(side="right", fill="y") + content_frame = Frame(canvas) + canvas.pack(side="left", fill="both", expand=True) + canvas.create_window((4, 4), window=content_frame, anchor="nw") + canvas.configure(yscrollcommand=y_scrollbar.set) + + def onFrameConfigure(canvas): + """Reset the scroll region to encompass the inner frame""" + canvas.configure(scrollregion=canvas.bbox("all")) + + content_frame.bind("", lambda event, canvas=canvas: onFrameConfigure(canvas)) + frame.pack(side=TOP, fill=X) + + sprites = [] + + for file in glob(output_path(path)): + sprites.append(Sprite(file)) + + sprites.sort(key=lambda s: str.lower(s.name or "").strip()) + + i = 0 + for sprite in sprites: + image = get_image_for_sprite(sprite) + if image is None: + continue + self.all_sprites.append(sprite) + button = Button(content_frame, image=image, command=lambda spr=sprite: self.select_sprite(spr)) + ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name else "")) + button.image = image + button.grid(row=i // 16, column=i % 16) + i += 1 + + if i == 0: + label = Label(content_frame, text=no_results_label) + label.pack() + + def update_official_sprites(self): + # need to wrap in try catch. We don't want errors getting the json or downloading the files to break us. + self.window.destroy() + self.parent.update() + def work(task): + resultmessage = "" + successful = True + + def finished(): + task.close_window() + if successful: + messagebox.showinfo("Sprite Updater", resultmessage) + else: + messagebox.showerror("Sprite Updater", resultmessage) + SpriteSelector(self.parent, self.callback, self.adjuster) + + try: + task.update_status("Downloading official sprites list") + with urlopen('https://alttpr.com/sprites') as response: + sprites_arr = json.loads(response.read().decode("utf-8")) + except Exception as e: + resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + task.queue_event(finished) + return + + try: + task.update_status("Determining needed sprites") + current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')] + official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] + needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites] + bundled_sprites = [os.path.basename(file) for file in glob(self.local_official_sprite_dir+'/*')] + # todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle. + + official_filenames = [filename for (_, filename) in official_sprites] + obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames] + except Exception as e: + resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + task.queue_event(finished) + return + + updated = 0 + for (sprite_url, filename) in needed_sprites: + try: + task.update_status("Downloading needed sprite %g/%g" % (updated + 1, len(needed_sprites))) + target = os.path.join(self.official_sprite_dir, filename) + with urlopen(sprite_url) as response, open(target, 'wb') as out: + shutil.copyfileobj(response, out) + except Exception as e: + resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + updated += 1 + + deleted = 0 + for sprite in obsolete_sprites: + try: + task.update_status("Removing obsolete sprite %g/%g" % (deleted + 1, len(obsolete_sprites))) + os.remove(os.path.join(self.official_sprite_dir, sprite)) + except Exception as e: + resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e) + successful = False + deleted += 1 + + if successful: + resultmessage = "official sprites updated successfully" + + task.queue_event(finished) + + BackgroundTaskProgress(self.parent, work, "Updating Sprites") + + def browse_for_sprite(self): + sprite = filedialog.askopenfilename( + filetypes=[("All Sprite Sources", (".zspr", ".spr", ".sfc", ".smc")), + ("ZSprite files", ".zspr"), + ("Sprite files", ".spr"), + ("Rom Files", (".sfc", ".smc")), + ("All Files", "*")]) + try: + self.callback(Sprite(sprite)) + except Exception: + self.callback(None) + self.window.destroy() + + def use_default_sprite(self): + self.callback(None, False) + self.window.destroy() + + def use_default_link_sprite(self): + self.callback(Sprite.default_link_sprite(), False) + self.window.destroy() + + def use_random_sprite(self): + self.callback(random.choice(self.all_sprites) if self.all_sprites else None, True) + self.window.destroy() + + def select_sprite(self, spritename): + self.callback(spritename, False) + self.window.destroy() + + def deploy_icons(self): + if not os.path.exists(self.unofficial_sprite_dir): + os.makedirs(self.unofficial_sprite_dir) + if not os.path.exists(self.official_sprite_dir): + shutil.copytree(self.local_official_sprite_dir, self.official_sprite_dir) + + @property + def official_sprite_dir(self): + if is_bundled(): + return output_path("sprites/official") + return self.local_official_sprite_dir + + @property + def local_official_sprite_dir(self): + return local_path("data/sprites/official") + + @property + def unofficial_sprite_dir(self): + if is_bundled(): + return output_path("sprites/unofficial") + return self.local_unofficial_sprite_dir + + @property + def local_unofficial_sprite_dir(self): + return local_path("data/sprites/unofficial") + + +def get_image_for_sprite(sprite): + if not sprite.valid: + return None + height = 24 + width = 16 + + def draw_sprite_into_gif(add_palette_color, set_pixel_color_index): + + def drawsprite(spr, pal_as_colors, offset): + for y, row in enumerate(spr): + for x, pal_index in enumerate(row): + if pal_index: + color = pal_as_colors[pal_index - 1] + set_pixel_color_index(x + offset[0], y + offset[1], color) + + add_palette_color(16, (40, 40, 40)) + shadow = [ + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + ] + + drawsprite(shadow, [16], (2, 17)) + + palettes = sprite.decode_palette() + for i in range(15): + add_palette_color(i + 1, palettes[0][i]) + + body = sprite.decode16(0x4C0) + drawsprite(body, list(range(1, 16)), (0, 8)) + head = sprite.decode16(0x40) + drawsprite(head, list(range(1, 16)), (0, 0)) + + def make_gif(callback): + gif_header = b'GIF89a' + + gif_lsd = bytearray(7) + gif_lsd[0] = width + gif_lsd[2] = height + gif_lsd[4] = 0xF4 # 32 color palette follows. transparant + 15 for sprite + 1 for shadow=17 which rounds up to 32 as nearest power of 2 + gif_lsd[5] = 0 # background color is zero + gif_lsd[6] = 0 # aspect raio not specified + gif_gct = bytearray(3 * 32) + + gif_gce = bytearray(8) + gif_gce[0] = 0x21 # start of extention blocked + gif_gce[1] = 0xF9 # identifies this as the Graphics Control extension + gif_gce[2] = 4 # we are suppling only the 4 four bytes + gif_gce[3] = 0x01 # this gif includes transparency + gif_gce[4] = gif_gce[5] = 0 # animation frrame delay (unused) + gif_gce[6] = 0 # transparent color is index 0 + gif_gce[7] = 0 # end of gif_gce + gif_id = bytearray(10) + gif_id[0] = 0x2c + # byte 1,2 are image left. 3,4 are image top both are left as zerosuitsamus + gif_id[5] = width + gif_id[7] = height + gif_id[9] = 0 # no local color table + + gif_img_minimum_code_size = bytes([7]) # we choose 7 bits, so that each pixel is represented by a byte, for conviennce. + + clear = 0x80 + stop = 0x81 + + unchunked_image_data = bytearray(height * (width + 1) + 1) + # we technically need a Clear code once every 125 bytes, but we do it at the start of every row for simplicity + for row in range(height): + unchunked_image_data[row * (width + 1)] = clear + unchunked_image_data[-1] = stop + + def add_palette_color(index, color): + gif_gct[3 * index] = color[0] + gif_gct[3 * index + 1] = color[1] + gif_gct[3 * index + 2] = color[2] + + def set_pixel_color_index(x, y, color): + unchunked_image_data[y * (width + 1) + x + 1] = color + + callback(add_palette_color, set_pixel_color_index) + + def chunk_image(img): + for i in range(0, len(img), 255): + chunk = img[i:i + 255] + yield bytes([len(chunk)]) + yield chunk + + gif_img = b''.join([gif_img_minimum_code_size] + list(chunk_image(unchunked_image_data)) + [b'\x00']) + + gif = b''.join([gif_header, gif_lsd, gif_gct, gif_gce, gif_id, gif_img, b'\x3b']) + + return gif + + gif_data = make_gif(draw_sprite_into_gif) + image = PhotoImage(data=gif_data) + + return image.zoom(2) diff --git a/classes/__init__.py b/classes/__init__.py new file mode 100644 index 00000000..a9d60931 --- /dev/null +++ b/classes/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "classes" package diff --git a/classes/constants.py b/classes/constants.py new file mode 100644 index 00000000..b822628f --- /dev/null +++ b/classes/constants.py @@ -0,0 +1,109 @@ +CUSTOMITEMS = [ + "bow", "progressivebow", "boomerang", "redmerang", "hookshot", + "mushroom", "powder", "firerod", "icerod", "bombos", + "ether", "quake", "lamp", "hammer", "shovel", + + "flute", "bugnet", "book", "bottle", "somaria", + "byrna", "cape", "mirror", "boots", "powerglove", + "titansmitt", "progressiveglove", "flippers", "pearl", "heartpiece", + + "heartcontainer", "sancheart", "sword1", "sword2", "sword3", + "sword4", "progressivesword", "shield1", "shield2", "shield3", + "progressiveshield", "mail2", "mail3", "progressivemail", "halfmagic", + + "quartermagic", "bombsplus5", "bombsplus10", "arrowsplus5", "arrowsplus10", + "arrow1", "arrow10", "bomb1", "bomb3", "bomb10", + "rupee1", "rupee5", "rupee20", "rupee50", "rupee100", + + "rupee300", "blueclock", "greenclock", "redclock", "silversupgrade", + "generickeys", "triforcepieces", "triforcepiecesgoal", "triforce", "rupoor", + "rupoorcost" +] + +CANTSTARTWITH = [ + "triforcepiecesgoal", "triforce", "rupoor", + "rupoorcost" +] + +CUSTOMITEMLABELS = [ + "Bow", "Progressive Bow", "Blue Boomerang", "Red Boomerang", "Hookshot", + "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", + "Ether", "Quake", "Lamp", "Hammer", "Shovel", + + "Ocarina", "Bug Catching Net", "Book of Mudora", "Bottle", "Cane of Somaria", + "Cane of Byrna", "Magic Cape", "Magic Mirror", "Pegasus Boots", "Power Glove", + "Titans Mitts", "Progressive Glove", "Flippers", "Moon Pearl", "Piece of Heart", + + "Boss Heart Container", "Sanctuary Heart Container", "Fighter Sword", "Master Sword", "Tempered Sword", + "Golden Sword", "Progressive Sword", "Blue Shield", "Red Shield", "Mirror Shield", + "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", + + "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", + "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Bombs (10)", + "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", + + "Rupees (300)", "Blue Clock", "Green Clock", "Red Clock", "Silver Arrows", + "Small Key (Universal)", "Triforce Piece", "Triforce Piece Goal", "Triforce", "Rupoor", + "Rupoor Cost" +] + +SETTINGSTOPROCESS = { + "randomizer": { + "item": { + "retro": "retro", + "worldstate": "mode", + "logiclevel": "logic", + "goal": "goal", + "crystals_gt": "crystals_gt", + "crystals_ganon": "crystals_ganon", + "weapons": "swords", + "itempool": "difficulty", + "itemfunction": "item_functionality", + "timer": "timer", + "progressives": "progressive", + "accessibility": "accessibility", + "sortingalgo": "algorithm" + }, + "entrance": { + "openpyramid": "openpyramid", + "shuffleganon": "shuffleganon", + "entranceshuffle": "shuffle" + }, + "enemizer": { + "potshuffle": "shufflepots", + "enemyshuffle": "shuffleenemies", + "bossshuffle": "shufflebosses", + "enemydamage": "enemy_damage", + "enemyhealth": "enemy_health" + }, + "dungeon": { + "mapshuffle": "mapshuffle", + "compassshuffle": "compassshuffle", + "smallkeyshuffle": "keyshuffle", + "bigkeyshuffle": "bigkeyshuffle", + "dungeondoorshuffle": "door_shuffle", + "experimental": "experimental", + "dungeon_counters": "dungeon_counters" + }, + "multiworld": { + "names": "names" + }, + "gameoptions": { + "hints": "hints", + "nobgm": "disablemusic", + "quickswap": "quickswap", + "heartcolor": "heartcolor", + "heartbeep": "heartbeep", + "menuspeed": "fastmenu", + "owpalettes": "ow_palettes", + "uwpalettes": "uw_palettes" + }, + "generation": { + "spoiler": "create_spoiler", + "suppressrom": "suppress_rom", + "usestartinventory": "usestartinventory", + "usecustompool": "custom", + "saveonexit": "saveonexit" + } + } +} diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 00000000..8c5232bc --- /dev/null +++ b/gui/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui" package diff --git a/gui/about/__init__.py b/gui/about/__init__.py new file mode 100644 index 00000000..1ad9a517 --- /dev/null +++ b/gui/about/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui.about" package diff --git a/gui/adjust/__init__.py b/gui/adjust/__init__.py new file mode 100644 index 00000000..3e1ae764 --- /dev/null +++ b/gui/adjust/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui.adjust" package diff --git a/gui/adjust/overview.py b/gui/adjust/overview.py new file mode 100644 index 00000000..8e3a7851 --- /dev/null +++ b/gui/adjust/overview.py @@ -0,0 +1,113 @@ +from tkinter import ttk, filedialog, messagebox, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT, X, BOTTOM +from AdjusterMain import adjust +from argparse import Namespace +from classes.SpriteSelector import SpriteSelector +import gui.widgets as widgets +import json +import logging +import os + +def adjust_page(top, parent, settings): + # Adjust page + self = ttk.Frame(parent) + + # Adjust options + self.widgets = {} + + # Adjust option sections + self.frames = {} + self.frames["checkboxes"] = Frame(self) + self.frames["checkboxes"].pack(anchor=W) + + self.frames["selectOptionsFrame"] = Frame(self) + self.frames["leftAdjustFrame"] = Frame(self.frames["selectOptionsFrame"]) + self.frames["rightAdjustFrame"] = Frame(self.frames["selectOptionsFrame"]) + self.frames["bottomAdjustFrame"] = Frame(self) + self.frames["selectOptionsFrame"].pack(fill=X) + self.frames["leftAdjustFrame"].pack(side=LEFT) + self.frames["rightAdjustFrame"].pack(side=RIGHT) + self.frames["bottomAdjustFrame"].pack(fill=X) + + with open(os.path.join("resources","app","gui","adjust","overview","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + packAttrs = {"anchor":E} + if self.widgets[key].type == "checkbox": + packAttrs["anchor"] = W + self.widgets[key].pack(packAttrs) + + # Sprite Selection + self.spriteNameVar2 = StringVar() + spriteDialogFrame2 = Frame(self.frames["leftAdjustFrame"]) + baseSpriteLabel2 = Label(spriteDialogFrame2, text='Sprite:') + spriteEntry2 = Label(spriteDialogFrame2, textvariable=self.spriteNameVar2) + self.sprite = None + + def set_sprite(sprite_param, random_sprite=False): + if sprite_param is None or not sprite_param.valid: + self.sprite = None + self.spriteNameVar2.set('(unchanged)') + else: + self.sprite = sprite_param + self.spriteNameVar2.set(self.sprite.name) + top.randomSprite.set(random_sprite) + + def SpriteSelectAdjuster(): + SpriteSelector(parent, set_sprite, adjuster=True) + + spriteSelectButton2 = Button(spriteDialogFrame2, text='...', command=SpriteSelectAdjuster) + + baseSpriteLabel2.pack(side=LEFT) + spriteEntry2.pack(side=LEFT) + spriteSelectButton2.pack(side=LEFT) + spriteDialogFrame2.pack(anchor=E) + + adjustRomFrame = Frame(self.frames["bottomAdjustFrame"]) + adjustRomLabel = Label(adjustRomFrame, text='Rom to adjust: ') + self.romVar2 = StringVar(value=settings["rom"]) + romEntry2 = Entry(adjustRomFrame, textvariable=self.romVar2) + + def RomSelect2(): + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")]) + if rom: + settings["rom"] = rom + self.romVar2.set(rom) + romSelectButton2 = Button(adjustRomFrame, text='Select Rom', command=RomSelect2) + + adjustRomLabel.pack(side=LEFT) + romEntry2.pack(side=LEFT, fill=X, expand=True) + romSelectButton2.pack(side=LEFT) + adjustRomFrame.pack(fill=X) + + def adjustRom(): + options = { + "heartbeep": "heartbeep", + "heartcolor": "heartcolor", + "menuspeed": "fastmenu", + "owpalettes": "ow_palettes", + "uwpalettes": "uw_palettes", + "quickswap": "quickswap", + "nobgm": "disablemusic" + } + guiargs = Namespace() + for option in options: + arg = options[option] + setattr(guiargs, arg, self.widgets[option].storageVar.get()) + guiargs.rom = self.romVar2.get() + guiargs.baserom = top.pages["randomizer"].pages["generation"].romVar.get() + guiargs.sprite = self.sprite + try: + adjust(args=guiargs) + except Exception as e: + logging.exception(e) + messagebox.showerror(title="Error while creating seed", message=str(e)) + else: + messagebox.showinfo(title="Success", message="Rom patched successfully") + + adjustButton = Button(self.frames["bottomAdjustFrame"], text='Adjust Rom', command=adjustRom) + adjustButton.pack(side=BOTTOM, padx=(5, 0)) + + return self,settings diff --git a/gui/bottom.py b/gui/bottom.py new file mode 100644 index 00000000..22d94d35 --- /dev/null +++ b/gui/bottom.py @@ -0,0 +1,147 @@ +from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, Spinbox, E, W, LEFT, RIGHT, X +from argparse import Namespace +from functools import partial +import logging +import os +import random +from CLI import parse_arguments, get_settings +from Main import main +from Utils import local_path, output_path, open_file +import classes.constants as CONST +import gui.widgets as widgets + + +def bottom_frame(self, parent, args=None): + # Bottom Frame + self = ttk.Frame(parent) + + # Bottom Frame options + self.widgets = {} + + seedCountFrame = Frame(self) + seedCountFrame.pack() + ## Seed # + seedLabel = Label(self, text='Seed #') + savedSeed = parent.settings["seed"] + self.seedVar = StringVar(value=savedSeed) + def saveSeed(caller,_,mode): + savedSeed = self.seedVar.get() + parent.settings["seed"] = int(savedSeed) if savedSeed.isdigit() else None + self.seedVar.trace_add("write",saveSeed) + seedEntry = Entry(self, width=15, textvariable=self.seedVar) + seedLabel.pack(side=LEFT) + seedEntry.pack(side=LEFT) + + ## Number of Generation attempts + key = "generationcount" + self.widgets[key] = widgets.make_widget( + self, + "spinbox", + self, + "Count", + None, + None, + {"label": {"side": LEFT}, "spinbox": {"side": RIGHT}} + ) + self.widgets[key].pack(side=LEFT) + + def generateRom(): + guiargs = create_guiargs(parent) + # get default values for missing parameters + for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items(): + if k not in vars(guiargs): + setattr(guiargs, k, v) + elif type(v) is dict: # use same settings for every player + setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)}) + try: + if guiargs.count is not None: + seed = guiargs.seed + for _ in range(guiargs.count): + main(seed=seed, args=guiargs) + seed = random.randint(0, 999999999) + else: + main(seed=guiargs.seed, args=guiargs) + except Exception as e: + logging.exception(e) + messagebox.showerror(title="Error while creating seed", message=str(e)) + else: + messagebox.showinfo(title="Success", message="Rom patched successfully") + + ## Generate Button + generateButton = Button(self, text='Generate Patched Rom', command=generateRom) + generateButton.pack(side=LEFT) + + def open_output(): + if args and args.outputpath: + open_file(output_path(args.outputpath)) + else: + open_file(output_path(parent.settings["outputpath"])) + + openOutputButton = Button(self, text='Open Output Directory', command=open_output) + openOutputButton.pack(side=RIGHT) + + ## Documentation Button + if os.path.exists(local_path('README.html')): + def open_readme(): + open_file(local_path('README.html')) + openReadmeButton = Button(self, text='Open Documentation', command=open_readme) + openReadmeButton.pack(side=RIGHT) + + return self + + +def create_guiargs(parent): + guiargs = Namespace() + + # set up settings to gather + # Page::Subpage::GUI-id::param-id + options = CONST.SETTINGSTOPROCESS + + for mainpage in options: + for subpage in options[mainpage]: + for widget in options[mainpage][subpage]: + arg = options[mainpage][subpage][widget] + setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get()) + + guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.get() + + guiargs.multi = int(parent.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.get()) + + guiargs.rom = parent.pages["randomizer"].pages["generation"].romVar.get() + guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get()) + + guiargs.seed = int(parent.frames["bottom"].seedVar.get()) if parent.frames["bottom"].seedVar.get() else None + guiargs.count = int(parent.frames["bottom"].widgets["generationcount"].storageVar.get()) if parent.frames["bottom"].widgets["generationcount"].storageVar.get() != '1' else None + + adjustargs = { + "nobgm": "disablemusic", + "quickswap": "quickswap", + "heartcolor": "heartcolor", + "heartbeep": "heartbeep", + "menuspeed": "fastmenu", + "owpalettes": "ow_palettes", + "uwpalettes": "uw_palettes" + } + for adjustarg in adjustargs: + internal = adjustargs[adjustarg] + setattr(guiargs,"adjust." + internal, parent.pages["adjust"].content.widgets[adjustarg].storageVar.get()) + + customitems = CONST.CUSTOMITEMS + guiargs.startinventory = [] + guiargs.customitemarray = {} + guiargs.startinventoryarray = {} + for customitem in customitems: + if customitem not in ["triforcepiecesgoal", "triforce", "rupoor", "rupoorcost"]: + amount = int(parent.pages["startinventory"].content.startingWidgets[customitem].storageVar.get()) + guiargs.startinventoryarray[customitem] = amount + for i in range(0, amount): + label = CONST.CUSTOMITEMLABELS[customitems.index(customitem)] + guiargs.startinventory.append(label) + guiargs.customitemarray[customitem] = int(parent.pages["custom"].content.customWidgets[customitem].storageVar.get()) + + guiargs.startinventory = ','.join(guiargs.startinventory) + + guiargs.sprite = parent.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] + guiargs.randomSprite = parent.randomSprite.get() + guiargs.outputpath = parent.outputPath.get() + return guiargs diff --git a/gui/custom/__init__.py b/gui/custom/__init__.py new file mode 100644 index 00000000..33cec31c --- /dev/null +++ b/gui/custom/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui.custom" package diff --git a/gui/custom/overview.py b/gui/custom/overview.py new file mode 100644 index 00000000..e7c7fa63 --- /dev/null +++ b/gui/custom/overview.py @@ -0,0 +1,54 @@ +from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, LEFT, RIGHT, X, VERTICAL, Y +import gui.widgets as widgets +import json +import os + +import classes.constants as CONST + +def custom_page(top,parent): + # Custom Item Pool + self = ttk.Frame(parent) + + def create_list_frame(parent, framename): + parent.frames[framename] = Frame(parent) + parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + parent.frames[framename].thisRow = 0 + parent.frames[framename].thisCol = 0 + + def create_vertical_rule(num=1): + for i in range(0,num): + ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y) + + def validation(P): + if str.isdigit(P) or P == "": + return True + else: + return False + vcmd=(self.register(validation), '%P') + + # Custom Item Pool options + self.customWidgets = {} + + # Custom Item Pool option sections + self.frames = {} + create_list_frame(self,"itemList1") + create_vertical_rule(2) + create_list_frame(self,"itemList2") + create_vertical_rule(2) + create_list_frame(self,"itemList3") + create_vertical_rule(2) + create_list_frame(self,"itemList4") + create_vertical_rule(2) + create_list_frame(self,"itemList5") + + with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.customWidgets[key] = dictWidgets[key] + + for key in CONST.CUSTOMITEMS: + self.customWidgets[key].storageVar.set(top.settings["customitemarray"][key]) + + return self diff --git a/gui/loadcliargs.py b/gui/loadcliargs.py new file mode 100644 index 00000000..846daaa8 --- /dev/null +++ b/gui/loadcliargs.py @@ -0,0 +1,74 @@ +from classes.SpriteSelector import SpriteSelector as spriteSelector +from gui.randomize.gameoptions import set_sprite +from Rom import Sprite, get_sprite_from_name +import classes.constants as CONST + +def loadcliargs(gui, args, settings=None): + if args is not None: +# for k, v in vars(args).items(): +# if type(v) is dict: +# setattr(args, k, v[1]) # only get values for player 1 for now + # load values from commandline args + + # set up options to get + # Page::Subpage::GUI-id::param-id + options = CONST.SETTINGSTOPROCESS + + for mainpage in options: + for subpage in options[mainpage]: + for widget in options[mainpage][subpage]: + arg = options[mainpage][subpage][widget] + gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg]) + if subpage == "gameoptions" and not widget == "hints": + hasSettings = settings is not None + hasWidget = ("adjust." + widget) in settings if hasSettings else None + if hasWidget is None: + gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg]) + + gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args["enemizercli"]) + gui.pages["randomizer"].pages["generation"].romVar.set(args["rom"]) + + if args["multi"]: + gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args["multi"])) + if args["seed"]: + gui.frames["bottom"].seedVar.set(str(args["seed"])) + if args["count"]: + gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args["count"])) + gui.outputPath.set(args["outputpath"]) + + def sprite_setter(spriteObject): + gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] = spriteObject + if args["sprite"] is not None: + sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"]) + set_sprite(sprite_obj, False, spriteSetter=sprite_setter, + spriteNameVar=gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteNameVar"], + randomSpriteVar=gui.randomSprite) + + def sprite_setter_adj(spriteObject): + gui.pages["adjust"].content.sprite = spriteObject + if args["sprite"] is not None: + sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"]) + set_sprite(sprite_obj, False, spriteSetter=sprite_setter_adj, + spriteNameVar=gui.pages["adjust"].content.spriteNameVar2, + randomSpriteVar=gui.randomSprite) + +def loadadjustargs(gui, settings): + options = { + "adjust": { + "content": { + "nobgm": "adjust.nobgm", + "quickswap": "adjust.quickswap", + "heartcolor": "adjust.heartcolor", + "heartbeep": "adjust.heartbeep", + "menuspeed": "adjust.menuspeed", + "owpalettes": "adjust.owpalettes", + "uwpalettes": "adjust.uwpalettes" + } + } + } + for mainpage in options: + for subpage in options[mainpage]: + for widget in options[mainpage][subpage]: + key = options[mainpage][subpage][widget] + if key in settings: + gui.pages[mainpage].content.widgets[widget].storageVar.set(settings[key]) diff --git a/gui/randomize/__init__.py b/gui/randomize/__init__.py new file mode 100644 index 00000000..ecf3a271 --- /dev/null +++ b/gui/randomize/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui.randomize" package diff --git a/gui/randomize/dungeon.py b/gui/randomize/dungeon.py new file mode 100644 index 00000000..1d575521 --- /dev/null +++ b/gui/randomize/dungeon.py @@ -0,0 +1,38 @@ +from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +import gui.widgets as widgets +import json +import os + +def dungeon_page(parent): + # Dungeon Shuffle + self = ttk.Frame(parent) + + # Dungeon Shuffle options + self.widgets = {} + + # Dungeon Shuffle option sections + self.frames = {} + self.frames["keysanity"] = Frame(self) + self.frames["keysanity"].pack(anchor=W) + + ## Dungeon Item Shuffle + mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ") + mscbLabel.pack(side=LEFT) + + with open(os.path.join("resources","app","gui","randomize","dungeon","keysanity.json")) as keysanityItems: + myDict = json.load(keysanityItems) + dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["keysanity"]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + self.widgets[key].pack(side=LEFT) + + self.frames["widgets"] = Frame(self) + self.frames["widgets"].pack(anchor=W) + with open(os.path.join("resources","app","gui","randomize","dungeon","widgets.json")) as dungeonWidgets: + myDict = json.load(dungeonWidgets) + dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + self.widgets[key].pack(anchor=W) + + return self diff --git a/gui/randomize/enemizer.py b/gui/randomize/enemizer.py new file mode 100644 index 00000000..cb78281c --- /dev/null +++ b/gui/randomize/enemizer.py @@ -0,0 +1,63 @@ +import os +from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, LabelFrame, OptionMenu, N, E, W, LEFT, RIGHT, BOTTOM, X +import gui.widgets as widgets +import json +import os +import webbrowser + +def enemizer_page(parent,settings): + def open_enemizer_download(_evt): + webbrowser.open("https://github.com/Bonta0/Enemizer/releases") + + # Enemizer + self = ttk.Frame(parent) + + # Enemizer options + self.widgets = {} + + # Enemizer option sections + self.frames = {} + + self.frames["checkboxes"] = Frame(self) + self.frames["checkboxes"].pack(anchor=W) + + self.frames["selectOptionsFrame"] = Frame(self) + self.frames["leftEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"]) + self.frames["rightEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"]) + self.frames["bottomEnemizerFrame"] = Frame(self) + self.frames["selectOptionsFrame"].pack(fill=X) + self.frames["leftEnemizerFrame"].pack(side=LEFT) + self.frames["rightEnemizerFrame"].pack(side=RIGHT) + self.frames["bottomEnemizerFrame"].pack(fill=X) + + with open(os.path.join("resources","app","gui","randomize","enemizer","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + packAttrs = {"anchor":E} + if self.widgets[key].type == "checkbox": + packAttrs["anchor"] = W + self.widgets[key].pack(packAttrs) + + ## Enemizer CLI Path + enemizerPathFrame = Frame(self.frames["bottomEnemizerFrame"]) + enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ") + enemizerCLIlabel.pack(side=LEFT) + enemizerURL = Label(enemizerPathFrame, text="(get online)", fg="blue", cursor="hand2") + enemizerURL.pack(side=LEFT) + enemizerURL.bind("", open_enemizer_download) + self.enemizerCLIpathVar = StringVar(value=settings["enemizercli"]) + enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=self.enemizerCLIpathVar) + enemizerCLIpathEntry.pack(side=LEFT, fill=X, expand=True) + def EnemizerSelectPath(): + path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")], initialdir=os.path.join(".")) + if path: + self.enemizerCLIpathVar.set(path) + settings["enemizercli"] = path + enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath) + enemizerCLIbrowseButton.pack(side=LEFT) + enemizerPathFrame.pack(fill=X) + + return self,settings diff --git a/gui/randomize/entrando.py b/gui/randomize/entrando.py new file mode 100644 index 00000000..3ad6bac4 --- /dev/null +++ b/gui/randomize/entrando.py @@ -0,0 +1,29 @@ +from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +import gui.widgets as widgets +import json +import os + +def entrando_page(parent): + # Entrance Randomizer + self = ttk.Frame(parent) + + # Entrance Randomizer options + self.widgets = {} + + # Entrance Randomizer option sections + self.frames = {} + self.frames["widgets"] = Frame(self) + self.frames["widgets"].pack(anchor=W) + + with open(os.path.join("resources","app","gui","randomize","entrando","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + packAttrs = {"anchor":E} + if self.widgets[key].type == "checkbox": + packAttrs["anchor"] = W + self.widgets[key].pack(packAttrs) + + return self diff --git a/gui/randomize/gameoptions.py b/gui/randomize/gameoptions.py new file mode 100644 index 00000000..eb107df4 --- /dev/null +++ b/gui/randomize/gameoptions.py @@ -0,0 +1,78 @@ +from tkinter import ttk, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +from functools import partial +import classes.SpriteSelector as spriteSelector +import gui.widgets as widgets +import json +import os + +def gameoptions_page(top, parent): + # Game Options + self = ttk.Frame(parent) + + # Game Options options + self.widgets = {} + + # Game Options option sections + self.frames = {} + self.frames["checkboxes"] = Frame(self) + self.frames["checkboxes"].pack(anchor=W) + + self.frames["leftRomOptionsFrame"] = Frame(self) + self.frames["rightRomOptionsFrame"] = Frame(self) + self.frames["leftRomOptionsFrame"].pack(side=LEFT) + self.frames["rightRomOptionsFrame"].pack(side=RIGHT) + + with open(os.path.join("resources","app","gui","randomize","gameoptions","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + packAttrs = {"anchor":E} + if self.widgets[key].type == "checkbox": + packAttrs["anchor"] = W + self.widgets[key].pack(packAttrs) + + ## Sprite selection + spriteDialogFrame = Frame(self.frames["leftRomOptionsFrame"]) + baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') + + self.widgets["sprite"] = {} + self.widgets["sprite"]["spriteObject"] = None + self.widgets["sprite"]["spriteNameVar"] = StringVar() + + self.widgets["sprite"]["spriteNameVar"].set('(unchanged)') + spriteEntry = Label(spriteDialogFrame, textvariable=self.widgets["sprite"]["spriteNameVar"]) + + def sprite_setter(spriteObject): + self.widgets["sprite"]["spriteObject"] = spriteObject + + def sprite_select(): + spriteSelector.SpriteSelector(parent, partial(set_sprite, spriteSetter=sprite_setter, + spriteNameVar=self.widgets["sprite"]["spriteNameVar"], + randomSpriteVar=top.randomSprite)) + + spriteSelectButton = Button(spriteDialogFrame, text='...', command=sprite_select) + + baseSpriteLabel.pack(side=LEFT) + spriteEntry.pack(side=LEFT) + spriteSelectButton.pack(side=LEFT) + spriteDialogFrame.pack(anchor=E) + + return self + + +def set_sprite(sprite_param, random_sprite=False, spriteSetter=None, spriteNameVar=None, randomSpriteVar=None): + if sprite_param is None or not sprite_param.valid: + if spriteSetter: + spriteSetter(None) + if spriteNameVar is not None: + spriteNameVar.set('(unchanged)') + else: + if spriteSetter: + spriteSetter(sprite_param) + if spriteNameVar is not None: + spriteNameVar.set(sprite_param.name) + if randomSpriteVar: + randomSpriteVar.set(random_sprite) + diff --git a/gui/randomize/generation.py b/gui/randomize/generation.py new file mode 100644 index 00000000..d5f5ce1c --- /dev/null +++ b/gui/randomize/generation.py @@ -0,0 +1,45 @@ +import os +from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, E, W, LEFT, RIGHT, X +import gui.widgets as widgets +import json +import os + +def generation_page(parent,settings): + # Generation Setup + self = ttk.Frame(parent) + + # Generation Setup options + self.widgets = {} + + # Generation Setup option sections + self.frames = {} + self.frames["checkboxes"] = Frame(self) + self.frames["checkboxes"].pack(anchor=W) + + with open(os.path.join("resources","app","gui","randomize","generation","checkboxes.json")) as checkboxes: + myDict = json.load(checkboxes) + dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["checkboxes"]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + self.widgets[key].pack(anchor=W) + + self.frames["baserom"] = Frame(self) + self.frames["baserom"].pack(anchor=W, fill=X) + ## Locate base ROM + baseRomFrame = Frame(self.frames["baserom"]) + baseRomLabel = Label(baseRomFrame, text='Base Rom: ') + self.romVar = StringVar() + romEntry = Entry(baseRomFrame, textvariable=self.romVar) + self.romVar.set(settings["rom"]) + + def RomSelect(): + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join(".")) + self.romVar.set(rom) + romSelectButton = Button(baseRomFrame, text='Select Rom', command=RomSelect) + + baseRomLabel.pack(side=LEFT) + romEntry.pack(side=LEFT, fill=X, expand=True) + romSelectButton.pack(side=LEFT) + baseRomFrame.pack(fill=X) + + return self,settings diff --git a/gui/randomize/item.py b/gui/randomize/item.py new file mode 100644 index 00000000..f962d574 --- /dev/null +++ b/gui/randomize/item.py @@ -0,0 +1,35 @@ +from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +import gui.widgets as widgets +import json +import os + +def item_page(parent): + # Item Randomizer + self = ttk.Frame(parent) + + # Item Randomizer options + self.widgets = {} + + # Item Randomizer option sections + self.frames = {} + + self.frames["checkboxes"] = Frame(self) + self.frames["checkboxes"].pack(anchor=W) + + self.frames["leftItemFrame"] = Frame(self) + self.frames["rightItemFrame"] = Frame(self) + self.frames["leftItemFrame"].pack(side=LEFT) + self.frames["rightItemFrame"].pack(side=RIGHT) + + with open(os.path.join("resources","app","gui","randomize","item","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + packAttrs = {"anchor":E} + if self.widgets[key].type == "checkbox": + packAttrs["anchor"] = W + self.widgets[key].pack(packAttrs) + + return self diff --git a/gui/randomize/multiworld.py b/gui/randomize/multiworld.py new file mode 100644 index 00000000..646b02d1 --- /dev/null +++ b/gui/randomize/multiworld.py @@ -0,0 +1,38 @@ +from tkinter import ttk, StringVar, Entry, Frame, Label, Spinbox, N, E, W, X, LEFT, RIGHT +import gui.widgets as widgets +import json +import os + +def multiworld_page(parent,settings): + # Multiworld + self = ttk.Frame(parent) + + # Multiworld options + self.widgets = {} + + # Multiworld option sections + self.frames = {} + self.frames["widgets"] = Frame(self) + self.frames["widgets"].pack(anchor=W, fill=X) + + with open(os.path.join("resources","app","gui","randomize","multiworld","widgets.json")) as multiworldItems: + myDict = json.load(multiworldItems) + dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"]) + for key in dictWidgets: + self.widgets[key] = dictWidgets[key] + self.widgets[key].pack(side=LEFT, anchor=N) + + ## List of Player Names + key = "names" + self.widgets[key] = Frame(self.frames["widgets"]) + self.widgets[key].label = Label(self.widgets[key], text='Player names') + self.widgets[key].storageVar = StringVar(value=settings["names"]) + def saveMultiNames(caller,_,mode): + settings["names"] = self.widgets[key].storageVar.get() + self.widgets[key].storageVar.trace_add("write",saveMultiNames) + self.widgets[key].textbox = Entry(self.widgets[key], textvariable=self.widgets[key].storageVar) + self.widgets[key].label.pack(side=LEFT) + self.widgets[key].textbox.pack(side=LEFT, fill=X, expand=True) + self.widgets[key].pack(anchor=N, fill=X, expand=True) + + return self,settings diff --git a/gui/startinventory/__init__.py b/gui/startinventory/__init__.py new file mode 100644 index 00000000..fa319bae --- /dev/null +++ b/gui/startinventory/__init__.py @@ -0,0 +1 @@ +# do nothing, just exist to make "gui.startinventory" package diff --git a/gui/startinventory/overview.py b/gui/startinventory/overview.py new file mode 100644 index 00000000..aaa601b3 --- /dev/null +++ b/gui/startinventory/overview.py @@ -0,0 +1,63 @@ +from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, LEFT, RIGHT, X, VERTICAL, Y +import gui.widgets as widgets +import json +import os + +import classes.constants as CONST + +def startinventory_page(top,parent): + # Starting Inventory + self = ttk.Frame(parent) + + def create_list_frame(parent, framename): + parent.frames[framename] = Frame(parent) + parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N) + parent.frames[framename].thisRow = 0 + parent.frames[framename].thisCol = 0 + + def create_vertical_rule(num=1): + for i in range(0,num): + ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y) + + def validation(P): + if str.isdigit(P) or P == "": + return True + else: + return False + vcmd=(self.register(validation), '%P') + + # Starting Inventory options + self.startingWidgets = {} + + # Starting Inventory option sections + self.frames = {} + create_list_frame(self,"itemList1") + create_vertical_rule(2) + create_list_frame(self,"itemList2") + create_vertical_rule(2) + create_list_frame(self,"itemList3") + create_vertical_rule(2) + create_list_frame(self,"itemList4") + create_vertical_rule(2) + create_list_frame(self,"itemList5") + + with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns: + myDict = json.load(widgetDefns) + for key in CONST.CANTSTARTWITH: + for num in range(1, 5 + 1): + thisList = "itemList" + str(num) + if key in myDict[thisList]: + del myDict[thisList][key] + for framename,theseWidgets in myDict.items(): + dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename]) + for key in dictWidgets: + self.startingWidgets[key] = dictWidgets[key] + + for key in CONST.CUSTOMITEMS: + if key not in CONST.CANTSTARTWITH: + val = 0 + if key in top.settings["startinventoryarray"]: + val = top.settings["startinventoryarray"][key] + self.startingWidgets[key].storageVar.set(val) + + return self diff --git a/gui/widgets.py b/gui/widgets.py new file mode 100644 index 00000000..b4d15557 --- /dev/null +++ b/gui/widgets.py @@ -0,0 +1,145 @@ +from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, RIGHT, X + +class Empty(): + pass + +class mySpinbox(Spinbox): + def __init__(self, *args, **kwargs): + Spinbox.__init__(self, *args, **kwargs) + self.bind('', self.mouseWheel) + self.bind('', self.mouseWheel) + self.bind('', self.mouseWheel) + + def mouseWheel(self, event): + if event.num == 5 or event.delta == -120: + self.invoke('buttondown') + elif event.num == 4 or event.delta == 120: + self.invoke('buttonup') + +def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): + self = Frame(parent, name="checkframe-" + label.lower()) + self.storageVar = storageVar + self.checkbox = Checkbutton(self, text=label, variable=self.storageVar, name="checkbox-" + label.lower()) + if managerAttrs is not None: + self.checkbox.pack(managerAttrs) + else: + self.checkbox.pack() + return self + +def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs): + def change_storage(*args): + self.storageVar.set(options[self.labelVar.get()]) + def change_selected(*args): + keys = options.keys() + vals = options.values() + keysList = list(keys) + valsList = list(vals) + self.labelVar.set(keysList[valsList.index(str(self.storageVar.get()))]) + self = Frame(parent, name="selectframe-" + label.lower()) + self.storageVar = storageVar + self.storageVar.trace_add("write",change_selected) + self.labelVar = StringVar() + self.labelVar.trace_add("write",change_storage) + self.label = Label(self, text=label) + if managerAttrs is not None and "label" in managerAttrs: + self.label.pack(managerAttrs["label"]) + else: + self.label.pack() + self.selectbox = OptionMenu(self, self.labelVar, *options.keys()) + self.selectbox.config(width=20) + self.labelVar.set(managerAttrs["default"] if "default" in managerAttrs else list(options.keys())[0]) + if managerAttrs is not None and "selectbox" in managerAttrs: + self.selectbox.pack(managerAttrs["selectbox"]) + else: + self.selectbox.pack() + return self + +def make_spinbox(self, parent, label, storageVar, manager, managerAttrs): + self = Frame(parent, name="spinframe-" + label.lower()) + self.storageVar = storageVar + self.label = Label(self, text=label) + if managerAttrs is not None and "label" in managerAttrs: + self.label.pack(managerAttrs["label"]) + else: + self.label.pack() + fromNum = 1 + toNum = 100 + if "spinbox" in managerAttrs: + if "from" in managerAttrs: + fromNum = managerAttrs["spinbox"]["from"] + if "to" in managerAttrs: + toNum = managerAttrs["spinbox"]["to"] + self.spinbox = mySpinbox(self, from_=fromNum, to=toNum, width=5, textvariable=self.storageVar, name="spinbox-" + label.lower()) + if managerAttrs is not None and "spinbox" in managerAttrs: + self.spinbox.pack(managerAttrs["spinbox"]) + else: + self.spinbox.pack() + return self + +def make_textbox(self, parent, label, storageVar, manager, managerAttrs): + widget = Empty() + widget.storageVar = storageVar + widget.label = Label(parent, text=label) + widget.textbox = Entry(parent, justify=RIGHT, textvariable=widget.storageVar, width=3) + if "default" in managerAttrs: + widget.storageVar.set(managerAttrs["default"]) + + # grid + if manager == "grid": + widget.label.grid(managerAttrs["label"] if managerAttrs is not None and "label" in managerAttrs else None, row=parent.thisRow, column=parent.thisCol) + parent.thisCol += 1 + widget.textbox.grid(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None, row=parent.thisRow, column=parent.thisCol) + parent.thisRow += 1 + parent.thisCol = 0 + + # pack + elif manager == "pack": + widget.label.pack(managerAttrs["label"] if managerAttrs is not None and "label" in managerAttrs else None) + widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None) + return widget + + +def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None): + widget = None + if manager is None: + manager = "pack" + thisStorageVar = storageVar + if isinstance(storageVar,str): + if storageVar == "int" or storageVar == "integer": + thisStorageVar = IntVar() + elif storageVar == "str" or storageVar == "string": + thisStorageVar = StringVar() + + if type == "checkbox": + if thisStorageVar is None: + thisStorageVar = IntVar() + widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs) + elif type == "selectbox": + if thisStorageVar is None: + thisStorageVar = StringVar() + widget = make_selectbox(self, parent, label, options, thisStorageVar, manager, managerAttrs) + elif type == "spinbox": + if thisStorageVar is None: + thisStorageVar = StringVar() + widget = make_spinbox(self, parent, label, thisStorageVar, manager, managerAttrs) + elif type == "textbox": + if thisStorageVar is None: + thisStorageVar = StringVar() + widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs) + widget.type = type + return widget + +def make_widget_from_dict(self, defn, parent): + type = defn["type"] if "type" in defn else None + label = defn["label"]["text"] if "label" in defn and "text" in defn["label"] else "" + manager = defn["manager"] if "manager" in defn else None + managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None + options = defn["options"] if "options" in defn else None + widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options) + return widget + +def make_widgets_from_dict(self, defns, parent): + widgets = {} + for key,defn in defns.items(): + widgets[key] = make_widget_from_dict(self, defn, parent) + return widgets diff --git a/resources/app/gui/adjust/overview/widgets.json b/resources/app/gui/adjust/overview/widgets.json new file mode 100644 index 00000000..30fe2445 --- /dev/null +++ b/resources/app/gui/adjust/overview/widgets.json @@ -0,0 +1,124 @@ +{ + "checkboxes": { + "nobgm": { + "type": "checkbox", + "label": { + "text": "Disable Music & MSU-1" + } + }, + "quickswap": { + "type": "checkbox", + "label": { + "text": "L/R Quickswapping" + } + } + }, + "leftAdjustFrame": { + "heartcolor": { + "type": "selectbox", + "label": { + "text": "Heart Color" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Red": "red", + "Blue": "blue", + "Green": "green", + "Yellow": "yellow", + "Random": "random" + } + }, + "heartbeep": { + "type": "selectbox", + "label": { + "text": "Heart Beep sound rate" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Normal" + }, + "options": { + "Double": "double", + "Normal": "normal", + "Half": "half", + "Quarter": "quarter", + "Off": "off" + } + } + }, + "rightAdjustFrame": { + "menuspeed": { + "type": "selectbox", + "label": { + "text": "Menu Speed" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Normal" + }, + "options": { + "Instant": "instant", + "Quadruple": "quadruple", + "Triple": "triple", + "Double": "double", + "Normal": "normal", + "Half": "half" + } + }, + "owpalettes": { + "type": "selectbox", + "label": { + "text": "Overworld Palettes" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Default": "default", + "Random": "random", + "Blackout": "blackout" + } + }, + "uwpalettes": { + "type": "selectbox", + "label": { + "text": "Underworld Palettes" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Default": "default", + "Random": "random", + "Blackout": "blackout" + } + } + } +} diff --git a/resources/app/gui/custom/overview/widgets.json b/resources/app/gui/custom/overview/widgets.json new file mode 100644 index 00000000..faaa9637 --- /dev/null +++ b/resources/app/gui/custom/overview/widgets.json @@ -0,0 +1,935 @@ +{ + "itemList1": { + "bow": { + "type": "textbox", + "label": { + "text": "Bow" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "progressivebow": { + "type": "textbox", + "label": { + "text": "Progressive Bow" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 2 + } + }, + "boomerang": { + "type": "textbox", + "label": { + "text": "Blue Boomerang" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "redmerang": { + "type": "textbox", + "label": { + "text": "Red Boomerang" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "hookshot": { + "type": "textbox", + "label": { + "text": "Hookshot" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "mushroom": { + "type": "textbox", + "label": { + "text": "Mushroom" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "powder": { + "type": "textbox", + "label": { + "text": "Magic Powder" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "firerod": { + "type": "textbox", + "label": { + "text": "Fire Rod" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "icerod": { + "type": "textbox", + "label": { + "text": "Ice Rod" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "bombos": { + "type": "textbox", + "label": { + "text": "Bombos" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "ether": { + "type": "textbox", + "label": { + "text": "Ether" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "quake": { + "type": "textbox", + "label": { + "text": "Quake" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "lamp": { + "type": "textbox", + "label": { + "text": "Lamp" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "hammer": { + "type": "textbox", + "label": { + "text": "Hammer" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "shovel": { + "type": "textbox", + "label": { + "text": "Shovel" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + } + }, + "itemList2": { + "flute": { + "type": "textbox", + "label": { + "text": "Flute" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "bugnet": { + "type": "textbox", + "label": { + "text": "Bug Net" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "book": { + "type": "textbox", + "label": { + "text": "Book" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "bottle": { + "type": "textbox", + "label": { + "text": "Bottle" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 4 + } + }, + "somaria": { + "type": "textbox", + "label": { + "text": "Cane of Somaria" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "byrna": { + "type": "textbox", + "label": { + "text": "Cane of Byrna" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "cape": { + "type": "textbox", + "label": { + "text": "Magic Cape" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "mirror": { + "type": "textbox", + "label": { + "text": "Magic Mirror" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "boots": { + "type": "textbox", + "label": { + "text": "Pegasus Boots" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "powerglove": { + "type": "textbox", + "label": { + "text": "Power Glove" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "titansmitt": { + "type": "textbox", + "label": { + "text": "Titan's Mitt" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "progressiveglove": { + "type": "textbox", + "label": { + "text": "Progressive Glove" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 2 + } + }, + "flippers": { + "type": "textbox", + "label": { + "text": "Flippers" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "pearl": { + "type": "textbox", + "label": { + "text": "Moon Pearl" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "heartpiece": { + "type": "textbox", + "label": { + "text": "Piece of Heart" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 24 + } + } + }, + "itemList3": { + "heartcontainer": { + "type": "textbox", + "label": { + "text": "Heart Container" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 10 + } + }, + "sancheart": { + "type": "textbox", + "label": { + "text": "Sanctuary Heart" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "sword1": { + "type": "textbox", + "label": { + "text": "Fighters' Sword" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "sword2": { + "type": "textbox", + "label": { + "text": "Master Sword" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "sword3": { + "type": "textbox", + "label": { + "text": "Tempered Sword" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "sword4": { + "type": "textbox", + "label": { + "text": "Golden Sword" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "progressivesword": { + "type": "textbox", + "label": { + "text": "Progressive Sword" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 4 + } + }, + "shield1": { + "type": "textbox", + "label": { + "text": "Fighters' Shield" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "shield2": { + "type": "textbox", + "label": { + "text": "Fire Shield" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "shield3": { + "type": "textbox", + "label": { + "text": "Mirror Shield" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "progressiveshield": { + "type": "textbox", + "label": { + "text": "Progressive Shield" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 3 + } + }, + "mail2": { + "type": "textbox", + "label": { + "text": "Blue Mail" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "mail3": { + "type": "textbox", + "label": { + "text": "Red Mail" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "progressivemail": { + "type": "textbox", + "label": { + "text": "Progressive Mail" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 2 + } + }, + "halfmagic": { + "type": "textbox", + "label": { + "text": "Half Magic" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + } + }, + "itemList4": { + "quartermagic": { + "type": "textbox", + "label": { + "text": "Quarter Magic" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "bombsplus5": { + "type": "textbox", + "label": { + "text": "Bomb Cap +5" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "bombsplus10": { + "type": "textbox", + "label": { + "text": "Bomb Cap +10" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "arrowsplus5": { + "type": "textbox", + "label": { + "text": "Arrow Cap +5" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "arrowsplus10": { + "type": "textbox", + "label": { + "text": "Arrow Cap +10" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "arrow1": { + "type": "textbox", + "label": { + "text": "Arrow (1)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "arrow10": { + "type": "textbox", + "label": { + "text": "Arrow (10)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 12 + } + }, + "bomb1": { + "type": "textbox", + "label": { + "text": "Bomb (1)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "bomb3": { + "type": "textbox", + "label": { + "text": "Bomb (3)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 16 + } + }, + "bomb10": { + "type": "textbox", + "label": { + "text": "Bomb (10)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + }, + "rupee1": { + "type": "textbox", + "label": { + "text": "Rupee (1)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 2 + } + }, + "rupee5": { + "type": "textbox", + "label": { + "text": "Rupee (5)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 4 + } + }, + "rupee20": { + "type": "textbox", + "label": { + "text": "Rupee (20)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 28 + } + }, + "rupee50": { + "type": "textbox", + "label": { + "text": "Rupee (50)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 7 + } + }, + "rupee100": { + "type": "textbox", + "label": { + "text": "Rupee (100)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 1 + } + } + }, + "itemList5": { + "rupee300": { + "type": "textbox", + "label": { + "text": "Rupee (300)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 5 + } + }, + "blueclock": { + "type": "textbox", + "label": { + "text": "Blue Clock" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "greenclock": { + "type": "textbox", + "label": { + "text": "Green Clock" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "redclock": { + "type": "textbox", + "label": { + "text": "Red Clock" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "silversupgrade": { + "type": "textbox", + "label": { + "text": "Silver Arrows Upgrade" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "generickeys": { + "type": "textbox", + "label": { + "text": "Generic Keys" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "triforcepieces": { + "type": "textbox", + "label": { + "text": "Triforce Pieces" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "triforcepiecesgoal": { + "type": "textbox", + "label": { + "text": "Triforce Pieces Goal" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "triforce": { + "type": "textbox", + "label": { + "text": "Triforce (win game)" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "rupoor": { + "type": "textbox", + "label": { + "text": "Rupoor" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 0 + } + }, + "rupoorcost": { + "type": "textbox", + "label": { + "text": "Rupoor Cost" + }, + "manager": "grid", + "managerAttrs": { + "label": { + "sticky": "w" + }, + "default": 10 + } + } + } +} \ No newline at end of file diff --git a/resources/app/gui/randomize/dungeon/keysanity.json b/resources/app/gui/randomize/dungeon/keysanity.json new file mode 100644 index 00000000..1be4c3fd --- /dev/null +++ b/resources/app/gui/randomize/dungeon/keysanity.json @@ -0,0 +1,26 @@ +{ + "mapshuffle": { + "type": "checkbox", + "label": { + "text": "Maps" + } + }, + "compassshuffle": { + "type": "checkbox", + "label": { + "text": "Compasses" + } + }, + "smallkeyshuffle": { + "type": "checkbox", + "label": { + "text": "Small Keys" + } + }, + "bigkeyshuffle": { + "type": "checkbox", + "label": { + "text": "Big Keys" + } + } +} diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json new file mode 100644 index 00000000..60255d8e --- /dev/null +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -0,0 +1,48 @@ +{ + "dungeondoorshuffle": { + "type": "selectbox", + "label": { + "text": "Dungeon Door Shuffle" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Basic" + }, + "options": { + "Vanilla": "vanilla", + "Basic": "basic", + "Crossed": "crossed" + } + }, + "experimental": { + "type": "checkbox", + "label": { + "text": "Enable Experimental Features" + } + }, + "dungeon_counters": { + "type": "selectbox", + "label": { + "text": "Dungeon Chest Counters" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Off" + }, + "options": { + "Off": "off", + "On": "on", + "On Compass Pickup": "pickup" + } + } +} diff --git a/resources/app/gui/randomize/enemizer/widgets.json b/resources/app/gui/randomize/enemizer/widgets.json new file mode 100644 index 00000000..4095ab5b --- /dev/null +++ b/resources/app/gui/randomize/enemizer/widgets.json @@ -0,0 +1,93 @@ +{ + "checkboxes": { + "potshuffle": { + "type": "checkbox", + "label": { + "text": "Pot Shuffle" + } + } + }, + "leftEnemizerFrame": { + "enemyshuffle": { + "type": "selectbox", + "label": { + "text": "Enemy Shuffle" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Vanilla": "none", + "Shuffled": "shuffled", + "Chaos": "chaos" + } + }, + "bossshuffle": { + "type": "selectbox", + "label": { + "text": "Boss Shuffle" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Vanilla": "none", + "Basic": "basic", + "Shuffled": "shuffled", + "Chaos": "chaos" + } + } + }, + "rightEnemizerFrame": { + "enemydamage": { + "type": "selectbox", + "label": { + "text": "Enemy Damage" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Vanilla": "default", + "Shuffled": "shuffled", + "Chaos": "chaos" + } + }, + "enemyhealth": { + "type": "selectbox", + "label": { + "text": "Enemy Health" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Vanilla": "default", + "Easy": "easy", + "Normal": "normal", + "Hard": "hard", + "Expert": "expert" + } + } + } +} \ No newline at end of file diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json new file mode 100644 index 00000000..1a3ae127 --- /dev/null +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -0,0 +1,40 @@ +{ + "widgets": { + "openpyramid": { + "type": "checkbox", + "label": { + "text": "Pre-open Pyramid Hole" + } + }, + "shuffleganon": { + "type": "checkbox", + "label": { + "text": "Include Ganon's Tower and Pyramid Hole in shuffle pool" + } + }, + "entranceshuffle": { + "type": "selectbox", + "label": { + "text": "Entrance Shuffle" + }, + "managerAttrs": { + "label": { "side": "left" }, + "selectbox": { "side": "right" } + }, + "options": { + "Vanilla": "vanilla", + "Simple": "simple", + "Restricted": "restricted", + "Full": "full", + "Crossed": "crossed", + "Insanity": "insanity", + "Restricted (Legacy)": "restricted_legacy", + "Full (Legacy)": "full_legacy", + "Madness (Legacy)": "madness_legacy", + "Insanity (Legacy)": "insanity_legacy", + "Dungeons + Full": "dungeonsfull", + "Dungeons + Simple": "dungeonssimple" + } + } + } +} diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json new file mode 100644 index 00000000..0a0a11ce --- /dev/null +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -0,0 +1,131 @@ +{ + "checkboxes": { + "hints": { + "type": "checkbox", + "label": { + "text": "Include Helpful Hints" + }, + "default": "true" + }, + "nobgm": { + "type": "checkbox", + "label": { + "text": "Disable Music & MSU-1" + } + }, + "quickswap": { + "type": "checkbox", + "label": { + "text": "L/R Quickswapping" + } + } + }, + "leftRomOptionsFrame": { + "heartcolor": { + "type": "selectbox", + "label": { + "text": "Heart Color" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Red": "red", + "Blue": "blue", + "Green": "green", + "Yellow": "yellow", + "Random": "random" + } + }, + "heartbeep": { + "type": "selectbox", + "label": { + "text": "Heart Beep sound rate" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Normal" + }, + "options": { + "Double": "double", + "Normal": "normal", + "Half": "half", + "Quarter": "quarter", + "Off": "off" + } + } + }, + "rightRomOptionsFrame": { + "menuspeed": { + "type": "selectbox", + "label": { + "text": "Menu Speed" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Normal" + }, + "options": { + "Instant": "instant", + "Quadruple": "quadruple", + "Triple": "triple", + "Double": "double", + "Normal": "normal", + "Half": "half" + } + }, + "owpalettes": { + "type": "selectbox", + "label": { + "text": "Overworld Palettes" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Default": "default", + "Random": "random", + "Blackout": "blackout" + } + }, + "uwpalettes": { + "type": "selectbox", + "label": { + "text": "Underworld Palettes" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Default": "default", + "Random": "random", + "Blackout": "blackout" + } + } + } +} diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json new file mode 100644 index 00000000..b7228c78 --- /dev/null +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -0,0 +1,45 @@ +{ + "spoiler": { + "type": "checkbox", + "label": { + "text": "Create Spoiler Log" + } + }, + "suppressrom": { + "type": "checkbox", + "label": { + "text": "Do not create patched ROM" + } + }, + "usestartinventory": { + "type": "checkbox", + "label": { + "text": "Use starting inventory" + } + }, + "usecustompool": { + "type": "checkbox", + "label": { + "text": "Use custom item pool" + } + }, + "saveonexit": { + "type": "selectbox", + "label": { + "text": "Save Settings on Exit" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Ask Me": "ask", + "Always": "always", + "Never": "never" + } + } +} diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json new file mode 100644 index 00000000..0c427418 --- /dev/null +++ b/resources/app/gui/randomize/item/widgets.json @@ -0,0 +1,266 @@ +{ + "checkboxes": { + "retro": { + "type": "checkbox", + "label": { + "text": "Retro mode (universal keys)" + } + } + }, + "leftItemFrame": { + "worldstate": { + "type": "selectbox", + "label": { + "text": "World State" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Open" + }, + "options": { + "Standard": "standard", + "Open": "open", + "Inverted": "inverted" + } + }, + "logiclevel": { + "type": "selectbox", + "label": { + "text": "Logic Level" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "No Glitches": "noglitches", + "Minor Glitches": "minorglitches", + "No Logic": "nologic" + } + }, + "goal": { + "type": "selectbox", + "label": { + "text": "Goal" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Defeat Ganon": "ganon", + "Master Sword Pedestal": "pedestal", + "All Dungeons": "dungeons", + "Triforce Hunt": "triforcehunt", + "Crystals": "crystals" + } + }, + "crystals_gt": { + "type": "selectbox", + "label": { + "text": "Crystals to open GT" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "Random": "random" + } + }, + "crystals_ganon": { + "type": "selectbox", + "label": { + "text": "Crystals to harm Ganon" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "0": "0", + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "Random": "random" + } + }, + "weapons": { + "type": "selectbox", + "label": { + "text": "Weapons" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Randomized": "random", + "Assured": "assured", + "Swordless": "swordless", + "Vanilla": "vanilla" + } + } + }, + "rightItemFrame": { + "itempool": { + "type": "selectbox", + "label": { + "text": "Item Pool" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Normal": "normal", + "Hard": "hard", + "Expert": "expert" + } + }, + "itemfunction": { + "type": "selectbox", + "label": { + "text": "Item Functionality" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "Normal": "normal", + "Hard": "hard", + "Expert": "expert" + } + }, + "timer": { + "type": "selectbox", + "label": { + "text": "Timer Setting" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "No Timer": "none", + "Stopwatch": "display", + "Timed": "timed", + "Timed OHKO": "timed-ohko", + "OHKO": "ohko", + "Timed Countdown": "timed-countdown" + } + }, + "progressives": { + "type": "selectbox", + "label": { + "text": "Progressive Items" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "On": "on", + "Off": "off", + "Random": "random" + } + }, + "accessibility": { + "type": "selectbox", + "label": { + "text": "Accessibility" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + } + }, + "options": { + "100% Inventory": "items", + "100% Locations": "locations", + "Beatable": "none" + } + }, + "sortingalgo": { + "type": "selectbox", + "label": { + "text": "Item Sorting" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "selectbox": { + "side": "right" + }, + "default": "Balanced" + }, + "options": { + "Freshness": "freshness", + "Flood": "flood", + "VT8.21": "vt21", + "VT8.22": "vt22", + "VT8.25": "vt25", + "VT8.26": "vt26", + "Balanced": "balanced" + } + } + } +} diff --git a/resources/app/gui/randomize/multiworld/widgets.json b/resources/app/gui/randomize/multiworld/widgets.json new file mode 100644 index 00000000..b943c017 --- /dev/null +++ b/resources/app/gui/randomize/multiworld/widgets.json @@ -0,0 +1,16 @@ +{ + "worlds": { + "type": "spinbox", + "label": { + "text": "Worlds" + }, + "managerAttrs": { + "label": { + "side": "left" + }, + "spinbox": { + "side": "right" + } + } + } +}