diff --git a/BaseClasses.py b/BaseClasses.py index ff001247..cb47063a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -73,6 +73,10 @@ class World(object): self.key_layout = defaultdict(dict) for player in range(1, players + 1): + # If World State is Retro, set to Open and set Retro flag + if self.mode[player] == "retro": + self.mode[player] = "open" + self.retro[player] = True def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val set_player_attr('_region_cache', {}) diff --git a/CLI.py b/CLI.py index 328e126f..3faa83c0 100644 --- a/CLI.py +++ b/CLI.py @@ -9,8 +9,6 @@ import shlex import sys from Main import main -from Utils import is_bundled, close_console -from Fill import FillError import source.classes.constants as CONST @@ -185,7 +183,7 @@ def parse_arguments(argv, no_defaults=False): Crossed: Doors are mixed between all dungeons. (Not yet implemented) Vanilla: All doors are connected the same way they were in the - base game. + 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']) @@ -477,7 +475,10 @@ def get_settings(): settings[k] = v return settings - +# Priority fallback is: +# 1: CLI +# 2: Settings file +# 3: Canned defaults def get_args_priority(settings_args, gui_args, cli_args): args = {} args["settings"] = get_settings() if settings_args is None else settings_args diff --git a/DoorShuffle.py b/DoorShuffle.py index 4e8a475a..22d91f30 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -197,8 +197,7 @@ def connect_simple_door(world, exit_name, region_name, player): d.dest = region -def connect_door_only(world, exit_name, region_name, player): - region = world.get_region(region_name, player) +def connect_door_only(world, exit_name, region, player): d = world.check_for_door(exit_name, player) if d is not None: d.dest = region @@ -1352,8 +1351,8 @@ def add_inaccessible_doors(world, player): # todo: ignore standard mode hyrule castle ledge? for inaccessible_region in world.inaccessible_regions[player]: region = world.get_region(inaccessible_region, player) - for exit in region.exits: - create_door(world, player, exit.name, region.name) + for ext in region.exits: + create_door(world, player, ext.name, region.name) def create_door(world, player, entName, region_name): @@ -1459,6 +1458,7 @@ def check_for_pinball_fix(state, bad_region, world, player): @unique class DROptions(Flag): + NoOptions = 0x00 Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart Town_Portal = 0x02 # If on, Players will start with mirror scroll Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required diff --git a/Gui.py b/Gui.py index 77ce5d43..c2f871a5 100755 --- a/Gui.py +++ b/Gui.py @@ -4,8 +4,7 @@ import os import sys from tkinter import Tk, Button, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, RIGHT, ttk, messagebox -from argparse import Namespace -from CLI import get_settings, get_args_priority +from CLI import get_args_priority from DungeonRandomizer import parse_arguments from source.gui.adjust.overview import adjust_page from source.gui.startinventory.overview import startinventory_page @@ -24,6 +23,7 @@ from Main import __version__ as ESVersion def guiMain(args=None): + # Save settings to file def save_settings(args): user_resources_path = os.path.join(".", "resources", "user") settings_path = os.path.join(user_resources_path) @@ -35,6 +35,7 @@ def guiMain(args=None): f.write(json.dumps(args, indent=2)) os.chmod(os.path.join(settings_path, "settings.json"),0o755) + # Save settings from GUI def save_settings_from_gui(confirm): gui_args = vars(create_guiargs(self)) if self.randomSprite.get(): @@ -83,6 +84,7 @@ def guiMain(args=None): # make array for frames self.frames = {} + # make pages for each section self.notebook = ttk.Notebook(self) self.pages["randomizer"] = ttk.Frame(self.notebook) self.pages["adjust"] = ttk.Frame(self.notebook) @@ -178,6 +180,7 @@ def guiMain(args=None): # load adjust settings into options loadadjustargs(self, self.settings) + # run main window mainWindow.mainloop() diff --git a/ItemList.py b/ItemList.py index 7c7684d0..1cec5aee 100644 --- a/ItemList.py +++ b/ItemList.py @@ -126,6 +126,7 @@ difficulties = { ), } +# Translate between Mike's label array and YAML/JSON keys def get_custom_array_key(item): label_switcher = { "silverarrow": "silversupgrade", @@ -177,6 +178,7 @@ def get_custom_array_key(item): key = label_switcher.get(key) return key + 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'] 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']): @@ -194,7 +196,7 @@ def generate_itempool(world, player): region = world.get_region('Light World',player) loc = Location(player, "Murahdahla", parent=region) - 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] + loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count[player] region.locations.append(loc) world.dynamic_locations.append(loc) @@ -253,7 +255,6 @@ def generate_itempool(world, player): world.get_location('Zelda Drop Off', player).event = True world.get_location('Zelda Drop Off', player).locked = True - # set up item pool 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) @@ -264,11 +265,11 @@ def generate_itempool(world, player): if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] if amt < 0: - for i in range(0, amt): - pool.remove(get_custom_array_key('Rupees (20)')) + for _ in range(0, amt): + pool.remove('Rupees (20)') elif amt > 0: - for i in range(0, amt): - pool.append(get_custom_array_key('Rupees (20)')) + for _ in range(0, amt): + pool.append('Rupees (20)') for item in precollected_items: world.push_precollected(ItemFactory(item, player)) @@ -407,6 +408,7 @@ def set_up_take_anys(world, player): world.initialize_regions() + def create_dynamic_shop_locations(world, player): for shop in world.shops: if shop.region.player == player: @@ -706,24 +708,25 @@ def test(): for difficulty in ['normal', 'hard', 'expert']: for goal in ['ganon', 'triforcehunt', 'pedestal']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: - for mode in ['open', 'standard', 'inverted']: + for mode in ['open', 'standard', 'inverted', 'retro']: for swords in ['random', 'assured', 'swordless', 'vanilla']: for progressive in ['on', 'off']: for shuffle in ['full', 'insanity_legacy']: for retro in [True, False]: - out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro) - count = len(out[0]) + len(out[1]) + for door_shuffle in ['basic', 'crossed', 'vanilla']: + out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle) + count = len(out[0]) + len(out[1]) - correct_count = total_items_to_place - if goal == 'pedestal' and swords != 'vanilla': - # pedestal goals generate one extra item - correct_count += 1 - if retro: - correct_count += 28 - try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro)) - except AssertionError as e: - print(e) + correct_count = total_items_to_place + if goal == 'pedestal' and swords != 'vanilla': + # pedestal goals generate one extra item + correct_count += 1 + if retro: + correct_count += 28 + try: + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro)) + except AssertionError as e: + print(e) if __name__ == '__main__': test() diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 9c3d07eb..4d82d257 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -705,7 +705,7 @@ def unique_doors(doors): def count_unique_sm_doors(doors): unique_d_set = set() for d in doors: - if d not in unique_d_set and d.dest not in unique_d_set and not d.bigKey: + if d not in unique_d_set and (d.dest not in unique_d_set or d.type == DoorType.SpiralStairs) and not d.bigKey: unique_d_set.add(d) return len(unique_d_set) @@ -718,7 +718,8 @@ def count_unique_small_doors(key_counter, proposal): if door in proposal and door not in counted: cnt += 1 counted.add(door) - counted.add(door.dest) + if door.type != DoorType.SpiralStairs: + counted.add(door.dest) return cnt @@ -1069,15 +1070,6 @@ def invalid_self_locking_key(state, prev_state, prev_avail, world, player): return prev_avail - 1 == 0 -# does not allow dest doors -def count_unique_sm_doors(doors): - unique_d_set = set() - for d in doors: - if d not in unique_d_set and d.dest not in unique_d_set and not d.bigKey: - unique_d_set.add(d) - return len(unique_d_set) - - def enough_small_locations(state, avail_small_loc): unique_d_set = set() for exp_door in state.small_doors: diff --git a/Main.py b/Main.py index 978d3e7e..dd3cbbf3 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.18dev' +__version__ = '0.0.18.2d' def main(args, seed=None): @@ -77,7 +77,8 @@ def main(args, seed=None): world.difficulty_requirements[player] = difficulties[world.difficulty[player]] if world.mode[player] == 'standard' and world.enemy_shuffle[player] != 'none': - world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it + if hasattr(world,"escape_assist") and player in world.escape_assist: + world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it for tok in filter(None, args.startinventory[player].split(',')): item = ItemFactory(tok.strip(), player) @@ -383,7 +384,7 @@ def copy_dynamic_regions_and_locations(world, ret): new_loc.always_allow = location.always_allow new_loc.item_rule = location.item_rule new_reg.locations.append(new_loc) - + ret.clear_location_cache() diff --git a/Mystery.py b/Mystery.py index 9937b5ad..d175be3e 100644 --- a/Mystery.py +++ b/Mystery.py @@ -148,6 +148,7 @@ def roll_settings(weights): ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' door_shuffle = get_choice('door_shuffle') ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla' + ret.experimental = get_choice('experimental') == 'on' goal = get_choice('goals') ret.goal = {'ganon': 'ganon', @@ -156,7 +157,7 @@ def roll_settings(weights): 'pedestal': 'pedestal', 'triforce-hunt': 'triforcehunt' }[goal] - ret.openpyramid = goal == 'fast_ganon' + ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.crystals_gt = get_choice('tower_open') diff --git a/README.md b/README.md index 4b7e469d..531e1253 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,6 @@ Doors are shuffled between dungeons as well. Doors are not shuffled. -### Experimental - -Used for development testing. This will be removed in a future version. Use at your own risk. Might play like a plando. - ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity) These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon. diff --git a/Rom.py b/Rom.py index 84bfa05e..1e2de084 100644 --- a/Rom.py +++ b/Rom.py @@ -591,7 +591,7 @@ def patch_rom(world, rom, player, team, enemized): patch_shuffled_dark_sanc(world, rom, player) # patch doors - dr_flags = DROptions.Eternal_Mini_Bosses if not world.experimental[player] else DROptions.Town_Portal + dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' or not world.experimental[player] else DROptions.Town_Portal if world.doorShuffle[player] == 'crossed': rom.write_byte(0x139004, 2) rom.write_byte(0x151f1, 2) diff --git a/build-dr.py b/build-dr.py index 06b2d510..08c1ce93 100644 --- a/build-dr.py +++ b/build-dr.py @@ -3,8 +3,10 @@ import os import shutil import sys +# Destination is current dir DEST_DIRECTORY = '.' +# Check for UPX if os.path.isdir("upx"): upx_string = "--upx-dir=upx" else: @@ -13,6 +15,7 @@ else: if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"): shutil.rmtree("build") +# Run pyinstaller for DungeonRandomizer subprocess.run(" ".join(["pyinstaller DungeonRandomizer.spec ", upx_string, "-y ", diff --git a/build-gui.py b/build-gui.py index 35965d3e..ff8ccc90 100644 --- a/build-gui.py +++ b/build-gui.py @@ -3,8 +3,10 @@ import os import shutil import sys +# Destination is current dir DEST_DIRECTORY = '.' +# Check for UPX if os.path.isdir("upx"): upx_string = "--upx-dir=upx" else: @@ -13,6 +15,7 @@ else: if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"): shutil.rmtree("build") +# Run pyinstaller for Gui subprocess.run(" ".join(["pyinstaller Gui.spec ", upx_string, "-y ", diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 0c427418..f63cffe7 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -25,7 +25,8 @@ "options": { "Standard": "standard", "Open": "open", - "Inverted": "inverted" + "Inverted": "inverted", + "Retro": "retro" } }, "logiclevel": { diff --git a/source/classes/SpriteSelector.py b/source/classes/SpriteSelector.py index 486b0792..0defb5d6 100644 --- a/source/classes/SpriteSelector.py +++ b/source/classes/SpriteSelector.py @@ -1,4 +1,4 @@ -from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, NSEW, LEFT, BOTTOM, X, RIGHT, TOP, HORIZONTAL, EW, NS +from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, LEFT, BOTTOM, X, RIGHT, TOP, EW, NS from glob import glob import json import os @@ -34,6 +34,7 @@ class SpriteSelector(object): def open_unofficial_sprite_dir(_evt): open_file(self.unofficial_sprite_dir) + # Open SpriteSomething directory for Link sprites def open_spritesomething_listing(_evt): webbrowser.open("https://artheau.github.io/SpriteSomething/?mode=zelda3/link") # webbrowser.open("https://artheau.github.io/SpriteSomething/resources/app/snes/zelda3/link/sprites.html") @@ -51,6 +52,7 @@ class SpriteSelector(object): unofficial_title_text.pack(side=LEFT) unofficial_title_link.pack(side=LEFT) unofficial_title_link.bind("", open_unofficial_sprite_dir) + # Include hyperlink to SpriteSomething directory for Link sprites spritesomething_title_link = Label(unofficial_frametitle, text="(SpriteSomething)", fg="blue", cursor="hand2") spritesomething_title_link.pack(side=LEFT) spritesomething_title_link.bind("", open_spritesomething_listing) diff --git a/source/classes/constants.py b/source/classes/constants.py index 36bf0ec2..8277cd3c 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -1,3 +1,4 @@ +# Ordered list of items in Custom Item Pool page and Starting Inventory page CUSTOMITEMS = [ "bow", "progressivebow", "boomerang", "redmerang", "hookshot", "mushroom", "powder", "firerod", "icerod", "bombos", @@ -20,11 +21,13 @@ CUSTOMITEMS = [ "rupoorcost" ] +# These can't be in the Starting Inventory page CANTSTARTWITH = [ "triforcepiecesgoal", "triforce", "rupoor", "rupoorcost" ] +# In the same order as CUSTOMITEMS, these are Pretty Labels for each option CUSTOMITEMLABELS = [ "Bow", "Progressive Bow", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", @@ -47,6 +50,8 @@ CUSTOMITEMLABELS = [ "Rupoor Cost" ] +# Stuff on each page to save, according to internal names as defined by the widgets definitions +# and how it eventually translates to YAML/JSON weight files SETTINGSTOPROCESS = { "randomizer": { "item": { diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index b7fcb78f..c524d02e 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, filedialog, messagebox, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT, X, BOTTOM +from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM from AdjusterMain import adjust from argparse import Namespace from source.classes.SpriteSelector import SpriteSelector @@ -19,6 +19,7 @@ def adjust_page(top, parent, settings): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) + # Adjust option frames self.frames["selectOptionsFrame"] = Frame(self) self.frames["leftAdjustFrame"] = Frame(self.frames["selectOptionsFrame"]) self.frames["rightAdjustFrame"] = Frame(self.frames["selectOptionsFrame"]) @@ -28,6 +29,8 @@ def adjust_page(top, parent, settings): self.frames["rightAdjustFrame"].pack(side=RIGHT) self.frames["bottomAdjustFrame"].pack(fill=X) + # Load Adjust option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes with open(os.path.join("resources","app","gui","adjust","overview","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): @@ -40,6 +43,7 @@ def adjust_page(top, parent, settings): self.widgets[key].pack(packAttrs) # Sprite Selection + # This one's more-complicated, build it and stuff it self.spriteNameVar2 = StringVar() spriteDialogFrame2 = Frame(self.frames["leftAdjustFrame"]) baseSpriteLabel2 = Label(spriteDialogFrame2, text='Sprite:') @@ -65,6 +69,8 @@ def adjust_page(top, parent, settings): spriteSelectButton2.pack(side=LEFT) spriteDialogFrame2.pack(anchor=E) + # Path to game file to Adjust + # This one's more-complicated, build it and stuff it adjustRomFrame = Frame(self.frames["bottomAdjustFrame"]) adjustRomLabel = Label(adjustRomFrame, text='Rom to adjust: ') self.romVar2 = StringVar(value=settings["rom"]) @@ -82,6 +88,7 @@ def adjust_page(top, parent, settings): romSelectButton2.pack(side=LEFT) adjustRomFrame.pack(fill=X) + # These are the options to Adjust def adjustRom(): options = { "heartbeep": "heartbeep", diff --git a/source/gui/bottom.py b/source/gui/bottom.py index 90fdc8c0..aeb26c94 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -1,10 +1,9 @@ -from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, Spinbox, E, W, LEFT, RIGHT, X +from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, 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 CLI import parse_arguments from Main import main from Utils import local_path, output_path, open_file import source.classes.constants as CONST @@ -97,22 +96,35 @@ def create_guiargs(parent): # Page::Subpage::GUI-id::param-id options = CONST.SETTINGSTOPROCESS + # Cycle through each page for mainpage in options: + # Cycle through each subpage (in case of Item Randomizer) for subpage in options[mainpage]: + # Cycle through each widget for widget in options[mainpage][subpage]: + # Get the value and set it arg = options[mainpage][subpage][widget] setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get()) + # Get EnemizerCLI setting guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.get() + # Get Multiworld Worlds count guiargs.multi = int(parent.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.get()) + # Get baserom path guiargs.rom = parent.pages["randomizer"].pages["generation"].romVar.get() + + # Get if we're using the Custom Item Pool guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get()) + # Get Seed ID guiargs.seed = int(parent.frames["bottom"].seedVar.get()) if parent.frames["bottom"].seedVar.get() else None + + # Get number of generations to run guiargs.count = int(parent.frames["bottom"].widgets["generationcount"].storageVar.get()) if parent.frames["bottom"].widgets["generationcount"].storageVar.get() != '1' else None + # Get Adjust settings adjustargs = { "nobgm": "disablemusic", "quickswap": "quickswap", @@ -126,22 +138,29 @@ def create_guiargs(parent): internal = adjustargs[adjustarg] setattr(guiargs,"adjust." + internal, parent.pages["adjust"].content.widgets[adjustarg].storageVar.get()) + # Get Custom Items and Starting Inventory Items customitems = CONST.CUSTOMITEMS guiargs.startinventory = [] guiargs.customitemarray = {} guiargs.startinventoryarray = {} for customitem in customitems: - if customitem not in ["triforcepiecesgoal", "triforce", "rupoor", "rupoorcost"]: + if customitem not in CONST.CANTSTARTWITH: + # Starting Inventory is a CSV amount = int(parent.pages["startinventory"].content.startingWidgets[customitem].storageVar.get()) guiargs.startinventoryarray[customitem] = amount - for i in range(0, amount): + for _ in range(0, amount): label = CONST.CUSTOMITEMLABELS[customitems.index(customitem)] guiargs.startinventory.append(label) + # Custom Item Pool is a dict of ints guiargs.customitemarray[customitem] = int(parent.pages["custom"].content.customWidgets[customitem].storageVar.get()) + # Starting Inventory is a CSV guiargs.startinventory = ','.join(guiargs.startinventory) + # Get Sprite Selection (set or random) guiargs.sprite = parent.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] guiargs.randomSprite = parent.randomSprite.get() + + # Get output path guiargs.outputpath = parent.outputPath.get() return guiargs diff --git a/source/gui/custom/overview.py b/source/gui/custom/overview.py index 54c4f7f3..c51c35be 100644 --- a/source/gui/custom/overview.py +++ b/source/gui/custom/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, LEFT, RIGHT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -9,16 +9,19 @@ def custom_page(top,parent): # Custom Item Pool self = ttk.Frame(parent) + # Create uniform list columns 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 + # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): - for i in range(0,num): + for _ in range(0,num): ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y) + # This was in here, I have no idea what it was but I left it just in case: MikeT def validation(P): if str.isdigit(P) or P == "": return True @@ -31,23 +34,27 @@ def custom_page(top,parent): # Custom Item Pool option sections self.frames = {} - create_list_frame(self,"itemList1") + # Create 5 columns with 2 vertical rules in between each + create_list_frame(self, "itemList1") create_vertical_rule(2) - create_list_frame(self,"itemList2") + create_list_frame(self, "itemList2") create_vertical_rule(2) - create_list_frame(self,"itemList3") + create_list_frame(self, "itemList3") create_vertical_rule(2) - create_list_frame(self,"itemList4") + create_list_frame(self, "itemList4") create_vertical_rule(2) - create_list_frame(self,"itemList5") + create_list_frame(self, "itemList5") - with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns: + # Load Custom option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + 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] + # Load Custom Item Pool settings from settings file for key in CONST.CUSTOMITEMS: self.customWidgets[key].storageVar.set(top.settings["customitemarray"][key]) diff --git a/source/gui/loadcliargs.py b/source/gui/loadcliargs.py index b62f5065..b807b599 100644 --- a/source/gui/loadcliargs.py +++ b/source/gui/loadcliargs.py @@ -3,6 +3,7 @@ from source.gui.randomize.gameoptions import set_sprite from Rom import Sprite, get_sprite_from_name import source.classes.constants as CONST +# Load args/settings for most tabs def loadcliargs(gui, args, settings=None): if args is not None: # for k, v in vars(args).items(): @@ -14,28 +15,47 @@ def loadcliargs(gui, args, settings=None): # Page::Subpage::GUI-id::param-id options = CONST.SETTINGSTOPROCESS + # Cycle through each page for mainpage in options: + # Cycle through each subpage (in case of Item Randomizer) for subpage in options[mainpage]: + # Cycle through each widget for widget in options[mainpage][subpage]: + # Get the value and set it arg = options[mainpage][subpage][widget] gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg]) + # If we're on the Game Options page and it's not about Hints if subpage == "gameoptions" and not widget == "hints": + # Check if we've got settings + # Check if we've got the widget in Adjust settings hasSettings = settings is not None hasWidget = ("adjust." + widget) in settings if hasSettings else None if hasWidget is None: + # If we've got a Game Options val and we don't have an Adjust val, use the Game Options val gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg]) + # Get EnemizerCLI setting gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args["enemizercli"]) + + # Get baserom path gui.pages["randomizer"].pages["generation"].romVar.set(args["rom"]) + # Get Multiworld Worlds count if args["multi"]: gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args["multi"])) + + # Get Seed ID if args["seed"]: gui.frames["bottom"].seedVar.set(str(args["seed"])) + + # Get number of generations to run if args["count"]: gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args["count"])) + + # Get output path gui.outputPath.set(args["outputpath"]) + # Figure out Sprite Selection def sprite_setter(spriteObject): gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] = spriteObject if args["sprite"] is not None: @@ -52,6 +72,7 @@ def loadcliargs(gui, args, settings=None): spriteNameVar=gui.pages["adjust"].content.spriteNameVar2, randomSpriteVar=gui.randomSprite) +# Load args/settings for Adjust tab def loadadjustargs(gui, settings): options = { "adjust": { diff --git a/source/gui/randomize/dungeon.py b/source/gui/randomize/dungeon.py index 11dd1b58..1d36c021 100644 --- a/source/gui/randomize/dungeon.py +++ b/source/gui/randomize/dungeon.py @@ -1,4 +1,4 @@ -from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT import source.gui.widgets as widgets import json import os @@ -19,6 +19,9 @@ def dungeon_page(parent): mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ") mscbLabel.pack(side=LEFT) + # Load Dungeon Shuffle option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + # This first set goes in the Keysanity frame 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"]) @@ -26,6 +29,7 @@ def dungeon_page(parent): self.widgets[key] = dictWidgets[key] self.widgets[key].pack(side=LEFT) + # These get split left & right 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: diff --git a/source/gui/randomize/enemizer.py b/source/gui/randomize/enemizer.py index 26aab8ec..8c92bd48 100644 --- a/source/gui/randomize/enemizer.py +++ b/source/gui/randomize/enemizer.py @@ -1,5 +1,4 @@ -import os -from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, LabelFrame, OptionMenu, N, E, W, LEFT, RIGHT, BOTTOM, X +from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, N, E, W, LEFT, RIGHT, BOTTOM, X import source.gui.widgets as widgets import json import os @@ -18,6 +17,7 @@ def enemizer_page(parent,settings): # Enemizer option sections self.frames = {} + # Enemizer option frames self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) @@ -30,6 +30,9 @@ def enemizer_page(parent,settings): self.frames["rightEnemizerFrame"].pack(side=RIGHT) self.frames["bottomEnemizerFrame"].pack(fill=X) + # Load Enemizer option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + # These get split left & right with open(os.path.join("resources","app","gui","randomize","enemizer","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): @@ -42,6 +45,7 @@ def enemizer_page(parent,settings): self.widgets[key].pack(packAttrs) ## Enemizer CLI Path + # This one's more-complicated, build it and stuff it enemizerPathFrame = Frame(self.frames["bottomEnemizerFrame"]) enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ") enemizerCLIlabel.pack(side=LEFT) diff --git a/source/gui/randomize/entrando.py b/source/gui/randomize/entrando.py index e094dc3b..0759218c 100644 --- a/source/gui/randomize/entrando.py +++ b/source/gui/randomize/entrando.py @@ -1,4 +1,4 @@ -from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, E, W, LEFT, RIGHT import source.gui.widgets as widgets import json import os @@ -15,6 +15,11 @@ def entrando_page(parent): self.frames["widgets"] = Frame(self) self.frames["widgets"].pack(anchor=W) + # Load Entrance Randomizer option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + # Checkboxes go West + # Everything else goes East + # They also get split left & right with open(os.path.join("resources","app","gui","randomize","entrando","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): diff --git a/source/gui/randomize/gameoptions.py b/source/gui/randomize/gameoptions.py index 3369f33f..46fda67a 100644 --- a/source/gui/randomize/gameoptions.py +++ b/source/gui/randomize/gameoptions.py @@ -1,4 +1,4 @@ -from tkinter import ttk, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +from tkinter import ttk, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT from functools import partial import source.classes.SpriteSelector as spriteSelector import source.gui.widgets as widgets @@ -17,11 +17,17 @@ def gameoptions_page(top, parent): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) + # Game Options frames self.frames["leftRomOptionsFrame"] = Frame(self) self.frames["rightRomOptionsFrame"] = Frame(self) self.frames["leftRomOptionsFrame"].pack(side=LEFT) self.frames["rightRomOptionsFrame"].pack(side=RIGHT) + # Load Game Options widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + # Checkboxes go West + # Everything else goes East + # They also get split left & 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(): @@ -34,6 +40,7 @@ def gameoptions_page(top, parent): self.widgets[key].pack(packAttrs) ## Sprite selection + # This one's more-complicated, build it and stuff it spriteDialogFrame = Frame(self.frames["leftRomOptionsFrame"]) baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:') @@ -75,4 +82,3 @@ def set_sprite(sprite_param, random_sprite=False, spriteSetter=None, spriteNameV spriteNameVar.set(sprite_param.name) if randomSpriteVar: randomSpriteVar.set(random_sprite) - diff --git a/source/gui/randomize/generation.py b/source/gui/randomize/generation.py index 725c0ad9..376fc564 100644 --- a/source/gui/randomize/generation.py +++ b/source/gui/randomize/generation.py @@ -1,5 +1,4 @@ -import os -from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, E, W, LEFT, RIGHT, X +from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E, W, LEFT, X import source.gui.widgets as widgets import json import os @@ -16,6 +15,8 @@ def generation_page(parent,settings): self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) + # Load Generation Setup option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes 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"]) @@ -26,6 +27,7 @@ def generation_page(parent,settings): self.frames["baserom"] = Frame(self) self.frames["baserom"].pack(anchor=W, fill=X) ## Locate base ROM + # This one's more-complicated, build it and stuff it baseRomFrame = Frame(self.frames["baserom"]) baseRomLabel = Label(baseRomFrame, text='Base Rom: ') self.romVar = StringVar() diff --git a/source/gui/randomize/item.py b/source/gui/randomize/item.py index 45714dfd..b01892ab 100644 --- a/source/gui/randomize/item.py +++ b/source/gui/randomize/item.py @@ -1,8 +1,8 @@ -from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT +from tkinter import ttk, Frame, E, W, LEFT, RIGHT import source.gui.widgets as widgets import json import os - + def item_page(parent): # Item Randomizer self = ttk.Frame(parent) @@ -13,6 +13,7 @@ def item_page(parent): # Item Randomizer option sections self.frames = {} + # Item Randomizer option frames self.frames["checkboxes"] = Frame(self) self.frames["checkboxes"].pack(anchor=W) @@ -21,6 +22,10 @@ def item_page(parent): self.frames["leftItemFrame"].pack(side=LEFT) self.frames["rightItemFrame"].pack(side=RIGHT) + # Load Item Randomizer option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes + # Checkboxes go West + # Everything else goes East with open(os.path.join("resources","app","gui","randomize","item","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for framename,theseWidgets in myDict.items(): diff --git a/source/gui/randomize/multiworld.py b/source/gui/randomize/multiworld.py index 3d67d38e..c36854ed 100644 --- a/source/gui/randomize/multiworld.py +++ b/source/gui/randomize/multiworld.py @@ -1,4 +1,4 @@ -from tkinter import ttk, StringVar, Entry, Frame, Label, Spinbox, N, E, W, X, LEFT, RIGHT +from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, X, LEFT import source.gui.widgets as widgets import json import os @@ -15,6 +15,8 @@ def multiworld_page(parent,settings): self.frames["widgets"] = Frame(self) self.frames["widgets"].pack(anchor=W, fill=X) + # Load Multiworld option widgets as defined by JSON file + # Defns include frame name, widget type, widget options, widget placement attributes 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"]) @@ -23,6 +25,7 @@ def multiworld_page(parent,settings): self.widgets[key].pack(side=LEFT, anchor=N) ## List of Player Names + # This one's more-complicated, build it and stuff it key = "names" self.widgets[key] = Frame(self.frames["widgets"]) self.widgets[key].label = Label(self.widgets[key], text='Player names') diff --git a/source/gui/startinventory/overview.py b/source/gui/startinventory/overview.py index 46dd74dc..fce40e5f 100644 --- a/source/gui/startinventory/overview.py +++ b/source/gui/startinventory/overview.py @@ -1,4 +1,4 @@ -from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, LEFT, RIGHT, X, VERTICAL, Y +from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y import source.gui.widgets as widgets import json import os @@ -9,16 +9,19 @@ def startinventory_page(top,parent): # Starting Inventory self = ttk.Frame(parent) + # Create uniform list columns 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 + # Create a vertical rule to help with splitting columns visually def create_vertical_rule(num=1): - for i in range(0,num): + for _ in range(0,num): ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y) + # This was in Custom Item Pool, I have no idea what it was but I left it just in case: MikeT def validation(P): if str.isdigit(P) or P == "": return True @@ -31,6 +34,7 @@ def startinventory_page(top,parent): # Starting Inventory option sections self.frames = {} + # Create 5 columns with 2 vertical rules in between each create_list_frame(self,"itemList1") create_vertical_rule(2) create_list_frame(self,"itemList2") @@ -41,6 +45,8 @@ def startinventory_page(top,parent): create_vertical_rule(2) create_list_frame(self,"itemList5") + # Load Starting Inventory option widgets as defined by JSON file, ignoring the ones to be excluded + # Defns include frame name, widget type, widget options, widget placement attributes with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns: myDict = json.load(widgetDefns) for key in CONST.CANTSTARTWITH: @@ -53,6 +59,7 @@ def startinventory_page(top,parent): for key in dictWidgets: self.startingWidgets[key] = dictWidgets[key] + # Load Custom Starting Inventory settings from settings file, ignoring ones to be excluded for key in CONST.CUSTOMITEMS: if key not in CONST.CANTSTARTWITH: val = 0 diff --git a/source/gui/widgets.py b/source/gui/widgets.py index b4d15557..95e7b1af 100644 --- a/source/gui/widgets.py +++ b/source/gui/widgets.py @@ -1,8 +1,10 @@ from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, RIGHT, X +# Need a dummy class class Empty(): pass +# Override Spinbox to include mousewheel support for changing value class mySpinbox(Spinbox): def __init__(self, *args, **kwargs): Spinbox.__init__(self, *args, **kwargs) @@ -16,6 +18,7 @@ class mySpinbox(Spinbox): elif event.num == 4 or event.delta == 120: self.invoke('buttonup') +# Make a Checkbutton with a label def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): self = Frame(parent, name="checkframe-" + label.lower()) self.storageVar = storageVar @@ -26,6 +29,7 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs): self.checkbox.pack() return self +# Make an OptionMenu with a label and pretty option labels def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs): def change_storage(*args): self.storageVar.set(options[self.labelVar.get()]) @@ -54,6 +58,7 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt self.selectbox.pack() return self +# Make a Spinbox with a label, limit 1-100 def make_spinbox(self, parent, label, storageVar, manager, managerAttrs): self = Frame(parent, name="spinframe-" + label.lower()) self.storageVar = storageVar @@ -76,6 +81,8 @@ def make_spinbox(self, parent, label, storageVar, manager, managerAttrs): self.spinbox.pack() return self +# Make an Entry box with a label +# Support for Grid or Pack so that the Custom Item Pool & Starting Inventory pages don't look ugly def make_textbox(self, parent, label, storageVar, manager, managerAttrs): widget = Empty() widget.storageVar = storageVar @@ -98,7 +105,7 @@ def make_textbox(self, parent, label, storageVar, manager, managerAttrs): widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None) return widget - +# Make a generic widget def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None): widget = None if manager is None: @@ -129,6 +136,7 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage widget.type = type return widget +# Make a generic widget from a dict 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 "" @@ -138,8 +146,9 @@ def make_widget_from_dict(self, defn, parent): widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options) return widget +# Make a set of generic widgets from a dict 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 + return widgets