#!/usr/bin/env python3 import argparse import os import logging import RaceRandom as random import textwrap import sys from Main import main from Utils import is_bundled, close_console, output_path class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) def start(): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') parser.add_argument('--json_spoiler', help='Output a JSON Spoiler File', action='store_true') parser.add_argument('--logic', default='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='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='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='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'ganonhunt', 'crystals', 'all_items', 'completionist'], 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. All Items: Requires collecting 216 items to defeat Ganon. Completionist: Same as above, plus All Dungeons ''') parser.add_argument('--difficulty', default='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='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='none', const='none', nargs='?', choices=['none', 'ohko'], help='''\ Select One-Hit KO (OHKO) game setting. (default: %(default)s) None: No timer. OHKO: Link will always die in one hit. ''') parser.add_argument('--progressive', default='on', const='on', nargs='?', choices=['on', 'off', 'random'], help='''\ Select progressive bow setting. Affects available itempool. (default: %(default)s) On: The first bow you find will give you access to the bow item. The second will give you Silver Arrows. Off: Bow and Silver Arrows are separate items. Random: Bows will randomly be progressive or not. ''') parser.add_argument('--shuffle', default='vanilla', const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', '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. Vanilla: All entrances are in the same locations they were in the base game. The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. ''') parser.add_argument('--crystals_ganon', default='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='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('--rom', default='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='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='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', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--pseudoboots', help='Enable pseduoboots (can dash but no logical checks).', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--keysanity', help='''\ Keys (and other dungeon items) are no longer restricted to their dungeons, but can be anywhere ''', action='store_true') parser.add_argument('--retro', 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('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by empty slots. ''', action='store_true') parser.add_argument('--accessibility', default='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', 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=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='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='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('--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('--huditemcounter', default=False, help='''\ Displays a (number of items collected) / (total items available) counter on the HUD. ''', action='store_true') parser.add_argument('--nofastrom', default=False, help='''\ Displays a (number of items collected) / (total items available) counter on the HUD. ''', action='store_true') 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=False) parser.add_argument('--enemizercli', default='') parser.add_argument('--shufflebosses', default='none', choices=['none', 'basic', 'normal', 'chaos']) parser.add_argument('--shuffleenemies', default=False, action='store_true') parser.add_argument('--enemy_health', default='default', choices=['default', 'easy', 'normal', 'hard', 'expert']) parser.add_argument('--enemy_damage', default='default', choices=['default', 'shuffled', 'chaos']) parser.add_argument('--shufflepalette', default=False, action='store_true') parser.add_argument('--shufflepots', default=False, action='store_true') parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--securerandom', default=False, action='store_true') parser.add_argument('--outputpath') parser.add_argument('--outputname') args = parser.parse_args() if args.outputpath and os.path.isdir(args.outputpath): output_path.cached_path = args.outputpath if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults from Gui import guiMain close_console() guiMain() sys.exit(0) # ToDo: Validate files further than mere existance if not args.jsonout and not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) sys.exit(1) if args.sprite is not None and not os.path.isfile(args.sprite): if not args.jsonout: input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) # set up logger loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel] logging.basicConfig(format='%(message)s', level=loglevel) if args.gui: from Gui import guiMain guiMain(args) elif args.count is not None: seed = args.seed for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) else: main(seed=args.seed, args=args) if __name__ == '__main__': start()