Merge remote-tracking branch 'origin' into OverworldShuffle

This commit is contained in:
2023-09-04 18:35:36 -05:00
76 changed files with 4251 additions and 2604 deletions

View File

@@ -2,12 +2,15 @@ import os
import urllib.request
import urllib.parse
import yaml
from typing import Any
from yaml.representer import Representer
from Utils import HexInt, hex_representer
from collections import defaultdict
from pathlib import Path
import RaceRandom as random
from BaseClasses import LocationType, DoorType
from OverworldShuffle import default_flute_connections, flute_data
from source.tools.MysteryUtils import roll_settings, get_weights
@@ -46,8 +49,8 @@ class CustomSettings(object):
return meta['players']
def adjust_args(self, args):
def get_setting(value, default):
if value:
def get_setting(value: Any, default):
if value or value == 0:
if isinstance(value, dict):
return random.choices(list(value.keys()), list(value.values()), k=1)[0]
else:
@@ -62,6 +65,7 @@ class CustomSettings(object):
args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom)
args.names = get_setting(meta['names'], args.names)
args.race = get_setting(meta['race'], args.race)
args.notes = get_setting(meta['user_notes'], args.notes)
self.player_range = range(1, args.multi + 1)
if 'settings' in self.file_source:
for p in self.player_range:
@@ -72,6 +76,14 @@ class CustomSettings(object):
args.mystery = True
else:
settings = defaultdict(lambda: None, player_setting)
args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p])
args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p])
args.ow_keepsimilar[p] = get_setting(settings['ow_keepsimilar'], args.ow_keepsimilar[p])
args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p])
args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p])
args.ow_fluteshuffle[p] = get_setting(settings['ow_fluteshuffle'], args.ow_fluteshuffle[p])
args.bonk_drops[p] = get_setting(settings['bonk_drops'], args.bonk_drops[p])
args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p])
args.door_shuffle[p] = get_setting(settings['door_shuffle'], args.door_shuffle[p])
args.logic[p] = get_setting(settings['logic'], args.logic[p])
@@ -112,10 +124,12 @@ class CustomSettings(object):
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[p])
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
args.experimental[p] = get_setting(settings['experimental'], args.experimental[p])
args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p])
args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p])
args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p])
args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p])
@@ -129,8 +143,8 @@ class CustomSettings(object):
args.mapshuffle[p] = True
args.compassshuffle[p] = True
args.shufflebosses[p] = get_setting(settings['shufflebosses'], args.shufflebosses[p])
args.shuffleenemies[p] = get_setting(settings['shuffleenemies'], args.shuffleenemies[p])
args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p])
args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p])
args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p])
args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p])
args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p])
@@ -142,6 +156,12 @@ class CustomSettings(object):
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p])
args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p])
args.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p])
args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p])
args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p])
args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p])
args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p])
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
# mystery usage
@@ -176,6 +196,16 @@ class CustomSettings(object):
return self.file_source['advanced_placements']
return None
def get_owtileflips(self):
if 'ow-tileflips' in self.file_source:
return self.file_source['ow-tileflips']
return None
def get_owflutespots(self):
if 'ow-flutespots' in self.file_source:
return self.file_source['ow-flutespots']
return None
def get_entrances(self):
if 'entrances' in self.file_source:
return self.file_source['entrances']
@@ -206,17 +236,26 @@ class CustomSettings(object):
return self.file_source['drops']
return None
def create_from_world(self, world, race):
def create_from_world(self, world, settings):
self.player_range = range(1, world.players + 1)
settings_dict, meta_dict = {}, {}
self.world_rep['meta'] = meta_dict
meta_dict['players'] = world.players
meta_dict['algorithm'] = world.algorithm
meta_dict['seed'] = world.seed
meta_dict['race'] = race
meta_dict['race'] = settings.race
meta_dict['user_notes'] = settings.notes
self.world_rep['settings'] = settings_dict
for p in self.player_range:
settings_dict[p] = {}
settings_dict[p]['ow_shuffle'] = world.owShuffle[p]
settings_dict[p]['ow_terrain'] = world.owTerrain[p]
settings_dict[p]['ow_crossed'] = world.owCrossed[p]
settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p]
settings_dict[p]['ow_mixed'] = world.owMixed[p]
settings_dict[p]['ow_whirlpool'] = world.owWhirlpoolShuffle[p]
settings_dict[p]['ow_fluteshuffle'] = world.owFluteShuffle[p]
settings_dict[p]['bonk_drops'] = world.shuffle_bonk_drops[p]
settings_dict[p]['shuffle'] = world.shuffle[p]
settings_dict[p]['door_shuffle'] = world.doorShuffle[p]
settings_dict[p]['intensity'] = world.intensity[p]
@@ -224,6 +263,7 @@ class CustomSettings(object):
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['door_self_loops'] = world.door_self_loops[p]
settings_dict[p]['logic'] = world.logic[p]
settings_dict[p]['mode'] = world.mode[p]
settings_dict[p]['swords'] = world.swords[p]
@@ -244,6 +284,7 @@ class CustomSettings(object):
settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p]
settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p]
settings_dict[p]['experimental'] = world.experimental[p]
settings_dict[p]['collection_rate'] = world.collection_rate[p]
settings_dict[p]['openpyramid'] = world.open_pyramid[p]
settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p]
settings_dict[p]['keyshuffle'] = world.keyshuffle[p]
@@ -313,6 +354,23 @@ class CustomSettings(object):
else:
placements[location.player][location.name] = location.item.name
def record_overworld(self, world):
self.world_rep['ow-tileflips'] = flips = {}
for p in self.player_range:
if p in world.owswaps and len(world.owswaps[p][0]) > 0:
flips[p] = {}
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0
self.world_rep['ow-flutespots'] = flute = {}
for p in self.player_range:
flute[p] = {}
if p in world.owflutespots:
flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p]))
else:
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
flute[p]['forbid'] = []
def record_entrances(self, world):
self.world_rep['entrances'] = entrances = {}
world.custom_entrances = {}
@@ -375,6 +433,7 @@ class CustomSettings(object):
def write_to_file(self, destination):
yaml.add_representer(defaultdict, Representer.represent_dict)
yaml.add_representer(HexInt, hex_representer)
with open(destination, 'w') as file:
yaml.dump(self.world_rep, file)

View File

@@ -56,10 +56,9 @@ SETTINGSTOPROCESS = {
"randomizer": {
"item": {
"hints": "hints",
"retro": "retro",
"bombbag": "bombbag",
"shopsanity": "shopsanity",
"pseudoboots": "pseudoboots",
"race": "race",
"worldstate": "mode",
"logiclevel": "logic",
"goal": "goal",
@@ -67,15 +66,28 @@ SETTINGSTOPROCESS = {
"crystals_ganon": "crystals_ganon",
"ganon_item": "ganon_item",
"weapons": "swords",
"itempool": "difficulty",
"retro": "retro",
"sortingalgo": "algorithm",
"accessibility": "accessibility",
"restrict_boss_items": "restrict_boss_items",
"itemfunction": "item_functionality",
"timer": "timer",
"shopsanity": "shopsanity",
"bonk_drops": "bonk_drops",
"pottery": "pottery",
"colorizepots": "colorizepots",
"potshuffle": "shufflepots",
"dropshuffle": "dropshuffle",
"keydropshuffle": "keydropshuffle",
"take_any": "take_any",
"itempool": "difficulty",
"flute_mode": "flute_mode",
"bow_mode": "bow_mode",
"timer": "timer",
"accessibility": "accessibility",
"sortingalgo": "algorithm",
"beemizer": "beemizer",
"restrict_boss_items": "restrict_boss_items"
"bombbag": "bombbag"
},
"overworld": {
"overworldshuffle": "ow_shuffle",
@@ -84,17 +96,32 @@ SETTINGSTOPROCESS = {
"keepsimilar": "ow_keepsimilar",
"mixed": "ow_mixed",
"whirlpool": "ow_whirlpool",
"bonk_drops": "bonk_drops",
"overworldflute": "ow_fluteshuffle"
},
"entrance": {
"openpyramid": "openpyramid",
"entranceshuffle": "shuffle",
"shuffleganon": "shuffleganon",
"shufflelinks": "shufflelinks",
"shuffletavern": "shuffletavern",
"entranceshuffle": "shuffle",
"openpyramid": "openpyramid",
"overworld_map": "overworld_map",
"take_any": "take_any",
},
"dungeon": {
"smallkeyshuffle": "keyshuffle",
"mapshuffle": "mapshuffle",
"compassshuffle": "compassshuffle",
"bigkeyshuffle": "bigkeyshuffle",
"key_logic_algorithm": "key_logic_algorithm",
"dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
"door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode",
"decoupledoors": "decoupledoors",
"door_self_loops": "door_self_loops",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel",
"standardize_palettes": "standardize_palettes",
},
"enemizer": {
"enemyshuffle": "shuffleenemies",
@@ -102,27 +129,6 @@ SETTINGSTOPROCESS = {
"enemydamage": "enemy_damage",
"enemyhealth": "enemy_health"
},
"dungeon": {
"mapshuffle": "mapshuffle",
"compassshuffle": "compassshuffle",
"smallkeyshuffle": "keyshuffle",
"bigkeyshuffle": "bigkeyshuffle",
"dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
"door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode",
"key_logic_algorithm": "key_logic_algorithm",
"decoupledoors": "decoupledoors",
"keydropshuffle": "keydropshuffle",
"dropshuffle": "dropshuffle",
"pottery": "pottery",
"colorizepots": "colorizepots",
"potshuffle": "shufflepots",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel",
"standardize_palettes": "standardize_palettes"
},
"gameoptions": {
"nobgm": "disablemusic",
"quickswap": "quickswap",
@@ -142,12 +148,15 @@ SETTINGSTOPROCESS = {
"createrom": "create_rom",
"calcplaythrough": "calc_playthrough",
"print_custom_yaml": "print_custom_yaml",
"usestartinventory": "usestartinventory",
"usecustompool": "custom",
"race": "race",
"saveonexit": "saveonexit"
}
},
"startinventory": {
"usestartinventory": "usestartinventory"
},
"custom": {
"usecustompool": "custom"
},
"bottom": {
"content": {
"names": "names",

View File

@@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
queue = collections.deque(proposed_map.items())
while len(queue) > 0:
a, b = queue.popleft()
if world.decoupledoors[player]:
if a == b or world.decoupledoors[player]:
connect_doors_one_way(a, b)
else:
connect_doors(a, b)
@@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player):
next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook])
opp_hook, secondary_door = type_map[next_hook], None
while (secondary_door is None or secondary_door == primary_door
while (secondary_door is None or (secondary_door == primary_door and not world.door_self_loops[player])
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_door)
secondary_bucket[opp_hook].remove(secondary_door)
if not world.decoupledoors[player]:
if primary_door != secondary_door and not world.decoupledoors[player]:
proposal[secondary_door] = primary_door
primary_bucket[opp_hook].remove(secondary_door)
secondary_bucket[next_hook].remove(primary_door)
@@ -200,11 +200,19 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
new_door = random.choice(unvisted_bucket[opp_hook])
old_target = proposed_map[attempt]
proposed_map[attempt] = new_door
if not world.decoupledoors[player]:
old_attempt = proposed_map[new_door]
else:
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
# ensure nothing gets messed up when something loops with itself
if attempt == old_target and old_attempt == new_door:
old_attempt = new_door
old_target = attempt
elif attempt == old_target:
old_target = old_attempt
elif old_attempt == new_door:
old_attempt = old_target
proposed_map[attempt] = new_door
proposed_map[old_attempt] = old_target
if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt

View File

@@ -203,13 +203,20 @@ def create_guiargs(parent):
# Cycle through each page
for mainpage in options:
subpage = None
_, v = next(iter(options[mainpage].items()))
if isinstance(v, str):
subpage = ""
# Cycle through each subpage (in case of Item Randomizer)
for subpage in options[mainpage]:
for subpage in (options[mainpage] if subpage is None else [subpage]):
# Cycle through each widget
for widget in options[mainpage][subpage]:
for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]):
# Get the value and set it
arg = options[mainpage][subpage][widget]
setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get())
arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget]
page = parent.pages[mainpage].pages[subpage] if subpage != "" else parent.pages[mainpage]
pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets
if hasattr(pagewidgets[widget], 'storageVar'):
setattr(guiargs, arg, pagewidgets[widget].storageVar.get())
# Get EnemizerCLI setting
guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get()
@@ -226,7 +233,7 @@ def create_guiargs(parent):
guiargs.customizer = customizer_value
# Get if we're using the Custom Item Pool
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
guiargs.custom = bool(parent.pages["custom"].content.customWidgets["usecustompool"].storageVar.get())
# Get Seed ID
guiargs.seed = None
@@ -280,17 +287,4 @@ def create_guiargs(parent):
guiargs = update_deprecated_args(guiargs)
# Key drop shuffle stuff
if guiargs.keydropshuffle:
guiargs.dropshuffle = 1
guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery
if guiargs.retro or guiargs.mode == 'retro':
if guiargs.bow_mode == 'progressive':
guiargs.bow_mode = 'retro'
elif guiargs.bow_mode == 'silvers':
guiargs.bow_mode = 'retro_silvers'
guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any
guiargs.keyshuffle = 'universal'
return guiargs

View File

@@ -1,4 +1,4 @@
from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y
from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y
import source.gui.widgets as widgets
import json
import os
@@ -11,10 +11,10 @@ def custom_page(top,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
self.frames[framename] = Frame(parent)
self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N)
self.frames[framename].thisRow = 0
self.frames[framename].thisCol = 0
# Create a vertical rule to help with splitting columns visually
def create_vertical_rule(num=1):
@@ -34,6 +34,8 @@ def custom_page(top,parent):
# Custom Item Pool option sections
self.frames = {}
self.frames["customHeader"] = Frame(self)
self.frames["customHeader"].pack(side=TOP, anchor=W)
# Create 5 columns with 2 vertical rules in between each
create_list_frame(self, "itemList1")
create_vertical_rule(2)
@@ -50,9 +52,14 @@ def custom_page(top,parent):
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]
if framename in self.frames:
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
for key in dictWidgets:
self.customWidgets[key] = dictWidgets[key]
if framename == "customHeader":
packAttrs = {"anchor":W}
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
self.customWidgets[key].pack(packAttrs)
# Load Custom Item Pool settings from settings file
for key in CONST.CUSTOMITEMS:

View File

@@ -23,34 +23,44 @@ def loadcliargs(gui, args, settings=None):
# Cycle through each page
for mainpage in options:
subpage = None
_, v = next(iter(options[mainpage].items()))
if isinstance(v, str):
subpage = ""
# Cycle through each subpage (in case of Item Randomizer)
for subpage in options[mainpage]:
for subpage in (options[mainpage] if subpage is None else [subpage]):
# Cycle through each widget
for widget in options[mainpage][subpage]:
if widget in gui.pages[mainpage].pages[subpage].widgets:
for widget in (options[mainpage][subpage] if subpage != "" else options[mainpage]):
page = gui.pages[mainpage].pages[subpage] if subpage != "" else gui.pages[mainpage]
pagewidgets = page.content.customWidgets if mainpage == "custom" else page.content.startingWidgets if mainpage == "startinventory" else page.widgets
if widget in pagewidgets:
thisType = ""
# Get the value and set it
arg = options[mainpage][subpage][widget]
arg = options[mainpage][subpage][widget] if subpage != "" else options[mainpage][widget]
if args[arg] == None:
args[arg] = ""
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
if hasattr(gui.pages[mainpage].pages[subpage].widgets[widget],"type"):
thisType = gui.pages[mainpage].pages[subpage].widgets[widget].type
label_ref = mainpage + ('.' + subpage if subpage != "" else '') + '.' + widget
label = fish.translate("gui","gui", label_ref)
if hasattr(pagewidgets[widget],"type"):
thisType = pagewidgets[widget].type
if thisType == "checkbox":
gui.pages[mainpage].pages[subpage].widgets[widget].checkbox.configure(text=label)
pagewidgets[widget].checkbox.configure(text=label)
elif thisType == "selectbox":
theseOptions = gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
theseOptions = pagewidgets[widget].selectbox.options
pagewidgets[widget].label.configure(text=label)
i = 0
for value in theseOptions["values"]:
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + '.' + str(value))
pagewidgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui", label_ref + '.' + str(value))
i += 1
for i in range(0, len(theseOptions["values"])):
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i])
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options = theseOptions
pagewidgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i])
pagewidgets[widget].selectbox.options = theseOptions
elif thisType == "spinbox":
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
pagewidgets[widget].label.configure(text=label)
elif thisType == 'button':
pagewidgets[widget].button.configure(text=label)
if hasattr(pagewidgets[widget], 'storageVar'):
pagewidgets[widget].storageVar.set(args[arg])
# If we're on the Game Options page and it's not about Hints
if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]:
# Check if we've got settings

View File

@@ -1,4 +1,4 @@
from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT
from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT, TOP
import source.gui.widgets as widgets
import json
import os
@@ -16,8 +16,8 @@ def dungeon_page(parent):
self.frames["keysanity"].pack(anchor=W)
## Dungeon Item Shuffle
mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ")
mscbLabel.pack(side=LEFT)
mscbLabel = Label(self.frames["keysanity"], text="Dungeon Items: ")
mscbLabel.pack(side=TOP, anchor=W)
# Load Dungeon Shuffle option widgets as defined by JSON file
# Defns include frame name, widget type, widget options, widget placement attributes
@@ -28,7 +28,9 @@ def dungeon_page(parent):
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)
packAttrs = {"side":LEFT}
packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key])
self.widgets[key].pack(packAttrs)
# These get split left & right
self.frames["widgets"] = Frame(self)
@@ -39,6 +41,8 @@ def dungeon_page(parent):
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)
packAttrs = {"anchor":W}
packAttrs = widgets.add_padding_from_config(packAttrs, myDict[key])
self.widgets[key].pack(packAttrs)
return self

View File

@@ -26,9 +26,8 @@ def entrando_page(parent):
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" or key in ["openpyramid", "take_any"]:
packAttrs["anchor"] = W
packAttrs = {"anchor":W}
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
self.widgets[key].pack(packAttrs)
return self

View File

@@ -1,4 +1,4 @@
from tkinter import ttk, Frame, E, W, LEFT, RIGHT, Label
from tkinter import ttk, font, Frame, E, W, NW, TOP, LEFT, RIGHT, Y, Label
import source.gui.widgets as widgets
import json
import os
@@ -17,13 +17,42 @@ def item_page(parent):
self.frames["checkboxes"] = Frame(self)
self.frames["checkboxes"].pack(anchor=W)
various_options = Label(self.frames["checkboxes"], text="")
various_options = Label(self.frames["checkboxes"], text="Options: ")
various_options.pack(side=LEFT)
self.frames["leftItemFrame"] = Frame(self)
self.frames["rightItemFrame"] = Frame(self)
self.frames["mainFrame"] = Frame(self)
self.frames["mainFrame"].pack(side=TOP, pady=(20,0))
self.frames["poolFrame"] = Frame(self)
self.frames["poolFrame"].pack(fill=Y)
self.frames["leftItemFrame"] = Frame(self.frames["mainFrame"])
self.frames["leftItemFrame"].pack(side=LEFT)
self.frames["rightItemFrame"] = Frame(self.frames["mainFrame"])
self.frames["rightItemFrame"].pack(side=RIGHT)
self.frames["leftPoolContainer"] = Frame(self.frames["poolFrame"])
self.frames["leftPoolContainer"].pack(side=LEFT, padx=(0,20))
base_font = font.nametofont('TkTextFont').actual()
underline_font = f'"{base_font["family"]}" {base_font["size"]} underline'
various_options = Label(self.frames["leftPoolContainer"], text="Pool Expansions", font=underline_font)
various_options.pack(side=TOP, pady=(20,0))
self.frames["leftPoolHeader"] = Frame(self.frames["leftPoolContainer"])
self.frames["leftPoolHeader"].pack(side=TOP, anchor=W)
self.frames["leftPoolFrame"] = Frame(self.frames["leftPoolContainer"])
self.frames["leftPoolFrame"].pack(side=TOP, fill=Y)
self.frames["leftPoolFrame2"] = Frame(self.frames["leftPoolContainer"])
self.frames["leftPoolFrame2"].pack(side=LEFT, fill=Y)
self.frames["rightPoolFrame"] = Frame(self.frames["poolFrame"])
self.frames["rightPoolFrame"].pack(side=RIGHT)
various_options = Label(self.frames["rightPoolFrame"], text="Pool Modifications", font=underline_font)
various_options.pack(side=TOP, pady=(20,0))
# Load Item Randomizer option widgets as defined by JSON file
# Defns include frame name, widget type, widget options, widget placement attributes
@@ -36,8 +65,17 @@ def item_page(parent):
for key in dictWidgets:
self.widgets[key] = dictWidgets[key]
packAttrs = {"anchor":E}
if self.widgets[key].type == "checkbox":
if key == "retro":
packAttrs["side"] = RIGHT
if self.widgets[key].type == "checkbox" or framename.startswith("leftPoolFrame"):
packAttrs["anchor"] = W
if framename == "checkboxes":
packAttrs["side"] = LEFT
packAttrs["padx"] = (10, 0)
elif framename == "leftPoolHeader":
packAttrs["side"] = LEFT
packAttrs["padx"] = (0, 20)
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
self.widgets[key].pack(packAttrs)
return self

View File

@@ -15,33 +15,24 @@ def overworld_page(parent):
# Load Overworld Shuffle option widgets as defined by JSON file
# Defns include frame name, widget type, widget options, widget placement attributes
self.frames["topOverworldFrame"] = Frame(self)
self.frames["leftOverworldFrame"] = Frame(self)
self.frames["rightOverworldFrame"] = Frame(self)
self.frames["topOverworldFrame"].pack(side=TOP, anchor=NW)
self.frames["leftOverworldFrame"].pack(side=LEFT, anchor=NW, fill=Y)
self.frames["rightOverworldFrame"].pack(anchor=NW, fill=Y)
shuffleLabel = Label(self.frames["topOverworldFrame"], text="Shuffle: ")
shuffleLabel.pack(side=LEFT)
with open(os.path.join("resources","app","gui","randomize","overworld","widgets.json")) as overworldWidgets:
myDict = json.load(overworldWidgets)
for framename,theseWidgets in myDict.items():
if not theseWidgets:
continue
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
for key in dictWidgets:
self.widgets[key] = dictWidgets[key]
packAttrs = {"anchor":E}
if key == "terrain":
packAttrs = {"anchor":W, "pady":(3,0)}
elif key == "keepsimilar":
packAttrs = {"anchor":W, "pady":(6,0)}
elif key == "overworldflute":
packAttrs["pady"] = (20,0)
elif key in ["mixed", "whirlpool"]:
packAttrs = {"anchor":W, "padx":(79,0)}
packAttrs = {"anchor":W}
if self.widgets[key].type != "checkbox":
packAttrs["anchor"] = E
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
self.widgets[key].pack(packAttrs)
return self

View File

@@ -1,4 +1,4 @@
from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y
from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y
import source.gui.widgets as widgets
import json
import os
@@ -11,10 +11,10 @@ def startinventory_page(top,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
self.frames[framename] = Frame(parent)
self.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N)
self.frames[framename].thisRow = 0
self.frames[framename].thisCol = 0
# Create a vertical rule to help with splitting columns visually
def create_vertical_rule(num=1):
@@ -34,6 +34,8 @@ def startinventory_page(top,parent):
# Starting Inventory option sections
self.frames = {}
self.frames["startHeader"] = Frame(self)
self.frames["startHeader"].pack(side=TOP, anchor=W)
# Create 5 columns with 2 vertical rules in between each
create_list_frame(self,"itemList1")
create_vertical_rule(2)
@@ -55,9 +57,14 @@ def startinventory_page(top,parent):
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]
if framename in self.frames:
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
for key in dictWidgets:
self.startingWidgets[key] = dictWidgets[key]
if framename == "startHeader":
packAttrs = {"anchor":W}
packAttrs = widgets.add_padding_from_config(packAttrs, theseWidgets[key])
self.startingWidgets[key].pack(packAttrs)
# Load Custom Starting Inventory settings from settings file, ignoring ones to be excluded
for key in CONST.CUSTOMITEMS:

View File

@@ -1,4 +1,5 @@
from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X
from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X
from tkinter import Button
from source.classes.Empty import Empty
# Override Spinbox to include mousewheel support for changing value
@@ -16,7 +17,7 @@ class mySpinbox(Spinbox):
self.invoke('buttonup')
# Make a Checkbutton with a label
def make_checkbox(self, parent, label, storageVar, manager, managerAttrs):
def make_checkbox(self, parent, label, storageVar, manager, managerAttrs, config):
self = Frame(parent)
self.storageVar = storageVar
if managerAttrs is not None and "default" in managerAttrs:
@@ -25,7 +26,10 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs):
elif managerAttrs["default"] == "false" or managerAttrs["default"] == False:
self.storageVar.set(False)
del managerAttrs["default"]
self.checkbox = Checkbutton(self, text=label, variable=self.storageVar)
options = {"text":label, "variable":self.storageVar}
if config and "command" in config:
options.update({"command":lambda m=config["command"]: widget_command(self, m)})
self.checkbox = Checkbutton(self, options)
if managerAttrs is not None:
self.checkbox.pack(managerAttrs)
else:
@@ -43,7 +47,11 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt
self.labelVar = StringVar()
self.storageVar = storageVar
self.selectbox = OptionMenu(self, self.labelVar, *labels)
if config and "command" in config:
self.command = config["command"]
self.selectbox = OptionMenu(self, self.labelVar, *labels, command=lambda m: widget_command(self, self.command))
else:
self.selectbox = OptionMenu(self, self.labelVar, *labels)
self.selectbox.options = {}
if isinstance(options,dict):
@@ -96,7 +104,7 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt
else:
self.label.pack(side=LEFT)
self.selectbox.config(width=config['width'] if config and config['width'] else 20)
self.selectbox.config(width=config['width'] if config and 'width' in config else 20)
idx = 0
default = self.selectbox.options["values"][idx]
if managerAttrs is not None and "default" in managerAttrs:
@@ -165,6 +173,22 @@ 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
def make_button(self, parent, label, manager, managerAttrs, config):
self = Frame(parent)
if config and "command" in config:
self.command = config["command"]
else:
self.command = lambda: None
self.button = Button(parent, text=label, command=lambda: widget_command(self, self.command))
if managerAttrs is not None:
self.button.pack(managerAttrs)
else:
self.button.pack(anchor='w')
return self
# Make a generic widget
def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(),
options=None, config=None):
@@ -181,7 +205,7 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage
if type == "checkbox":
if thisStorageVar is None:
thisStorageVar = IntVar()
widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs)
widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs, config)
elif type == "selectbox":
if thisStorageVar is None:
thisStorageVar = StringVar()
@@ -194,6 +218,8 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage
if thisStorageVar is None:
thisStorageVar = StringVar()
widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs)
elif type == 'button':
widget = make_button(self, parent, label, manager, managerAttrs, config)
widget.type = type
return widget
@@ -221,3 +247,50 @@ def make_widgets_from_dict(self, defns, parent):
for key,defn in defns.items():
widgets[key] = make_widget_from_dict(self, defn, parent)
return widgets
# Add padding to widget
def add_padding_from_config(packAttrs, defn):
if "config" in defn:
config = defn["config"]
if 'padx' in config:
packAttrs["padx"] = config['padx']
if 'pady' in config:
packAttrs["pady"] = config['pady']
return packAttrs
# Callback when a widget issues a command
def widget_command(widget, command=""):
root = widget.winfo_toplevel()
text_output = ""
if command == "retro":
temp_widget = root.pages["randomizer"].pages["dungeon"].widgets["smallkeyshuffle"]
text_output += f'\n {temp_widget.label.cget("text")}'
temp_widget.storageVar.set('universal')
temp_widget = root.pages["randomizer"].pages["item"].widgets["bow_mode"]
text_output += f'\n {temp_widget.label.cget("text")}'
if temp_widget.storageVar.get() == 'progressive':
temp_widget.storageVar.set('retro')
elif temp_widget.storageVar.get() == 'silvers':
temp_widget.storageVar.set('retro_silvers')
temp_widget = root.pages["randomizer"].pages["item"].widgets["take_any"]
text_output += f'\n {temp_widget.label.cget("text")}'
if temp_widget.storageVar.get() == 'none':
temp_widget.storageVar.set('random')
messagebox.showinfo('', f'The following settings were changed:{text_output}')
elif command == "keydropshuffle":
temp_widget = root.pages["randomizer"].pages["item"].widgets["pottery"]
text_output += f'\n {temp_widget.label.cget("text")}'
if temp_widget.storageVar.get() == 'none':
temp_widget.storageVar.set('keys')
temp_widget = root.pages["randomizer"].pages["item"].widgets["dropshuffle"]
text_output += f'\n {temp_widget.checkbox.cget("text")}'
if temp_widget.storageVar.get() == 0:
temp_widget.storageVar.set(1)
if text_output:
messagebox.showinfo('', f'The following settings were changed:{text_output}')

View File

@@ -119,7 +119,7 @@ def resolve_districts(world):
if not location.item and location.real:
district.locations.add(location.name)
for ext in region.exits:
if ext.connected_region is not None and ext.connected_region not in visited:
if ext.connected_region and ext.connected_region not in visited:
queue.appendleft(ext.connected_region)
elif region.type == RegionType.Dungeon and region.dungeon:
district.dungeons.add(region.dungeon.name)
@@ -138,10 +138,10 @@ def find_reachable_locations(state, player):
return check_set
inaccessible_regions_std = {'Desert Palace Stairs', 'Bumper Cave Ledge', 'Skull Woods Forest (West)',
inaccessible_regions_std = {'Desert Mouth', 'Bumper Cave Ledge', 'Skull Woods Forest (West)',
'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge',
'Dark Death Mountain Floating Island'}
'Death Mountain Floating Island'}
inaccessible_regions_inv = {'Desert Palace Stairs', 'Maze Race Ledge', 'Desert Ledge',
'Desert Palace Entrance (North) Spot', 'Hyrule Castle Ledge', 'Mountain Entry Ledge'}
inaccessible_regions_inv = {'Desert Mouth', 'Maze Race Ledge', 'Desert Ledge',
'Desert Ledge Keep', 'Hyrule Castle Ledge', 'Mountain Pass Ledge'}

View File

@@ -3,7 +3,7 @@ import logging
from collections import defaultdict
from source.item.District import resolve_districts
from BaseClasses import PotItem, PotFlags
from BaseClasses import PotItem, PotFlags, LocationType
from DoorShuffle import validate_vanilla_reservation
from Dungeons import dungeon_table
from Items import item_table, ItemFactory
@@ -82,8 +82,8 @@ def create_item_pool_config(world):
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
item = pot_items[pot.item]
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
location = f'{pot.room} {descriptor}'
config.static_placement[player][item].append(location)
loc = f'{pot.room} {descriptor}'
config.static_placement[player][item].append(loc)
if world.shopsanity[player]:
for item, locs in shop_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
@@ -151,9 +151,6 @@ def create_item_pool_config(world):
config.item_pool[player] = determine_major_items(world, player)
config.location_groups[0].locations = set(groups.locations)
config.reserved_locations[player].update(groups.locations)
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup)
elif world.algorithm == 'dungeon_only':
config.location_groups = [
LocationGroup('Dungeons'),
@@ -164,12 +161,13 @@ def create_item_pool_config(world):
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
mode_grouping['Pot Keys'] + mode_grouping['Big Key Drops'])
dungeon_set = set(dungeon_set)
for loc in world.get_locations():
if loc.parent_region.dungeon and loc.type in [LocationType.Pot, LocationType.Drop]:
dungeon_set.add(loc.name)
for player in range(1, world.players + 1):
config.item_pool[player] = determine_major_items(world, player)
config.location_groups[0].locations = set(dungeon_set)
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup)
def district_item_pool_config(world):
@@ -217,7 +215,7 @@ def district_item_pool_config(world):
scale_factors = defaultdict(int)
scale_total = 0
for p in range(1, world.players + 1):
ent = 'Agahnims Tower' if world.is_atgt_swapped(player) else 'Ganons Tower'
ent = 'Agahnims Tower' if world.is_atgt_swapped(p) else 'Ganons Tower'
dungeon = world.get_entrance(ent, p).connected_region.dungeon
if dungeon:
scale = world.crystals_needed_for_gt[p]
@@ -359,7 +357,7 @@ def determine_major_items(world, player):
major_item_set.add('Single Arrow')
if world.keyshuffle[player] == 'universal':
major_item_set.add('Small Key (Universal)')
if world.goal in ['triforcehunt', 'trinity']:
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
major_item_set.add('Triforce Piece')
if world.bombbag[player]:
major_item_set.add('Bomb Upgrade (+10)')
@@ -415,14 +413,11 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
if item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations
filtered = [l for l in locations if l.name in restricted]
if len(filtered) == 0:
restricted = config.location_groups[1].locations
filtered = [l for l in locations if l.name in restricted]
# bias toward certain location in overflow? (thinking about this for major_bias)
return filtered if len(filtered) > 0 else locations
return filtered
if world.algorithm == 'district':
config = world.item_pool_config
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
if ((isinstance(item_to_place,str) and item_to_place == 'Placeholder')
or item_to_place.name in config.item_pool[item_to_place.player]):
restricted = config.location_groups[0].locations
filtered = [l for l in locations if l.name in restricted and l.player in restricted[l.name]]
return filtered if len(filtered) > 0 else locations
@@ -816,7 +811,7 @@ trash_items = {
'Nothing': -1,
'Bee Trap': 0,
'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1,
'Rupees (20)': 2, 'Small Magic': 2,
'Rupees (20)': 2, 'Small Magic': 2, 'Good Bee': 2,
'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3, 'Apples': 3,
'Fairy': 4, 'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4,
'Rupees (300)': 5,
@@ -830,9 +825,10 @@ pot_items = {
PotItem.OneRupee: 'Rupee (1)',
PotItem.FiveRupees: 'Rupees (5)',
PotItem.Heart: 'Small Heart',
PotItem.BigMagic: 'Big Magic', # fast fill
PotItem.BigMagic: 'Big Magic',
PotItem.SmallMagic: 'Small Magic',
PotItem.Chicken: 'Chicken' # fast fill
PotItem.Chicken: 'Chicken',
PotItem.Fairy: 'Fairy'
}
valid_pot_items = {y: x for x, y in pot_items.items()}

File diff suppressed because it is too large Load Diff

View File

@@ -98,6 +98,7 @@ def roll_settings(weights):
ret.trap_door_mode = get_choice('trap_door_mode')
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
ret.decoupledoors = get_choice('decoupledoors') == 'on'
ret.door_self_loops = get_choice('door_self_loops') == 'on'
ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on'
@@ -111,7 +112,7 @@ def roll_settings(weights):
ret.dropshuffle = get_choice('dropshuffle') == 'on' or keydropshuffle
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery
ret.colorizepots = get_choice('colorizepots') == 'on'
ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on'
ret.shufflepots = get_choice('pot_shuffle') == 'on'
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights
@@ -142,15 +143,14 @@ def roll_settings(weights):
ganon_item = get_choice('ganon_item')
ret.ganon_item = ganon_item if ganon_item != 'none' else 'default'
from ItemList import set_default_triforce
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal))
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.triforce_pool = get_choice_default('triforce_pool', default=0)
ret.triforce_goal = get_choice_default('triforce_goal', default=0)
ret.triforce_pool_min = get_choice_default('triforce_pool_min', default=0)
ret.triforce_pool_max = get_choice_default('triforce_pool_max', default=0)
ret.triforce_goal_min = get_choice_default('triforce_goal_min', default=0)
ret.triforce_goal_max = get_choice_default('triforce_goal_max', default=0)
ret.triforce_min_difference = get_choice_default('triforce_min_difference', default=0)
ret.triforce_max_difference = get_choice_default('triforce_max_difference', default=10000)
ret.mode = get_choice('world_state')
if ret.mode == 'retro':