Merge in latest door dev changes

This commit is contained in:
aerinon
2020-03-02 15:41:44 -07:00
48 changed files with 4067 additions and 1960 deletions

6
.gitignore vendored
View File

@@ -16,7 +16,13 @@ README.html
EnemizerCLI/ EnemizerCLI/
.mypy_cache/ .mypy_cache/
RaceRom.py RaceRom.py
upx/
weights/ weights/
settings.json
working_dirs.json
*.exe
venv venv
test test

View File

@@ -21,6 +21,9 @@ def adjust(args):
else: else:
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') 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) 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)) rom.write_to_file(output_path('%s.sfc' % outfilebase))

View File

@@ -769,6 +769,16 @@ class CollectionState(object):
else: else:
self.prog_items.add(('Bow', item.player)) self.prog_items.add(('Bow', item.player))
changed = True 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'): elif item.name.startswith('Bottle'):
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit: if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
self.prog_items.add((item.name, item.player)) self.prog_items.add((item.name, item.player))

View File

@@ -183,6 +183,10 @@ def place_bosses(world, player):
raise FillError('Could not place boss for location %s' % loc_text) raise FillError('Could not place boss for location %s' % loc_text)
bosses.remove(boss) 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) logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random
@@ -193,5 +197,9 @@ def place_bosses(world, player):
except IndexError: except IndexError:
raise FillError('Could not place boss for location %s' % loc_text) 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) logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)

508
CLI.py Normal file
View File

@@ -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

View File

@@ -8,313 +8,12 @@ import textwrap
import shlex import shlex
import sys import sys
from CLI import parse_arguments
from Main import main from Main import main
from Rom import get_sprite_from_name from Rom import get_sprite_from_name
from Utils import is_bundled, close_console from Utils import is_bundled, close_console
from Fill import FillError 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(): def start():
args = parse_arguments(None) args = parse_arguments(None)
@@ -360,7 +59,12 @@ def start():
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)
for fail in failures: for fail in failures:
logger.info('%s seed failed with: %s', fail[1], fail[0]) 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: else:
main(seed=args.seed, args=args) main(seed=args.seed, args=args)

59
DungeonRandomizer.spec Normal file
View File

@@ -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

View File

@@ -2298,8 +2298,6 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (East)',
'Death Mountain Return Cave (West)', 'Death Mountain Return Cave (West)',
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave Peak',
'Spectacle Rock Cave',
'Spectacle Rock Cave (Bottom)',
'Paradox Cave (Bottom)', 'Paradox Cave (Bottom)',
'Paradox Cave (Middle)', 'Paradox Cave (Middle)',
'Paradox Cave (Top)', 'Paradox Cave (Top)',

1684
Gui.py

File diff suppressed because it is too large Load Diff

59
Gui.spec Normal file
View File

@@ -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

View File

@@ -9,6 +9,8 @@ from EntranceShuffle import connect_entrance
from Fill import FillError, fill_restrictive from Fill import FillError, fill_restrictive
from Items import ItemFactory 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. #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. #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): 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'] 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']): 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) region = world.get_region('Light World',player)
loc = Location(player, "Murahdahla", parent=region) 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) region.locations.append(loc)
world.dynamic_locations.append(loc) world.dynamic_locations.append(loc)
@@ -204,7 +257,7 @@ def generate_itempool(world, player):
# set up item pool # set up item pool
if world.custom: 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) (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: 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]) (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] amt = world.pool_adjustment[player]
if amt < 0: if amt < 0:
for i in range(0, amt): for i in range(0, amt):
pool.remove('Rupees (20)') pool.remove(get_custom_array_key('Rupees (20)'))
elif amt > 0: elif amt > 0:
for i in range(0, amt): for i in range(0, amt):
pool.append('Rupees (20)') pool.append(get_custom_array_key('Rupees (20)'))
for item in precollected_items: for item in precollected_items:
world.push_precollected(ItemFactory(item, player)) 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) # 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) # 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) # 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 [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] adv_heart_pieces = [item for item in items if item.name == 'Piece of Heart'][0:4]
for hp in adv_heart_pieces: for hp in adv_heart_pieces:
hp.advancement = True hp.advancement = True
@@ -558,80 +611,31 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
placed_items[loc] = item placed_items[loc] = item
# Correct for insanely oversized item counts and take initial steps to handle undersized pools. # Correct for insanely oversized item counts and take initial steps to handle undersized pools.
for x in range(0, 66): # Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
if customitemarray[x] > total_items_to_place: for x in [*range(0, 66 + 1), 68, 69]:
customitemarray[x] = total_items_to_place key = CONST.CUSTOMITEMS[x]
if customitemarray[68] > total_items_to_place: if customitemarray[key] > total_items_to_place:
customitemarray[68] = total_items_to_place customitemarray[key] = total_items_to_place
itemtotal = 0
for x in range(0, 66):
itemtotal = itemtotal + customitemarray[x]
itemtotal = itemtotal + customitemarray[68]
itemtotal = itemtotal + customitemarray[70]
pool.extend(['Bow'] * customitemarray[0]) # Triforce
pool.extend(['Silver Arrows']* customitemarray[1]) if customitemarray["triforce"] > total_items_to_place:
pool.extend(['Blue Boomerang'] * customitemarray[2]) customitemarray["triforce"] = total_items_to_place
pool.extend(['Red Boomerang'] * customitemarray[3])
pool.extend(['Hookshot'] * customitemarray[4]) itemtotal = 0
pool.extend(['Mushroom'] * customitemarray[5]) # Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
pool.extend(['Magic Powder'] * customitemarray[6]) for x in [*range(0, 66 + 1), 68, 69]:
pool.extend(['Fire Rod'] * customitemarray[7]) key = CONST.CUSTOMITEMS[x]
pool.extend(['Ice Rod'] * customitemarray[8]) itemtotal = itemtotal + customitemarray[key]
pool.extend(['Bombos'] * customitemarray[9]) # Triforce
pool.extend(['Ether'] * customitemarray[10]) itemtotal = itemtotal + customitemarray["triforce"]
pool.extend(['Quake'] * customitemarray[11]) # Generic Keys
pool.extend(['Lamp'] * customitemarray[12]) itemtotal = itemtotal + customitemarray["generickeys"]
pool.extend(['Hammer'] * customitemarray[13])
pool.extend(['Shovel'] * customitemarray[14]) customitems = [
pool.extend(['Ocarina'] * customitemarray[15]) "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"
pool.extend(['Bug Catching Net'] * customitemarray[16]) ]
pool.extend(['Book of Mudora'] * customitemarray[17]) for customitem in customitems:
pool.extend(['Cane of Somaria'] * customitemarray[19]) pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)])
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])
diff = difficulties[difficulty] 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 # all bottles, since only one bottle is available
if diff.same_bottle: if diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = random.choice(diff.bottles)
for _ in range(customitemarray[18]): for _ in range(customitemarray["bottle"]):
if not diff.same_bottle: if not diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = random.choice(diff.bottles)
pool.append(thisbottle) pool.append(thisbottle)
if customitemarray[66] > 0 or customitemarray[67] > 0: if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99. treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], 99), 1) #To display, count must be between 1 and 99.
treasure_hunt_icon = 'Triforce Piece' treasure_hunt_icon = 'Triforce Piece'
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling. # 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): if (customitemarray["triforcepieces"] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray["triforce"] == 0):
extrapieces = treasure_hunt_count - customitemarray[66] extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
pool.extend(['Triforce Piece'] * extrapieces) pool.extend(['Triforce Piece'] * extrapieces)
itemtotal = itemtotal + extrapieces itemtotal = itemtotal + extrapieces
@@ -670,28 +674,30 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
if retro: if retro:
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) 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)') 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: else:
pool.extend(['Small Key (Universal)'] * customitemarray[70]) pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"])
else: else:
pool.extend(['Small Key (Universal)'] * customitemarray[70]) pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"])
pool.extend(['Fighter Sword'] * customitemarray[32]) pool.extend(['Fighter Sword'] * customitemarray["sword1"])
pool.extend(['Progressive Sword'] * customitemarray[36]) pool.extend(['Progressive Sword'] * customitemarray["progressivesword"])
if shuffle == 'insanity_legacy': if shuffle == 'insanity_legacy':
place_item('Link\'s House', 'Magic Mirror') place_item('Link\'s House', 'Magic Mirror')
place_item('Sanctuary', 'Moon Pearl') place_item('Sanctuary', 'Moon Pearl')
pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0)) pool.extend(['Magic Mirror'] * max((customitemarray["mirror"] -1 ), 0))
pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0)) pool.extend(['Moon Pearl'] * max((customitemarray["pearl"] - 1), 0))
else: else:
pool.extend(['Magic Mirror'] * customitemarray[22]) pool.extend(['Magic Mirror'] * customitemarray["mirror"])
pool.extend(['Moon Pearl'] * customitemarray[28]) pool.extend(['Moon Pearl'] * customitemarray["pearl"])
if retro: if retro:
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
if itemtotal < total_items_to_place: 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) return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)

12
Main.py
View File

@@ -57,8 +57,8 @@ def main(args, seed=None):
world.enemy_health = args.enemy_health.copy() world.enemy_health = args.enemy_health.copy()
world.enemy_damage = args.enemy_damage.copy() world.enemy_damage = args.enemy_damage.copy()
world.beemizer = args.beemizer.copy() world.beemizer = args.beemizer.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.experimental = args.experimental.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)} 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) patch_rom(world, rom, player, team, use_enemizer)
if use_enemizer and (args.enemizercli or not args.jsonout): 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 os.path.exists(args.enemizercli):
if not args.jsonout: patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000) 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: if args.race:
patch_race_rom(rom) patch_race_rom(rom)

12
Rom.py
View File

@@ -981,8 +981,8 @@ def patch_rom(world, rom, player, team, enemized):
startingstate = CollectionState(world) startingstate = CollectionState(world)
if startingstate.has('Bow', player): if startingstate.has('Bow', player):
equip[0x340] = 1 equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
if not world.retro[player]: if not world.retro[player]:
equip[0x38E] |= 0x80 equip[0x38E] |= 0x80
if startingstate.has('Silver Arrows', player): 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 rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count # 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 rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
elif world.dungeon_counters[player]: elif world.dungeon_counters[player] == 'on':
rom.write_byte(0x18003C, 0x02) # show always rom.write_byte(0x18003C, 0x02) # always on
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla': elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
rom.write_byte(0x18003C, 0x01) # show on pickup rom.write_byte(0x18003C, 0x01) # show on pickup
else: else:
rom.write_byte(0x18003C, 0x00) rom.write_byte(0x18003C, 0x00)

View File

@@ -1185,14 +1185,13 @@ def set_inverted_big_bomb_rules(world, player):
'Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (East)',
'Inverted Ganons Tower', 'Inverted Ganons Tower',
'Cave 45', 'Cave 45',
'Checkerboard Cave'] 'Checkerboard Cave',
'Inverted Big Bomb Shop']
LW_DM_entrances = ['Old Man Cave (East)', LW_DM_entrances = ['Old Man Cave (East)',
'Old Man House (Bottom)', 'Old Man House (Bottom)',
'Old Man House (Top)', 'Old Man House (Top)',
'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (East)',
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave Peak',
'Spectacle Rock Cave',
'Spectacle Rock Cave (Bottom)',
'Tower of Hera', 'Tower of Hera',
'Death Mountain Return Cave (West)', 'Death Mountain Return Cave (West)',
'Paradox Cave (Top)', 'Paradox Cave (Top)',
@@ -1212,7 +1211,7 @@ def set_inverted_big_bomb_rules(world, player):
'Chest Game', 'Chest Game',
'Dark World Hammer Peg Cave', 'Dark World Hammer Peg Cave',
'Red Shield Shop', 'Red Shield Shop',
'Dark Sanctuary Hint', 'Inverted Dark Sanctuary',
'Fortune Teller (Dark)', 'Fortune Teller (Dark)',
'Dark World Shop', 'Dark World Shop',
'Dark World Lumberjack Shop', 'Dark World Lumberjack Shop',
@@ -1222,7 +1221,7 @@ def set_inverted_big_bomb_rules(world, player):
Southern_DW_entrances = ['Hype Cave', Southern_DW_entrances = ['Hype Cave',
'Bonk Fairy (Dark)', 'Bonk Fairy (Dark)',
'Archery Game', 'Archery Game',
'Inverted Big Bomb Shop', 'Inverted Links House',
'Dark Lake Hylia Shop', 'Dark Lake Hylia Shop',
'Swamp Palace'] 'Swamp Palace']
Isolated_DW_entrances = ['Spike Cave', Isolated_DW_entrances = ['Spike Cave',

21
build-dr.py Normal file
View File

@@ -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)

21
build-gui.py Normal file
View File

@@ -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)

347
classes/SpriteSelector.py Normal file
View File

@@ -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("<Button-1>", 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("<Button-1>", 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("<Button-1>", 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("<Configure>", 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)

1
classes/__init__.py Normal file
View File

@@ -0,0 +1 @@
# do nothing, just exist to make "classes" package

109
classes/constants.py Normal file
View File

@@ -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"
}
}
}

1
gui/__init__.py Normal file
View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui" package

1
gui/about/__init__.py Normal file
View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui.about" package

1
gui/adjust/__init__.py Normal file
View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui.adjust" package

113
gui/adjust/overview.py Normal file
View File

@@ -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

147
gui/bottom.py Normal file
View File

@@ -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

1
gui/custom/__init__.py Normal file
View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui.custom" package

54
gui/custom/overview.py Normal file
View File

@@ -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

74
gui/loadcliargs.py Normal file
View File

@@ -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])

View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui.randomize" package

38
gui/randomize/dungeon.py Normal file
View File

@@ -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

63
gui/randomize/enemizer.py Normal file
View File

@@ -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("<Button-1>", 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

29
gui/randomize/entrando.py Normal file
View File

@@ -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

View File

@@ -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)

View File

@@ -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

35
gui/randomize/item.py Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
# do nothing, just exist to make "gui.startinventory" package

View File

@@ -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

145
gui/widgets.py Normal file
View File

@@ -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('<MouseWheel>', self.mouseWheel)
self.bind('<Button-4>', self.mouseWheel)
self.bind('<Button-5>', 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

View File

@@ -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"
}
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}
}

View File

@@ -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"
}
}
}
}

View File

@@ -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"
}
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"worlds": {
"type": "spinbox",
"label": {
"text": "Worlds"
},
"managerAttrs": {
"label": {
"side": "left"
},
"spinbox": {
"side": "right"
}
}
}
}