Merge branch 'main' into breaking_changes

# Conflicts:
#	Adjuster.py
#	Gui.py
#	MultiClient.py
#	setup.py
#	worlds/alttp/AdjusterMain.py
#	worlds/alttp/Main.py
This commit is contained in:
Fabian Dill 2021-02-21 20:15:07 +01:00
commit dcce53f8c8
23 changed files with 424 additions and 404 deletions

View File

@ -4,9 +4,11 @@ import os
import logging import logging
import textwrap import textwrap
import sys import sys
import time
from AdjusterMain import adjust from AdjusterMain import adjust
from worlds.alttp.Rom import Sprite from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings
from Utils import output_path
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
@ -27,6 +29,7 @@ def main():
''') ''')
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
parser.add_argument('--enableflashing', help='Reenable flashing animations (unfriendly to epilepsy, always disabled in race roms)', action='store_false', dest="reduceflashing")
parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'], parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
help='''\ help='''\
Select the rate at which the heart beep sound is played at Select the rate at which the heart beep sound is played at
@ -50,25 +53,127 @@ def main():
parser.add_argument('--names', default='', type=str) parser.add_argument('--names', default='', type=str)
args = parser.parse_args() args = parser.parse_args()
# ToDo: Validate files further than mere existance
if not os.path.isfile(args.rom):
input(
'Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
sys.exit(1)
if args.sprite is not None and not os.path.isfile(args.sprite) and not Sprite.get_sprite_from_name(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
sys.exit(1)
# set up logger # set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[ loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[
args.loglevel] args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel) logging.basicConfig(format='%(message)s', level=loglevel)
if not os.path.isfile(args.rom):
adjustGUI()
else:
if args.sprite is not None and not os.path.isfile(args.sprite) and not Sprite.get_sprite_from_name(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
sys.exit(1)
args, path = adjust(args=args) args, path = adjust(args=args)
from Utils import persistent_store from Utils import persistent_store
from Rom import Sprite
if isinstance(args.sprite, Sprite): if isinstance(args.sprite, Sprite):
args.sprite = args.sprite.name args.sprite = args.sprite.name
persistent_store("adjuster", "last_settings_3", args) persistent_store("adjuster", "last_settings_3", args)
def adjust(args):
start = time.perf_counter()
logger = logging.getLogger('Adjuster')
logger.info('Patching ROM.')
vanillaRom = args.baserom
if os.path.splitext(args.rom)[-1].lower() == '.bmbp':
import Patch
meta, args.rom = Patch.create_rom_file(args.rom)
if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc':
rom = LocalRom(args.rom, patch=False, vanillaRom=vanillaRom)
else:
raise RuntimeError(
'Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
palettes_options={}
palettes_options['dungeon']=args.uw_palettes
palettes_options['overworld']=args.ow_palettes
palettes_options['hud']=args.hud_palettes
palettes_options['sword']=args.sword_palettes
palettes_options['shield']=args.shield_palettes
# palettes_options['link']=args.link_palettesvera
racerom = rom.read_byte(0x180213) > 0
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom)
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
rom.write_to_file(path)
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s', time.perf_counter() - start)
return args, path
def adjustGUI():
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, \
StringVar, IntVar, Frame, Label, W, E, X, BOTH, Entry, Spinbox, Button, filedialog, messagebox, ttk
from Gui import get_rom_options_frame, get_rom_frame
from GuiUtils import set_icon
from argparse import Namespace
from Main import __version__ as MWVersion
adjustWindow = Tk()
adjustWindow.wm_title("Berserker's Multiworld %s LttP Adjuster" % MWVersion)
set_icon(adjustWindow)
rom_options_frame, rom_vars, set_sprite = get_rom_options_frame(adjustWindow)
bottomFrame2 = Frame(adjustWindow)
romFrame, romVar = get_rom_frame(adjustWindow)
romDialogFrame = Frame(adjustWindow)
baseRomLabel2 = Label(romDialogFrame, text='Rom to adjust')
romVar2 = StringVar()
romEntry2 = Entry(romDialogFrame, textvariable=romVar2)
def RomSelect2():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".bmbp")), ("All Files", "*")])
romVar2.set(rom)
romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2)
romDialogFrame.pack(side=TOP, expand=True, fill=X)
baseRomLabel2.pack(side=LEFT)
romEntry2.pack(side=LEFT, expand=True, fill=X)
romSelectButton2.pack(side=LEFT)
def adjustRom():
guiargs = Namespace()
guiargs.heartbeep = rom_vars.heartbeepVar.get()
guiargs.heartcolor = rom_vars.heartcolorVar.get()
guiargs.fastmenu = rom_vars.fastMenuVar.get()
guiargs.ow_palettes = rom_vars.owPalettesVar.get()
guiargs.uw_palettes = rom_vars.uwPalettesVar.get()
guiargs.hud_palettes = rom_vars.hudPalettesVar.get()
guiargs.sword_palettes = rom_vars.swordPalettesVar.get()
guiargs.shield_palettes = rom_vars.shieldPalettesVar.get()
guiargs.quickswap = bool(rom_vars.quickSwapVar.get())
guiargs.disablemusic = bool(rom_vars.disableMusicVar.get())
guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get())
guiargs.rom = romVar2.get()
guiargs.baserom = romVar.get()
guiargs.sprite = rom_vars.sprite
try:
guiargs, path = adjust(args=guiargs)
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while adjusting Rom", message=str(e))
else:
messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}")
from Utils import persistent_store
from Rom import Sprite
if isinstance(guiargs.sprite, Sprite):
guiargs.sprite = guiargs.sprite.name
persistent_store("adjuster", "last_settings_3", guiargs)
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
rom_options_frame.pack(side=TOP)
adjustButton.pack(side=BOTTOM, padx=(5, 5))
bottomFrame2.pack(side=BOTTOM, pady=(5, 5))
adjustWindow.mainloop()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -4,6 +4,7 @@ import copy
from enum import Enum, unique from enum import Enum, unique
import logging import logging
import json import json
import functools
from collections import OrderedDict, Counter, deque from collections import OrderedDict, Counter, deque
from typing import * from typing import *
import secrets import secrets
@ -25,6 +26,7 @@ class MultiWorld():
plando_texts: List[Dict[str, str]] plando_texts: List[Dict[str, str]]
plando_items: List[PlandoItem] plando_items: List[PlandoItem]
plando_connections: List[PlandoConnection] plando_connections: List[PlandoConnection]
er_seeds: Dict[int, str]
def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, item_functionality, timer, def __init__(self, players: int, shuffle, logic, mode, swords, difficulty, item_functionality, timer,
progressive, progressive,
@ -157,6 +159,10 @@ class MultiWorld():
region.world = self region.world = self
self._region_cache[region.player][region.name] = region self._region_cache[region.player][region.name] = region
@functools.cached_property
def world_name_lookup(self):
return {self.player_names[player_id][0]: player_id for player_id in self.player_ids}
def _recache(self): def _recache(self):
"""Rebuild world cache""" """Rebuild world cache"""
for region in self.regions: for region in self.regions:
@ -1285,7 +1291,8 @@ class Spoiler(object):
'shop_shuffle_slots': self.world.shop_shuffle_slots, 'shop_shuffle_slots': self.world.shop_shuffle_slots,
'shuffle_prizes': self.world.shuffle_prizes, 'shuffle_prizes': self.world.shuffle_prizes,
'sprite_pool': self.world.sprite_pool, 'sprite_pool': self.world.sprite_pool,
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss 'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss,
'er_seeds': self.world.er_seeds
} }
def to_json(self): def to_json(self):
@ -1349,6 +1356,8 @@ class Spoiler(object):
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player]) outfile.write('Item Progression: %s\n' % self.metadata['progressive'][player])
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
if self.metadata['shuffle'][player] != "vanilla":
outfile.write('Entrance Shuffle Seed %s\n' % self.metadata['er_seeds'][player])
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player]) outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player])
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player]) outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player])
outfile.write('Pyramid hole pre-opened: %s\n' % ( outfile.write('Pyramid hole pre-opened: %s\n' % (

View File

@ -359,7 +359,7 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked=
def distribute_planned(world): def distribute_planned(world):
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids} world_name_lookup = world.world_name_lookup
for player in world.player_ids: for player in world.player_ids:
placement: PlandoItem placement: PlandoItem

493
Gui.py
View File

@ -19,7 +19,7 @@ from worlds.alttp.EntranceRandomizer import parse_arguments
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
from worlds.alttp.Main import main, get_seed, __version__ as MWVersion from worlds.alttp.Main import main, get_seed, __version__ as MWVersion
from worlds.alttp.Rom import Sprite from worlds.alttp.Rom import Sprite
from Utils import is_bundled, local_path, output_path, open_file from Utils import local_path, output_path, open_file
def guiMain(args=None): def guiMain(args=None):
@ -30,10 +30,8 @@ def guiMain(args=None):
notebook = ttk.Notebook(mainWindow) notebook = ttk.Notebook(mainWindow)
randomizerWindow = ttk.Frame(notebook) randomizerWindow = ttk.Frame(notebook)
adjustWindow = ttk.Frame(notebook)
customWindow = ttk.Frame(notebook) customWindow = ttk.Frame(notebook)
notebook.add(randomizerWindow, text='Randomize') notebook.add(randomizerWindow, text='Randomize')
notebook.add(adjustWindow, text='Adjust')
notebook.add(customWindow, text='Custom Items') notebook.add(customWindow, text='Custom Items')
notebook.pack() notebook.pack()
@ -57,6 +55,8 @@ def guiMain(args=None):
# randomizer controls # randomizer controls
topFrame = Frame(randomizerWindow) topFrame = Frame(randomizerWindow)
romFrame, romVar = get_rom_frame(topFrame)
rightHalfFrame = Frame(topFrame) rightHalfFrame = Frame(topFrame)
checkBoxFrame = Frame(rightHalfFrame) checkBoxFrame = Frame(rightHalfFrame)
@ -119,154 +119,9 @@ def guiMain(args=None):
hintsCheckbutton.pack(expand=True, anchor=W) hintsCheckbutton.pack(expand=True, anchor=W)
tileShuffleButton.pack(expand=True, anchor=W) tileShuffleButton.pack(expand=True, anchor=W)
romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options")
romOptionsFrame.columnconfigure(0, weight=1)
romOptionsFrame.columnconfigure(1, weight=1)
for i in range(5):
romOptionsFrame.rowconfigure(i, weight=1)
disableMusicVar = IntVar()
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=disableMusicVar)
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
spriteDialogFrame = Frame(romOptionsFrame)
spriteDialogFrame.grid(row=0, column=1)
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
spriteNameVar = StringVar()
sprite = None
def set_sprite(sprite_param):
nonlocal sprite
if isinstance(sprite_param, str):
sprite = sprite_param
spriteNameVar.set(sprite_param)
elif sprite_param is None or not sprite_param.valid:
sprite = None
spriteNameVar.set('(unchanged)')
else:
sprite = sprite_param
spriteNameVar.set(sprite.name)
set_sprite(None)
spriteNameVar.set('(unchanged)')
spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar)
def SpriteSelect():
SpriteSelector(mainWindow, set_sprite)
spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect)
baseSpriteLabel.pack(side=LEFT)
spriteEntry.pack(side=LEFT)
spriteSelectButton.pack(side=LEFT)
quickSwapVar = IntVar(value=1)
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=quickSwapVar)
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
fastMenuFrame = Frame(romOptionsFrame)
fastMenuFrame.grid(row=1, column=1, sticky=E)
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
fastMenuLabel.pack(side=LEFT)
fastMenuVar = StringVar()
fastMenuVar.set('normal')
fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
fastMenuOptionMenu.pack(side=LEFT)
heartcolorFrame = Frame(romOptionsFrame)
heartcolorFrame.grid(row=2, column=0, sticky=E)
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
heartcolorLabel.pack(side=LEFT)
heartcolorVar = StringVar()
heartcolorVar.set('red')
heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
heartcolorOptionMenu.pack(side=LEFT)
heartbeepFrame = Frame(romOptionsFrame)
heartbeepFrame.grid(row=2, column=1, sticky=E)
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep')
heartbeepLabel.pack(side=LEFT)
heartbeepVar = StringVar()
heartbeepVar.set('normal')
heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
heartbeepOptionMenu.pack(side=LEFT)
owPalettesFrame = Frame(romOptionsFrame)
owPalettesFrame.grid(row=3, column=0, sticky=E)
owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes')
owPalettesLabel.pack(side=LEFT)
owPalettesVar = StringVar()
owPalettesVar.set('default')
owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
owPalettesOptionMenu.pack(side=LEFT)
uwPalettesFrame = Frame(romOptionsFrame)
uwPalettesFrame.grid(row=3, column=1, sticky=E)
uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes')
uwPalettesLabel.pack(side=LEFT)
uwPalettesVar = StringVar()
uwPalettesVar.set('default')
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
uwPalettesOptionMenu.pack(side=LEFT)
hudPalettesFrame = Frame(romOptionsFrame)
hudPalettesFrame.grid(row=4, column=0, sticky=E)
hudPalettesLabel = Label(hudPalettesFrame, text='HUD palettes')
hudPalettesLabel.pack(side=LEFT)
hudPalettesVar = StringVar()
hudPalettesVar.set('default')
hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
hudPalettesOptionMenu.pack(side=LEFT)
swordPalettesFrame = Frame(romOptionsFrame)
swordPalettesFrame.grid(row=4, column=1, sticky=E)
swordPalettesLabel = Label(swordPalettesFrame, text='Sword palettes')
swordPalettesLabel.pack(side=LEFT)
swordPalettesVar = StringVar()
swordPalettesVar.set('default')
swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
swordPalettesOptionMenu.pack(side=LEFT)
shieldPalettesFrame = Frame(romOptionsFrame)
shieldPalettesFrame.grid(row=5, column=0, sticky=E)
shieldPalettesLabel = Label(shieldPalettesFrame, text='Shield palettes')
shieldPalettesLabel.pack(side=LEFT)
shieldPalettesVar = StringVar()
shieldPalettesVar.set('default')
shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
shieldPalettesOptionMenu.pack(side=LEFT)
romDialogFrame = Frame(romOptionsFrame)
romDialogFrame.grid(row=6, column=0, columnspan=2, sticky=W+E)
baseRomLabel = Label(romDialogFrame, text='Base Rom: ')
romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc")
romEntry = Entry(romDialogFrame, textvariable=romVar)
def RomSelect():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
import Patch
try:
Patch.get_base_rom_bytes(rom) # throws error on checksum fail
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while reading ROM", message=str(e))
else:
romVar.set(rom)
romSelectButton['state'] = "disabled"
romSelectButton["text"] = "ROM verified"
romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect)
baseRomLabel.pack(side=LEFT)
romEntry.pack(side=LEFT, expand=True, fill=X)
romSelectButton.pack(side=LEFT)
checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10) checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10)
romOptionsFrame, rom_vars, set_sprite = get_rom_options_frame(rightHalfFrame)
romOptionsFrame.pack(expand=True, fill=BOTH, padx=3) romOptionsFrame.pack(expand=True, fill=BOTH, padx=3)
drowDownFrame = Frame(topFrame) drowDownFrame = Frame(topFrame)
@ -382,7 +237,8 @@ def guiMain(args=None):
shuffleVar.set('vanilla') shuffleVar.set('vanilla')
shuffleOptionMenu = OptionMenu(shuffleFrame, shuffleVar, 'vanilla', 'simple', 'restricted', 'full', 'crossed', shuffleOptionMenu = OptionMenu(shuffleFrame, shuffleVar, 'vanilla', 'simple', 'restricted', 'full', 'crossed',
'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy',
'dungeonsfull', 'dungeonssimple') 'dungeonsfull', 'dungeonssimple', "same simple", "same restricted", "same full",
"same crossed", "same insanity", "same dungeonsfull", "same dungeonssimple")
shuffleOptionMenu.pack(side=RIGHT) shuffleOptionMenu.pack(side=RIGHT)
shuffleLabel = Label(shuffleFrame, text='Entrance shuffle') shuffleLabel = Label(shuffleFrame, text='Entrance shuffle')
shuffleLabel.pack(side=LEFT) shuffleLabel.pack(side=LEFT)
@ -563,9 +419,12 @@ def guiMain(args=None):
guiargs.accessibility = accessibilityVar.get() guiargs.accessibility = accessibilityVar.get()
guiargs.algorithm = algorithmVar.get() guiargs.algorithm = algorithmVar.get()
guiargs.shuffle = shuffleVar.get() guiargs.shuffle = shuffleVar.get()
guiargs.heartbeep = heartbeepVar.get() if "same " in guiargs.shuffle:
guiargs.heartcolor = heartcolorVar.get() guiargs.shuffle = guiargs.shuffle[5:] + "-" + str(seedVar.get() if seedVar.get() else
guiargs.fastmenu = fastMenuVar.get() random.randint(0, 2**64))
guiargs.heartbeep = rom_vars.heartbeepVar.get()
guiargs.heartcolor = rom_vars.heartcolorVar.get()
guiargs.fastmenu = rom_vars.fastMenuVar.get()
guiargs.create_spoiler = bool(createSpoilerVar.get()) guiargs.create_spoiler = bool(createSpoilerVar.get())
guiargs.skip_playthrough = not bool(createSpoilerVar.get()) guiargs.skip_playthrough = not bool(createSpoilerVar.get())
guiargs.suppress_rom = bool(suppressRomVar.get()) guiargs.suppress_rom = bool(suppressRomVar.get())
@ -575,13 +434,14 @@ def guiMain(args=None):
guiargs.keyshuffle = {"on": True, "universal": "universal", "off": False}[keyshuffleVar.get()] guiargs.keyshuffle = {"on": True, "universal": "universal", "off": False}[keyshuffleVar.get()]
guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get()) guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get())
guiargs.retro = bool(retroVar.get()) guiargs.retro = bool(retroVar.get())
guiargs.quickswap = bool(quickSwapVar.get()) guiargs.quickswap = bool(rom_vars.quickSwapVar.get())
guiargs.disablemusic = bool(disableMusicVar.get()) guiargs.disablemusic = bool(rom_vars.disableMusicVar.get())
guiargs.ow_palettes = owPalettesVar.get() guiargs.reduceflashing = bool(rom_vars.disableFlashingVar.get())
guiargs.uw_palettes = uwPalettesVar.get() guiargs.ow_palettes = rom_vars.owPalettesVar.get()
guiargs.hud_palettes = hudPalettesVar.get() guiargs.uw_palettes = rom_vars.uwPalettesVar.get()
guiargs.sword_palettes = swordPalettesVar.get() guiargs.hud_palettes = rom_vars.hudPalettesVar.get()
guiargs.shield_palettes = shieldPalettesVar.get() guiargs.sword_palettes = rom_vars.swordPalettesVar.get()
guiargs.shield_palettes = rom_vars.shieldPalettesVar.get()
guiargs.shuffleganon = bool(shuffleGanonVar.get()) guiargs.shuffleganon = bool(shuffleGanonVar.get())
guiargs.hints = bool(hintsVar.get()) guiargs.hints = bool(hintsVar.get())
guiargs.enemizercli = enemizerCLIpathVar.get() guiargs.enemizercli = enemizerCLIpathVar.get()
@ -641,7 +501,7 @@ def guiMain(args=None):
int(rupoorcostVar.get()), int(triforceVar.get())] int(rupoorcostVar.get()), int(triforceVar.get())]
guiargs.rom = romVar.get() guiargs.rom = romVar.get()
guiargs.create_diff = patchesVar.get() guiargs.create_diff = patchesVar.get()
guiargs.sprite = sprite guiargs.sprite = rom_vars.sprite
# get default values for missing parameters # get default values for missing parameters
for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items(): for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items():
if k not in vars(guiargs): if k not in vars(guiargs):
@ -689,150 +549,6 @@ def guiMain(args=None):
enemizerFrame.pack(side=BOTTOM, fill=BOTH) enemizerFrame.pack(side=BOTTOM, fill=BOTH)
shopframe.pack(side=BOTTOM, expand=True, fill=X) shopframe.pack(side=BOTTOM, expand=True, fill=X)
# Adjuster Controls
topFrame2 = Frame(adjustWindow)
rightHalfFrame2 = Frame(topFrame2)
checkBoxFrame2 = Frame(rightHalfFrame2)
quickSwapCheckbutton2 = Checkbutton(checkBoxFrame2, text="L/R Item quickswapping", variable=quickSwapVar)
disableMusicCheckbutton2 = Checkbutton(checkBoxFrame2, text="Disable game music", variable=disableMusicVar)
quickSwapCheckbutton2.pack(expand=True, anchor=W)
disableMusicCheckbutton2.pack(expand=True, anchor=W)
fileDialogFrame2 = Frame(rightHalfFrame2)
romDialogFrame2 = Frame(fileDialogFrame2)
baseRomLabel2 = Label(romDialogFrame2, text='Rom to adjust')
romVar2 = StringVar()
romEntry2 = Entry(romDialogFrame2, textvariable=romVar2)
def RomSelect2():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")])
romVar2.set(rom)
romSelectButton2 = Button(romDialogFrame2, text='Select Rom', command=RomSelect2)
baseRomLabel2.pack(side=LEFT)
romEntry2.pack(side=LEFT)
romSelectButton2.pack(side=LEFT)
spriteDialogFrame2 = Frame(fileDialogFrame2)
baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite')
spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar)
def SpriteSelectAdjuster():
SpriteSelector(mainWindow, set_sprite, adjuster=True)
spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelectAdjuster)
baseSpriteLabel2.pack(side=LEFT)
spriteEntry2.pack(side=LEFT)
spriteSelectButton2.pack(side=LEFT)
romDialogFrame2.pack()
spriteDialogFrame2.pack()
checkBoxFrame2.pack()
fileDialogFrame2.pack()
drowDownFrame2 = Frame(topFrame2)
heartbeepFrame2 = Frame(drowDownFrame2)
heartbeepOptionMenu2 = OptionMenu(heartbeepFrame2, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
heartbeepOptionMenu2.pack(side=RIGHT)
heartbeepLabel2 = Label(heartbeepFrame2, text='Heartbeep sound rate')
heartbeepLabel2.pack(side=LEFT)
heartcolorFrame2 = Frame(drowDownFrame2)
heartcolorOptionMenu2 = OptionMenu(heartcolorFrame2, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
heartcolorOptionMenu2.pack(side=RIGHT)
heartcolorLabel2 = Label(heartcolorFrame2, text='Heart color')
heartcolorLabel2.pack(side=LEFT)
fastMenuFrame2 = Frame(drowDownFrame2)
fastMenuOptionMenu2 = OptionMenu(fastMenuFrame2, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
fastMenuOptionMenu2.pack(side=RIGHT)
fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed')
fastMenuLabel2.pack(side=LEFT)
owPalettesFrame2 = Frame(drowDownFrame2)
owPalettesOptionMenu2 = OptionMenu(owPalettesFrame2, owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
owPalettesOptionMenu2.pack(side=RIGHT)
owPalettesLabel2 = Label(owPalettesFrame2, text='Overworld palettes')
owPalettesLabel2.pack(side=LEFT)
uwPalettesFrame2 = Frame(drowDownFrame2)
uwPalettesOptionMenu2 = OptionMenu(uwPalettesFrame2, uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
uwPalettesOptionMenu2.pack(side=RIGHT)
uwPalettesLabel2 = Label(uwPalettesFrame2, text='Dungeon palettes')
uwPalettesLabel2.pack(side=LEFT)
hudPalettesFrame2 = Frame(drowDownFrame2)
hudPalettesOptionMenu2 = OptionMenu(hudPalettesFrame2, hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
hudPalettesOptionMenu2.pack(side=RIGHT)
hudPalettesLabel2 = Label(hudPalettesFrame2, text='HUD palettes')
hudPalettesLabel2.pack(side=LEFT)
swordPalettesFrame2 = Frame(drowDownFrame2)
swordPalettesOptionMenu2 = OptionMenu(swordPalettesFrame2, swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
swordPalettesOptionMenu2.pack(side=RIGHT)
swordPalettesLabel2 = Label(swordPalettesFrame2, text='Sword palettes')
swordPalettesLabel2.pack(side=LEFT)
shieldPalettesFrame2 = Frame(drowDownFrame2)
shieldPalettesOptionMenu2 = OptionMenu(shieldPalettesFrame2, shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
shieldPalettesOptionMenu2.pack(side=RIGHT)
shieldPalettesLabel2 = Label(shieldPalettesFrame2, text='Shield palettes')
shieldPalettesLabel2.pack(side=LEFT)
heartbeepFrame2.pack(expand=True, anchor=E)
heartcolorFrame2.pack(expand=True, anchor=E)
fastMenuFrame2.pack(expand=True, anchor=E)
owPalettesFrame2.pack(expand=True, anchor=E)
uwPalettesFrame2.pack(expand=True, anchor=E)
hudPalettesFrame2.pack(expand=True, anchor=E)
swordPalettesFrame2.pack(expand=True, anchor=E)
shieldPalettesFrame2.pack(expand=True, anchor=E)
bottomFrame2 = Frame(topFrame2)
def adjustRom():
guiargs = Namespace()
guiargs.heartbeep = heartbeepVar.get()
guiargs.heartcolor = heartcolorVar.get()
guiargs.fastmenu = fastMenuVar.get()
guiargs.ow_palettes = owPalettesVar.get()
guiargs.uw_palettes = uwPalettesVar.get()
guiargs.hud_palettes = hudPalettesVar.get()
guiargs.sword_palettes = swordPalettesVar.get()
guiargs.shield_palettes = shieldPalettesVar.get()
guiargs.quickswap = bool(quickSwapVar.get())
guiargs.disablemusic = bool(disableMusicVar.get())
guiargs.rom = romVar2.get()
guiargs.baserom = romVar.get()
guiargs.sprite = sprite
try:
guiargs, path = adjust(args=guiargs)
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while adjusting Rom", message=str(e))
else:
messagebox.showinfo(title="Success", message="Rom patched successfully")
from Utils import persistent_store
from Rom import Sprite
if isinstance(guiargs.sprite, Sprite):
guiargs.sprite = guiargs.sprite.name
persistent_store("adjuster", "last_settings_3", guiargs)
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
adjustButton.pack(side=LEFT, padx=(5, 0))
drowDownFrame2.pack(side=LEFT, pady=(0, 40))
rightHalfFrame2.pack(side=RIGHT)
topFrame2.pack(side=TOP, pady=70)
bottomFrame2.pack(side=BOTTOM, pady=(180, 0))
# Custom Controls # Custom Controls
topFrame3 = Frame(customWindow) topFrame3 = Frame(customWindow)
@ -1490,8 +1206,9 @@ def guiMain(args=None):
keyshuffleVar.set(args.keyshuffle) keyshuffleVar.set(args.keyshuffle)
bigkeyshuffleVar.set(args.bigkeyshuffle) bigkeyshuffleVar.set(args.bigkeyshuffle)
retroVar.set(args.retro) retroVar.set(args.retro)
quickSwapVar.set(int(args.quickswap)) rom_vars.quickSwapVar.set(int(args.quickswap))
disableMusicVar.set(int(args.disablemusic)) rom_vars.disableMusicVar.set(int(args.disablemusic))
rom_vars.disableFlashingVar.set(int(args.reduceflashing))
if args.count: if args.count:
countVar.set(str(args.count)) countVar.set(str(args.count))
if args.seed: if args.seed:
@ -1512,10 +1229,10 @@ def guiMain(args=None):
crystalsGanonVar.set(args.crystals_ganon) crystalsGanonVar.set(args.crystals_ganon)
algorithmVar.set(args.algorithm) algorithmVar.set(args.algorithm)
shuffleVar.set(args.shuffle) shuffleVar.set(args.shuffle)
heartbeepVar.set(args.heartbeep) rom_vars.heartbeepVar.set(args.heartbeep)
fastMenuVar.set(args.fastmenu) rom_vars.fastMenuVar.set(args.fastmenu)
logicVar.set(args.logic) logicVar.set(args.logic)
romVar.set(args.rom) rom_vars.romVar.set(args.rom)
shuffleGanonVar.set(args.shuffleganon) shuffleGanonVar.set(args.shuffleganon)
hintsVar.set(args.hints) hintsVar.set(args.hints)
if args.sprite is not None: if args.sprite is not None:
@ -1523,10 +1240,164 @@ def guiMain(args=None):
mainWindow.mainloop() mainWindow.mainloop()
def get_rom_frame(parent=None):
romFrame = Frame(parent)
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc")
romEntry = Entry(romFrame, textvariable=romVar)
def RomSelect():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
import Patch
try:
Patch.get_base_rom_bytes(rom) # throws error on checksum fail
except Exception as e:
logging.exception(e)
messagebox.showerror(title="Error while reading ROM", message=str(e))
else:
romVar.set(rom)
romSelectButton['state'] = "disabled"
romSelectButton["text"] = "ROM verified"
romSelectButton = Button(romFrame, text='Select Rom', command=RomSelect)
baseRomLabel.pack(side=LEFT)
romEntry.pack(side=LEFT, expand=True, fill=X)
romSelectButton.pack(side=LEFT)
romFrame.pack(side=TOP, expand=True, fill=X)
return romFrame, romVar
def get_rom_options_frame(parent=None):
romOptionsFrame = LabelFrame(parent, text="Rom options")
romOptionsFrame.columnconfigure(0, weight=1)
romOptionsFrame.columnconfigure(1, weight=1)
for i in range(5):
romOptionsFrame.rowconfigure(i, weight=1)
vars = Namespace()
vars.disableMusicVar = IntVar()
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=vars.disableMusicVar)
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
vars.disableFlashingVar = IntVar(value=1)
disableFlashingCheckbutton = Checkbutton(romOptionsFrame, text="Disable flashing (anti-epilepsy)", variable=vars.disableFlashingVar)
disableFlashingCheckbutton.grid(row=6, column=0, sticky=E)
spriteDialogFrame = Frame(romOptionsFrame)
spriteDialogFrame.grid(row=0, column=1)
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
vars.spriteNameVar = StringVar()
vars.sprite = None
def set_sprite(sprite_param):
nonlocal vars
if isinstance(sprite_param, str):
vars.sprite = sprite_param
vars.spriteNameVar.set(sprite_param)
elif sprite_param is None or not sprite_param.valid:
vars.sprite = None
vars.spriteNameVar.set('(unchanged)')
else:
vars.sprite = sprite_param
vars.spriteNameVar.set(vars.sprite.name)
set_sprite(None)
vars.spriteNameVar.set('(unchanged)')
spriteEntry = Label(spriteDialogFrame, textvariable=vars.spriteNameVar)
def SpriteSelect():
SpriteSelector(parent, set_sprite)
spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect)
baseSpriteLabel.pack(side=LEFT)
spriteEntry.pack(side=LEFT)
spriteSelectButton.pack(side=LEFT)
vars.quickSwapVar = IntVar(value=1)
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar)
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
fastMenuFrame = Frame(romOptionsFrame)
fastMenuFrame.grid(row=1, column=1, sticky=E)
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
fastMenuLabel.pack(side=LEFT)
vars.fastMenuVar = StringVar()
vars.fastMenuVar.set('normal')
fastMenuOptionMenu = OptionMenu(fastMenuFrame, vars.fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
fastMenuOptionMenu.pack(side=LEFT)
heartcolorFrame = Frame(romOptionsFrame)
heartcolorFrame.grid(row=2, column=0, sticky=E)
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
heartcolorLabel.pack(side=LEFT)
vars.heartcolorVar = StringVar()
vars.heartcolorVar.set('red')
heartcolorOptionMenu = OptionMenu(heartcolorFrame, vars.heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
heartcolorOptionMenu.pack(side=LEFT)
heartbeepFrame = Frame(romOptionsFrame)
heartbeepFrame.grid(row=2, column=1, sticky=E)
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep')
heartbeepLabel.pack(side=LEFT)
vars.heartbeepVar = StringVar()
vars.heartbeepVar.set('normal')
heartbeepOptionMenu = OptionMenu(heartbeepFrame, vars.heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
heartbeepOptionMenu.pack(side=LEFT)
owPalettesFrame = Frame(romOptionsFrame)
owPalettesFrame.grid(row=3, column=0, sticky=E)
owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes')
owPalettesLabel.pack(side=LEFT)
vars.owPalettesVar = StringVar()
vars.owPalettesVar.set('default')
owPalettesOptionMenu = OptionMenu(owPalettesFrame, vars.owPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
owPalettesOptionMenu.pack(side=LEFT)
uwPalettesFrame = Frame(romOptionsFrame)
uwPalettesFrame.grid(row=3, column=1, sticky=E)
uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes')
uwPalettesLabel.pack(side=LEFT)
vars.uwPalettesVar = StringVar()
vars.uwPalettesVar.set('default')
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, vars.uwPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
uwPalettesOptionMenu.pack(side=LEFT)
hudPalettesFrame = Frame(romOptionsFrame)
hudPalettesFrame.grid(row=4, column=0, sticky=E)
hudPalettesLabel = Label(hudPalettesFrame, text='HUD palettes')
hudPalettesLabel.pack(side=LEFT)
vars.hudPalettesVar = StringVar()
vars.hudPalettesVar.set('default')
hudPalettesOptionMenu = OptionMenu(hudPalettesFrame, vars.hudPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
hudPalettesOptionMenu.pack(side=LEFT)
swordPalettesFrame = Frame(romOptionsFrame)
swordPalettesFrame.grid(row=4, column=1, sticky=E)
swordPalettesLabel = Label(swordPalettesFrame, text='Sword palettes')
swordPalettesLabel.pack(side=LEFT)
vars.swordPalettesVar = StringVar()
vars.swordPalettesVar.set('default')
swordPalettesOptionMenu = OptionMenu(swordPalettesFrame, vars.swordPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
swordPalettesOptionMenu.pack(side=LEFT)
shieldPalettesFrame = Frame(romOptionsFrame)
shieldPalettesFrame.grid(row=5, column=0, sticky=E)
shieldPalettesLabel = Label(shieldPalettesFrame, text='Shield palettes')
shieldPalettesLabel.pack(side=LEFT)
vars.shieldPalettesVar = StringVar()
vars.shieldPalettesVar.set('default')
shieldPalettesOptionMenu = OptionMenu(shieldPalettesFrame, vars.shieldPalettesVar, 'default', 'random', 'blackout', 'grayscale', 'negative', 'classic', 'dizzy', 'sick', 'puke')
shieldPalettesOptionMenu.pack(side=LEFT)
return romOptionsFrame, vars, set_sprite
class SpriteSelector(): class SpriteSelector():
def __init__(self, parent, callback, adjuster=False): def __init__(self, parent, callback, adjuster=False):
if is_bundled():
self.deploy_icons() self.deploy_icons()
self.parent = parent self.parent = parent
self.window = Toplevel(parent) self.window = Toplevel(parent)

View File

@ -935,7 +935,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
found = ReceivedItem(*args["item"]) found = ReceivedItem(*args["item"])
ctx.ui_node.notify_item_found(ctx.player_names[found.player], get_item_name_from_id(found.item), ctx.ui_node.notify_item_found(ctx.player_names[found.player], get_item_name_from_id(found.item),
get_location_name_from_address(found.location), found.player == ctx.slot, get_location_name_from_address(found.location), found.player == ctx.slot,
True if get_item_name_from_id(found.item) in Items.progression_items else False) get_item_name_from_id(found.item) in Items.progression_items)
item = color_item(found.item, found.player == ctx.slot) item = color_item(found.item, found.player == ctx.slot)
player_sent = color(ctx.player_names[found.player], 'yellow' if found.player != ctx.slot else 'magenta') player_sent = color(ctx.player_names[found.player], 'yellow' if found.player != ctx.slot else 'magenta')
logging.info('%s found %s (%s)' % (player_sent, item, color(get_location_name_from_address(found.location), logging.info('%s found %s (%s)' % (player_sent, item, color(get_location_name_from_address(found.location),
@ -1066,7 +1066,7 @@ class ClientCommandProcessor(CommandProcessor):
self.ctx.ui_node.notify_item_received(self.ctx.player_names[item.player], get_item_name_from_id(item.item), self.ctx.ui_node.notify_item_received(self.ctx.player_names[item.player], get_item_name_from_id(item.item),
get_location_name_from_address(item.location), index, get_location_name_from_address(item.location), index,
len(self.ctx.items_received), len(self.ctx.items_received),
True if get_item_name_from_id(item.item) in Items.progression_items else False) get_item_name_from_id(item.item) in Items.progression_items)
logging.info('%s from %s (%s) (%d/%d in list)' % ( logging.info('%s from %s (%s) (%d/%d in list)' % (
color(get_item_name_from_id(item.item), 'red', 'bold'), color(get_item_name_from_id(item.item), 'red', 'bold'),
color(self.ctx.player_names[item.player], 'yellow'), color(self.ctx.player_names[item.player], 'yellow'),
@ -1327,7 +1327,7 @@ async def game_watcher(ctx: Context):
ctx.ui_node.notify_item_received(ctx.player_names[item.player], get_item_name_from_id(item.item), ctx.ui_node.notify_item_received(ctx.player_names[item.player], get_item_name_from_id(item.item),
get_location_name_from_address(item.location), recv_index + 1, get_location_name_from_address(item.location), recv_index + 1,
len(ctx.items_received), len(ctx.items_received),
True if get_item_name_from_id(item.item) in Items.progression_items else False) get_item_name_from_id(item.item) in Items.progression_items)
logging.info('Received %s from %s (%s) (%d/%d in list)' % ( logging.info('Received %s from %s (%s) (%d/%d in list)' % (
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received))) get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))

View File

@ -325,6 +325,7 @@ def roll_linked_options(weights: dict) -> dict:
def roll_triggers(weights: dict) -> dict: def roll_triggers(weights: dict) -> dict:
weights = weights.copy() # make sure we don't write back to other weights sets in same_settings weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
weights["_Generator_Version"] = "Main" # Some means for triggers to know if the seed is on main or doors.
for option_set in weights["triggers"]: for option_set in weights["triggers"]:
try: try:
key = get_choice("option_name", option_set) key = get_choice("option_name", option_set)
@ -691,6 +692,7 @@ def roll_settings(weights: dict, plando_options: typing.Set[str] = frozenset(("b
ret.disablemusic = get_choice('disablemusic', romweights, False) ret.disablemusic = get_choice('disablemusic', romweights, False)
ret.quickswap = get_choice('quickswap', romweights, True) ret.quickswap = get_choice('quickswap', romweights, True)
ret.fastmenu = get_choice('menuspeed', romweights, "normal") ret.fastmenu = get_choice('menuspeed', romweights, "normal")
ret.reduceflashing = get_choice('reduceflashing', romweights, False)
ret.heartcolor = get_choice('heartcolor', romweights, "red") ret.heartcolor = get_choice('heartcolor', romweights, "red")
ret.heartbeep = convert_to_on_off(get_choice('heartbeep', romweights, "normal")) ret.heartbeep = convert_to_on_off(get_choice('heartbeep', romweights, "normal"))
ret.ow_palettes = get_choice('ow_palettes', romweights, "default") ret.ow_palettes = get_choice('ow_palettes', romweights, "default")

View File

@ -341,8 +341,8 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]:
f"Enter yes, no or never: ") f"Enter yes, no or never: ")
if adjust_wanted and adjust_wanted.startswith("y"): if adjust_wanted and adjust_wanted.startswith("y"):
adjusted = True adjusted = True
import AdjusterMain import Adjuster
_, romfile = AdjusterMain.adjust(adjuster_settings) _, romfile = Adjuster.adjust(adjuster_settings)
elif adjust_wanted and "never" in adjust_wanted: elif adjust_wanted and "never" in adjust_wanted:
persistent_store("adjuster", "never_adjust", True) persistent_store("adjuster", "never_adjust", True)
return romfile, False return romfile, False

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import logging import logging
import multiprocessing import multiprocessing
from datetime import timedelta, datetime from datetime import timedelta, datetime
import concurrent.futures
import sys import sys
import typing import typing
import time import time
@ -135,6 +136,7 @@ def autohost(config: dict):
multiworlds = {} multiworlds = {}
guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian")
class MultiworldInstance(): class MultiworldInstance():
def __init__(self, room: Room, config: dict): def __init__(self, room: Room, config: dict):
@ -152,12 +154,18 @@ class MultiworldInstance():
args=(self.room_id, self.ponyconfig), args=(self.room_id, self.ponyconfig),
name="MultiHost") name="MultiHost")
self.process.start() self.process.start()
self.guardian = guardians.submit(self._collect)
def stop(self): def stop(self):
if self.process: if self.process:
self.process.terminate() self.process.terminate()
self.process = None self.process = None
def _collect(self):
self.process.join() # wait for process to finish
self.process = None
self.guardian = None
from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed
from .customserver import run_server_process from .customserver import run_server_process

View File

@ -55,7 +55,7 @@ window.addEventListener('load', () => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
adjustTableHeight(); adjustTableHeight();
tables.draw() tables.draw();
}); });
$(".table-wrapper").scrollsync({ $(".table-wrapper").scrollsync({

View File

@ -1602,6 +1602,26 @@
} }
} }
}, },
"reduceflashing": {
"keyString": "rom.reduceflashing",
"friendlyName": "Full-Screen Flashing Effects",
"description": "Enable or disable full-screen flashing effects in game.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "rom.reduceflashing.on",
"friendlyName": "Disabled",
"description": "Disables flashing.",
"defaultValue": 50
},
"off": {
"keyString": "rom.reduceflashing.off",
"friendlyName": "Enabled",
"description": "Enables flashing.",
"defaultValue": 0
}
}
},
"quickswap": { "quickswap": {
"keyString": "rom.quickswap", "keyString": "rom.quickswap",
"friendlyName": "Item Quick-Swap", "friendlyName": "Item Quick-Swap",
@ -1797,7 +1817,7 @@
"defaultValue": 0 "defaultValue": 0
}, },
"puke": { "puke": {
"keyString": "rom.ow_palettes.Puke", "keyString": "rom.ow_palettes.puke",
"friendlyName": "Puke", "friendlyName": "Puke",
"description": "No logic at all.", "description": "No logic at all.",
"defaultValue": 0 "defaultValue": 0
@ -1859,7 +1879,7 @@
"defaultValue": 0 "defaultValue": 0
}, },
"puke": { "puke": {
"keyString": "rom.uw_palettes.Puke", "keyString": "rom.uw_palettes.puke",
"friendlyName": "Puke", "friendlyName": "Puke",
"description": "No logic at all.", "description": "No logic at all.",
"defaultValue": 0 "defaultValue": 0
@ -1921,7 +1941,7 @@
"defaultValue": 0 "defaultValue": 0
}, },
"puke": { "puke": {
"keyString": "rom.hud_palettes.Puke", "keyString": "rom.hud_palettes.puke",
"friendlyName": "Puke", "friendlyName": "Puke",
"description": "No logic at all.", "description": "No logic at all.",
"defaultValue": 0 "defaultValue": 0
@ -1983,7 +2003,7 @@
"defaultValue": 0 "defaultValue": 0
}, },
"puke": { "puke": {
"keyString": "rom.shield_palettes.Puke", "keyString": "rom.shield_palettes.puke",
"friendlyName": "Puke", "friendlyName": "Puke",
"description": "No logic at all.", "description": "No logic at all.",
"defaultValue": 0 "defaultValue": 0
@ -2045,7 +2065,7 @@
"defaultValue": 0 "defaultValue": 0
}, },
"puke": { "puke": {
"keyString": "rom.sword_palettes.Puke", "keyString": "rom.sword_palettes.puke",
"friendlyName": "Puke", "friendlyName": "Puke",
"description": "No logic at all.", "description": "No logic at all.",
"defaultValue": 0 "defaultValue": 0

View File

@ -20,7 +20,7 @@
# For use with the weighted-settings page on the website. Changing this value will cause all users to be prompted # For use with the weighted-settings page on the website. Changing this value will cause all users to be prompted
# to update their settings. The version number should match the current released version number, and the revision # to update their settings. The version number should match the current released version number, and the revision
# should be updated manually by whoever edits this file. # should be updated manually by whoever edits this file.
ws_version: 4.0.1 rev0 ws_version: 4.0.1 rev1
description: Template Name # Used to describe your yaml. Useful if you have multiple files description: Template Name # Used to describe your yaml. Useful if you have multiple files
name: YourName # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit name: YourName # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit
@ -363,6 +363,9 @@ rom:
quickswap: # Enable switching items by pressing the L+R shoulder buttons quickswap: # Enable switching items by pressing the L+R shoulder buttons
on: 50 on: 50
off: 0 off: 0
reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more.
on: 50
off: 0
menuspeed: # Controls how fast the item menu opens and closes menuspeed: # Controls how fast the item menu opens and closes
normal: 50 normal: 50
instant: 0 instant: 0

View File

@ -1,7 +1,7 @@
{% extends 'tablepage.html' %} {% extends 'tablepage.html' %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<title>Multiworld Tracker for Room {{ room.id }}</title> <title>Multiworld Tracker</title>
<link rel="stylesheet" type="text/css" href="{{ static_autoversion("styles/tracker.css") }}"/> <link rel="stylesheet" type="text/css" href="{{ static_autoversion("styles/tracker.css") }}"/>
<script type="application/ecmascript" src="{{ static_autoversion("assets/jquery.scrollsync.js") }}"></script> <script type="application/ecmascript" src="{{ static_autoversion("assets/jquery.scrollsync.js") }}"></script>
<script type="application/ecmascript" src="{{ static_autoversion("assets/tracker.js") }}"></script> <script type="application/ecmascript" src="{{ static_autoversion("assets/tracker.js") }}"></script>
@ -151,7 +151,7 @@
{% endfor %} {% endfor %}
{% for team, hints in hints.items() %} {% for team, hints in hints.items() %}
<div class="table-wrapper"> <div class="table-wrapper">
<table class="table non-unique-item-table"> <table class="table non-unique-item-table" data-order='[[5, "asc"], [0, "asc"]]'>
<thead> <thead>
<tr> <tr>
<th>Finder</th> <th>Finder</th>

View File

@ -61,9 +61,9 @@ class WebUiClient(Node, logging.Handler):
'recipient': recipient, 'recipient': recipient,
'item': item, 'item': item,
'location': location, 'location': location,
'iAmFinder': 1 if i_am_finder else 0, 'iAmFinder': int(i_am_finder),
'iAmRecipient': 1 if i_am_recipient else 0, 'iAmRecipient': int(i_am_recipient),
'itemIsUnique': 1 if item_is_unique else 0, 'itemIsUnique': int(item_is_unique),
})) }))
def notify_item_found(self, finder: str, item: str, location: str, i_am_finder: bool, item_is_unique: bool = False): def notify_item_found(self, finder: str, item: str, location: str, i_am_finder: bool, item_is_unique: bool = False):
@ -71,8 +71,8 @@ class WebUiClient(Node, logging.Handler):
'finder': finder, 'finder': finder,
'item': item, 'item': item,
'location': location, 'location': location,
'iAmFinder': 1 if i_am_finder else 0, 'iAmFinder': int(i_am_finder),
'itemIsUnique': 1 if item_is_unique else 0, 'itemIsUnique': int(item_is_unique),
})) }))
def notify_item_received(self, finder: str, item: str, location: str, item_index: int, queue_length: int, def notify_item_received(self, finder: str, item: str, location: str, item_index: int, queue_length: int,
@ -83,7 +83,7 @@ class WebUiClient(Node, logging.Handler):
'location': location, 'location': location,
'itemIndex': item_index, 'itemIndex': item_index,
'queueLength': queue_length, 'queueLength': queue_length,
'itemIsUnique': 1 if item_is_unique else 0, 'itemIsUnique': int(item_is_unique),
})) }))
def send_hint(self, finder, recipient, item, location, found, i_am_finder: bool, i_am_recipient: bool, def send_hint(self, finder, recipient, item, location, found, i_am_finder: bool, i_am_recipient: bool,

View File

@ -87,6 +87,9 @@ entrance_shuffle: # Documentation: https://alttpr.com/en/options#entrance_shuffl
full: 0 # Less strict than restricted full: 0 # Less strict than restricted
crossed: 0 # Less strict than full crossed: 0 # Less strict than full
insanity: 0 # Very few grouping rules. Good luck insanity: 0 # Very few grouping rules. Good luck
# you can also define entrance shuffle seed, like so:
crossed-1000: 0 # using this method, you can have the same layout as another player and share entrance information
# however, many other settings like logic, world state, retro etc. may affect the shuffle result as well.
goals: goals:
ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon
fast_ganon: 0 # Only killing Ganon is required. However, items may still be placed in GT fast_ganon: 0 # Only killing Ganon is required. However, items may still be placed in GT
@ -401,6 +404,9 @@ rom:
quickswap: # Enable switching items by pressing the L+R shoulder buttons quickswap: # Enable switching items by pressing the L+R shoulder buttons
on: 50 on: 50
off: 0 off: 0
reduceflashing: # Reduces instances of flashing such as lightning attacks, weather, ether and more.
on: 50
off: 0
menuspeed: # Controls how fast the item menu opens and closes menuspeed: # Controls how fast the item menu opens and closes
normal: 50 normal: 50
instant: 0 instant: 0

View File

@ -3,7 +3,7 @@ websockets>=8.1
PyYAML>=5.4.1 PyYAML>=5.4.1
fuzzywuzzy>=0.18.0 fuzzywuzzy>=0.18.0
bsdiff4>=1.2.0 bsdiff4>=1.2.0
prompt_toolkit>=3.0.14 prompt_toolkit>=3.0.16
appdirs>=1.4.4 appdirs>=1.4.4
maseya-z3pr>=1.0.0rc1 maseya-z3pr>=1.0.0rc1
xxtea>=2.0.0.post0 xxtea>=2.0.0.post0

View File

@ -48,8 +48,8 @@ def manifest_creation():
path = os.path.join(dirpath, filename) path = os.path.join(dirpath, filename)
hashes[os.path.relpath(path, start=buildfolder)] = pool.submit(_threaded_hash, path) hashes[os.path.relpath(path, start=buildfolder)] = pool.submit(_threaded_hash, path)
import json import json
manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds")} manifest = {"buildtime": buildtime.isoformat(sep=" ", timespec="seconds"),
manifest["hashes"] = {path: hash.result() for path, hash in hashes.items()} "hashes": {path: hash.result() for path, hash in hashes.items()}}
json.dump(manifest, open(manifestpath, "wt"), indent=4) json.dump(manifest, open(manifestpath, "wt"), indent=4)
print("Created Manifest") print("Created Manifest")
@ -58,7 +58,8 @@ scripts = {"MultiClient.py": "ArchipelagoClient",
"MultiMystery.py": "ArchipelagoMultiMystery", "MultiMystery.py": "ArchipelagoMultiMystery",
"MultiServer.py": "ArchipelagoServer", "MultiServer.py": "ArchipelagoServer",
"gui.py": "ArchipelagoCreator", "gui.py": "ArchipelagoCreator",
"Mystery.py": "ArchipelagoMystery"} "Mystery.py": "ArchipelagoMystery",
"Adjuster.py": "ArchipelagoLttPAdjuster"}
exes = [] exes = []

View File

@ -1,40 +0,0 @@
import os
import time
import logging
from Utils import output_path
from worlds.alttp.Rom import LocalRom, apply_rom_settings
def adjust(args):
start = time.perf_counter()
logger = logging.getLogger('Adjuster')
logger.info('Patching ROM.')
vanillaRom = args.baserom
if os.path.splitext(args.rom)[-1].lower() == '.apbp':
import Patch
meta, args.rom = Patch.create_rom_file(args.rom)
if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc':
rom = LocalRom(args.rom, patch=False, vanillaRom=vanillaRom)
else:
raise RuntimeError(
'Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
palettes_options={}
palettes_options['dungeon']=args.uw_palettes
palettes_options['overworld']=args.ow_palettes
palettes_options['hud']=args.hud_palettes
palettes_options['sword']=args.sword_palettes
palettes_options['shield']=args.shield_palettes
# palettes_options['link']=args.link_palettesvera
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
args.sprite, palettes_options)
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
rom.write_to_file(path)
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s', time.perf_counter() - start)
return args, path

View File

@ -251,6 +251,7 @@ def parse_arguments(argv, no_defaults=False):
''') ''')
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
parser.add_argument('--enableflashing', help='Reenable flashing animations (unfriendly to epilepsy, always disabled in race roms)', action='store_false', dest="reduceflashing")
parser.add_argument('--mapshuffle', default=defval(False), parser.add_argument('--mapshuffle', default=defval(False),
help='Maps are no longer restricted to their dungeons, but can be anywhere', help='Maps are no longer restricted to their dungeons, but can be anywhere',
action='store_true') action='store_true')
@ -377,6 +378,7 @@ def parse_arguments(argv, no_defaults=False):
ret.plando_items = [] ret.plando_items = []
ret.plando_texts = {} ret.plando_texts = {}
ret.plando_connections = [] ret.plando_connections = []
ret.er_seeds = {}
ret.glitch_boots = not ret.disable_glitch_boots ret.glitch_boots = not ret.disable_glitch_boots
if ret.timer == "none": if ret.timer == "none":
@ -407,10 +409,10 @@ def parse_arguments(argv, no_defaults=False):
'heartbeep', "skip_progression_balancing", "triforce_pieces_available", 'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots", "triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
"required_medallions", "required_medallions",
"plando_items", "plando_texts", "plando_connections", "plando_items", "plando_texts", "plando_connections", "er_seeds",
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves', 'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
'restrict_dungeon_item_on_boss', 'restrict_dungeon_item_on_boss', 'reduceflashing',
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']: 'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:

View File

@ -2054,10 +2054,11 @@ def connect_doors(world, doors, targets, player):
"""This works inplace""" """This works inplace"""
world.random.shuffle(doors) world.random.shuffle(doors)
world.random.shuffle(targets) world.random.shuffle(targets)
while doors: placing = min(len(doors), len(targets))
door = doors.pop() for door, target in zip(doors, targets):
target = targets.pop()
connect_entrance(world, door, target, player) connect_entrance(world, door, target, player)
doors[:] = doors[placing:]
targets[:] = targets[placing:]
def skull_woods_shuffle(world, player): def skull_woods_shuffle(world, player):

View File

@ -93,11 +93,20 @@ def main(args, seed=None):
world.plando_items = args.plando_items.copy() world.plando_items = args.plando_items.copy()
world.plando_texts = args.plando_texts.copy() world.plando_texts = args.plando_texts.copy()
world.plando_connections = args.plando_connections.copy() world.plando_connections = args.plando_connections.copy()
world.er_seeds = args.er_seeds.copy()
world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy() world.restrict_dungeon_item_on_boss = args.restrict_dungeon_item_on_boss.copy()
world.required_medallions = args.required_medallions.copy() world.required_medallions = args.required_medallions.copy()
world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)}
for player in range(1, world.players+1):
world.er_seeds[player] = str(world.random.randint(0, 2 ** 64))
if "-" in world.shuffle[player]:
shuffle, seed = world.shuffle[player].split("-")
world.shuffle[player] = shuffle
world.er_seeds[player] = seed
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed) logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
parsed_names = parse_player_names(args.names, world.players, args.teams) parsed_names = parse_player_names(args.names, world.players, args.teams)
@ -171,12 +180,18 @@ def main(args, seed=None):
{"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}: {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}:
world.fix_fake_world[player] = False world.fix_fake_world[player] = False
# seeded entrance shuffle
old_random = world.random
world.random = random.Random(world.er_seeds[player])
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
link_entrances(world, player) link_entrances(world, player)
mark_light_world_regions(world, player) mark_light_world_regions(world, player)
else: else:
link_inverted_entrances(world, player) link_inverted_entrances(world, player)
mark_dark_world_regions(world, player) mark_dark_world_regions(world, player)
world.random = old_random
plando_connect(world, player) plando_connect(world, player)
logger.info('Generating Item Pool.') logger.info('Generating Item Pool.')
@ -260,7 +275,7 @@ def main(args, seed=None):
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player],
palettes_options, world, player, True) palettes_options, world, player, True, reduceflashing=args.reduceflashing[player] if not args.race else True)
mcsb_name = '' mcsb_name = ''
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],

View File

@ -1672,7 +1672,7 @@ def hud_format_text(text):
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options, def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite: str, palettes_options,
world=None, player=1, allow_random_on_event=False): world=None, player=1, allow_random_on_event=False, reduceflashing=False):
local_random = random if not world else world.rom_seeds[player] local_random = random if not world else world.rom_seeds[player]
# enable instant item menu # enable instant item menu
@ -1697,6 +1697,23 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
else: else:
rom.write_byte(0x180048, 0x08) rom.write_byte(0x180048, 0x08)
# Reduce flashing by nopping out instructions
if reduceflashing:
rom.write_bytes(0x17E07, [0x06]) # reduce amount of colors changed, add this branch if we need to reduce more ""+ [0x80] + [(0x81-0x08)]""
rom.write_bytes(0x17EAB, [0xD0, 0x03, 0xA9, 0x40, 0x29, 0x60]) # nullifies aga lightning, cutscene, vitreous, bat, ether
# ONLY write to black values with this low pale blue to indicate flashing, that's IT. ""BNE + : LDA #$2940 : + : RTS""
rom.write_bytes(0x123FE, [0x72]) # set lightning flash in misery mire (and standard) to brightness 0x72
rom.write_bytes(0x3FA7B, [0x80, 0xac-0x7b]) # branch from palette writing lightning on death mountain
rom.write_byte(0x10817F, 0x01) # internal rom option
else:
rom.write_bytes(0x17E07, [0x00])
rom.write_bytes(0x17EAB, [0x85, 0x00, 0x29, 0x1F, 0x00, 0x18])
rom.write_bytes(0x123FE, [0x32]) # original weather flash value
rom.write_bytes(0x3FA7B, [0xc2, 0x20]) # rep #$20
rom.write_byte(0x10817F, 0x00) # internal rom option
rom.write_byte(0x18004B, 0x01 if quickswap else 0x00) rom.write_byte(0x18004B, 0x01 if quickswap else 0x00)
rom.write_byte(0x0CFE18, 0x00 if disable_music else rom.orig_buffer[0x0CFE18] if rom.orig_buffer else 0x70) rom.write_byte(0x0CFE18, 0x00 if disable_music else rom.orig_buffer[0x0CFE18] if rom.orig_buffer else 0x70)

View File

@ -1884,7 +1884,7 @@ class TextTable(object):
text['item_get_whole_heart'] = CompressedTextMapper.convert("You got a whole ♥!!\nGo you!") text['item_get_whole_heart'] = CompressedTextMapper.convert("You got a whole ♥!!\nGo you!")
text['item_get_sanc_heart'] = CompressedTextMapper.convert("You got a whole ♥!\nGo you!") text['item_get_sanc_heart'] = CompressedTextMapper.convert("You got a whole ♥!\nGo you!")
text['fairy_fountain_refill'] = CompressedTextMapper.convert("Well done, lettuce have a cup of tea…") text['fairy_fountain_refill'] = CompressedTextMapper.convert("Well done, lettuce have a cup of tea…")
text['death_mountain_bullied_no_pearl'] = CompressedTextMapper.convert("The following license applies to the base patch for the randomizer.\n\nCopyright (c) 2017 LLCoolDave\n\nCopyright (c) 2020 Berserker66\n\nCopyright (c) 2020 CaitSith2\n\nCopyright 2016, 2017 Equilateral IT\n\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.") text['death_mountain_bullied_no_pearl'] = CompressedTextMapper.convert("The following license applies to the base patch for the randomizer.\n\nCopyright (c) 2017 LLCoolDave\n\nCopyright (c) 2021 Berserker66\n\nCopyright (c) 2021 CaitSith2\n\nCopyright 2016, 2017 Equilateral IT\n\n Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.")
text['death_mountain_bullied_with_pearl'] = CompressedTextMapper.convert("The software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of\nmerchantability,\nfitness for a particular purpose and\nnoninfringement.\nIn no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the software.") text['death_mountain_bullied_with_pearl'] = CompressedTextMapper.convert("The software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of\nmerchantability,\nfitness for a particular purpose and\nnoninfringement.\nIn no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the software.")
text['death_mountain_bully_no_pearl'] = CompressedTextMapper.convert("Add garlic, ginger and apple and cook for 2 minutes. Add carrots, potatoes, garam masala and curry powder and stir well. Add tomato paste, stir well and slowly add red wine and bring to a boil. Add sugar, soy sauce and water, stir and bring to a boil again.") text['death_mountain_bully_no_pearl'] = CompressedTextMapper.convert("Add garlic, ginger and apple and cook for 2 minutes. Add carrots, potatoes, garam masala and curry powder and stir well. Add tomato paste, stir well and slowly add red wine and bring to a boil. Add sugar, soy sauce and water, stir and bring to a boil again.")
text['death_mountain_bully_with_pearl'] = CompressedTextMapper.convert("I think I forgot how to smile…") text['death_mountain_bully_with_pearl'] = CompressedTextMapper.convert("I think I forgot how to smile…")