293 lines
20 KiB
Python
293 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import copy
|
|
import textwrap
|
|
import shlex
|
|
|
|
"""Legacy module, undergoing dismantling."""
|
|
|
|
|
|
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: max(int(value), 1))
|
|
multiargs, _ = parser.parse_known_args(argv)
|
|
|
|
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'hybridglitches', 'nologic'],
|
|
help='''\
|
|
Select Enforcement of Item Requirements. (default: %(default)s)
|
|
No Glitches:
|
|
Minor Glitches: May require Fake Flippers, Bunny Revival
|
|
and Dark Room Navigation.
|
|
Overworld Glitches: May require overworld glitches.
|
|
Hybrid Major Glitches: May require both overworld and underworld clipping.
|
|
No Logic: Distribute items without regard for
|
|
item requirements.
|
|
''')
|
|
parser.add_argument('--glitch_triforce', help='Allow glitching to Triforce from Ganon\'s room', action='store_true')
|
|
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('--goal', default=defval('ganon'), const='ganon', nargs='?',
|
|
choices=['ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'],
|
|
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.
|
|
Ganon Pedestal: Pull the Master Sword Pedestal, then defeat Ganon.
|
|
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.
|
|
Local Triforce Hunt: Places 30 Triforce Pieces in your world, collect
|
|
20 of them to beat the game.
|
|
Ganon Triforce Hunt: Places 30 Triforce Pieces in the world, collect
|
|
20 of them, then defeat Ganon.
|
|
Local Ganon Triforce Hunt: Places 30 Triforce Pieces in your world,
|
|
collect 20 of them, then defeat Ganon.
|
|
''')
|
|
parser.add_argument('--triforce_pieces_available', default=defval(30),
|
|
type=lambda value: min(max(int(value), 1), 90),
|
|
help='''Set Triforce Pieces available in item pool.''')
|
|
parser.add_argument('--triforce_pieces_required', default=defval(20),
|
|
type=lambda value: min(max(int(value), 1), 90),
|
|
help='''Set Triforce Pieces required to win a Triforce Hunt''')
|
|
parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?',
|
|
choices=['easy', 'normal', 'hard', 'expert'],
|
|
help='''\
|
|
Select game difficulty. Affects available itempool. (default: %(default)s)
|
|
Easy: An easier setting with some equipment duplicated and increased health.
|
|
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=['easy', 'normal', 'hard', 'expert'],
|
|
help='''\
|
|
Select limits on item functionality to increase difficulty. (default: %(default)s)
|
|
Easy: Easy functionality. (Medallions usable without sword)
|
|
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('--countdown_start_time', default=defval(10), type=int,
|
|
help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''')
|
|
parser.add_argument('--red_clock_time', default=defval(-2), type=int,
|
|
help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''')
|
|
parser.add_argument('--blue_clock_time', default=defval(2), type=int,
|
|
help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''')
|
|
parser.add_argument('--green_clock_time', default=defval(4), type=int,
|
|
help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''')
|
|
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
|
|
help='''\
|
|
Select dungeon counter display settings. (default: %(default)s)
|
|
(Note, since timer takes up the same space on the hud as dungeon
|
|
counters, timer settings override dungeon counter settings.)
|
|
Default: Dungeon counters only show when the compass is
|
|
picked up, or otherwise sent, only when compass
|
|
shuffle is turned on.
|
|
On: Dungeon counters are always displayed.
|
|
Pickup: Dungeon counters are shown when the compass is
|
|
picked up, even when compass shuffle is turned
|
|
off.
|
|
Off: Dungeon counters are never shown.
|
|
''')
|
|
|
|
parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?',
|
|
choices=['freshness', 'flood', 'vt25', 'vt26', 'balanced'],
|
|
help='''\
|
|
Select item filling algorithm. (default: %(default)s
|
|
balanced: vt26 derivitive 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.
|
|
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='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed'],
|
|
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('--open_pyramid', default=defval('auto'), help='''\
|
|
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
|
|
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
|
|
fast ganon goals are crystals, ganontriforcehunt, localganontriforcehunt, pedestalganon
|
|
auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle
|
|
is vanilla, dungeonssimple or dungeonsfull.
|
|
goal - Opens pyramid hole if the goal specifies a fast ganon.
|
|
yes - Always opens the pyramid hole.
|
|
no - Never opens the pyramid hole.
|
|
''', choices=['auto', 'goal', 'yes', 'no'])
|
|
|
|
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('--custom', default=defval(False), help='Not supported.')
|
|
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
|
|
# 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('--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('--gui', help='Launch the GUI', action='store_true')
|
|
|
|
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
|
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos',
|
|
"singularity"])
|
|
|
|
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('--beemizer_total_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100))
|
|
parser.add_argument('--beemizer_trap_chance', default=defval(0), type=lambda value: min(max(int(value), 0), 100))
|
|
parser.add_argument('--shop_shuffle', default='', help='''\
|
|
combine letters for options:
|
|
g: generate default inventories for light and dark world shops, and unique shops
|
|
f: generate default inventories for each shop individually
|
|
i: shuffle the default inventories of the shops around
|
|
p: randomize the prices of the items in shop inventories
|
|
u: shuffle capacity upgrades into the item pool
|
|
w: consider witch's hut like any other shop and shuffle/randomize it too
|
|
''')
|
|
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
|
parser.add_argument('--sprite_pool', help='''\
|
|
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
|
parser.add_argument('--dark_room_logic', default=('Lamp'), choices=["lamp", "torches", "none"], help='''\
|
|
For unlit dark rooms, require the Lamp to be considered in logic by default.
|
|
Torches means additionally easily accessible Torches that can be lit with Fire Rod are considered doable.
|
|
None means full traversal through dark rooms without tools is considered doable.''')
|
|
parser.add_argument('--multi', default=defval(1), type=lambda value: max(int(value), 1))
|
|
parser.add_argument('--names', default=defval(''))
|
|
parser.add_argument('--outputpath')
|
|
parser.add_argument('--game', default="A Link to the Past")
|
|
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)
|
|
|
|
# shuffle medallions
|
|
|
|
ret.required_medallions = ("random", "random")
|
|
# cannot be set through CLI currently
|
|
ret.plando_items = []
|
|
ret.plando_texts = {}
|
|
ret.plando_connections = []
|
|
|
|
if ret.timer == "none":
|
|
ret.timer = False
|
|
if ret.dungeon_counters == 'on':
|
|
ret.dungeon_counters = True
|
|
elif ret.dungeon_counters == 'off':
|
|
ret.dungeon_counters = False
|
|
|
|
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', 'goal', 'difficulty', 'item_functionality',
|
|
'shuffle', 'open_pyramid', 'timer',
|
|
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
|
|
'beemizer_total_chance', 'beemizer_trap_chance',
|
|
'shufflebosses', 'enemy_health', 'enemy_damage',
|
|
'sprite',
|
|
"triforce_pieces_available",
|
|
"triforce_pieces_required", "shop_shuffle",
|
|
"required_medallions",
|
|
"plando_items", "plando_texts", "plando_connections",
|
|
'dungeon_counters',
|
|
'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
|
'game']:
|
|
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 |