diff --git a/DungeonRandomizer.py b/DungeonRandomizer.py index d04d6fb5..f7b481c3 100755 --- a/DungeonRandomizer.py +++ b/DungeonRandomizer.py @@ -8,312 +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', 'experimental'], - 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. - Experimental: Experimental mixes live here. Use at your own risk. - ''') - 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', - '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) diff --git a/Gui.py b/Gui.py index 000f228a..02db9e42 100755 --- a/Gui.py +++ b/Gui.py @@ -14,6 +14,7 @@ from AdjusterMain import adjust from DungeonRandomizer import parse_arguments from gui.adjust.overview import adjust_page from gui.custom.overview import custom_page +from gui.loadcliargs import loadcliargs from gui.randomize.item import item_page from gui.randomize.entrando import entrando_page from gui.randomize.enemizer import enemizer_page @@ -109,57 +110,8 @@ def guiMain(args=None): return False vcmd=(self.customContent.register(validation), '%P') - 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 - self.generationSetupWindow.createSpoilerVar.set(int(args.create_spoiler)) - self.generationSetupWindow.suppressRomVar.set(int(args.suppress_rom)) - self.dungeonRandoWindow.mapshuffleVar.set(args.mapshuffle) - self.dungeonRandoWindow.compassshuffleVar.set(args.compassshuffle) - self.dungeonRandoWindow.keyshuffleVar.set(args.keyshuffle) - self.dungeonRandoWindow.bigkeyshuffleVar.set(args.bigkeyshuffle) - self.itemWindow.retroVar.set(args.retro) - self.entrandoWindow.openpyramidVar.set(args.openpyramid) - self.gameOptionsWindow.quickSwapVar.set(int(args.quickswap)) - self.gameOptionsWindow.disableMusicVar.set(int(args.disablemusic)) - if args.multi: - self.multiworldWindow.worldVar.set(str(args.multi)) - if args.count: - self.farBottomFrame.countVar.set(str(args.count)) - if args.seed: - self.farBottomFrame.seedVar.set(str(args.seed)) - self.itemWindow.modeVar.set(args.mode) - self.itemWindow.swordVar.set(args.swords) - self.itemWindow.difficultyVar.set(args.difficulty) - self.itemWindow.itemfunctionVar.set(args.item_functionality) - self.itemWindow.timerVar.set(args.timer) - self.itemWindow.progressiveVar.set(args.progressive) - self.itemWindow.accessibilityVar.set(args.accessibility) - self.itemWindow.goalVar.set(args.goal) - self.itemWindow.crystalsGTVar.set(args.crystals_gt) - self.itemWindow.crystalsGanonVar.set(args.crystals_ganon) - self.itemWindow.algorithmVar.set(args.algorithm) - self.entrandoWindow.shuffleVar.set(args.shuffle) - self.dungeonRandoWindow.doorShuffleVar.set(args.door_shuffle) - self.gameOptionsWindow.heartcolorVar.set(args.heartcolor) - self.gameOptionsWindow.heartbeepVar.set(args.heartbeep) - self.gameOptionsWindow.fastMenuVar.set(args.fastmenu) - self.itemWindow.logicVar.set(args.logic) - self.generationSetupWindow.romVar.set(args.rom) - self.entrandoWindow.shuffleGanonVar.set(args.shuffleganon) - self.gameOptionsWindow.hintsVar.set(args.hints) - self.enemizerWindow.enemizerCLIpathVar.set(args.enemizercli) - self.enemizerWindow.potShuffleVar.set(args.shufflepots) - self.enemizerWindow.enemyShuffleVar.set(args.shuffleenemies) - self.enemizerWindow.enemizerBossVar.set(args.shufflebosses) - self.enemizerWindow.enemizerDamageVar.set(args.enemy_damage) - self.enemizerWindow.enemizerHealthVar.set(args.enemy_health) - self.gameOptionsWindow.owPalettesVar.set(args.ow_palettes) - self.gameOptionsWindow.uwPalettesVar.set(args.uw_palettes) -# if args.sprite is not None: -# self.gameOptionsWindow.set_sprite(Sprite(args.sprite)) + # load args from CLI into options + loadcliargs(self,args) mainWindow.mainloop() diff --git a/gui/loadcliargs.py b/gui/loadcliargs.py new file mode 100644 index 00000000..4b8fb2c4 --- /dev/null +++ b/gui/loadcliargs.py @@ -0,0 +1,52 @@ +def loadcliargs(gui,args): + 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 + gui.generationSetupWindow.createSpoilerVar.set(int(args.create_spoiler)) + gui.generationSetupWindow.suppressRomVar.set(int(args.suppress_rom)) + gui.dungeonRandoWindow.mapshuffleVar.set(args.mapshuffle) + gui.dungeonRandoWindow.compassshuffleVar.set(args.compassshuffle) + gui.dungeonRandoWindow.keyshuffleVar.set(args.keyshuffle) + gui.dungeonRandoWindow.bigkeyshuffleVar.set(args.bigkeyshuffle) + gui.itemWindow.retroVar.set(args.retro) + gui.entrandoWindow.openpyramidVar.set(args.openpyramid) + gui.gameOptionsWindow.quickSwapVar.set(int(args.quickswap)) + gui.gameOptionsWindow.disableMusicVar.set(int(args.disablemusic)) + if args.multi: + gui.multiworldWindow.worldVar.set(str(args.multi)) + if args.count: + gui.farBottomFrame.countVar.set(str(args.count)) + if args.seed: + gui.farBottomFrame.seedVar.set(str(args.seed)) + gui.itemWindow.modeVar.set(args.mode) + gui.itemWindow.swordVar.set(args.swords) + gui.itemWindow.difficultyVar.set(args.difficulty) + gui.itemWindow.itemfunctionVar.set(args.item_functionality) + gui.itemWindow.timerVar.set(args.timer) + gui.itemWindow.progressiveVar.set(args.progressive) + gui.itemWindow.accessibilityVar.set(args.accessibility) + gui.itemWindow.goalVar.set(args.goal) + gui.itemWindow.crystalsGTVar.set(args.crystals_gt) + gui.itemWindow.crystalsGanonVar.set(args.crystals_ganon) + gui.itemWindow.algorithmVar.set(args.algorithm) + gui.entrandoWindow.shuffleVar.set(args.shuffle) + gui.dungeonRandoWindow.doorShuffleVar.set(args.door_shuffle) + gui.gameOptionsWindow.heartcolorVar.set(args.heartcolor) + gui.gameOptionsWindow.heartbeepVar.set(args.heartbeep) + gui.gameOptionsWindow.fastMenuVar.set(args.fastmenu) + gui.itemWindow.logicVar.set(args.logic) + gui.generationSetupWindow.romVar.set(args.rom) + gui.entrandoWindow.shuffleGanonVar.set(args.shuffleganon) + gui.gameOptionsWindow.hintsVar.set(args.hints) + gui.enemizerWindow.enemizerCLIpathVar.set(args.enemizercli) + gui.enemizerWindow.potShuffleVar.set(args.shufflepots) + gui.enemizerWindow.enemyShuffleVar.set(args.shuffleenemies) + gui.enemizerWindow.enemizerBossVar.set(args.shufflebosses) + gui.enemizerWindow.enemizerDamageVar.set(args.enemy_damage) + gui.enemizerWindow.enemizerHealthVar.set(args.enemy_health) + gui.gameOptionsWindow.owPalettesVar.set(args.ow_palettes) + gui.gameOptionsWindow.uwPalettesVar.set(args.uw_palettes) +# if args.sprite is not None: +# gui.gameOptionsWindow.set_sprite(Sprite(args.sprite)) \ No newline at end of file