diff --git a/BaseClasses.py b/BaseClasses.py index 03c40c21..ff001247 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -769,6 +769,16 @@ class CollectionState(object): else: self.prog_items.add(('Bow', item.player)) 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'): if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit: self.prog_items.add((item.name, item.player)) diff --git a/Bosses.py b/Bosses.py index 4c748391..ea02b8ba 100644 --- a/Bosses.py +++ b/Bosses.py @@ -183,6 +183,10 @@ def place_bosses(world, player): raise FillError('Could not place boss for location %s' % loc_text) 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) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random @@ -193,5 +197,9 @@ def place_bosses(world, player): except IndexError: 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) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) diff --git a/CLI.py b/CLI.py index 60391631..328e126f 100644 --- a/CLI.py +++ b/CLI.py @@ -9,7 +9,6 @@ import shlex import sys from Main import main -from Rom import get_sprite_from_name from Utils import is_bundled, close_console from Fill import FillError @@ -212,7 +211,7 @@ def parse_arguments(argv, no_defaults=False): 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 1), help='''\ + 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 @@ -264,7 +263,7 @@ def parse_arguments(argv, no_defaults=False): 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', help='''\ + 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. @@ -291,6 +290,7 @@ def parse_arguments(argv, no_defaults=False): 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: @@ -322,185 +322,187 @@ def parse_arguments(argv, no_defaults=False): 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", + # 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", + "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"), + "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", + "mapshuffle": False, + "compassshuffle": False, + "keyshuffle": False, + "bigkeyshuffle": False, + "keysanity": False, + "door_shuffle": "basic", + "experimental": 0, + "dungeon_counters": "off", - "multi": 1, - "names": "", + "multi": 1, + "names": "", - "hints": True, - "disablemusic": False, - "quickswap": False, - "heartcolor": "red", - "heartbeep": "normal", - "sprite": None, - "fastmenu": "normal", - "ow_palettes": "default", - "uw_palettes": "default", + "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": True, - "suppress_rom": False, - "usestartinventory": False, - "custom": False, - "rom": os.path.join(".","Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), + "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": 1, - "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(".") - } - settings["startinventoryarray"] = {} - if sys.platform.lower().find("windows"): - settings["enemizercli"] += ".exe" + "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 - # 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) - if 'sprite' in data.keys() and data['sprite']: - data['sprite'] = get_sprite_from_name(data['sprite']) - 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 = {} + 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] + 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] + 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 + return args diff --git a/EntranceShuffle.py b/EntranceShuffle.py index e1813011..c4747374 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -2298,8 +2298,6 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)', 'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (West)', 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', diff --git a/Gui.py b/Gui.py index 76215dce..77ce5d43 100755 --- a/Gui.py +++ b/Gui.py @@ -21,7 +21,6 @@ from source.gui.randomize.generation import generation_page from source.gui.bottom import bottom_frame, create_guiargs from GuiUtils import set_icon from Main import __version__ as ESVersion -from Rom import get_sprite_from_name def guiMain(args=None): @@ -36,21 +35,29 @@ def guiMain(args=None): f.write(json.dumps(args, indent=2)) os.chmod(os.path.join(settings_path, "settings.json"),0o755) - def save_settings_from_gui(): + def save_settings_from_gui(confirm): gui_args = vars(create_guiargs(self)) if self.randomSprite.get(): gui_args['sprite'] = 'random' elif gui_args['sprite']: gui_args['sprite'] = gui_args['sprite'].name save_settings(gui_args) - messagebox.showinfo("Door Shuffle " + ESVersion,"Settings saved from GUI.") + if confirm: + messagebox.showinfo("Door Shuffle " + ESVersion, "Settings saved from GUI.") # routine for exiting the app def guiExit(): - dosave = messagebox.askyesno("Door Shuffle " + ESVersion, "Save settings before exit?") - if dosave: - save_settings_from_gui() - sys.exit(0) + skip_exit = False + if self.settings['saveonexit'] == 'ask': + dosave = messagebox.askyesnocancel("Door Shuffle " + ESVersion, "Save settings before exit?") + if dosave: + save_settings_from_gui(True) + if dosave is None: + skip_exit = True + elif self.settings['saveonexit'] == 'always': + save_settings_from_gui(False) + if not skip_exit: + sys.exit(0) # make main window # add program title & version number @@ -137,7 +144,7 @@ def guiMain(args=None): # bottom of window: Open Output Directory, Open Documentation (if exists) self.frames["bottom"] = bottom_frame(self, self, None) ## Save Settings Button - savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=save_settings_from_gui) + savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=lambda: save_settings_from_gui(True)) savesettingsButton.pack(side=RIGHT) # set bottom frame to main window diff --git a/Main.py b/Main.py index 57fcc29c..978d3e7e 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names -__version__ = '0.0.17.2p' +__version__ = '0.0.18dev' def main(args, seed=None): diff --git a/Rom.py b/Rom.py index ba09a30d..84bfa05e 100644 --- a/Rom.py +++ b/Rom.py @@ -981,8 +981,8 @@ def patch_rom(world, rom, player, team, enemized): startingstate = CollectionState(world) if startingstate.has('Bow', player): - equip[0x340] = 1 - equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases + equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1 + equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases if not world.retro[player]: equip[0x38E] |= 0x80 if startingstate.has('Silver Arrows', player): diff --git a/Rules.py b/Rules.py index c243ce2d..291eb11e 100644 --- a/Rules.py +++ b/Rules.py @@ -1185,14 +1185,13 @@ def set_inverted_big_bomb_rules(world, player): 'Hyrule Castle Entrance (East)', 'Inverted Ganons Tower', 'Cave 45', - 'Checkerboard Cave'] + 'Checkerboard Cave', + 'Inverted Big Bomb Shop'] LW_DM_entrances = ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave Peak', - 'Spectacle Rock Cave', - 'Spectacle Rock Cave (Bottom)', 'Tower of Hera', 'Death Mountain Return Cave (West)', 'Paradox Cave (Top)', @@ -1212,7 +1211,7 @@ def set_inverted_big_bomb_rules(world, player): 'Chest Game', 'Dark World Hammer Peg Cave', 'Red Shield Shop', - 'Dark Sanctuary Hint', + 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop', @@ -1222,7 +1221,7 @@ def set_inverted_big_bomb_rules(world, player): Southern_DW_entrances = ['Hype Cave', 'Bonk Fairy (Dark)', 'Archery Game', - 'Inverted Big Bomb Shop', + 'Inverted Links House', 'Dark Lake Hylia Shop', 'Swamp Palace'] Isolated_DW_entrances = ['Spike Cave', diff --git a/resources/app/gui/randomize/generation/checkboxes.json b/resources/app/gui/randomize/generation/checkboxes.json index 7d0fedbf..b7228c78 100644 --- a/resources/app/gui/randomize/generation/checkboxes.json +++ b/resources/app/gui/randomize/generation/checkboxes.json @@ -22,5 +22,24 @@ "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" + } } }