265 lines
17 KiB
Python
Executable File
265 lines
17 KiB
Python
Executable File
#!/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()
|