Merge branch 'DoorDev' of https://github.com/aerinon/ALttPDoorRandomizer into aerinon-DoorDev

This commit is contained in:
Mike A. Trethewey
2020-03-03 07:05:23 -08:00
9 changed files with 232 additions and 189 deletions

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)

344
CLI.py
View File

@@ -9,7 +9,6 @@ import shlex
import sys import sys
from Main import main from Main import main
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
@@ -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('--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('--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('--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. Use to batch generate multiple seeds with same settings.
If --seed is provided, it will be used for the first seed, then 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 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)') 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('--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('--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 Path to a sprite sheet to use for Link. Needs to be in
binary format and have a length of 0x7000 (28672) bytes, binary format and have a length of 0x7000 (28672) bytes,
or 0x7078 (28792) bytes including palette data. 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('--teams', default=defval(1), type=lambda value: max(int(value), 1))
parser.add_argument('--outputpath', default=defval(settings["outputpath"])) parser.add_argument('--outputpath', default=defval(settings["outputpath"]))
parser.add_argument('--race', default=defval(settings["race"] != 0), action='store_true') 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') parser.add_argument('--outputname')
if multiargs.multi: if multiargs.multi:
@@ -322,185 +322,187 @@ def parse_arguments(argv, no_defaults=False):
return ret return ret
def get_settings(): def get_settings():
# set default settings # set default settings
settings = { settings = {
"retro": False, "retro": False,
"mode": "open", "mode": "open",
"logic": "noglitches", "logic": "noglitches",
"goal": "ganon", "goal": "ganon",
"crystals_gt": "7", "crystals_gt": "7",
"crystals_ganon": "7", "crystals_ganon": "7",
"swords": "random", "swords": "random",
"difficulty": "normal", "difficulty": "normal",
"item_functionality": "normal", "item_functionality": "normal",
"timer": "none", "timer": "none",
"progressive": "on", "progressive": "on",
"accessibility": "items", "accessibility": "items",
"algorithm": "balanced", "algorithm": "balanced",
"openpyramid": False, "openpyramid": False,
"shuffleganon": False, "shuffleganon": False,
"shuffle": "vanilla", "shuffle": "vanilla",
"shufflepots": False, "shufflepots": False,
"shuffleenemies": "none", "shuffleenemies": "none",
"shufflebosses": "none", "shufflebosses": "none",
"enemy_damage": "default", "enemy_damage": "default",
"enemy_health": "default", "enemy_health": "default",
"enemizercli": os.path.join(".","EnemizerCLI","EnemizerCLI.Core"), "enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
"mapshuffle": False, "mapshuffle": False,
"compassshuffle": False, "compassshuffle": False,
"keyshuffle": False, "keyshuffle": False,
"bigkeyshuffle": False, "bigkeyshuffle": False,
"keysanity": False, "keysanity": False,
"door_shuffle": "basic", "door_shuffle": "basic",
"experimental": 0, "experimental": 0,
"dungeon_counters": "off", "dungeon_counters": "off",
"multi": 1, "multi": 1,
"names": "", "names": "",
"hints": True, "hints": True,
"disablemusic": False, "disablemusic": False,
"quickswap": False, "quickswap": False,
"heartcolor": "red", "heartcolor": "red",
"heartbeep": "normal", "heartbeep": "normal",
"sprite": None, "sprite": None,
"fastmenu": "normal", "fastmenu": "normal",
"ow_palettes": "default", "ow_palettes": "default",
"uw_palettes": "default", "uw_palettes": "default",
"create_spoiler": False, "create_spoiler": False,
"skip_playthrough": True, "skip_playthrough": False,
"suppress_rom": False, "suppress_rom": False,
"usestartinventory": False, "usestartinventory": False,
"custom": False, "custom": False,
"rom": os.path.join(".","Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"), "rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
"seed": None, "seed": None,
"count": 1, "count": None,
"startinventory": "", "startinventory": "",
"beemizer": 0, "beemizer": 0,
"remote_items": False, "remote_items": False,
"race": False, "race": False,
"customitemarray": { "customitemarray": {
"bow": 0, "bow": 0,
"progressivebow": 2, "progressivebow": 2,
"boomerang": 1, "boomerang": 1,
"redmerang": 1, "redmerang": 1,
"hookshot": 1, "hookshot": 1,
"mushroom": 1, "mushroom": 1,
"powder": 1, "powder": 1,
"firerod": 1, "firerod": 1,
"icerod": 1, "icerod": 1,
"bombos": 1, "bombos": 1,
"ether": 1, "ether": 1,
"quake": 1, "quake": 1,
"lamp": 1, "lamp": 1,
"hammer": 1, "hammer": 1,
"shovel": 1, "shovel": 1,
"flute": 1, "flute": 1,
"bugnet": 1, "bugnet": 1,
"book": 1, "book": 1,
"bottle": 4, "bottle": 4,
"somaria": 1, "somaria": 1,
"byrna": 1, "byrna": 1,
"cape": 1, "cape": 1,
"mirror": 1, "mirror": 1,
"boots": 1, "boots": 1,
"powerglove": 0, "powerglove": 0,
"titansmitt": 0, "titansmitt": 0,
"progressiveglove": 2, "progressiveglove": 2,
"flippers": 1, "flippers": 1,
"pearl": 1, "pearl": 1,
"heartpiece": 24, "heartpiece": 24,
"heartcontainer": 10, "heartcontainer": 10,
"sancheart": 1, "sancheart": 1,
"sword1": 0, "sword1": 0,
"sword2": 0, "sword2": 0,
"sword3": 0, "sword3": 0,
"sword4": 0, "sword4": 0,
"progressivesword": 4, "progressivesword": 4,
"shield1": 0, "shield1": 0,
"shield2": 0, "shield2": 0,
"shield3": 0, "shield3": 0,
"progressiveshield": 3, "progressiveshield": 3,
"mail2": 0, "mail2": 0,
"mail3": 0, "mail3": 0,
"progressivemail": 2, "progressivemail": 2,
"halfmagic": 1, "halfmagic": 1,
"quartermagic": 0, "quartermagic": 0,
"bombsplus5": 0, "bombsplus5": 0,
"bombsplus10": 0, "bombsplus10": 0,
"arrowsplus5": 0, "arrowsplus5": 0,
"arrowsplus10": 0, "arrowsplus10": 0,
"arrow1": 1, "arrow1": 1,
"arrow10": 12, "arrow10": 12,
"bomb1": 0, "bomb1": 0,
"bomb3": 16, "bomb3": 16,
"bomb10": 1, "bomb10": 1,
"rupee1": 2, "rupee1": 2,
"rupee5": 4, "rupee5": 4,
"rupee20": 28, "rupee20": 28,
"rupee50": 7, "rupee50": 7,
"rupee100": 1, "rupee100": 1,
"rupee300": 5, "rupee300": 5,
"blueclock": 0, "blueclock": 0,
"greenclock": 0, "greenclock": 0,
"redclock": 0, "redclock": 0,
"silversupgrade": 0, "silversupgrade": 0,
"generickeys": 0, "generickeys": 0,
"triforcepieces": 0, "triforcepieces": 0,
"triforcepiecesgoal": 0, "triforcepiecesgoal": 0,
"triforce": 0, "triforce": 0,
"rupoor": 0, "rupoor": 0,
"rupoorcost": 10 "rupoorcost": 10
}, },
"randomSprite": False, "randomSprite": False,
"outputpath": os.path.join(".") "outputpath": os.path.join("."),
} "saveonexit": "ask",
settings["startinventoryarray"] = {} "startinventoryarray": {}
if sys.platform.lower().find("windows"): }
settings["enemizercli"] += ".exe"
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): def get_args_priority(settings_args, gui_args, cli_args):
args = {} args = {}
args["settings"] = get_settings() if settings_args is None else settings_args args["settings"] = get_settings() if settings_args is None else settings_args
args["gui"] = {} if gui_args is None else gui_args args["gui"] = {} if gui_args is None else gui_args
args["cli"] = cli_args args["cli"] = cli_args
args["load"] = args["settings"] args["load"] = args["settings"]
if args["gui"] is not None: if args["gui"] is not None:
for k in args["gui"]: for k in args["gui"]:
if k not in args["load"] or args["load"][k] != args["gui"]: if k not in args["load"] or args["load"][k] != args["gui"]:
args["load"][k] = args["gui"][k] args["load"][k] = args["gui"][k]
if args["cli"] is None: if args["cli"] is None:
args["cli"] = {} args["cli"] = {}
cli = vars(parse_arguments(None)) cli = vars(parse_arguments(None))
for k,v in cli.items(): for k, v in cli.items():
if isinstance(v,dict) and 1 in v: if isinstance(v, dict) and 1 in v:
args["cli"][k] = v[1] args["cli"][k] = v[1]
else: else:
args["cli"][k] = v args["cli"][k] = v
load_doesnt_have_key = k not in args["load"] 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]) 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 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 load_doesnt_have_key or different_val:
if not cli_has_empty_dict: if not cli_has_empty_dict:
args["load"][k] = args["cli"][k] args["load"][k] = args["cli"][k]
return args return args

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)',

23
Gui.py
View File

@@ -21,7 +21,6 @@ from source.gui.randomize.generation import generation_page
from source.gui.bottom import bottom_frame, create_guiargs from source.gui.bottom import bottom_frame, create_guiargs
from GuiUtils import set_icon from GuiUtils import set_icon
from Main import __version__ as ESVersion from Main import __version__ as ESVersion
from Rom import get_sprite_from_name
def guiMain(args=None): def guiMain(args=None):
@@ -36,21 +35,29 @@ def guiMain(args=None):
f.write(json.dumps(args, indent=2)) f.write(json.dumps(args, indent=2))
os.chmod(os.path.join(settings_path, "settings.json"),0o755) 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)) gui_args = vars(create_guiargs(self))
if self.randomSprite.get(): if self.randomSprite.get():
gui_args['sprite'] = 'random' gui_args['sprite'] = 'random'
elif gui_args['sprite']: elif gui_args['sprite']:
gui_args['sprite'] = gui_args['sprite'].name gui_args['sprite'] = gui_args['sprite'].name
save_settings(gui_args) 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 # routine for exiting the app
def guiExit(): def guiExit():
dosave = messagebox.askyesno("Door Shuffle " + ESVersion, "Save settings before exit?") skip_exit = False
if dosave: if self.settings['saveonexit'] == 'ask':
save_settings_from_gui() dosave = messagebox.askyesnocancel("Door Shuffle " + ESVersion, "Save settings before exit?")
sys.exit(0) 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 # make main window
# add program title & version number # add program title & version number
@@ -137,7 +144,7 @@ def guiMain(args=None):
# bottom of window: Open Output Directory, Open Documentation (if exists) # bottom of window: Open Output Directory, Open Documentation (if exists)
self.frames["bottom"] = bottom_frame(self, self, None) self.frames["bottom"] = bottom_frame(self, self, None)
## Save Settings Button ## 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) savesettingsButton.pack(side=RIGHT)
# set bottom frame to main window # set bottom frame to main window

View File

@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.17.2p' __version__ = '0.0.18dev'
def main(args, seed=None): def main(args, seed=None):

4
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):

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',

View File

@@ -22,5 +22,24 @@
"label": { "label": {
"text": "Use custom item pool" "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"
}
} }
} }