Merge branch 'OverworldShuffle' into GwaaKiwi

This commit is contained in:
2025-12-14 09:14:20 -06:00
188 changed files with 1479 additions and 6109 deletions

View File

@@ -208,6 +208,7 @@ class CustomSettings(object):
# rom adjust stuff
args.sprite[p] = get_setting(settings['sprite'], args.sprite[p])
args.triforce_gfx[p] = get_setting(settings['triforce_gfx'], args.triforce_gfx[p])
args.disablemusic[p] = get_setting(settings['disablemusic'], args.disablemusic[p])
args.quickswap[p] = get_setting(settings['quickswap'], args.quickswap[p])
args.reduce_flashing[p] = get_setting(settings['reduce_flashing'], args.reduce_flashing[p])
@@ -221,6 +222,14 @@ class CustomSettings(object):
args.shuffle_songinstruments[p] = get_setting(settings['shuffle_songinstruments'], args.shuffle_songinstruments[p])
args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p])
def has_setting(self, player, setting):
if 'settings' in self.file_source and player in self.file_source['settings']:
return setting in self.file_source['settings'][player]
return False
def get_setting(self, player, setting):
return self.file_source['settings'][player][setting]
def get_item_pool(self):
if 'item_pool' in self.file_source:
return self.file_source['item_pool']
@@ -299,9 +308,9 @@ class CustomSettings(object):
return self.file_source['enemies']
return None
def get_gtentry(self):
if 'gt_entry' in self.file_source:
return self.file_source['gt_entry']
def get_goals(self):
if 'goals' in self.file_source:
return self.file_source['goals']
return None
@@ -577,9 +586,13 @@ class CustomSettings(object):
def load_yaml(path):
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
try:
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
except yaml.YAMLError as e:
error_msg = f"Error parsing YAML file '{path}':\n{str(e)}"
raise ValueError(error_msg) from e

View File

@@ -0,0 +1,130 @@
from tkinter import Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, LEFT, BOTTOM, X, RIGHT, TOP
import os
from GuiUtils import ToolTips, set_icon
from Utils import local_path
class ItemGfxSelector(object):
def __init__(self, parent, callback, valid_items=None, adjuster=False):
self.parent = parent
self.window = Toplevel(parent)
self.window.geometry("800x650")
self.callback = callback
self.valid_items = valid_items if valid_items else []
self.adjuster = adjuster
self.window.wm_title("Select Triforce Piece Graphics")
self.window['padx'] = 5
self.window['pady'] = 5
def open_itemgfx_dir(_evt):
from Utils import open_file
itemgfx_dir = local_path(os.path.join("data", "itemgfx"))
if not os.path.isdir(itemgfx_dir):
os.makedirs(itemgfx_dir)
open_file(itemgfx_dir)
frametitle = Frame(self.window)
title_text = Label(frametitle, text="Item Graphics")
title_text.pack(side=LEFT)
local_title_link = Label(frametitle, text="(open folder)", fg="blue", cursor="hand2")
local_title_link.pack(side=LEFT)
local_title_link.bind("<Button-1>", open_itemgfx_dir)
self.icon_section(frametitle)
frame = Frame(self.window)
frame.pack(side=BOTTOM, fill=X, pady=5)
button = Button(frame, text="Default (Triforce)", command=self.use_default)
button.pack(side=LEFT, padx=(0, 5))
if adjuster:
button = Button(frame, text="Current triforce from rom", command=self.use_default_unchanged)
button.pack(side=LEFT, padx=(0, 5))
set_icon(self.window)
self.window.focus()
def icon_section(self, frame_label):
frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5)
canvas = Canvas(frame, borderwidth=0, width=780)
y_scrollbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
y_scrollbar.pack(side="right", fill="y")
content_frame = Frame(canvas)
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4, 4), window=content_frame, anchor="nw")
canvas.configure(yscrollcommand=y_scrollbar.set)
def onFrameConfigure(canvas):
"""Reset the scroll region to encompass the inner frame"""
canvas.configure(scrollregion=canvas.bbox("all"))
content_frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
frame.pack(side=TOP, fill="both", expand=True)
itemgfx_dir = local_path(os.path.join("data", "itemgfx"))
if not os.path.exists(itemgfx_dir):
label = Label(content_frame, text='No item graphics found in data/itemgfx folder.')
label.pack()
return
# Get all GIF files (converted from PNG)
gif_files = []
for file in os.listdir(itemgfx_dir):
if file.lower().endswith('.gif'):
item_name = os.path.splitext(file)[0]
# Only include if it's in the valid_items list
if item_name in self.valid_items:
gif_files.append((file, item_name))
# Sort by name
gif_files.sort(key=lambda x: str.lower(x[1]))
if len(gif_files) == 0:
label = Label(content_frame, text='No valid item graphics found. Items must match names from Tables.py.')
label.pack()
return
# Calculate how many columns can fit (assuming ~40px per icon with padding)
max_columns = 18
i = 0
for filename, item_name in gif_files:
filepath = os.path.join(itemgfx_dir, filename)
image = self.get_image_for_item(filepath)
if image is None:
continue
button = Button(content_frame, image=image, command=lambda name=item_name: self.select_item(name))
ToolTips.register(button, item_name)
button.image = image
button.grid(row=i // max_columns, column=i % max_columns, padx=2, pady=2)
i += 1
if i == 0:
label = Label(content_frame, text='No valid item graphics could be loaded.')
label.pack()
def get_image_for_item(self, filepath):
"""Load and prepare an item graphic for display"""
try:
# Load GIF with native Tkinter PhotoImage (no PIL required)
photo = PhotoImage(file=filepath)
return photo
except Exception as e:
print(f"Error loading image {filepath}: {e}")
return None
def use_default(self):
self.callback("Triforce")
self.window.destroy()
def use_default_unchanged(self):
self.callback(None)
self.window.destroy()
def select_item(self, item_name):
self.callback(item_name)
self.window.destroy()

View File

@@ -333,13 +333,13 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
if portal.destination:
paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon':
paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
paths.append(world.default_zelda_region[player])
paths.append((world.default_zelda_region[player], 'Hyrule Castle Throne Room'))
entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South')
# todo: in non-er, we can use the other portals too
paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name))
paths.append((world.default_zelda_region[player], entrance.door.entrance.parent_region.name))
paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name,
'Hyrule Dungeon Cellblock']))
world.default_zelda_region[player]]))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names:

View File

@@ -1,4 +1,5 @@
import RaceRandom as random
from collections import defaultdict
from Utils import snes_to_pc
from source.dungeon.EnemyList import SpriteType, EnemySprite, sprite_translation
@@ -446,13 +447,16 @@ def randomize_enemies(world, player):
set_mimics(data_tables)
elif world.enemy_shuffle[player] != 'none':
data_tables = world.data_tables[player]
custom_ow, custom_uw = {}, {}
enemy_map = world.customizer.get_enemies() if world.customizer else None
if enemy_map and player in enemy_map:
if 'Underworld' in enemy_map[player]:
custom_uw = enemy_map[player]['Underworld']
if 'Overworld' in enemy_map[player]:
custom_ow = enemy_map[player]['Overworld']
if world.force_enemy[player]:
custom_ow = {area_id: {i: world.force_enemy[player] for i, s in enumerate(sprite_list)} for area_id, sprite_list in world.data_tables[player].ow_enemy_table.items()}
custom_uw = {room_id: {i: world.force_enemy[player] for i, s in enumerate(sprite_list)} for room_id, sprite_list in world.data_tables[player].uw_enemy_table.room_map.items()}
else:
enemy_map = world.customizer.get_enemies() if world.customizer else None
if enemy_map and player in enemy_map:
if 'Underworld' in enemy_map[player]:
custom_uw = enemy_map[player]['Underworld']
if 'Overworld' in enemy_map[player]:
custom_ow = enemy_map[player]['Overworld']
randomize_underworld_sprite_sheets(data_tables.sprite_sheets, data_tables, custom_uw)
randomize_underworld_rooms(data_tables, world, player, custom_uw)
randomize_overworld_sprite_sheets(data_tables.sprite_sheets, data_tables, custom_ow)
@@ -525,6 +529,21 @@ def randomize_enemies(world, player):
green_mail, blue_mail, red_mail = original_table[idx]
del original_table[idx]
world.data_tables[player].enemy_damage[i] = [green_mail, blue_mail, red_mail]
# determine default zelda follower location
if world.mode[player] == 'standard' and world.doorShuffle[player] == 'crossed' and world.shuffle_followers[player]:
def random_zelda():
world.default_zelda_region[player] = random.choice(['Hyrule Dungeon Cellblock', 'Thieves Blind\'s Cell'])
if world.customizer:
placements = world.customizer.get_placements()
if placements and player in placements and 'Zelda Herself' in placements[player].values():
location = [l for (l, item) in placements[player].items() if item == 'Zelda Herself'][0]
if location == 'Suspicious Maiden':
world.default_zelda_region[player] = 'Thieves Blind\'s Cell'
else:
random_zelda()
else:
random_zelda()
def write_enemy_shuffle_settings(world, player, rom):

View File

@@ -363,7 +363,7 @@ def init_sprite_requirements():
SpriteRequirement(EnemySprite.TrinexxFireHead).exalt().sub_group(0, 0x40).sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.TrinexxIceHead).exalt().sub_group(0, 0x40).sub_group(3, 0x3f),
SpriteRequirement(EnemySprite.Blind).exalt().sub_group(1, 0x2c).sub_group(2, 0x3b),
SpriteRequirement(EnemySprite.Swamola).no_drop().sub_group(3, 0x19),
SpriteRequirement(EnemySprite.Swamola).skip().no_drop().sub_group(3, 0x19),
SpriteRequirement(EnemySprite.Lynel).sub_group(3, 0x14),
SpriteRequirement(EnemySprite.BunnyBeam).no_drop().ow_skip(),
SpriteRequirement(EnemySprite.FloppingFish).uw_skip().immune(),
@@ -683,9 +683,11 @@ def setup_custom_enemy_sheets(custom_enemies, sheets, data_tables, sheet_range,
if key not in requirements:
continue
req = requirements[key]
if isinstance(req, dict):
if isinstance(req, dict) and room_id in req:
req = req[room_id]
if req.static or not req.can_randomize:
else:
req = None
if req and (req.static or not req.can_randomize):
try:
combine_req(sub_groups_choices, req)
except IncompatibleEnemyException:

View File

@@ -84,7 +84,7 @@ UwGeneralDeny:
- [ 0x0039, 4, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 1"
- [0x0039, 5, ["RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "Bumper", "AntiFairyCircle"]] #"Skull Woods - Play Pen - Hardhat Beetle"
- [ 0x0039, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "FirebarCW", "FirebarCCW" ] ] #"Skull Woods - Play Pen - Spike Trap 2"
- [0x003a, 1, ["RollerVerticalUp"]]
- [0x003a, 1, ["RollerVerticalUp", "RollerVerticalDown"]]
- [ 0x003b, 1, [ "Bumper", "AntiFairyCircle" ]]
- [ 0x003b, 4, ["RollerVerticalUp", "RollerVerticalDown"]]
- [ 0x003c, 0, ["BigSpike"]]
@@ -339,7 +339,11 @@ UwGeneralDeny:
- [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur"
- [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4"
- [0x00c6, 2, ["Bumper", "AntiFairyCircle"]]
- [0x00c6, 3, ["Bumper", "AntiFairyCircle"]]
- [0x00c6, 4, ["Bumper", "AntiFairyCircle"]]
- [0x00c6, 5, ["Bumper", "AntiFairyCircle"]]
- [0x00c6, 6, ["Bumper", "AntiFairyCircle"]]
- [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1"
- [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2"
@@ -397,6 +401,7 @@ UwGeneralDeny:
- [ 0x00e4, 2, [ "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home - Keese 3"
- [ 0x00e5, 4, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 5"
- [ 0x00e5, 5, [ "RollerVerticalDown", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Old Man Home Circle - Keese 6"
- [0x00e6, 0, ["Statue"]] # Death Mountain Descent Cave Left - Keese 1 - Statue blocking pot access
- [ 0x00e7, 0, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 1"
- [ 0x00e7, 1, [ "RollerVerticalUp", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Death Mountain Descent Cave Right - Keese 2"
- [ 0x00e7, 2, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalRight" ] ] #"Death Mountain Descent Cave Right - Keese 3"
@@ -441,7 +446,7 @@ OwGeneralDeny:
- [0x05, 10, ["Bumper", "AntiFairyCircle"]] # Blocks path
- [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal
- [0x07, 3, ["Bumper", "AntiFairyCircle", "RollerHorizontalRight", "RollerHorizontalLeft"]] # Blocks path to ladder
- [0x07, 4, ["RollerHorizontalLeft"]] # Blocks path to ladder
- [0x07, 4, ["Bumper", "AntiFairyCircle", "RollerHorizontalLeft"]] # Blocks path to ladder
- [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here
- [0x35, 8, ["RollerVerticalUp", "RollerVerticalDown"]] # blocks the dock
- [0x37, 5, ["RollerVerticalUp"]] # combines with a roller above to make the way impassable
@@ -450,7 +455,7 @@ OwGeneralDeny:
- [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 16, ["RollerVerticalUp", "RollerVerticalDown"]] # Ropa near back hole is really large as a roller
- [0x55, 6, ["BigSpike"]]
- [0x55, 6, ["BigSpike", "Bumper", "AntiFairyCircle"]]
- [0x57, 5, ["RollerVerticalUp", "RollerVerticalDown"]]
- [0x5b, 0, ["AntiFairyCircle", "Bumper"]] # ropa on pyramid
- [0x5e, 0, ["Gibo"]] # kiki eating Gibo
@@ -480,6 +485,7 @@ OwGeneralDeny:
- [0x77, 1, ["Bumper", "AntiFairyCircle"]] # soft-lock potential near ladder
- [0x7f, 1, ["Bumper", "AntiFairyCircle"]] # soft-lock potential near ladder
UwEnemyDrop:
- [0x0015, 8, ["Zoro"]] # layer issues
- [0x0085, 9, ["Babasu"]] # ran off the edge and didn't return
- [0x00cb, 3, ["Zoro"]] # layer issues
- [0x00cb, 5, ["Zoro"]] # layer issues

View File

@@ -2,6 +2,7 @@ from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame
from AdjusterMain import adjust, patch
from argparse import Namespace
from source.classes.SpriteSelector import SpriteSelector
from source.classes.ItemGfxSelector import ItemGfxSelector
import source.gui.widgets as widgets
import json
import logging
@@ -71,6 +72,30 @@ def adjust_page(top, parent, settings):
spriteSelectButton2.pack(side=LEFT)
spriteDialogFrame2.pack(anchor=E)
# Triforce Piece GFX Selection
self.triforceGfxNameVar = StringVar()
self.triforceGfxNameVar.set('(unchanged)')
triforceGfxDialogFrame = Frame(self.frames["leftAdjustFrame"])
triforceGfxLabel = Label(triforceGfxDialogFrame, text='Triforce Piece:')
triforceGfxEntry = Label(triforceGfxDialogFrame, textvariable=self.triforceGfxNameVar)
self.triforce_gfx = None
def set_triforce_gfx(item_name):
self.triforce_gfx = item_name
self.triforceGfxNameVar.set(item_name if item_name else '(unchanged)')
def TriforceGfxSelectAdjuster():
from Tables import item_gfx_table
valid_items = list(item_gfx_table.keys())
ItemGfxSelector(parent, set_triforce_gfx, valid_items=valid_items, adjuster=True)
triforceGfxSelectButton = Button(triforceGfxDialogFrame, text='...', command=TriforceGfxSelectAdjuster)
triforceGfxLabel.pack(side=LEFT)
triforceGfxEntry.pack(side=LEFT)
triforceGfxSelectButton.pack(side=LEFT)
triforceGfxDialogFrame.pack(anchor=E)
# Path to game file to Adjust
# This one's more-complicated, build it and stuff it
adjustRomFrame = Frame(self.frames["bottomAdjustFrame"])
@@ -117,6 +142,7 @@ def adjust_page(top, parent, settings):
guiargs.rom = self.romVar2.get()
guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
guiargs.sprite = self.sprite
guiargs.triforce_gfx = self.triforce_gfx
guiargs.outputpath = os.path.dirname(guiargs.rom)
try:
adjust(args=guiargs)
@@ -171,6 +197,7 @@ def adjust_page(top, parent, settings):
guiargs.patch = self.patchVar.get()
guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
guiargs.sprite = self.sprite
guiargs.triforce_gfx = self.triforce_gfx
guiargs.outputpath = os.path.dirname(guiargs.patch)
try:
patch(args=guiargs)

View File

@@ -77,32 +77,37 @@ def bottom_frame(self, parent, args=None):
argsDump['sprite'] = argsDump['sprite'].name
save_settings(parent, argsDump, "last.json")
guiargs = create_guiargs(parent)
# get default values for missing parameters
cliargs = ['--multi', str(guiargs.multi)]
if hasattr(guiargs, 'customizer'):
cliargs.extend(['--customizer', str(guiargs.customizer)])
for k,v in vars(parse_cli(cliargs)).items():
if k not in vars(guiargs):
setattr(guiargs, k, v)
elif type(v) is dict: # use same settings for every player
players = guiargs.multi if len(v) == 0 else len(v)
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, players + 1)})
argsDump = vars(guiargs)
needEnemizer = False
falsey = ["none", "default", False, 0]
for enemizerOption in ["shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health"]:
if enemizerOption in argsDump:
if isinstance(argsDump[enemizerOption], dict):
for playerID,playerSetting in argsDump[enemizerOption].items():
if not playerSetting in falsey:
needEnemizer = True
elif not argsDump[enemizerOption] in falsey:
needEnemizer = True
seeds = []
try:
guiargs = create_guiargs(parent)
# get default values for missing parameters
cliargs = ['--multi', str(guiargs.multi)]
if hasattr(guiargs, 'customizer'):
cliargs.extend(['--customizer', str(guiargs.customizer)])
for k,v in vars(parse_cli(cliargs)).items():
if k not in vars(guiargs):
setattr(guiargs, k, v)
elif type(v) is dict: # use same settings for every player
players = guiargs.multi if len(v) == 0 else len(v)
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, players + 1)})
if guiargs.multi == 1 and guiargs.names.endswith("=="):
# allow settings code thru player names entry
guiargs.code[1] = guiargs.names
guiargs.names = ""
argsDump = vars(guiargs)
needEnemizer = False
falsey = ["none", "default", False, 0]
for enemizerOption in ["shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health"]:
if enemizerOption in argsDump:
if isinstance(argsDump[enemizerOption], dict):
for playerID,playerSetting in argsDump[enemizerOption].items():
if not playerSetting in falsey:
needEnemizer = True
elif not argsDump[enemizerOption] in falsey:
needEnemizer = True
seeds = []
if guiargs.count is not None and guiargs.seed:
seed = guiargs.seed
for _ in range(guiargs.count):
@@ -340,6 +345,9 @@ def create_guiargs(parent):
guiargs.sprite = parent.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"]
guiargs.randomSprite = parent.randomSprite.get()
# Get Triforce Piece GFX Selection
guiargs.triforce_gfx = parent.pages["randomizer"].pages["gameoptions"].widgets["triforce_gfx"]["selectedItem"]
# Get output path
guiargs.outputpath = parent.settings["outputpath"]

View File

@@ -197,6 +197,13 @@ def loadcliargs(gui, args, settings=None):
spriteNameVar=gui.pages["adjust"].content.spriteNameVar2,
randomSpriteVar=gui.randomSprite)
# Figure out Triforce GFX Selection
if "triforce_gfx" in args and args["triforce_gfx"] is not None:
gui.pages["randomizer"].pages["gameoptions"].widgets["triforce_gfx"]["selectedItem"] = args["triforce_gfx"]
gui.pages["randomizer"].pages["gameoptions"].widgets["triforce_gfx"]["itemNameVar"].set(args["triforce_gfx"])
gui.pages["adjust"].content.triforce_gfx = args["triforce_gfx"]
gui.pages["adjust"].content.triforceGfxNameVar.set(args["triforce_gfx"])
# Load args/settings for Adjust tab
def loadadjustargs(gui, settings):
options = {

View File

@@ -1,6 +1,7 @@
from tkinter import ttk, StringVar, Button, Entry, Frame, Label, NE, NW, E, W, LEFT, RIGHT
from functools import partial
import source.classes.SpriteSelector as spriteSelector
import source.classes.ItemGfxSelector as itemGfxSelector
import source.gui.widgets as widgets
import json
import os
@@ -66,6 +67,34 @@ def gameoptions_page(top, parent):
spriteSelectButton.pack(side=LEFT)
spriteDialogFrame.pack(anchor=E)
## Triforce Piece graphics selection
triforcegfxDialogFrame = Frame(self.frames["leftRomOptionsFrame"])
triforceGfxLabel = Label(triforcegfxDialogFrame, text='Triforce Piece:')
self.widgets["triforce_gfx"] = {}
self.widgets["triforce_gfx"]["selectedItem"] = None
self.widgets["triforce_gfx"]["itemNameVar"] = StringVar()
self.widgets["triforce_gfx"]["itemNameVar"].set('Triforce')
triforceGfxEntry = Label(triforcegfxDialogFrame, textvariable=self.widgets["triforce_gfx"]["itemNameVar"])
def triforce_gfx_setter(item_name):
self.widgets["triforce_gfx"]["selectedItem"] = item_name
self.widgets["triforce_gfx"]["itemNameVar"].set(item_name)
def triforce_gfx_select():
# Import Tables to get valid item names
from Tables import item_gfx_table
valid_items = list(item_gfx_table.keys())
itemGfxSelector.ItemGfxSelector(parent, triforce_gfx_setter, valid_items=valid_items)
triforceGfxSelectButton = Button(triforcegfxDialogFrame, text='...', command=triforce_gfx_select)
triforceGfxLabel.pack(side=LEFT)
triforceGfxEntry.pack(side=LEFT)
triforceGfxSelectButton.pack(side=LEFT)
triforcegfxDialogFrame.pack(anchor=E)
return self

View File

@@ -403,6 +403,7 @@ def do_old_man_cave_exit(entrances, exits, avail, cross_world):
else:
region_name = 'West Dark Death Mountain (Top)'
om_cave_options = list(get_accessible_entrances(region_name, avail, [], cross_world, True, True, True, True))
om_cave_options = [e for e in om_cave_options if e in avail.entrances]
if avail.swapped:
om_cave_options = [e for e in om_cave_options if e not in Forbidden_Swap_Entrances]
assert len(om_cave_options), 'No available entrances left to place Old Man Cave'
@@ -643,7 +644,6 @@ def do_dark_sanc(entrances, exits, avail):
entrances.remove(choice)
exits.remove('Dark Sanctuary Hint')
connect_entrance(choice, 'Dark Sanctuary Hint', avail)
ext.connect(avail.world.get_entrance(choice, avail.player).parent_region)
if not avail.coupled:
avail.decoupled_entrances.remove(choice)
if avail.swapped and choice != 'Dark Sanctuary Hint':
@@ -651,8 +651,7 @@ def do_dark_sanc(entrances, exits, avail):
entrances.remove(swap_ent)
exits.remove(swap_ext)
elif not ext.connected_region:
# default to output to vanilla area, assume vanilla connection
ext.connect(avail.world.get_region('Dark Chapel Area', avail.player))
raise Exception('Dark Sanctuary Hint was placed earlier but its exit not properly connected')
def do_links_house(entrances, exits, avail, cross_world):
@@ -722,8 +721,6 @@ def do_links_house(entrances, exits, avail, cross_world):
connect_two_way(links_house, lh_exit, avail)
else:
connect_entrance(links_house, lh_exit, avail)
ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player)
ext.connect(avail.world.get_entrance(links_house, avail.player).parent_region)
entrances.remove(links_house)
exits.remove(lh_exit)
if not avail.coupled:
@@ -867,7 +864,7 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo
for p in range(1, avail.world.players + 1):
avail.world.key_logic[p] = {}
base_world = copy_world_premature(avail.world, avail.player)
base_world = copy_world_premature(avail.world, avail.player, create_flute_exits=True)
base_world.override_bomb_check = True
connect_simple(base_world, 'Links House S&Q', start_region, avail.player)
@@ -1907,6 +1904,12 @@ def connect_entrance(entrancename, exit_name, avail):
avail.entrances.remove(entrancename)
if avail.coupled:
avail.exits.remove(exit_name)
if exit_name == 'Big Bomb Shop' and avail.world.is_bombshop_start(avail.player):
ext = avail.world.get_entrance('Big Bomb Shop Exit', avail.player)
ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region)
if exit_name == 'Dark Sanctuary Hint' and avail.world.is_dark_chapel_start(avail.player):
ext = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player)
ext.connect(avail.world.get_entrance(entrancename, avail.player).parent_region)
world.spoiler.set_entrance(entrance.name, exit.name if exit is not None else region.name, 'entrance', player)
logging.getLogger('').debug(f'Connected (entr) {entrance.name} to {exit.name if exit is not None else region.name}')

View File

@@ -261,6 +261,7 @@ def roll_settings(weights):
if 'rom' in weights:
romweights = weights['rom']
ret.sprite = get_choice('sprite', romweights)
ret.triforce_gfx = get_choice('triforce_gfx', romweights)
ret.disablemusic = get_choice_bool('disablemusic', romweights)
ret.quickswap = get_choice_bool('quickswap', romweights)
ret.reduce_flashing = get_choice_bool('reduce_flashing', romweights)