commit
df8a16c5ac
|
@ -4,3 +4,6 @@ build
|
||||||
.idea
|
.idea
|
||||||
*.sfc
|
*.sfc
|
||||||
*_Spoiler.txt
|
*_Spoiler.txt
|
||||||
|
bundle/components.wxs
|
||||||
|
*.wixobj
|
||||||
|
README.html
|
||||||
|
|
|
@ -6,7 +6,7 @@ from collections import OrderedDict
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, keysanity):
|
def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity):
|
||||||
self.shuffle = shuffle
|
self.shuffle = shuffle
|
||||||
self.logic = logic
|
self.logic = logic
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -51,6 +51,7 @@ class World(object):
|
||||||
self.can_access_trock_eyebridge = None
|
self.can_access_trock_eyebridge = None
|
||||||
self.quickswap = quickswap
|
self.quickswap = quickswap
|
||||||
self.fastmenu = fastmenu
|
self.fastmenu = fastmenu
|
||||||
|
self.disable_music = disable_music
|
||||||
self.keysanity = keysanity
|
self.keysanity = keysanity
|
||||||
self.spoiler = Spoiler(self)
|
self.spoiler = Spoiler(self)
|
||||||
|
|
||||||
|
@ -232,7 +233,7 @@ class World(object):
|
||||||
goal = ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'].index(self.goal)
|
goal = ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'].index(self.goal)
|
||||||
shuffle = ['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'].index(self.shuffle)
|
shuffle = ['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'].index(self.shuffle)
|
||||||
difficulty = ['easy', 'normal', 'hard', 'expert', 'insane'].index(self.difficulty)
|
difficulty = ['easy', 'normal', 'hard', 'expert', 'insane'].index(self.difficulty)
|
||||||
timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'].index(self.timer)
|
timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown','ohko'].index(self.timer)
|
||||||
progressive = ['on', 'off', 'random'].index(self.progressive)
|
progressive = ['on', 'off', 'random'].index(self.progressive)
|
||||||
algorithm = ['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'].index(self.algorithm)
|
algorithm = ['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'].index(self.algorithm)
|
||||||
beatableonly = 1 if self.check_beatable_only else 0
|
beatableonly = 1 if self.check_beatable_only else 0
|
||||||
|
@ -337,7 +338,7 @@ class CollectionState(object):
|
||||||
return self.has('Power Glove') or self.has('Titans Mitts')
|
return self.has('Power Glove') or self.has('Titans Mitts')
|
||||||
|
|
||||||
def has_bottle(self):
|
def has_bottle(self):
|
||||||
return self.has('Bottle') or self.has('BottleRedPotion') or self.has('BottleGreenPotion') or self.has('BottleBluePotion') or self.has('BottleFairy') or self.has('BottleBee') or self.has('BottleGoodBee')
|
return self.has('Bottle') or self.has('Bottle (Red Potion)') or self.has('Bottle (Green Potion)') or self.has('Bottle (Blue Potion)') or self.has('Bottle (Fairy)') or self.has('Bottle (Bee)') or self.has('Bottle (Good Bee)')
|
||||||
|
|
||||||
def can_lift_heavy_rocks(self):
|
def can_lift_heavy_rocks(self):
|
||||||
return self.has('Titans Mitts')
|
return self.has('Titans Mitts')
|
||||||
|
@ -672,6 +673,7 @@ class Spoiler(object):
|
||||||
'dungeonitems': self.world.place_dungeon_items,
|
'dungeonitems': self.world.place_dungeon_items,
|
||||||
'quickswap': self.world.quickswap,
|
'quickswap': self.world.quickswap,
|
||||||
'fastmenu': self.world.fastmenu,
|
'fastmenu': self.world.fastmenu,
|
||||||
|
'disable_music': self.world.disable_music,
|
||||||
'keysanity': self.world.keysanity}
|
'keysanity': self.world.keysanity}
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
|
|
|
@ -3,9 +3,11 @@ import os
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import sys
|
||||||
|
|
||||||
from Main import main
|
from Main import main
|
||||||
from Gui import guiMain
|
from Gui import guiMain
|
||||||
|
from Utils import is_bundled, close_console
|
||||||
|
|
||||||
|
|
||||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
|
@ -60,7 +62,7 @@ if __name__ == '__main__':
|
||||||
Expert: A harder yet setting with minimum equipment and health.
|
Expert: A harder yet setting with minimum equipment and health.
|
||||||
Insane: A setting with the absolute minimum in equipment and no extra health.
|
Insane: A setting with the absolute minimum in equipment and no extra health.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'],
|
parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
|
||||||
help='''\
|
help='''\
|
||||||
Select game timer setting. Affects available itempool. (default: %(default)s)
|
Select game timer setting. Affects available itempool. (default: %(default)s)
|
||||||
None: No timer.
|
None: No timer.
|
||||||
|
@ -74,6 +76,8 @@ if __name__ == '__main__':
|
||||||
Timed OHKO: Starts clock at 10 minutes. Green Clocks add
|
Timed OHKO: Starts clock at 10 minutes. Green Clocks add
|
||||||
5 minutes (Total: 25). As long as clock is at 0,
|
5 minutes (Total: 25). As long as clock is at 0,
|
||||||
Link will die in one hit.
|
Link will die in one hit.
|
||||||
|
OHKO: Like Timed OHKO, but no clock items are present
|
||||||
|
and the clock is permenantly at zero.
|
||||||
Timed Countdown: Starts with clock at 40 minutes. Same clocks as
|
Timed Countdown: Starts with clock at 40 minutes. Same clocks as
|
||||||
Timed mode. If time runs out, you lose (but can
|
Timed mode. If time runs out, you lose (but can
|
||||||
still keep playing).
|
still keep playing).
|
||||||
|
@ -149,6 +153,7 @@ if __name__ == '__main__':
|
||||||
''', type=int)
|
''', type=int)
|
||||||
parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true')
|
parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true')
|
||||||
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('--keysanity', help='''\
|
parser.add_argument('--keysanity', help='''\
|
||||||
Keys (and other dungeon items) are no longer restricted to
|
Keys (and other dungeon items) are no longer restricted to
|
||||||
their dungeons, but can be anywhere
|
their dungeons, but can be anywhere
|
||||||
|
@ -186,14 +191,23 @@ if __name__ == '__main__':
|
||||||
''')
|
''')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if is_bundled and len(sys.argv) == 1 :
|
||||||
|
# for the bundled builds, if we have no arguments, the user
|
||||||
|
# probably wants the gui. Users of the bundled build who want the command line
|
||||||
|
# interface shouuld specify at least one option, possibly setting a value to a
|
||||||
|
# default if they like all the defaults
|
||||||
|
close_console()
|
||||||
|
guiMain()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# ToDo: Validate files further than mere existance
|
# ToDo: Validate files further than mere existance
|
||||||
if not args.jsonout and not os.path.isfile(args.rom):
|
if not args.jsonout and not os.path.isfile(args.rom):
|
||||||
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
if args.sprite is not None and not os.path.isfile(args.sprite):
|
if args.sprite is not None and not os.path.isfile(args.sprite):
|
||||||
if not args.jsonout:
|
if not args.jsonout:
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
raise IOError('Cannot find sprite file at %s' % args.sprite)
|
raise IOError('Cannot find sprite file at %s' % args.sprite)
|
||||||
|
|
||||||
|
|
40
Gui.py
40
Gui.py
|
@ -1,14 +1,19 @@
|
||||||
from Main import main, __version__ as ESVersion
|
from Main import main, __version__ as ESVersion
|
||||||
|
from Utils import is_bundled, local_path, output_path, open_file
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
import random
|
import random
|
||||||
|
import subprocess
|
||||||
from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, Entry, Spinbox, Button, filedialog, messagebox
|
import os
|
||||||
|
import sys
|
||||||
|
from tkinter import Checkbutton, OptionMenu, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, PhotoImage
|
||||||
|
|
||||||
|
|
||||||
def guiMain(args=None):
|
def guiMain(args=None):
|
||||||
mainWindow = Tk()
|
mainWindow = Tk()
|
||||||
mainWindow.wm_title("Entrance Shuffle %s" % ESVersion)
|
mainWindow.wm_title("Entrance Shuffle %s" % ESVersion)
|
||||||
|
|
||||||
|
set_icon(mainWindow)
|
||||||
|
|
||||||
topFrame = Frame(mainWindow)
|
topFrame = Frame(mainWindow)
|
||||||
rightHalfFrame = Frame(topFrame)
|
rightHalfFrame = Frame(topFrame)
|
||||||
checkBoxFrame = Frame(rightHalfFrame)
|
checkBoxFrame = Frame(rightHalfFrame)
|
||||||
|
@ -27,6 +32,8 @@ def guiMain(args=None):
|
||||||
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
|
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
|
||||||
beatableOnlyVar = IntVar()
|
beatableOnlyVar = IntVar()
|
||||||
beatableOnlyCheckbutton = Checkbutton(checkBoxFrame, text="Only ensure seed is beatable, not all items must be reachable", variable=beatableOnlyVar)
|
beatableOnlyCheckbutton = Checkbutton(checkBoxFrame, text="Only ensure seed is beatable, not all items must be reachable", variable=beatableOnlyVar)
|
||||||
|
disableMusicVar = IntVar()
|
||||||
|
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
|
||||||
shuffleGanonVar = IntVar()
|
shuffleGanonVar = IntVar()
|
||||||
shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar)
|
shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar)
|
||||||
|
|
||||||
|
@ -37,6 +44,7 @@ def guiMain(args=None):
|
||||||
keysanityCheckbutton.pack(expand=True, anchor=W)
|
keysanityCheckbutton.pack(expand=True, anchor=W)
|
||||||
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
||||||
beatableOnlyCheckbutton.pack(expand=True, anchor=W)
|
beatableOnlyCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
||||||
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
|
||||||
fileDialogFrame = Frame(rightHalfFrame)
|
fileDialogFrame = Frame(rightHalfFrame)
|
||||||
|
@ -113,7 +121,7 @@ def guiMain(args=None):
|
||||||
timerFrame = Frame(drowDownFrame)
|
timerFrame = Frame(drowDownFrame)
|
||||||
timerVar = StringVar()
|
timerVar = StringVar()
|
||||||
timerVar.set('none')
|
timerVar.set('none')
|
||||||
timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'timed-countdown')
|
timerOptionMenu = OptionMenu(timerFrame, timerVar, 'none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown')
|
||||||
timerOptionMenu.pack(side=RIGHT)
|
timerOptionMenu.pack(side=RIGHT)
|
||||||
timerLabel = Label(timerFrame, text='Timer setting')
|
timerLabel = Label(timerFrame, text='Timer setting')
|
||||||
timerLabel.pack(side=LEFT)
|
timerLabel.pack(side=LEFT)
|
||||||
|
@ -161,6 +169,7 @@ def guiMain(args=None):
|
||||||
heartbeepFrame.pack(expand=True, anchor=E)
|
heartbeepFrame.pack(expand=True, anchor=E)
|
||||||
|
|
||||||
bottomFrame = Frame(mainWindow)
|
bottomFrame = Frame(mainWindow)
|
||||||
|
farBottomFrame = Frame(mainWindow)
|
||||||
|
|
||||||
seedLabel = Label(bottomFrame, text='Seed #')
|
seedLabel = Label(bottomFrame, text='Seed #')
|
||||||
seedVar = StringVar()
|
seedVar = StringVar()
|
||||||
|
@ -189,6 +198,7 @@ def guiMain(args=None):
|
||||||
guiargs.beatableonly = bool(beatableOnlyVar.get())
|
guiargs.beatableonly = bool(beatableOnlyVar.get())
|
||||||
guiargs.fastmenu = bool(fastMenuVar.get())
|
guiargs.fastmenu = bool(fastMenuVar.get())
|
||||||
guiargs.quickswap = bool(quickSwapVar.get())
|
guiargs.quickswap = bool(quickSwapVar.get())
|
||||||
|
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||||
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
||||||
guiargs.rom = romVar.get()
|
guiargs.rom = romVar.get()
|
||||||
guiargs.jsonout = None
|
guiargs.jsonout = None
|
||||||
|
@ -208,15 +218,29 @@ def guiMain(args=None):
|
||||||
|
|
||||||
generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom)
|
generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom)
|
||||||
|
|
||||||
|
def open_output():
|
||||||
|
open_file(output_path(''))
|
||||||
|
|
||||||
|
openOutputButton = Button(farBottomFrame, text='Open Output Directory', command=open_output)
|
||||||
|
|
||||||
|
if os.path.exists(local_path('README.html')):
|
||||||
|
def open_readme():
|
||||||
|
open_file(local_path('README.html'))
|
||||||
|
openReadmeButton = Button(farBottomFrame, text='Open Documentation', command=open_readme)
|
||||||
|
openReadmeButton.pack(side=LEFT)
|
||||||
|
|
||||||
seedLabel.pack(side=LEFT)
|
seedLabel.pack(side=LEFT)
|
||||||
seedEntry.pack(side=LEFT)
|
seedEntry.pack(side=LEFT)
|
||||||
countLabel.pack(side=LEFT)
|
countLabel.pack(side=LEFT, padx=(5,0))
|
||||||
countSpinbox.pack(side=LEFT)
|
countSpinbox.pack(side=LEFT)
|
||||||
generateButton.pack(side=LEFT)
|
generateButton.pack(side=LEFT, padx=(5,0))
|
||||||
|
|
||||||
|
openOutputButton.pack(side=RIGHT)
|
||||||
|
|
||||||
drowDownFrame.pack(side=LEFT)
|
drowDownFrame.pack(side=LEFT)
|
||||||
rightHalfFrame.pack(side=RIGHT)
|
rightHalfFrame.pack(side=RIGHT)
|
||||||
topFrame.pack(side=TOP)
|
topFrame.pack(side=TOP)
|
||||||
|
farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||||
bottomFrame.pack(side=BOTTOM)
|
bottomFrame.pack(side=BOTTOM)
|
||||||
|
|
||||||
if args is not None:
|
if args is not None:
|
||||||
|
@ -229,6 +253,7 @@ def guiMain(args=None):
|
||||||
beatableOnlyVar.set(int(args.beatableonly))
|
beatableOnlyVar.set(int(args.beatableonly))
|
||||||
fastMenuVar.set(int(args.fastmenu))
|
fastMenuVar.set(int(args.fastmenu))
|
||||||
quickSwapVar.set(int(args.quickswap))
|
quickSwapVar.set(int(args.quickswap))
|
||||||
|
disableMusicVar.set(int(args.disablemusic))
|
||||||
if args.count:
|
if args.count:
|
||||||
countVar.set(str(args.count))
|
countVar.set(str(args.count))
|
||||||
if args.seed:
|
if args.seed:
|
||||||
|
@ -249,6 +274,11 @@ def guiMain(args=None):
|
||||||
|
|
||||||
mainWindow.mainloop()
|
mainWindow.mainloop()
|
||||||
|
|
||||||
|
def set_icon(window):
|
||||||
|
er16 = PhotoImage(file=local_path('data/ER16.gif'))
|
||||||
|
er32 = PhotoImage(file=local_path('data/ER32.gif'))
|
||||||
|
er48 = PhotoImage(file=local_path('data/ER32.gif'))
|
||||||
|
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
guiMain()
|
guiMain()
|
||||||
|
|
724
ItemList.py
724
ItemList.py
|
@ -1,5 +1,6 @@
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Fill import fill_restrictive
|
from Fill import fill_restrictive
|
||||||
|
from collections import namedtuple
|
||||||
import random
|
import random
|
||||||
|
|
||||||
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||||
|
@ -10,8 +11,8 @@ alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'F
|
||||||
progressivegloves = ['Progressive Glove'] * 2
|
progressivegloves = ['Progressive Glove'] * 2
|
||||||
basicgloves = ['Power Glove', 'Titans Mitts']
|
basicgloves = ['Power Glove', 'Titans Mitts']
|
||||||
|
|
||||||
normalbottles = ['Bottle', 'BottleRedPotion', 'BottleGreenPotion', 'BottleBluePotion', 'BottleFairy', 'BottleBee', 'BottleGoodBee']
|
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
hardbottles = ['Bottle', 'BottleRedPotion', 'BottleGreenPotion', 'BottleBluePotion', 'BottleBee', 'BottleGoodBee']
|
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
|
|
||||||
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 +
|
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 +
|
||||||
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
||||||
|
@ -20,36 +21,17 @@ normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)']
|
||||||
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
|
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
|
||||||
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
|
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
|
||||||
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||||
normaltimedohko = ['Green Clock'] * 25
|
|
||||||
normaltimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10
|
|
||||||
normaltriforcehunt = ['Triforce Piece'] * 30
|
|
||||||
normalprogressivesword = ['Progressive Sword'] * 3
|
|
||||||
normalbasicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword']
|
|
||||||
normalswordless = ['Rupees (20)'] * 4
|
|
||||||
normalprogressiveshield = ['Progressive Shield'] * 3
|
|
||||||
normalbasicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield']
|
|
||||||
normalprogressivearmor = ['Progressive Armor'] * 2
|
|
||||||
normalbasicarmor = ['Blue Mail', 'Red Mail']
|
|
||||||
|
|
||||||
easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 +
|
easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 +
|
||||||
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
|
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
|
||||||
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
|
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
|
||||||
easylimitedextra = ['Boss Heart Container'] * 3
|
easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart
|
||||||
easyfirst15extra = ['Rupees (100)'] + ['Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Bombs (3)']
|
easyfirst15extra = ['Rupees (100)'] + ['Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Bombs (3)']
|
||||||
easysecond10extra = ['Bombs (3)'] * 9 + ['Rupee (1)']
|
easysecond10extra = ['Bombs (3)'] * 9 + ['Rupee (1)']
|
||||||
easythird5extra = ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (5)']
|
easythird5extra = ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (5)']
|
||||||
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 3 + ['Rupees (5)'] * 3
|
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 3 + ['Rupees (5)'] * 3
|
||||||
easytimedohko = ['Green Clock'] * 25
|
|
||||||
easytimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5
|
|
||||||
easytimedotherextra = ['Red Clock'] * 5
|
easytimedotherextra = ['Red Clock'] * 5
|
||||||
easytriforcehunt = ['Triforce Piece'] * 30
|
|
||||||
easyprogressivesword = ['Progressive Sword'] * 7
|
|
||||||
easybasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword', 'Tempered Sword', 'Golden Sword', 'Golden Sword']
|
|
||||||
easyswordless = ['Rupees (20)'] * 8
|
|
||||||
easyprogressiveshield = ['Progressive Shield'] * 6
|
|
||||||
easybasicshield = ['Blue Shield', 'Blue Shield', 'Red Shield', 'Red Shield', 'Mirror Shield', 'Mirror Shield']
|
|
||||||
easyprogressivearmor = ['Progressive Armor'] * 4
|
|
||||||
easybasicarmor = ['Blue Mail', 'Blue Mail', 'Red Mail', 'Red Mail']
|
|
||||||
|
|
||||||
hardbaseitems = (['Silver Arrows', 'Single Arrow'] + ['Rupees (300)'] + ['Rupees (100)'] * 2 + ['Rupees (50)'] + ['Bombs (3)'] +
|
hardbaseitems = (['Silver Arrows', 'Single Arrow'] + ['Rupees (300)'] + ['Rupees (100)'] * 2 + ['Rupees (50)'] + ['Bombs (3)'] +
|
||||||
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24)
|
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24)
|
||||||
|
@ -57,15 +39,6 @@ hardfirst20extra = ['Bombs (3)'] * 4 + ['Single Bomb'] * 4 + ['Rupees (5)'] * 5
|
||||||
hardsecond20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (20)'] * 2 + ['Rupee (1)'] * 3 + ['Arrows (10)']
|
hardsecond20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (20)'] * 2 + ['Rupee (1)'] * 3 + ['Arrows (10)']
|
||||||
hardthird20extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3 + ['Single Bomb'] * 5 + ['Single Arrow'] * 5
|
hardthird20extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3 + ['Single Bomb'] * 5 + ['Single Arrow'] * 5
|
||||||
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
|
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
|
||||||
hardtimedohko = ['Green Clock'] * 20
|
|
||||||
hardtimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10
|
|
||||||
hardtriforcehunt = ['Triforce Piece'] * 40
|
|
||||||
hardprogressivesword = ['Progressive Sword'] * 3
|
|
||||||
hardbasicsword = ['Master Sword', 'Master Sword', 'Tempered Sword']
|
|
||||||
hardswordless = ['Rupees (20)'] * 4
|
|
||||||
hardprogressiveshield = ['Progressive Shield'] * 3
|
|
||||||
hardbasicshield = ['Blue Shield', 'Red Shield', 'Red Shield']
|
|
||||||
hardarmor = ['Progressive Armor', 'Progressive Armor']
|
|
||||||
|
|
||||||
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 +
|
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 +
|
||||||
['Rupees (20)'] + ['Single Bomb'] * 2 + ['Piece of Heart'] * 24)
|
['Rupees (20)'] + ['Single Bomb'] * 2 + ['Piece of Heart'] * 24)
|
||||||
|
@ -73,12 +46,6 @@ expertfirst15extra = ['Single Bomb'] * 13 + ['Rupees (20)'] * 2
|
||||||
expertsecond25extra = ['Single Bomb'] * 8 + ['Single Arrow'] * 9 + ['Rupees (20)'] * 3 + ['Rupee (1)'] * 5
|
expertsecond25extra = ['Single Bomb'] * 8 + ['Single Arrow'] * 9 + ['Rupees (20)'] * 3 + ['Rupee (1)'] * 5
|
||||||
expertthird15extra = ['Rupees (5)'] * 5 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 5
|
expertthird15extra = ['Rupees (5)'] * 5 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 5
|
||||||
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18
|
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18
|
||||||
experttimedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5
|
|
||||||
experttimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10
|
|
||||||
experttriforcehunt = ['Triforce Piece'] * 40
|
|
||||||
expertprogressivesword = ['Progressive Sword'] * 3
|
|
||||||
expertbasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword']
|
|
||||||
expertswordless = ['Rupees (20)'] * 3 + ['Silver Arrows']
|
|
||||||
|
|
||||||
insanebaseitems = (['Single Arrow', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 3 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 4 + ['Rupees (100)'] * 3 +
|
insanebaseitems = (['Single Arrow', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 3 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 4 + ['Rupees (100)'] * 3 +
|
||||||
['Rupee (1)'] * 4 + ['Single Bomb'] * 4)
|
['Rupee (1)'] * 4 + ['Single Bomb'] * 4)
|
||||||
|
@ -87,16 +54,131 @@ insanesecond25extra = ['Single Bomb'] * 7 + ['Single Arrow'] * 7 + ['Rupee (1)']
|
||||||
insanethird10extra = ['Single Bomb'] * 3 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 + ['Rupees (20)']
|
insanethird10extra = ['Single Bomb'] * 3 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 + ['Rupees (20)']
|
||||||
insanefourth15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5
|
insanefourth15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5
|
||||||
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
|
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
|
||||||
insanetimedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5
|
|
||||||
insanetimedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10
|
Difficulty = namedtuple('Difficulty',
|
||||||
insanetriforcehunt = ['Triforce Piece'] * 50
|
['baseitems', 'bottles', 'bottle_count','same_bottle', 'progressiveshield',
|
||||||
insaneprogressivesword = ['Progressive Sword'] * 3
|
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
|
||||||
insanebasicsword = ['Fighter Sword', 'Master Sword', 'Master Sword']
|
'progressivesword', 'basicsword', 'timedohko', 'timedother',
|
||||||
insaneswordless = ['Rupees (20)'] * 3 + ['Silver Arrows']
|
'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
|
||||||
|
'extras'])
|
||||||
|
|
||||||
|
TotalItemsToPlace = 153
|
||||||
|
|
||||||
|
def easy_conditional_extras(timer, goal, mode, pool, placed_items):
|
||||||
|
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
|
||||||
|
if extraitems < len(easyextra):
|
||||||
|
return easylimitedextra
|
||||||
|
if timer in ['timed', 'timed-countdown']:
|
||||||
|
return easytimedotherextra
|
||||||
|
return []
|
||||||
|
|
||||||
|
def no_conditonal_extras(*args):
|
||||||
|
return []
|
||||||
|
|
||||||
|
#def Difficulty(**kwargs):
|
||||||
|
# protodifficulty._replace(**kwargs)
|
||||||
|
|
||||||
|
difficulties= {
|
||||||
|
'normal': Difficulty(
|
||||||
|
baseitems = normalbaseitems,
|
||||||
|
bottles = normalbottles,
|
||||||
|
bottle_count = 4,
|
||||||
|
same_bottle = False,
|
||||||
|
progressiveshield = ['Progressive Shield'] * 3,
|
||||||
|
basicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield'],
|
||||||
|
progressivearmor = ['Progressive Armor'] * 2,
|
||||||
|
basicarmor = ['Blue Mail', 'Red Mail'],
|
||||||
|
swordless = ['Rupees (20)'] * 4,
|
||||||
|
progressivesword = ['Progressive Sword'] * 3,
|
||||||
|
basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'],
|
||||||
|
timedohko = ['Green Clock'] * 25,
|
||||||
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
|
triforce_pieces_required = 20,
|
||||||
|
conditional_extras = no_conditonal_extras,
|
||||||
|
extras = [normalfirst15extra,normalsecond15extra,normalthird10extra,normalfourth5extra,normalfinal25extra]
|
||||||
|
),
|
||||||
|
'easy': Difficulty(
|
||||||
|
baseitems = easybaseitems,
|
||||||
|
bottles = normalbottles,
|
||||||
|
bottle_count = 8,
|
||||||
|
same_bottle = False,
|
||||||
|
progressiveshield = ['Progressive Shield'] * 6,
|
||||||
|
basicshield = ['Blue Shield', 'Red Shield', 'Mirror Shield'] * 2,
|
||||||
|
progressivearmor = ['Progressive Armor'] * 4,
|
||||||
|
basicarmor = ['Blue Mail', 'Red Mail'] * 2,
|
||||||
|
swordless = ['Rupees (20)'] * 8,
|
||||||
|
progressivesword = ['Progressive Sword'] * 7,
|
||||||
|
basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'] *2 + ['Fighter Sword'],
|
||||||
|
timedohko = ['Green Clock'] * 25,
|
||||||
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room
|
||||||
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
|
triforce_pieces_required = 10,
|
||||||
|
conditional_extras = easy_conditional_extras,
|
||||||
|
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
|
||||||
|
),
|
||||||
|
'hard': Difficulty(
|
||||||
|
baseitems = hardbaseitems,
|
||||||
|
bottles = hardbottles,
|
||||||
|
bottle_count = 4,
|
||||||
|
same_bottle = False,
|
||||||
|
progressiveshield = ['Progressive Shield'] * 3,
|
||||||
|
basicshield = ['Blue Shield', 'Red Shield', 'Red Shield'],
|
||||||
|
progressivearmor = ['Progressive Armor'] * 2,
|
||||||
|
basicarmor = ['Progressive Armor'] * 2, #only the first one will upgrade, making this equivalent to two blue mail
|
||||||
|
swordless = ['Rupees (20)'] * 4,
|
||||||
|
progressivesword = ['Progressive Sword'] * 3,
|
||||||
|
basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'],
|
||||||
|
timedohko = ['Green Clock'] * 20,
|
||||||
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
|
triforcehunt = ['Triforce Piece'] * 40,
|
||||||
|
triforce_pieces_required = 30,
|
||||||
|
conditional_extras = no_conditonal_extras,
|
||||||
|
extras = [hardfirst20extra, hardsecond20extra, hardthird20extra, hardfinal20extra],
|
||||||
|
),
|
||||||
|
'expert': Difficulty(
|
||||||
|
baseitems = expertbaseitems,
|
||||||
|
bottles = hardbottles,
|
||||||
|
bottle_count = 4,
|
||||||
|
same_bottle = True,
|
||||||
|
progressiveshield = [],
|
||||||
|
basicshield = [],
|
||||||
|
progressivearmor = [],
|
||||||
|
basicarmor = [],
|
||||||
|
swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'],
|
||||||
|
progressivesword = ['Progressive Sword'] * 3,
|
||||||
|
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
||||||
|
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||||
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
|
triforcehunt = ['Triforce Piece'] * 40,
|
||||||
|
triforce_pieces_required = 40,
|
||||||
|
conditional_extras = no_conditonal_extras,
|
||||||
|
extras = [expertfirst15extra, expertsecond25extra, expertthird15extra, expertfinal25extra],
|
||||||
|
),
|
||||||
|
'insane': Difficulty(
|
||||||
|
baseitems = insanebaseitems,
|
||||||
|
bottles = hardbottles,
|
||||||
|
bottle_count = 4,
|
||||||
|
same_bottle = True,
|
||||||
|
progressiveshield = [],
|
||||||
|
basicshield = [],
|
||||||
|
progressivearmor = [],
|
||||||
|
basicarmor = [],
|
||||||
|
swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'],
|
||||||
|
progressivesword = ['Progressive Sword'] * 3,
|
||||||
|
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
||||||
|
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||||
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
|
triforcehunt = ['Triforce Piece'] * 50,
|
||||||
|
triforce_pieces_required = 50,
|
||||||
|
conditional_extras = no_conditonal_extras,
|
||||||
|
extras = [insanefirst15extra, insanesecond25extra, insanethird10extra, insanefourth15extra, insanefinal25extra],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
def generate_itempool(world):
|
def generate_itempool(world):
|
||||||
if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
|
if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
|
||||||
or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
||||||
raise NotImplementedError('Not supported yet')
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
world.push_item('Ganon', ItemFactory('Triforce'), False)
|
world.push_item('Ganon', ItemFactory('Triforce'), False)
|
||||||
|
@ -107,431 +189,17 @@ def generate_itempool(world):
|
||||||
world.get_location('Agahnim 2').event = True
|
world.get_location('Agahnim 2').event = True
|
||||||
|
|
||||||
# set up item pool
|
# set up item pool
|
||||||
world.itempool = ItemFactory(alwaysitems)
|
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode)
|
||||||
if world.progressive == 'on':
|
world.itempool = ItemFactory(pool)
|
||||||
world.itempool.extend(ItemFactory(progressivegloves))
|
for (location, item) in placed_items:
|
||||||
elif world.progressive == 'off':
|
world.push_item(location, ItemFactory(item), False)
|
||||||
world.itempool.extend(ItemFactory(basicgloves))
|
world.get_location(location).event = True
|
||||||
else:
|
if clock_mode is not None:
|
||||||
randvalue = random.randint(0, 1)
|
world.clock_mode = clock_mode
|
||||||
if (randvalue == 0):
|
if treasure_hunt_count is not None:
|
||||||
world.itempool.extend(ItemFactory(progressivegloves))
|
world.treasure_hunt_count = treasure_hunt_count
|
||||||
else:
|
if treasure_hunt_icon is not None:
|
||||||
world.itempool.extend(ItemFactory(basicgloves))
|
world.treasure_hunt_icon = treasure_hunt_icon
|
||||||
|
|
||||||
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
|
|
||||||
if world.shuffle == 'insanity':
|
|
||||||
world.push_item('Link\'s House', ItemFactory('Magic Mirror'), False)
|
|
||||||
world.get_location('Link\'s House').event = True
|
|
||||||
world.push_item('Sanctuary', ItemFactory('Moon Pearl'), False)
|
|
||||||
world.get_location('Sanctuary').event = True
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(['Magic Mirror', 'Moon Pearl']))
|
|
||||||
|
|
||||||
if world.timer == 'display':
|
|
||||||
world.clock_mode = 'stopwatch'
|
|
||||||
|
|
||||||
if world.difficulty == 'normal':
|
|
||||||
world.itempool.extend(ItemFactory(normalbaseitems))
|
|
||||||
for i in range (0, 4):
|
|
||||||
thisbottle = normalbottles[random.randint(0, 6)]
|
|
||||||
world.itempool.append(ItemFactory(thisbottle))
|
|
||||||
extraitems = 70
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(normaltimedother))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown'
|
|
||||||
elif world.timer == 'timed-ohko':
|
|
||||||
world.itempool.extend(ItemFactory(normaltimedohko))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
world.clock_mode = 'ohko'
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
world.itempool.extend(ItemFactory(normaltriforcehunt))
|
|
||||||
extraitems = extraitems - 30
|
|
||||||
world.treasure_hunt_count = 20
|
|
||||||
world.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(normalfirst15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(normalsecond15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(normalthird10extra))
|
|
||||||
extraitems = extraitems - 10
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(normalfourth5extra))
|
|
||||||
extraitems = extraitems - 5
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(normalfinal25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressiveshield))
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivearmor))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicshield))
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicarmor))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressiveshield))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicshield))
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivearmor))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicarmor))
|
|
||||||
if world.mode == 'swordless':
|
|
||||||
world.itempool.extend(ItemFactory(normalswordless))
|
|
||||||
elif world.mode == 'standard':
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivesword))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicsword))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivesword))
|
|
||||||
else:
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicsword))
|
|
||||||
else:
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(normalprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(normalbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
|
|
||||||
elif world.difficulty == 'easy':
|
|
||||||
world.itempool.extend(ItemFactory(easybaseitems))
|
|
||||||
for i in range (0, 8):
|
|
||||||
thisbottle = normalbottles[random.randint(0, 6)]
|
|
||||||
world.itempool.append(ItemFactory(thisbottle))
|
|
||||||
extraitems = 70
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(easytimedother))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown'
|
|
||||||
elif world.timer == 'timed-ohko':
|
|
||||||
world.itempool.extend(ItemFactory(easytimedohko))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
world.clock_mode = 'ohko'
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
world.itempool.extend(ItemFactory(easytriforcehunt))
|
|
||||||
extraitems = extraitems - 30
|
|
||||||
world.treasure_hunt_count = 10
|
|
||||||
world.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
if extraitems == 0:
|
|
||||||
world.itempool.extend(ItemFactory(easylimitedextra))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(easyextra))
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(easytimedotherextra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(easyfirst15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(easysecond10extra))
|
|
||||||
extraitems = extraitems - 10
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(easythird5extra))
|
|
||||||
extraitems = extraitems - 5
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(easyfinal25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressiveshield))
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivearmor))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(easybasicshield))
|
|
||||||
world.itempool.extend(ItemFactory(easybasicarmor))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressiveshield))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(easybasicshield))
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivearmor))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(easybasicarmor))
|
|
||||||
if world.mode == 'swordless':
|
|
||||||
world.itempool.extend(ItemFactory(easyswordless))
|
|
||||||
elif world.mode == 'standard':
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivesword))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(easybasicsword))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivesword))
|
|
||||||
else:
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(easybasicsword))
|
|
||||||
else:
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(easybasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(easyprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(easybasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
|
|
||||||
elif world.difficulty == 'hard':
|
|
||||||
world.itempool.extend(ItemFactory(hardbaseitems))
|
|
||||||
for i in range (0, 4):
|
|
||||||
thisbottle = hardbottles[random.randint(0, 5)]
|
|
||||||
world.itempool.append(ItemFactory(thisbottle))
|
|
||||||
extraitems = 80
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(hardtimedother))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown'
|
|
||||||
elif world.timer == 'timed-ohko':
|
|
||||||
world.itempool.extend(ItemFactory(hardtimedohko))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
world.clock_mode = 'ohko'
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
world.itempool.extend(ItemFactory(hardtriforcehunt))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.treasure_hunt_count = 30
|
|
||||||
world.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(hardfirst20extra))
|
|
||||||
extraitems = extraitems - 20
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(hardsecond20extra))
|
|
||||||
extraitems = extraitems - 20
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(hardthird20extra))
|
|
||||||
extraitems = extraitems - 20
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(hardfinal20extra))
|
|
||||||
extraitems = extraitems - 20
|
|
||||||
world.itempool.extend(ItemFactory(hardarmor))
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressiveshield))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicshield))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressiveshield))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicshield))
|
|
||||||
if world.mode == 'swordless':
|
|
||||||
world.itempool.extend(ItemFactory(hardswordless))
|
|
||||||
elif world.mode == 'standard':
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressivesword))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicsword))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressivesword))
|
|
||||||
else:
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicsword))
|
|
||||||
else:
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(hardprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(hardbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
|
|
||||||
elif world.difficulty == 'expert':
|
|
||||||
world.itempool.extend(ItemFactory(expertbaseitems))
|
|
||||||
thisbottle = hardbottles[random.randint(0, 5)]
|
|
||||||
for i in range (0, 4):
|
|
||||||
world.itempool.append(ItemFactory(thisbottle))
|
|
||||||
extraitems = 80
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(experttimedother))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown'
|
|
||||||
elif world.timer == 'timed-ohko':
|
|
||||||
world.itempool.extend(ItemFactory(experttimedohko))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
world.clock_mode = 'ohko'
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
world.itempool.extend(ItemFactory(experttriforcehunt))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.treasure_hunt_count = 40
|
|
||||||
world.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(expertfirst15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(expertsecond25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(expertthird15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(expertfinal25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if world.mode == 'swordless':
|
|
||||||
world.itempool.extend(ItemFactory(expertswordless))
|
|
||||||
elif world.mode == 'standard':
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(expertprogressivesword))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(expertbasicsword))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(expertprogressivesword))
|
|
||||||
else:
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(expertbasicsword))
|
|
||||||
else:
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(expertprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(expertbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(expertprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(expertbasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
|
|
||||||
elif world.difficulty == 'insane':
|
|
||||||
world.itempool.extend(ItemFactory(insanebaseitems))
|
|
||||||
thisbottle = hardbottles[random.randint(0, 5)]
|
|
||||||
for i in range (0, 4):
|
|
||||||
world.itempool.append(ItemFactory(thisbottle))
|
|
||||||
extraitems = 90
|
|
||||||
if world.timer in ['timed', 'timed-countdown']:
|
|
||||||
world.itempool.extend(ItemFactory(insanetimedother))
|
|
||||||
extraitems = extraitems - 40
|
|
||||||
world.clock_mode = 'stopwatch' if world.timer == 'timed' else 'countdown'
|
|
||||||
elif world.timer == 'timed-ohko':
|
|
||||||
world.itempool.extend(ItemFactory(insanetimedohko))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
world.clock_mode = 'ohko'
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
world.itempool.extend(ItemFactory(insanetriforcehunt))
|
|
||||||
extraitems = extraitems - 50
|
|
||||||
world.treasure_hunt_count = 50
|
|
||||||
world.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(insanefirst15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(insanesecond25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(insanethird10extra))
|
|
||||||
extraitems = extraitems - 10
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(insanefourth15extra))
|
|
||||||
extraitems = extraitems - 15
|
|
||||||
if extraitems > 0:
|
|
||||||
world.itempool.extend(ItemFactory(insanefinal25extra))
|
|
||||||
extraitems = extraitems - 25
|
|
||||||
if world.mode == 'swordless':
|
|
||||||
world.itempool.extend(ItemFactory(insaneswordless))
|
|
||||||
elif world.mode == 'standard':
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(insaneprogressivesword))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(insanebasicsword))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Progressive Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(insaneprogressivesword))
|
|
||||||
else:
|
|
||||||
world.push_item('Link\'s Uncle', ItemFactory('Fighter Sword'), False)
|
|
||||||
world.get_location('Link\'s Uncle').event = True
|
|
||||||
world.itempool.extend(ItemFactory(insanebasicsword))
|
|
||||||
else:
|
|
||||||
if world.progressive == 'on':
|
|
||||||
world.itempool.extend(ItemFactory(insaneprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
elif world.progressive == 'off':
|
|
||||||
world.itempool.extend(ItemFactory(insanebasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
else:
|
|
||||||
randvalue = random.randint(0, 1)
|
|
||||||
if (randvalue == 0):
|
|
||||||
world.itempool.extend(ItemFactory(insaneprogressivesword))
|
|
||||||
world.itempool.extend(ItemFactory(['Progressive Sword']))
|
|
||||||
else:
|
|
||||||
world.itempool.extend(ItemFactory(insanebasicsword))
|
|
||||||
world.itempool.extend(ItemFactory(['Fighter Sword']))
|
|
||||||
|
|
||||||
if world.goal == 'pedestal':
|
|
||||||
world.push_item('Master Sword Pedestal', ItemFactory('Triforce'), False)
|
|
||||||
world.get_location('Master Sword Pedestal').event = True
|
|
||||||
|
|
||||||
# shuffle medallions
|
# shuffle medallions
|
||||||
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||||
|
@ -547,3 +215,119 @@ def generate_itempool(world):
|
||||||
random.shuffle(crystal_locations)
|
random.shuffle(crystal_locations)
|
||||||
|
|
||||||
fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals)
|
fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
||||||
|
pool=[]
|
||||||
|
placed_items=[]
|
||||||
|
clock_mode=None
|
||||||
|
treasure_hunt_count=None
|
||||||
|
treasure_hunt_icon=None
|
||||||
|
|
||||||
|
pool.extend(alwaysitems)
|
||||||
|
|
||||||
|
def wantProgressives():
|
||||||
|
return random.choice([True, False]) if progressive == 'random' else progressive=='on'
|
||||||
|
|
||||||
|
if wantProgressives():
|
||||||
|
pool.extend(progressivegloves)
|
||||||
|
else:
|
||||||
|
pool.extend(basicgloves)
|
||||||
|
|
||||||
|
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
|
||||||
|
if shuffle == 'insanity':
|
||||||
|
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
||||||
|
placed_items.append(('Sanctuary', 'Moon Pearl'))
|
||||||
|
else:
|
||||||
|
pool.extend(['Magic Mirror', 'Moon Pearl'])
|
||||||
|
|
||||||
|
if timer == 'display':
|
||||||
|
clock_mode = 'stopwatch'
|
||||||
|
elif timer == 'ohko':
|
||||||
|
clock_mode = 'ohko'
|
||||||
|
|
||||||
|
diff = difficulties[difficulty]
|
||||||
|
pool.extend(diff.baseitems)
|
||||||
|
|
||||||
|
# expert+ difficulties produce the same contents for
|
||||||
|
# all bottles, since only one bottle is available
|
||||||
|
if diff.same_bottle:
|
||||||
|
thisbottle = random.choice(diff.bottles)
|
||||||
|
for i in range (diff.bottle_count):
|
||||||
|
if not diff.same_bottle:
|
||||||
|
thisbottle = random.choice(diff.bottles)
|
||||||
|
pool.append(thisbottle)
|
||||||
|
|
||||||
|
if wantProgressives():
|
||||||
|
pool.extend(diff.progressiveshield)
|
||||||
|
else:
|
||||||
|
pool.extend(diff.basicshield)
|
||||||
|
|
||||||
|
if wantProgressives():
|
||||||
|
pool.extend(diff.progressivearmor)
|
||||||
|
else:
|
||||||
|
pool.extend(diff.basicarmor)
|
||||||
|
|
||||||
|
if mode == 'swordless':
|
||||||
|
pool.extend(diff.swordless)
|
||||||
|
elif mode == 'standard':
|
||||||
|
if wantProgressives():
|
||||||
|
placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
|
||||||
|
pool.extend(diff.progressivesword)
|
||||||
|
else:
|
||||||
|
placed_items.append(('Link\'s Uncle', 'Fighter Sword'))
|
||||||
|
pool.extend(diff.basicsword)
|
||||||
|
else:
|
||||||
|
if wantProgressives():
|
||||||
|
pool.extend(diff.progressivesword)
|
||||||
|
pool.extend(['Progressive Sword'])
|
||||||
|
else:
|
||||||
|
pool.extend(diff.basicsword)
|
||||||
|
pool.extend(['Fighter Sword'])
|
||||||
|
|
||||||
|
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
|
||||||
|
|
||||||
|
if timer in ['timed', 'timed-countdown']:
|
||||||
|
pool.extend(diff.timedother)
|
||||||
|
extraitems -= len(diff.timedother)
|
||||||
|
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
|
||||||
|
elif timer == 'timed-ohko':
|
||||||
|
pool.extend(diff.timedohko)
|
||||||
|
extraitems -= len(diff.timedohko)
|
||||||
|
clock_mode = 'countdown-ohko'
|
||||||
|
if goal == 'triforcehunt':
|
||||||
|
pool.extend(diff.triforcehunt)
|
||||||
|
extraitems -= len(diff.triforcehunt)
|
||||||
|
treasure_hunt_count = diff.triforce_pieces_required
|
||||||
|
treasure_hunt_icon = 'Triforce Piece'
|
||||||
|
|
||||||
|
cond_extras = diff.conditional_extras(timer, goal, mode, pool, placed_items)
|
||||||
|
pool.extend(cond_extras)
|
||||||
|
extraitems -= len(cond_extras)
|
||||||
|
|
||||||
|
for extra in diff.extras:
|
||||||
|
if(extraitems > 0):
|
||||||
|
pool.extend(extra )
|
||||||
|
extraitems -= len(extra)
|
||||||
|
|
||||||
|
if goal == 'pedestal':
|
||||||
|
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
||||||
|
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon)
|
||||||
|
|
||||||
|
# A quick test to ensure all combinations generate the correct amount of items.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
for difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']:
|
||||||
|
for goal in ['ganon', 'triforcehunt', 'pedestal']:
|
||||||
|
for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']:
|
||||||
|
for mode in ['open', 'standard', 'swordless']:
|
||||||
|
for progressive in ['on','off']:
|
||||||
|
for shuffle in ['full','insane']:
|
||||||
|
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
|
||||||
|
count = len(out[0]) + len(out[1])
|
||||||
|
|
||||||
|
correct_count = TotalItemsToPlace
|
||||||
|
if goal in ['pedestal']:
|
||||||
|
# pedestal goals generate one extra item
|
||||||
|
correct_count += 1
|
||||||
|
|
||||||
|
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode))
|
||||||
|
|
12
Items.py
12
Items.py
|
@ -47,12 +47,12 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||||
'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'boy hides coin again'),
|
'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'boy hides coin again'),
|
||||||
'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'boy hides coin again'),
|
'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'boy hides coin again'),
|
||||||
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'boy stores things again'),
|
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'boy stores things again'),
|
||||||
'BottleRedPotion': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'),
|
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'),
|
||||||
'BottleGreenPotion': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'),
|
'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy drinks again'),
|
||||||
'BottleBluePotion': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy stores drinks again'),
|
'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'boy stores drinks again'),
|
||||||
'BottleFairy': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'boy revives again'),
|
'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'boy revives again'),
|
||||||
'BottleBee': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'),
|
'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'),
|
||||||
'BottleGoodBee': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'),
|
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'boy is stung again'),
|
||||||
'Master Sword': (True, False, None, 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'boy fights again'),
|
'Master Sword': (True, False, None, 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'boy fights again'),
|
||||||
'Tempered Sword': (True, False, None, 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'boy fights again'),
|
'Tempered Sword': (True, False, None, 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'boy fights again'),
|
||||||
'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'boy fights again'),
|
'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'boy fights again'),
|
||||||
|
|
27
Main.py
27
Main.py
|
@ -8,28 +8,29 @@ from Items import ItemFactory
|
||||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items
|
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from ItemList import generate_itempool
|
from ItemList import generate_itempool
|
||||||
|
from Utils import output_path
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
__version__ = '0.5.0-dev'
|
__version__ = '0.5.1-dev'
|
||||||
|
|
||||||
logic_hash = [217, 163, 29, 168, 46, 16, 56, 85, 183, 60, 44, 118, 98, 125, 64, 42, 161, 36, 131, 95, 247, 37, 127, 164, 47, 14, 19, 40, 96, 174, 67, 200,
|
logic_hash = [117, 227, 77, 12, 94, 219, 67, 70, 58, 42, 7, 75, 132, 55, 130, 97, 235, 46, 206, 185, 243, 64, 109, 161, 107, 91, 224, 142, 25, 84, 4, 78,
|
||||||
240, 119, 189, 4, 243, 155, 162, 32, 159, 186, 84, 180, 233, 99, 86, 242, 105, 26, 216, 196, 249, 214, 45, 70, 72, 224, 78, 87, 93, 182, 38, 248,
|
160, 245, 143, 18, 251, 114, 165, 157, 13, 26, 119, 92, 188, 216, 27, 39, 76, 238, 152, 113, 231, 193, 191, 103, 118, 182, 213, 134, 41, 90, 246, 82,
|
||||||
173, 109, 30, 205, 73, 7, 193, 113, 241, 251, 52, 62, 171, 43, 41, 222, 138, 49, 145, 170, 103, 48, 21, 235, 74, 110, 176, 201, 253, 114, 68, 117,
|
57, 225, 150, 139, 99, 151, 184, 11, 85, 209, 144, 147, 47, 56, 129, 247, 121, 177, 79, 1, 215, 207, 126, 136, 105, 100, 180, 5, 2, 14, 153, 6,
|
||||||
89, 207, 82, 54, 211, 61, 53, 88, 158, 226, 218, 177, 50, 213, 25, 9, 104, 140, 203, 169, 166, 116, 152, 2, 33, 149, 20, 220, 165, 108, 254, 179,
|
163, 192, 198, 88, 98, 174, 149, 201, 249, 200, 158, 116, 196, 80, 220, 31, 111, 214, 194, 248, 221, 167, 250, 115, 38, 10, 32, 218, 133, 19, 253, 122,
|
||||||
107, 6, 22, 128, 69, 250, 231, 94, 92, 97, 252, 160, 172, 148, 237, 81, 77, 199, 35, 215, 184, 187, 136, 28, 129, 71, 210, 178, 102, 195, 198, 121,
|
239, 16, 52, 48, 156, 205, 127, 3, 138, 237, 234, 190, 37, 112, 189, 86, 223, 236, 195, 54, 71, 181, 43, 49, 226, 255, 0, 135, 186, 203, 175, 87,
|
||||||
80, 135, 111, 151, 17, 223, 228, 238, 51, 147, 133, 79, 55, 12, 122, 1, 100, 120, 225, 202, 144, 63, 185, 208, 181, 204, 134, 142, 188, 146, 126, 27,
|
21, 229, 120, 124, 145, 171, 252, 155, 22, 62, 199, 51, 35, 179, 159, 44, 69, 30, 172, 242, 140, 74, 9, 83, 183, 93, 202, 137, 108, 241, 173, 23,
|
||||||
153, 91, 191, 13, 157, 5, 59, 234, 83, 141, 23, 15, 18, 236, 137, 31, 143, 209, 229, 34, 132, 57, 75, 0, 230, 190, 90, 115, 76, 123, 197, 39,
|
164, 45, 222, 232, 166, 176, 230, 63, 154, 96, 170, 34, 66, 50, 17, 211, 95, 53, 208, 244, 36, 123, 81, 187, 106, 131, 169, 29, 104, 72, 101, 141,
|
||||||
3, 206, 255, 112, 244, 167, 212, 154, 65, 124, 219, 221, 106, 139, 175, 10, 101, 239, 150, 227, 11, 246, 24, 156, 8, 130, 245, 66, 194, 58, 232, 192]
|
68, 24, 168, 125, 217, 240, 15, 162, 148, 8, 40, 102, 33, 89, 128, 61, 210, 204, 73, 228, 59, 146, 28, 110, 233, 178, 254, 65, 197, 20, 212, 60]
|
||||||
|
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
start = time.clock()
|
start = time.clock()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.keysanity)
|
world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
if seed is None:
|
if seed is None:
|
||||||
random.seed(None)
|
random.seed(None)
|
||||||
|
@ -105,10 +106,10 @@ def main(args, seed=None):
|
||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()}))
|
print(json.dumps({'patch': rom.patches, 'spoiler': world.spoiler.to_json()}))
|
||||||
else:
|
else:
|
||||||
rom.write_to_file(args.jsonout or '%s.sfc' % outfilebase)
|
rom.write_to_file(args.jsonout or output_path('%s.sfc' % outfilebase))
|
||||||
|
|
||||||
if args.create_spoiler and not args.jsonout:
|
if args.create_spoiler and not args.jsonout:
|
||||||
world.spoiler.to_file('%s_Spoiler.txt' % outfilebase)
|
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||||
|
|
||||||
logger.info('Done. Enjoy.')
|
logger.info('Done. Enjoy.')
|
||||||
logger.debug('Total Time: %s' % (time.clock() - start))
|
logger.debug('Total Time: %s' % (time.clock() - start))
|
||||||
|
@ -117,7 +118,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.keysanity)
|
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity)
|
||||||
ret.required_medallions = list(world.required_medallions)
|
ret.required_medallions = list(world.required_medallions)
|
||||||
ret.swamp_patch_required = world.swamp_patch_required
|
ret.swamp_patch_required = world.swamp_patch_required
|
||||||
ret.ganon_at_pyramid = world.ganon_at_pyramid
|
ret.ganon_at_pyramid = world.ganon_at_pyramid
|
||||||
|
|
19
Plando.py
19
Plando.py
|
@ -1,7 +1,7 @@
|
||||||
from BaseClasses import World
|
from BaseClasses import World
|
||||||
from Regions import create_regions
|
from Regions import create_regions
|
||||||
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
|
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
|
||||||
from Rom import patch_rom, LocalRom, write_string_to_rom, write_credits_string_to_rom
|
from Rom import patch_rom, LocalRom, write_string_to_rom
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons
|
from Dungeons import create_dungeons
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
@ -12,6 +12,7 @@ import logging
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import sys
|
||||||
|
|
||||||
__version__ = '0.2-dev'
|
__version__ = '0.2-dev'
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ def main(args, seed=None):
|
||||||
start = time.clock()
|
start = time.clock()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, False)
|
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
|
||||||
hasher = hashlib.md5()
|
hasher = hashlib.md5()
|
||||||
|
@ -161,9 +162,10 @@ def fill_world(world, plando, text_patches):
|
||||||
elif line.startswith('!text_'):
|
elif line.startswith('!text_'):
|
||||||
textname, text = line.split(':', 1)
|
textname, text = line.split(':', 1)
|
||||||
text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()])
|
text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()])
|
||||||
elif line.startswith('!credits_'):
|
#temporarilly removed. New credits system not ready to handle this.
|
||||||
textname, text = line.split(':', 1)
|
#elif line.startswith('!credits_'):
|
||||||
text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()])
|
# textname, text = line.split(':', 1)
|
||||||
|
# text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
locationstr, itemstr = line.split(':', 1)
|
locationstr, itemstr = line.split(':', 1)
|
||||||
|
@ -205,6 +207,7 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||||
parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true')
|
parser.add_argument('--fastmenu', help='Enable instant menu', action='store_true')
|
||||||
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('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'],
|
parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'],
|
||||||
help='Select the rate at which the heart beep sound is played at low health.')
|
help='Select the rate at which the heart beep sound is played at low health.')
|
||||||
parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.')
|
parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.')
|
||||||
|
@ -214,13 +217,13 @@ if __name__ == '__main__':
|
||||||
# ToDo: Validate files further than mere existance
|
# ToDo: Validate files further than mere existance
|
||||||
if not os.path.isfile(args.rom):
|
if not os.path.isfile(args.rom):
|
||||||
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
if not os.path.isfile(args.plando):
|
if not os.path.isfile(args.plando):
|
||||||
input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando)
|
input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
if args.sprite is not None and not os.path.isfile(args.rom):
|
if args.sprite is not None and not os.path.isfile(args.rom):
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# set up logger
|
# set up logger
|
||||||
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
|
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
|
||||||
|
|
248
README.md
248
README.md
|
@ -3,7 +3,7 @@
|
||||||
This is a entrance randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES.
|
This is a entrance randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES.
|
||||||
See http://vt.alttp.run for more details on the normal randomizer.
|
See http://vt.alttp.run for more details on the normal randomizer.
|
||||||
|
|
||||||
## Installation
|
# Installation
|
||||||
|
|
||||||
Clone this repository and then run ```EntranceRandomizer.py``` (requires Python 3).
|
Clone this repository and then run ```EntranceRandomizer.py``` (requires Python 3).
|
||||||
|
|
||||||
|
@ -11,40 +11,9 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface.
|
||||||
|
|
||||||
For releases, a Windows standalone executable is available for users without Python 3.
|
For releases, a Windows standalone executable is available for users without Python 3.
|
||||||
|
|
||||||
## Options
|
# Settings
|
||||||
|
|
||||||
|
## Game Mode
|
||||||
```
|
|
||||||
-h, --help
|
|
||||||
```
|
|
||||||
|
|
||||||
Show the help message and exit.
|
|
||||||
|
|
||||||
```
|
|
||||||
--create_spoiler
|
|
||||||
```
|
|
||||||
|
|
||||||
Output a Spoiler File (default: False)
|
|
||||||
|
|
||||||
```
|
|
||||||
--logic [{noglitches,minorglitches}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select Enforcement of Item Requirements.
|
|
||||||
|
|
||||||
### No Glitches
|
|
||||||
|
|
||||||
The game can be completed without knowing how to perform glitches of any kind.
|
|
||||||
|
|
||||||
### Minor Glitches
|
|
||||||
|
|
||||||
May require Fake Flippers, Bunny Revival. (default: noglitches)
|
|
||||||
|
|
||||||
```
|
|
||||||
--mode [{standard,open,swordless}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select game mode. (default: open)
|
|
||||||
|
|
||||||
### Standard
|
### Standard
|
||||||
|
|
||||||
|
@ -74,13 +43,20 @@ Special notes:
|
||||||
- The magic barrier to Hyrule Castle Tower can be broken with a Hammer.
|
- The magic barrier to Hyrule Castle Tower can be broken with a Hammer.
|
||||||
- The Hammer can be used to activate the Ether and Bombos tablets.
|
- The Hammer can be used to activate the Ether and Bombos tablets.
|
||||||
|
|
||||||
```
|
## Game Logic
|
||||||
--goal [{ganon,pedestal,dungeons,triforcehunt,crystals}]
|
This determines the Item Requirements for each location.
|
||||||
```
|
|
||||||
|
|
||||||
Select completion goal.
|
### No Glitches
|
||||||
|
|
||||||
### Ganon (Default)
|
The game can be completed without knowing how to perform glitches of any kind.
|
||||||
|
|
||||||
|
### Minor Glitches
|
||||||
|
|
||||||
|
May require Fake Flippers, Bunny Revival.
|
||||||
|
|
||||||
|
## Game Goal
|
||||||
|
|
||||||
|
### Ganon
|
||||||
|
|
||||||
Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon.
|
Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon.
|
||||||
|
|
||||||
|
@ -109,18 +85,14 @@ Standard game completion requiring you to collect the 7 crystals and then beat G
|
||||||
|
|
||||||
This is only noticeably different if the --shuffleganon option is enabled.
|
This is only noticeably different if the --shuffleganon option is enabled.
|
||||||
|
|
||||||
```
|
## Game Difficulty
|
||||||
--difficulty [{easy,normal,hard,expert,insane}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select game difficulty. Affects available itempool. (default: normal)
|
|
||||||
|
|
||||||
### Easy
|
### Easy
|
||||||
|
|
||||||
This setting doubles the number of swords, shields, armors, and bottles in the item pool.
|
This setting doubles the number of swords, shields, armors, and bottles in the item pool.
|
||||||
Within dungeons, the number of items found will be displayed on screen if there is no timer.
|
Within dungeons, the number of items found will be displayed on screen if there is no timer.
|
||||||
|
|
||||||
### Normal (Default)
|
### Normal
|
||||||
|
|
||||||
This is the default setting that has an item pool most similar to the original
|
This is the default setting that has an item pool most similar to the original
|
||||||
The Legend of Zelda: A Link to the Past.
|
The Legend of Zelda: A Link to the Past.
|
||||||
|
@ -142,13 +114,9 @@ pool is less helpful, and the player can find no armor, only a Master Sword, and
|
||||||
This setting is a modest step up from Expert. The main difference is that the player will never find any
|
This setting is a modest step up from Expert. The main difference is that the player will never find any
|
||||||
additional health.
|
additional health.
|
||||||
|
|
||||||
```
|
## Timer Setting
|
||||||
--timer [{none,display,timed,timed-ohko,timed-countdown}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select the timer setting.
|
### None
|
||||||
|
|
||||||
### None (Default)
|
|
||||||
|
|
||||||
Does not invoke a timer.
|
Does not invoke a timer.
|
||||||
|
|
||||||
|
@ -168,18 +136,21 @@ Displays a countdown timer on screen that, when it hits zero, will put the playe
|
||||||
knockout state until more time is added to the clock via some of the Green Clocks that will be added
|
knockout state until more time is added to the clock via some of the Green Clocks that will be added
|
||||||
to the itempool.
|
to the itempool.
|
||||||
|
|
||||||
|
### OHKO
|
||||||
|
|
||||||
|
The player will be in a one hit knockout state the entire game. This is the same as Timed-OHKO except
|
||||||
|
without the Clock items and the timer permanently at zero.
|
||||||
|
|
||||||
### Timed-countdown
|
### Timed-countdown
|
||||||
|
|
||||||
Displays a countdown timer on screen that can be increased with Green Clocks and Blue Clocks or
|
Displays a countdown timer on screen that can be increased with Green Clocks and Blue Clocks or
|
||||||
increased with Red Clocks found in chests that will be added to the itempool. The goal of this mode
|
decreased with Red Clocks found in chests that will be added to the itempool. The goal of this mode
|
||||||
is to finish the game without the timer reaching zero, but the game will continue uninterrupted if
|
is to finish the game without the timer reaching zero, but the game will continue uninterrupted if
|
||||||
the player runs out of time.
|
the player runs out of time.
|
||||||
|
|
||||||
```
|
## Progressive equipment
|
||||||
--progressive [{on,off,random}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select the setting for progressive equipment.
|
Determines if Sword, Shield, and gloves are progressive (upgrading in sequence) or not.
|
||||||
|
|
||||||
### On (Default)
|
### On (Default)
|
||||||
|
|
||||||
|
@ -199,14 +170,12 @@ will simply do nothing.
|
||||||
This setting makes swords, shields, armor, and gloves randomly either progressive or not. Each category is independently
|
This setting makes swords, shields, armor, and gloves randomly either progressive or not. Each category is independently
|
||||||
randomized.
|
randomized.
|
||||||
|
|
||||||
```
|
## Item Distribution Algorithm
|
||||||
--algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select item filling algorithm.
|
Determines how the items are shuffled.
|
||||||
|
|
||||||
### Balanced (Default)
|
### Balanced
|
||||||
This is a variation of vt26 that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm.
|
This is a variation of VT26 that aims to strike a balance between the overworld heavy VT25 and the dungeon heavy VT26 algorithm.
|
||||||
It does this by reshuffling the remaining locations after placing dungeon items.
|
It does this by reshuffling the remaining locations after placing dungeon items.
|
||||||
|
|
||||||
### VT26
|
### VT26
|
||||||
|
@ -218,14 +187,14 @@ the sheer number of chests in that single location.
|
||||||
Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot
|
Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot
|
||||||
is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions.
|
is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions.
|
||||||
|
|
||||||
|
### VT22
|
||||||
|
The ordinary VT v8.22 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations
|
||||||
|
after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time.
|
||||||
|
|
||||||
### VT21
|
### VT21
|
||||||
The ordinary VT v8.21 algorithm. Unbiased placement of items into unlocked locations, placing items that unlock new locations first.
|
The ordinary VT v8.21 algorithm. Unbiased placement of items into unlocked locations, placing items that unlock new locations first.
|
||||||
May lead to distributions that seem a bit wonky (high likelyhood of ice rod in Turtle Rock, for instance)
|
May lead to distributions that seem a bit wonky (high likelyhood of ice rod in Turtle Rock, for instance)
|
||||||
|
|
||||||
### VT22
|
|
||||||
The ordinary VT v8.21 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations
|
|
||||||
after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time.
|
|
||||||
|
|
||||||
### Flood
|
### Flood
|
||||||
Pushes out items starting from Link's House and is slightly biased to placing progression items with less restrictions. Use for relatively simple distributions.
|
Pushes out items starting from Link's House and is slightly biased to placing progression items with less restrictions. Use for relatively simple distributions.
|
||||||
|
|
||||||
|
@ -233,11 +202,9 @@ Pushes out items starting from Link's House and is slightly biased to placing pr
|
||||||
Alternative approach to VT22 to improve on VT21 flaws. Locations that are skipped because they are currently unreachable increase in
|
Alternative approach to VT22 to improve on VT21 flaws. Locations that are skipped because they are currently unreachable increase in
|
||||||
staleness, decreasing the likelihood of receiving a progress item.
|
staleness, decreasing the likelihood of receiving a progress item.
|
||||||
|
|
||||||
```
|
## Entrance Shuffle Algorithm
|
||||||
--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}]
|
|
||||||
```
|
|
||||||
|
|
||||||
Select Entrance Shuffling Algorithm.
|
Determines how locations are shuffled.
|
||||||
|
|
||||||
### Default
|
### Default
|
||||||
|
|
||||||
|
@ -248,7 +215,7 @@ Is the Vanilla layout.
|
||||||
Shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. Outside Light World Death Mountain, interiors are shuffled but still connect the same points
|
Shuffles Dungeon Entrances/Exits between each other and keeps all 4-entrance dungeons confined to one location. Outside Light World Death Mountain, interiors are shuffled but still connect the same points
|
||||||
on the overworld. On Death Mountain, entrances are connected more freely.
|
on the overworld. On Death Mountain, entrances are connected more freely.
|
||||||
|
|
||||||
### Full (Default)
|
### Full
|
||||||
|
|
||||||
Mixes cave and dungeon entrances freely.
|
Mixes cave and dungeon entrances freely.
|
||||||
|
|
||||||
|
@ -268,6 +235,118 @@ Madness, but without the light/dark world restrictions. Gives access to Mirror a
|
||||||
|
|
||||||
The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.
|
The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.
|
||||||
|
|
||||||
|
## Heartbeep Sound Rate
|
||||||
|
|
||||||
|
Select frequency of beeps when on low health. Can completely disable them.
|
||||||
|
|
||||||
|
## Create Spoiler Log
|
||||||
|
|
||||||
|
Output a Spoiler File.
|
||||||
|
|
||||||
|
## Do not Create Patched Rom
|
||||||
|
|
||||||
|
If set, will not produce a patched rom as output. Useful in conjunction with the spoiler log option to batch
|
||||||
|
generate spoilers for statistical analysis.
|
||||||
|
|
||||||
|
## Enable L/R button quickswapping
|
||||||
|
|
||||||
|
Use to enable quick item swap with L/R buttons
|
||||||
|
|
||||||
|
## Instant Menu
|
||||||
|
|
||||||
|
As an alternative to quickswap, opens menu instantly.
|
||||||
|
|
||||||
|
## Keysanity
|
||||||
|
|
||||||
|
This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just
|
||||||
|
in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that
|
||||||
|
is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but
|
||||||
|
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
||||||
|
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
||||||
|
|
||||||
|
## Place Dungeon Items
|
||||||
|
|
||||||
|
If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
|
||||||
|
This may lead to different amount of itempool items being placed in a dungeon than you are used to.
|
||||||
|
|
||||||
|
## Only Ensure Seed Beatable
|
||||||
|
|
||||||
|
If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms.
|
||||||
|
|
||||||
|
## Include Ganon's Tower and Pyramid Hole in Shuffle pool
|
||||||
|
|
||||||
|
If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled. This setting removes any bias against Ganon's Tower that some algorithms may have.
|
||||||
|
|
||||||
|
## Seed
|
||||||
|
|
||||||
|
Can be used to set a seed number to generate. Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output.
|
||||||
|
|
||||||
|
## Count
|
||||||
|
|
||||||
|
Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time).
|
||||||
|
|
||||||
|
# Command Line Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Show the help message and exit.
|
||||||
|
|
||||||
|
```
|
||||||
|
--create_spoiler
|
||||||
|
```
|
||||||
|
|
||||||
|
Output a Spoiler File (default: False)
|
||||||
|
|
||||||
|
```
|
||||||
|
--logic [{noglitches,minorglitches}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the game logic (default: noglitches)
|
||||||
|
|
||||||
|
```
|
||||||
|
--mode [{standard,open,swordless}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the game mode. (default: open)
|
||||||
|
|
||||||
|
```
|
||||||
|
--goal [{ganon,pedestal,dungeons,triforcehunt,crystals}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the game completion goal. (default: ganon)
|
||||||
|
|
||||||
|
```
|
||||||
|
--difficulty [{easy,normal,hard,expert,insane}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the game difficulty. Affects available itempool. (default: normal)
|
||||||
|
|
||||||
|
```
|
||||||
|
--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the timer setting. (default: none)
|
||||||
|
|
||||||
|
```
|
||||||
|
--progressive [{on,off,random}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the setting for progressive equipment. (default: on)
|
||||||
|
|
||||||
|
```
|
||||||
|
--algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select item distribution algorithm. (default: balanced)
|
||||||
|
|
||||||
|
```
|
||||||
|
--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select entrance shuffle algorithm. (default: full)
|
||||||
|
|
||||||
```
|
```
|
||||||
--rom ROM
|
--rom ROM
|
||||||
```
|
```
|
||||||
|
@ -284,14 +363,13 @@ Select level of logging for output. (default: info)
|
||||||
--seed SEED
|
--seed SEED
|
||||||
```
|
```
|
||||||
|
|
||||||
Define seed number to generate. (default: None) Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output.
|
Define seed number to generate. (default: None)
|
||||||
|
|
||||||
```
|
```
|
||||||
--count COUNT
|
--count COUNT
|
||||||
```
|
```
|
||||||
|
|
||||||
Use to batch generate multiple seeds with same settings.
|
Set the count option (default: None)
|
||||||
If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). (default: None)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
--quickswap
|
--quickswap
|
||||||
|
@ -305,15 +383,18 @@ Use to enable quick item swap with L/R buttons. (default: False)
|
||||||
|
|
||||||
As an alternative to quickswap, opens menu instantly. (default: False)
|
As an alternative to quickswap, opens menu instantly. (default: False)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
--disablemusic
|
||||||
|
```
|
||||||
|
|
||||||
|
Disables game music, resulting in the game sound being just the SFX. (default: False)
|
||||||
|
|
||||||
```
|
```
|
||||||
--keysanity
|
--keysanity
|
||||||
```
|
```
|
||||||
|
|
||||||
This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just
|
Enable Keysanity (default: False)
|
||||||
in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that
|
|
||||||
is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but
|
|
||||||
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
|
||||||
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
--nodungeonitems
|
--nodungeonitems
|
||||||
|
@ -338,22 +419,19 @@ Use to select a different sprite sheet to use for Link. Path to a binary file of
|
||||||
--beatableonly
|
--beatableonly
|
||||||
```
|
```
|
||||||
|
|
||||||
If set, will only ensure the goal can be achieved, but not necessarily that all locations are reachable. Currently only affects VT25, VT26 and balanced algorithms.
|
Enables the "Only Ensure Seed Beatable" option (default: False)
|
||||||
|
|
||||||
```
|
```
|
||||||
--shuffleganon
|
--shuffleganon
|
||||||
```
|
```
|
||||||
|
|
||||||
If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled.
|
Enables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: false)
|
||||||
This setting removes any bias against Ganon's Tower that some algorithms may have.
|
|
||||||
|
|
||||||
Note: This option is under development and may sometimes lead to dungeon and crystal distributions that cannot be solved. If this is the case, the generation will fail. Simply retry with a different seed number if you run into this issue.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
--suppress_rom
|
--suppress_rom
|
||||||
```
|
```
|
||||||
|
|
||||||
If set, will not produce a patched rom as output. Useful to batch generate spoilers for statistical analysis.
|
Enables the "Do not Create Patched Rom" option. (default: False)
|
||||||
|
|
||||||
```
|
```
|
||||||
--gui
|
--gui
|
||||||
|
|
|
@ -458,10 +458,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||||
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'),
|
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Trinexx': (0x180159, False, 'with Trinexx'),
|
'Turtle Rock - Trinexx': (0x180159, False, 'with Trinexx'),
|
||||||
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - The Arena - Bridge': (0xEA3A, False, 'in Palace of Darkness'),
|
'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - The Arena - Ledge': (0xEA3D, False, 'in Palace of Darkness'),
|
'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'),
|
||||||
|
|
94
Rom.py
94
Rom.py
|
@ -2,6 +2,7 @@ from Dungeons import dungeon_music_addresses
|
||||||
from Text import string_to_alttp_text, text_addresses, Credits
|
from Text import string_to_alttp_text, text_addresses, Credits
|
||||||
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
|
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
|
||||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
|
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts
|
||||||
|
from Utils import local_path
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -66,7 +67,7 @@ class LocalRom(object):
|
||||||
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
|
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
|
||||||
|
|
||||||
# load randomizer patches
|
# load randomizer patches
|
||||||
patches = json.load(open('base2current.json', 'r'))
|
patches = json.load(open(local_path('data/base2current.json'), 'r'))
|
||||||
for patch in patches:
|
for patch in patches:
|
||||||
if isinstance(patch, dict):
|
if isinstance(patch, dict):
|
||||||
for baseaddress, values in patch.items():
|
for baseaddress, values in patch.items():
|
||||||
|
@ -391,33 +392,39 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
|
||||||
# set up clocks for timed modes
|
# set up clocks for timed modes
|
||||||
if world.clock_mode == 'off':
|
if world.clock_mode == 'off':
|
||||||
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
|
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
|
||||||
rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32)
|
||||||
elif world.clock_mode == 'ohko':
|
elif world.clock_mode == 'ohko':
|
||||||
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||||
rom.write_bytes(0x180200, [0x00, 0x00, 0x00, 0x81]) # red clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||||
|
rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32)
|
||||||
|
elif world.clock_mode == 'countdown-ohko':
|
||||||
|
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||||
|
rom.write_int32_to_rom(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
|
rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
|
rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
if world.difficulty == 'easy':
|
if world.difficulty == 'easy':
|
||||||
rom.write_bytes(0x18020C, [0x40, 0x19, 0x01, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 20 * 60 * 60) # starting time (in frames, sint32)
|
||||||
elif world.difficulty == 'normal':
|
elif world.difficulty == 'normal':
|
||||||
rom.write_bytes(0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 10 * 60 * 60) # starting time (in frames, sint32)
|
||||||
else:
|
else:
|
||||||
rom.write_bytes(0x18020C, [0x50, 0x46, 0x00, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 5 * 60 * 60) # starting time (in frames, sint32)
|
||||||
if world.clock_mode == 'stopwatch':
|
if world.clock_mode == 'stopwatch':
|
||||||
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
|
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
|
||||||
rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 0) # starting time (in frames, sint32)
|
||||||
if world.clock_mode == 'countdown':
|
if world.clock_mode == 'countdown':
|
||||||
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
|
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
|
||||||
rom.write_bytes(0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
|
rom.write_int32_to_rom(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
rom.write_bytes(0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, sint32)
|
rom.write_int32_to_rom(0x18020C, 40 * 60 * 60) # starting time (in frames, sint32)
|
||||||
|
|
||||||
# set up goals for treasure hunt
|
# set up goals for treasure hunt
|
||||||
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
|
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
|
||||||
|
@ -528,6 +535,53 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
|
||||||
|
|
||||||
write_strings(rom, world)
|
write_strings(rom, world)
|
||||||
|
|
||||||
|
if world.disable_music:
|
||||||
|
volumeaddresses = [0xD373B, 0xD375B, 0xD90F8, 0xDA710, 0xDA7A4, 0xDA7BB, 0xDA7D2, 0xD5954, 0xD653B, 0xDA736, 0xDA752, 0xDA772, 0xDA792,
|
||||||
|
0xD5B47, 0xD5B5E, 0xD4306, 0xD6878, 0xD6883, 0xD6E48, 0xD6E76, 0xD6EFB, 0xD6F2D, 0xDA211, 0xDA35B, 0xDA37B, 0xDA38E,
|
||||||
|
0xDA39F, 0xDA5C3, 0xDA691, 0xDA6A8, 0xDA6DF, 0xD2349, 0xD3F45, 0xD42EB, 0xD48B9, 0xD48FF, 0xD543F, 0xD5817, 0xD5957,
|
||||||
|
0xD5ACB, 0xD5AE8, 0xD5B4A, 0xDA5DE, 0xDA608, 0xDA635, 0xDA662, 0xDA71F, 0xDA7AF, 0xDA7C6, 0xDA7DD, 0xD2F00, 0xDA3D5,
|
||||||
|
0xD249C, 0xD24CD, 0xD2C09, 0xD2C53, 0xD2CAF, 0xD2CEB, 0xD2D91, 0xD2EE6, 0xD38ED, 0xD3C91, 0xD3CD3, 0xD3CE8, 0xD3F0C,
|
||||||
|
0xD3F82, 0xD405F, 0xD4139, 0xD4198, 0xD41D5, 0xD41F6, 0xD422B, 0xD4270, 0xD42B1, 0xD4334, 0xD4371, 0xD43A6, 0xD43DB,
|
||||||
|
0xD441E, 0xD4597, 0xD4B3C, 0xD4BAB, 0xD4C03, 0xD4C53, 0xD4C7F, 0xD4D9C, 0xD5424, 0xD65D2, 0xD664F, 0xD6698, 0xD66FF,
|
||||||
|
0xD6985, 0xD6C5C, 0xD6C6F, 0xD6C8E, 0xD6CB4, 0xD6D7D, 0xD827D, 0xD960C, 0xD9828, 0xDA233, 0xDA3A2, 0xDA49E, 0xDA72B,
|
||||||
|
0xDA745, 0xDA765, 0xDA785, 0xDABF6, 0xDAC0D, 0xDAEBE, 0xDAFAC, 0xD9A02, 0xD9BD6, 0xD21CD, 0xD2279, 0xD2E66, 0xD2E70,
|
||||||
|
0xD2EAB, 0xD3B97, 0xD3BAC, 0xD3BE8, 0xD3C0D, 0xD3C39, 0xD3C68, 0xD3C9F, 0xD3CBC, 0xD401E, 0xD4290, 0xD443E, 0xD456F,
|
||||||
|
0xD47D3, 0xD4D43, 0xD4DCC, 0xD4EBA, 0xD4F0B, 0xD4FE5, 0xD5012, 0xD54BC, 0xD54D5, 0xD54F0, 0xD5509, 0xD57D8, 0xD59B9,
|
||||||
|
0xD5A2F, 0xD5AEB, 0xD5E5E, 0xD5FE9, 0xD658F, 0xD674A, 0xD6827, 0xD69D6, 0xD69F5, 0xD6A05, 0xD6AE9, 0xD6DCF, 0xD6E20,
|
||||||
|
0xD6ECB, 0xD71D4, 0xD71E6, 0xD7203, 0xD721E, 0xD8724, 0xD8732, 0xD9652, 0xD9698, 0xD9CBC, 0xD9DC0, 0xD9E49, 0xDAA68,
|
||||||
|
0xDAA77, 0xDAA88, 0xDAA99, 0xDAF04, 0xD1D28, 0xD1D41, 0xD1D5C, 0xD1D77, 0xD1EEE, 0xD311D, 0xD31D1, 0xD4148, 0xD5543,
|
||||||
|
0xD5B6F, 0xD65B3, 0xD6760, 0xD6B6B, 0xD6DF6, 0xD6E0D, 0xD73A1, 0xD814C, 0xD825D, 0xD82BE, 0xD8340, 0xD8394, 0xD842C,
|
||||||
|
0xD8796, 0xD8903, 0xD892A, 0xD91E8, 0xD922B, 0xD92E0, 0xD937E, 0xD93C1, 0xDA958, 0xDA971, 0xDA98C, 0xDA9A7, 0xD1D92,
|
||||||
|
0xD1DBD, 0xD1DEB, 0xD1F5D, 0xD1F9F, 0xD1FBD, 0xD1FDC, 0xD1FEA, 0xD20CA, 0xD21BB, 0xD22C9, 0xD2754, 0xD284C, 0xD2866,
|
||||||
|
0xD2887, 0xD28A0, 0xD28BA, 0xD28DB, 0xD28F4, 0xD293E, 0xD2BF3, 0xD2C1F, 0xD2C69, 0xD2CA1, 0xD2CC5, 0xD2D05, 0xD2D73,
|
||||||
|
0xD2DAF, 0xD2E3D, 0xD2F36, 0xD2F46, 0xD2F6F, 0xD2FCF, 0xD2FDF, 0xD302B, 0xD3086, 0xD3099, 0xD30A5, 0xD30CD, 0xD30F6,
|
||||||
|
0xD3154, 0xD3184, 0xD333A, 0xD33D9, 0xD349F, 0xD354A, 0xD35E5, 0xD3624, 0xD363C, 0xD3672, 0xD3691, 0xD36B4, 0xD36C6,
|
||||||
|
0xD3724, 0xD3767, 0xD38CB, 0xD3B1D, 0xD3B2F, 0xD3B55, 0xD3B70, 0xD3B81, 0xD3BBF, 0xD3D34, 0xD3D55, 0xD3D6E, 0xD3DC6,
|
||||||
|
0xD3E04, 0xD3E38, 0xD3F65, 0xD3FA6, 0xD404F, 0xD4087, 0xD417A, 0xD41A0, 0xD425C, 0xD4319, 0xD433C, 0xD43EF, 0xD440C,
|
||||||
|
0xD4452, 0xD4494, 0xD44B5, 0xD4512, 0xD45D1, 0xD45EF, 0xD4682, 0xD46C3, 0xD483C, 0xD4848, 0xD4855, 0xD4862, 0xD486F,
|
||||||
|
0xD487C, 0xD4A1C, 0xD4A3B, 0xD4A60, 0xD4B27, 0xD4C7A, 0xD4D12, 0xD4D81, 0xD4E90, 0xD4ED6, 0xD4EE2, 0xD5005, 0xD502E,
|
||||||
|
0xD503C, 0xD5081, 0xD51B1, 0xD51C7, 0xD51CF, 0xD51EF, 0xD520C, 0xD5214, 0xD5231, 0xD5257, 0xD526D, 0xD5275, 0xD52AF,
|
||||||
|
0xD52BD, 0xD52CD, 0xD52DB, 0xD549C, 0xD5801, 0xD58A4, 0xD5A68, 0xD5A7F, 0xD5C12, 0xD5D71, 0xD5E10, 0xD5E9A, 0xD5F8B,
|
||||||
|
0xD5FA4, 0xD651A, 0xD6542, 0xD65ED, 0xD661D, 0xD66D7, 0xD6776, 0xD68BD, 0xD68E5, 0xD6956, 0xD6973, 0xD69A8, 0xD6A51,
|
||||||
|
0xD6A86, 0xD6B96, 0xD6C3E, 0xD6D4A, 0xD6E9C, 0xD6F80, 0xD717E, 0xD7190, 0xD71B9, 0xD811D, 0xD8139, 0xD816B, 0xD818A,
|
||||||
|
0xD819E, 0xD81BE, 0xD829C, 0xD82E1, 0xD8306, 0xD830E, 0xD835E, 0xD83AB, 0xD83CA, 0xD83F0, 0xD83F8, 0xD844B, 0xD8479,
|
||||||
|
0xD849E, 0xD84CB, 0xD84EB, 0xD84F3, 0xD854A, 0xD8573, 0xD859D, 0xD85B4, 0xD85CE, 0xD862A, 0xD8681, 0xD87E3, 0xD87FF,
|
||||||
|
0xD887B, 0xD88C6, 0xD88E3, 0xD8944, 0xD897B, 0xD8C97, 0xD8CA4, 0xD8CB3, 0xD8CC2, 0xD8CD1, 0xD8D01, 0xD917B, 0xD918C,
|
||||||
|
0xD919A, 0xD91B5, 0xD91D0, 0xD91DD, 0xD9220, 0xD9273, 0xD9284, 0xD9292, 0xD92AD, 0xD92C8, 0xD92D5, 0xD9311, 0xD9322,
|
||||||
|
0xD9330, 0xD934B, 0xD9366, 0xD9373, 0xD93B6, 0xD97A6, 0xD97C2, 0xD97DC, 0xD97FB, 0xD9811, 0xD98FF, 0xD996F, 0xD99A8,
|
||||||
|
0xD99D5, 0xD9A30, 0xD9A4E, 0xD9A6B, 0xD9A88, 0xD9AF7, 0xD9B1D, 0xD9B43, 0xD9B7C, 0xD9BA9, 0xD9C84, 0xD9C8D, 0xD9CAC,
|
||||||
|
0xD9CE8, 0xD9CF3, 0xD9CFD, 0xD9D46, 0xDA35E, 0xDA37E, 0xDA391, 0xDA478, 0xDA4C3, 0xDA4D7, 0xDA4F6, 0xDA515, 0xDA6E2,
|
||||||
|
0xDA9C2, 0xDA9ED, 0xDAA1B, 0xDAA57, 0xDABAF, 0xDABC9, 0xDABE2, 0xDAC28, 0xDAC46, 0xDAC63, 0xDACB8, 0xDACEC, 0xDAD08,
|
||||||
|
0xDAD25, 0xDAD42, 0xDAD5F, 0xDAE17, 0xDAE34, 0xDAE51, 0xDAF2E, 0xDAF55, 0xDAF6B, 0xDAF81, 0xDB14F, 0xDB16B, 0xDB180,
|
||||||
|
0xDB195, 0xDB1AA, 0xD2B88, 0xD364A, 0xD369F, 0xD3747, 0xD213F, 0xD2174, 0xD229E, 0xD2426, 0xD4731, 0xD4753, 0xD4774,
|
||||||
|
0xD4795, 0xD47B6, 0xD4AA5, 0xD4AE4, 0xD4B96, 0xD4CA5, 0xD5477, 0xD5A3D, 0xD6566, 0xD672C, 0xD67C0, 0xD69B8, 0xD6AB1,
|
||||||
|
0xD6C05, 0xD6DB3, 0xD71AB, 0xD8E2D, 0xD8F0D, 0xD94E0, 0xD9544, 0xD95A8, 0xD9982, 0xD9B56, 0xDA694, 0xDA6AB, 0xDAE88,
|
||||||
|
0xDAEC8, 0xDAEE6, 0xDB1BF, 0xD210A, 0xD22DC, 0xD2447, 0xD5A4D, 0xD5DDC, 0xDA251, 0xDA26C, 0xD945E, 0xD967D, 0xD96C2,
|
||||||
|
0xD9C95, 0xD9EE6, 0xDA5C6, 0xD2047, 0xD24C2, 0xD24EC, 0xD25A4, 0xD3DAA, 0xD51A8, 0xD51E6, 0xD524E, 0xD529E, 0xD6045,
|
||||||
|
0xD81DE, 0xD821E, 0xD94AA, 0xD9A9E, 0xD9AE4, 0xDA289, 0xD2085, 0xD21C5, 0xD5F28]
|
||||||
|
for address in volumeaddresses:
|
||||||
|
rom.write_byte(address, 0x00)
|
||||||
|
|
||||||
# set rom name
|
# set rom name
|
||||||
# 21 bytes
|
# 21 bytes
|
||||||
rom.write_bytes(0x7FC0, bytearray('ER_050_%09d\0' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big'))
|
rom.write_bytes(0x7FC0, bytearray('ER_050_%09d\0' % world.seed, 'utf8') + world.option_identifier.to_bytes(4, 'big'))
|
||||||
|
@ -620,7 +674,7 @@ def write_strings(rom, world):
|
||||||
magicshopitem = world.get_location('Potion Shop').item
|
magicshopitem = world.get_location('Potion Shop').item
|
||||||
magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text
|
magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text
|
||||||
|
|
||||||
fluteboyitem = world.get_location('Stumpy').item
|
fluteboyitem = world.get_location('Flute Spot').item
|
||||||
fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text
|
fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text
|
||||||
|
|
||||||
credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts))
|
credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts))
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def is_bundled():
|
||||||
|
return getattr(sys, 'frozen', False)
|
||||||
|
|
||||||
|
def local_path(path):
|
||||||
|
if local_path.cached_path is not None:
|
||||||
|
return os.path.join(local_path.cached_path, path)
|
||||||
|
|
||||||
|
if is_bundled():
|
||||||
|
# we are running in a bundle
|
||||||
|
local_path.cached_path = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
# we are running in a normal Python environment
|
||||||
|
local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
return os.path.join(local_path.cached_path, path)
|
||||||
|
|
||||||
|
local_path.cached_path = None
|
||||||
|
|
||||||
|
def output_path(path):
|
||||||
|
if output_path.cached_path is not None:
|
||||||
|
return os.path.join(output_path.cached_path, path)
|
||||||
|
|
||||||
|
if not is_bundled():
|
||||||
|
output_path.cached_path = '.'
|
||||||
|
return os.path.join(output_path.cached_path, path)
|
||||||
|
else:
|
||||||
|
# has been packaged, so cannot use CWD for output.
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
#windows
|
||||||
|
import ctypes.wintypes
|
||||||
|
CSIDL_PERSONAL = 5 # My Documents
|
||||||
|
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
|
||||||
|
|
||||||
|
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
||||||
|
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
|
||||||
|
|
||||||
|
documents = buf.value
|
||||||
|
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
from AppKit import NSSearchPathForDirectoriesInDomains
|
||||||
|
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
|
||||||
|
NSDocumentDirectory = 9
|
||||||
|
NSUserDomainMask = 1
|
||||||
|
# True for expanding the tilde into a fully qualified path
|
||||||
|
documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
|
output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer')
|
||||||
|
if not os.path.exists(output_path.cached_path):
|
||||||
|
os.mkdir(output_path.cached_path)
|
||||||
|
return os.path.join(output_path.cached_path, path)
|
||||||
|
|
||||||
|
output_path.cached_path = None
|
||||||
|
|
||||||
|
def open_file(filename):
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
os.startfile(filename)
|
||||||
|
else:
|
||||||
|
open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
||||||
|
subprocess.call([open_command, filename])
|
||||||
|
|
||||||
|
def close_console():
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
#windows
|
||||||
|
import ctypes.wintypes
|
||||||
|
try:
|
||||||
|
ctypes.windll.kernel32.FreeConsole()
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,28 @@
|
||||||
|
version: '{build}'
|
||||||
|
pull_requests:
|
||||||
|
do_not_increment_build_number: true
|
||||||
|
environment:
|
||||||
|
ProjectVersion: build$(APPVEYOR_BUILD_VERSION)
|
||||||
|
matrix:
|
||||||
|
- PYTHON: C:\PYTHON36
|
||||||
|
install:
|
||||||
|
- ps: 'if(Test-Path env:APPVEYOR_REPO_TAG_NAME) {$env:ProjectVersion=$env:APPVEYOR_REPO_TAG_NAME}'
|
||||||
|
- '%PYTHON%\python.exe --version'
|
||||||
|
- '%PYTHON%\Scripts\pip install pyinstaller'
|
||||||
|
- '%PYTHON%\Scripts\pip install markdown'
|
||||||
|
- '%PYTHON%\python.exe -m markdown README.md > README.html'
|
||||||
|
- '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec'
|
||||||
|
- 'mkdir dist\EntranceRandomizer\ext'
|
||||||
|
- 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext'
|
||||||
|
- 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext'
|
||||||
|
- 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext'
|
||||||
|
- ps: '$env:ER_Version= &"$env:PYTHON\python.exe" -c "import Main; import re; print(re.match(''[0-9]+\\.[0-9]+\\.[0-9]+'',Main.__version__).group(0))"'
|
||||||
|
- '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs'
|
||||||
|
- '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs'
|
||||||
|
- '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer'
|
||||||
|
build: off
|
||||||
|
artifacts:
|
||||||
|
- path: dist/EntranceRandomizer*.msi
|
||||||
|
name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi
|
||||||
|
- path: dist/EntranceRandomizer/
|
||||||
|
name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip
|
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- mode: python -*-
|
||||||
|
from PyInstaller.compat import is_win
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
# Todo: the runtime hooks should only be installed on windows
|
||||||
|
a = Analysis(['../EntranceRandomizer.py'],
|
||||||
|
pathex=['bundle'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[('../data/', 'data/'), ('../README.html', '.')],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
runtime_hooks=['bundle/_rt_hook.py'],
|
||||||
|
excludes=['lzma', 'bz2'],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
exclude_binaries=True,
|
||||||
|
name='EntranceRandomizer',
|
||||||
|
debug=False,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
icon='data/ER.ico',
|
||||||
|
console=is_win )
|
||||||
|
coll = COLLECT(exe,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
name='EntranceRandomizer')
|
||||||
|
app = BUNDLE(coll,
|
||||||
|
name ='EntranceRandomizer.app',
|
||||||
|
icon = 'data/ER.icns',
|
||||||
|
bundle_identifier = None)
|
|
@ -0,0 +1,4 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.append(os.path.join(sys._MEIPASS, "ext"))
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xsl:stylesheet version="1.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
|
||||||
|
exclude-result-prefixes="msxsl"
|
||||||
|
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
|
||||||
|
<xsl:output method="xml" indent="no"/>
|
||||||
|
|
||||||
|
<xsl:strip-space elements="*"/>
|
||||||
|
|
||||||
|
<xsl:template match="@*|node()">
|
||||||
|
<xsl:copy>
|
||||||
|
<xsl:apply-templates select="@*|node()"/>
|
||||||
|
</xsl:copy>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="wix:File[@Source='SourceDir\EntranceRandomizer.exe']">
|
||||||
|
<xsl:copy-of select="." />
|
||||||
|
<wix:Shortcut Id="ProgramShortcut"
|
||||||
|
Name="ALttP Entrance Randomizer"
|
||||||
|
Advertise="yes"
|
||||||
|
Description="ALttP Entrance Randomizer"
|
||||||
|
Directory="ApplicationProgramsFolder" />
|
||||||
|
</xsl:template>
|
||||||
|
<xsl:template match="wix:File[@Source='SourceDir\README.hmtl']">
|
||||||
|
<xsl:copy-of select="." />
|
||||||
|
<wix:Shortcut Id="ReadmeShortcut"
|
||||||
|
Name="ALttP Entrance Randomizer README"
|
||||||
|
Advertise="yes"
|
||||||
|
Description="ALttP Entrance Randomizer README"
|
||||||
|
Directory="ApplicationProgramsFolder" />
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<!-- Setting the product ID to "*" means all upgrades are treated as major upgrades, and will uninstall the old version
|
||||||
|
before installing the new one. This is desireable because it means nothing breaks if we fail to follow the component rules precisely. -->
|
||||||
|
<Product Id="*" Name="ALttP Entrance Randomizer" Language="1033" Version="$(env.ER_Version)" Manufacturer="Randomizer Community" UpgradeCode="0229C621-5F8A-4D59-962A-5826C58B93DD" >
|
||||||
|
<Package Id="*" InstallerVersion="400" Compressed="yes" InstallScope="perMachine" />
|
||||||
|
<!-- Allowing downgrades will cause a harmless warning to be emitted. This wearning is not relevant here, allowing downgrades via a new major upgrade is safe for a simple standalone app like this-->
|
||||||
|
<MajorUpgrade AllowDowngrades="yes"/>
|
||||||
|
<Media Id="1" Cabinet="contents.cab" EmbedCab="yes" CompressionLevel="high"/>
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
<Directory Id='ProgramFilesFolder' Name='PFiles'>
|
||||||
|
<Directory Id='INSTALLDIR' Name='ALttP Entrance Randomizer'/>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ApplicationProgramsFolder" Name="ALttP Entrance Randomizer"/>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||||
|
<Component Id="ApplicationShortcut" Guid="0054698A-5A56-4B36-8176-8FEC1762EF2D">
|
||||||
|
<RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\ALttPEntranceRandomizer" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
<Feature Id="Complete"
|
||||||
|
Title="ALttP Entrance Randomizer"
|
||||||
|
Description="ALttP Entrance Randomizer"
|
||||||
|
Level="1">
|
||||||
|
<ComponentGroupRef Id="ERFiles"/>
|
||||||
|
<ComponentRef Id="ApplicationShortcut"/>
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<Icon Id="ER.ico" SourceFile="Data/ER.ico" />
|
||||||
|
<Property Id="DISABLEADVTSHORTCUTS" Secure="yes">1</Property>
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="ER.ico" />
|
||||||
|
<Property Id="WIXUI_INSTALLDIR">INSTALLDIR</Property>
|
||||||
|
<UI>
|
||||||
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
|
<UIRef Id="WixUI_ErrorProgressText" />
|
||||||
|
<!-- Skip license page -->
|
||||||
|
<Publish Dialog="WelcomeDlg"
|
||||||
|
Control="Next"
|
||||||
|
Event="NewDialog"
|
||||||
|
Value="InstallDirDlg"
|
||||||
|
Order="2">1</Publish>
|
||||||
|
<Publish Dialog="InstallDirDlg"
|
||||||
|
Control="Back"
|
||||||
|
Event="NewDialog"
|
||||||
|
Value="WelcomeDlg"
|
||||||
|
Order="2">1</Publish>
|
||||||
|
</UI>
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 123 B |
Binary file not shown.
After Width: | Height: | Size: 370 B |
Binary file not shown.
After Width: | Height: | Size: 882 B |
Loading…
Reference in New Issue