Merge pull request #6 from KevinCathcart/Dev
Merge in lots of v28/holiday dev work
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import logging
|
||||
|
@ -11,8 +12,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
|||
def _get_help_string(self, action):
|
||||
return textwrap.dedent(action.help)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||
|
@ -47,3 +47,6 @@ if __name__ == '__main__':
|
|||
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||
|
||||
adjust(args=args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from collections import OrderedDict
|
||||
from Utils import output_path
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
|
||||
from Rom import LocalRom, apply_rom_settings
|
||||
from Utils import output_path
|
||||
from Rom import LocalRom, Sprite, apply_rom_settings
|
||||
|
||||
|
||||
def adjust(args):
|
||||
|
@ -13,13 +12,16 @@ def adjust(args):
|
|||
logger.info('Patching ROM.')
|
||||
|
||||
if args.sprite is not None:
|
||||
sprite = bytearray(open(args.sprite, 'rb').read())
|
||||
if isinstance(args.sprite, Sprite):
|
||||
sprite = args.sprite
|
||||
else:
|
||||
sprite = Sprite(args.sprite)
|
||||
else:
|
||||
sprite = None
|
||||
|
||||
outfilebase = 'ER_adjusted'
|
||||
|
||||
if (os.stat(args.rom).st_size == 2097152 and os.path.splitext(args.rom)[-1].lower() == '.sfc'):
|
||||
if os.stat(args.rom).st_size == 2097152 and os.path.splitext(args.rom)[-1].lower() == '.sfc':
|
||||
rom = LocalRom(args.rom, False)
|
||||
else:
|
||||
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
|
||||
|
@ -29,6 +31,6 @@ def adjust(args):
|
|||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||
|
||||
logger.info('Done. Enjoy.')
|
||||
logger.debug('Total Time: %s' % (time.clock() - start))
|
||||
logger.debug('Total Time: %s', time.clock() - start)
|
||||
|
||||
return args
|
||||
|
|
|
@ -26,7 +26,6 @@ class World(object):
|
|||
self._region_cache = {}
|
||||
self._entrance_cache = {}
|
||||
self._location_cache = {}
|
||||
self._item_cache = {}
|
||||
self.required_locations = []
|
||||
self.place_dungeon_items = place_dungeon_items # configurable in future
|
||||
self.shuffle_bonk_prizes = False
|
||||
|
@ -53,6 +52,7 @@ class World(object):
|
|||
self.fastmenu = fastmenu
|
||||
self.disable_music = disable_music
|
||||
self.keysanity = keysanity
|
||||
self.can_take_damage = True
|
||||
self.spoiler = Spoiler(self)
|
||||
|
||||
def intialize_regions(self):
|
||||
|
@ -120,6 +120,15 @@ class World(object):
|
|||
ret.prog_items.append('Titans Mitts')
|
||||
else:
|
||||
ret.prog_items.append('Power Glove')
|
||||
elif 'Shield' in item.name:
|
||||
if ret.has('Mirror Shield'):
|
||||
pass
|
||||
elif ret.has('Red Shield'):
|
||||
ret.prog_items.append('Mirror Shield')
|
||||
elif ret.has('Blue Shield'):
|
||||
ret.prog_items.append('Red Shield')
|
||||
else:
|
||||
ret.prog_items.append('Blue Shield')
|
||||
|
||||
elif item.advancement or item.key:
|
||||
ret.prog_items.append(item.name)
|
||||
|
@ -133,9 +142,12 @@ class World(object):
|
|||
'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + ['Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', 'Big Key (Ganons Tower)'] + ['Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + ['Small Key (Ganons Tower)'] * 4):
|
||||
soft_collect(item)
|
||||
ret.sweep_for_events()
|
||||
ret._clear_cache()
|
||||
ret.clear_cached_unreachable()
|
||||
return ret
|
||||
|
||||
def get_items(self):
|
||||
return [loc.item for loc in self.get_filled_locations()] + self.itempool
|
||||
|
||||
def find_items(self, item):
|
||||
return [location for location in self.get_locations() if location.item is not None and location.item.name == item]
|
||||
|
||||
|
@ -143,13 +155,13 @@ class World(object):
|
|||
if not isinstance(location, Location):
|
||||
location = self.get_location(location)
|
||||
|
||||
if location.can_fill(item):
|
||||
if location.can_fill(self.state, item, False):
|
||||
location.item = item
|
||||
item.location = location
|
||||
if collect:
|
||||
self.state.collect(item, location.event)
|
||||
|
||||
logging.getLogger('').debug('Placed %s at %s' % (item, location))
|
||||
logging.getLogger('').debug('Placed %s at %s', item, location)
|
||||
else:
|
||||
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
|
||||
|
||||
|
@ -178,7 +190,7 @@ class World(object):
|
|||
|
||||
def unlocks_new_location(self, item):
|
||||
temp_state = self.state.copy()
|
||||
temp_state._clear_cache()
|
||||
temp_state.clear_cached_unreachable()
|
||||
temp_state.collect(item, True)
|
||||
|
||||
for location in self.get_unfilled_locations():
|
||||
|
@ -188,7 +200,8 @@ class World(object):
|
|||
return False
|
||||
|
||||
def has_beaten_game(self, state):
|
||||
if state.has('Triforce'): return True
|
||||
if state.has('Triforce'):
|
||||
return True
|
||||
if self.goal in ['triforcehunt']:
|
||||
if state.item_count('Triforce Piece') + state.item_count('Power Star') > self.treasure_hunt_count:
|
||||
return True
|
||||
|
@ -253,7 +266,7 @@ class CollectionState(object):
|
|||
self.recursion_count = 0
|
||||
self.events = []
|
||||
|
||||
def _clear_cache(self):
|
||||
def clear_cached_unreachable(self):
|
||||
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
|
||||
self.region_cache = {k: v for k, v in self.region_cache.items() if v}
|
||||
self.location_cache = {k: v for k, v in self.location_cache.items() if v}
|
||||
|
@ -328,7 +341,6 @@ class CollectionState(object):
|
|||
def has(self, item, count=1):
|
||||
if count == 1:
|
||||
return item in self.prog_items
|
||||
else:
|
||||
return self.item_count(item) >= count
|
||||
|
||||
def item_count(self, item):
|
||||
|
@ -343,6 +355,17 @@ class CollectionState(object):
|
|||
def can_lift_heavy_rocks(self):
|
||||
return self.has('Titans Mitts')
|
||||
|
||||
def can_extend_magic(self):
|
||||
return self.has('Half Magic') or self.has('Quarter Magic') or self.has_bottle() # FIXME bottle should really also have a requirement that we can reach some shop that sells green or blue potions
|
||||
|
||||
def can_kill_most_things(self, enemies=5):
|
||||
return (self.has_blunt_weapon()
|
||||
or self.has('Cane of Somaria')
|
||||
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
|
||||
or self.has('Bow')
|
||||
or self.has('Fire Rod')
|
||||
)
|
||||
|
||||
def has_sword(self):
|
||||
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
||||
|
||||
|
@ -415,10 +438,10 @@ class CollectionState(object):
|
|||
changed = True
|
||||
|
||||
if changed:
|
||||
self._clear_cache()
|
||||
self.clear_cached_unreachable()
|
||||
if not event:
|
||||
self.sweep_for_events()
|
||||
self._clear_cache()
|
||||
self.clear_cached_unreachable()
|
||||
|
||||
def remove(self, item):
|
||||
if item.advancement:
|
||||
|
@ -488,10 +511,7 @@ class Region(object):
|
|||
is_dungeon_item = item.key or item.map or item.compass
|
||||
sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)'
|
||||
if sewer_hack or (is_dungeon_item and not self.world.keysanity):
|
||||
if self.dungeon and self.dungeon.is_dungeon_item(item):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.dungeon and self.dungeon.is_dungeon_item(item)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -513,9 +533,7 @@ class Entrance(object):
|
|||
self.spot_type = 'Entrance'
|
||||
self.recursion_count = 0
|
||||
self.vanilla = None
|
||||
|
||||
def access_rule(self, state):
|
||||
return True
|
||||
self.access_rule = lambda state: True
|
||||
|
||||
def can_reach(self, state):
|
||||
if self.access_rule(state) and state.can_reach(self.parent_region):
|
||||
|
@ -577,15 +595,12 @@ class Location(object):
|
|||
self.recursion_count = 0
|
||||
self.staleness_count = 0
|
||||
self.event = False
|
||||
self.always_allow = lambda item, state: False
|
||||
self.access_rule = lambda state: True
|
||||
self.item_rule = lambda item: True
|
||||
|
||||
def access_rule(self, state):
|
||||
return True
|
||||
|
||||
def item_rule(self, item):
|
||||
return True
|
||||
|
||||
def can_fill(self, item):
|
||||
return self.parent_region.can_fill(item) and self.item_rule(item)
|
||||
def can_fill(self, state, item, check_access=True):
|
||||
return self.always_allow(item, self) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
|
||||
|
||||
def can_reach(self, state):
|
||||
if self.access_rule(state) and state.can_reach(self.parent_region):
|
||||
|
|
26
Dungeons.py
|
@ -1,7 +1,8 @@
|
|||
from Items import ItemFactory
|
||||
import random
|
||||
|
||||
from BaseClasses import Dungeon
|
||||
from Fill import fill_restrictive
|
||||
import random
|
||||
from Items import ItemFactory
|
||||
|
||||
|
||||
def create_dungeons(world):
|
||||
|
@ -62,7 +63,7 @@ def fill_dungeons(world):
|
|||
world.push_item(bk_location, big_key, False)
|
||||
bk_location.event = True
|
||||
dungeon_locations.remove(bk_location)
|
||||
all_state._clear_cache()
|
||||
all_state.clear_cached_unreachable()
|
||||
big_key = None
|
||||
|
||||
# next place small keys
|
||||
|
@ -88,7 +89,7 @@ def fill_dungeons(world):
|
|||
world.push_item(sk_location, small_key, False)
|
||||
sk_location.event = True
|
||||
dungeon_locations.remove(sk_location)
|
||||
all_state._clear_cache()
|
||||
all_state.clear_cached_unreachable()
|
||||
|
||||
if small_keys:
|
||||
# key placement not finished, loop again
|
||||
|
@ -100,8 +101,10 @@ def fill_dungeons(world):
|
|||
di_location = dungeon_locations.pop()
|
||||
world.push_item(di_location, dungeon_item, False)
|
||||
|
||||
world.state._clear_cache()
|
||||
world.state.clear_cached_unreachable()
|
||||
|
||||
def get_dungeon_item_pool(world):
|
||||
return [item for dungeon in world.dungeons for item in dungeon.all_items if item.key or world.place_dungeon_items]
|
||||
|
||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||
all_state_base = world.get_all_state()
|
||||
|
@ -111,7 +114,16 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||
skull_woods_big_chest.event = True
|
||||
shuffled_locations.remove(skull_woods_big_chest)
|
||||
|
||||
dungeon_items = [item for dungeon in world.dungeons for item in dungeon.all_items if item.key or world.place_dungeon_items]
|
||||
if world.keysanity:
|
||||
#in keysanity dungeon items are distributed as part of the normal item pool
|
||||
for item in world.get_items():
|
||||
if item.key:
|
||||
item.advancement = True
|
||||
elif item.map or item.compass:
|
||||
item.priority = True
|
||||
return
|
||||
|
||||
dungeon_items = get_dungeon_item_pool(world)
|
||||
|
||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||
|
@ -119,7 +131,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||
|
||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items)
|
||||
|
||||
world.state._clear_cache()
|
||||
world.state.clear_cached_unreachable()
|
||||
|
||||
|
||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import logging
|
||||
|
@ -5,8 +6,8 @@ import random
|
|||
import textwrap
|
||||
import sys
|
||||
|
||||
from Main import main
|
||||
from Gui import guiMain
|
||||
from Main import main
|
||||
from Utils import is_bundled, close_console
|
||||
|
||||
|
||||
|
@ -16,7 +17,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
|||
return textwrap.dedent(action.help)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def start():
|
||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
|
||||
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'],
|
||||
|
@ -219,8 +220,11 @@ if __name__ == '__main__':
|
|||
guiMain(args)
|
||||
elif args.count is not None:
|
||||
seed = args.seed
|
||||
for i in range(args.count):
|
||||
for _ in range(args.count):
|
||||
main(seed=seed, args=args)
|
||||
seed = random.randint(0, 999999999)
|
||||
else:
|
||||
main(seed=args.seed, args=args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
||||
|
|
23
Fill.py
|
@ -56,8 +56,8 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
|
|||
raise RuntimeError('No more progress items left to place.')
|
||||
|
||||
spot_to_fill = None
|
||||
for location in (fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations)):
|
||||
if world.state.can_reach(location) and location.can_fill(item_to_place):
|
||||
for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
|
||||
if location.can_fill(world.state, item_to_place):
|
||||
spot_to_fill = location
|
||||
break
|
||||
|
||||
|
@ -72,7 +72,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
|
|||
itempool.remove(item_to_place)
|
||||
fill_locations.remove(spot_to_fill)
|
||||
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in itempool], [location.name for location in fill_locations]))
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations])
|
||||
|
||||
|
||||
def distribute_items_staleness(world):
|
||||
|
@ -129,7 +129,7 @@ def distribute_items_staleness(world):
|
|||
if not progress_done and random.randint(0, location.staleness_count) > 2:
|
||||
continue
|
||||
|
||||
if world.state.can_reach(location) and location.can_fill(item_to_place):
|
||||
if location.can_fill(world.state, item_to_place):
|
||||
spot_to_fill = location
|
||||
break
|
||||
else:
|
||||
|
@ -138,7 +138,7 @@ def distribute_items_staleness(world):
|
|||
# might have skipped too many locations due to potential staleness. Do not check for staleness now to find a candidate
|
||||
if spot_to_fill is None:
|
||||
for location in fill_locations:
|
||||
if world.state.can_reach(location) and location.can_fill(item_to_place):
|
||||
if location.can_fill(world.state, item_to_place):
|
||||
spot_to_fill = location
|
||||
break
|
||||
|
||||
|
@ -153,7 +153,7 @@ def distribute_items_staleness(world):
|
|||
itempool.remove(item_to_place)
|
||||
fill_locations.remove(spot_to_fill)
|
||||
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in itempool], [location.name for location in fill_locations]))
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations])
|
||||
|
||||
|
||||
def fill_restrictive(world, base_state, locations, itempool):
|
||||
|
@ -168,13 +168,14 @@ def fill_restrictive(world, base_state, locations, itempool):
|
|||
item_to_place = itempool.pop()
|
||||
maximum_exploration_state = sweep_from_pool()
|
||||
|
||||
perform_access_check = True
|
||||
if world.check_beatable_only:
|
||||
can_beat_without = world.has_beaten_game(maximum_exploration_state)
|
||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state)
|
||||
|
||||
|
||||
spot_to_fill = None
|
||||
for location in locations:
|
||||
if location.can_fill(item_to_place):
|
||||
if (world.check_beatable_only and can_beat_without) or maximum_exploration_state.can_reach(location):
|
||||
if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
|
||||
spot_to_fill = location
|
||||
break
|
||||
|
||||
|
@ -226,7 +227,7 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
|
|||
|
||||
fast_fill(world, restitempool, fill_locations)
|
||||
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % ([item.name for item in progitempool + prioitempool + restitempool], [location.name for location in fill_locations]))
|
||||
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in progitempool + prioitempool + restitempool], [location.name for location in fill_locations])
|
||||
|
||||
|
||||
def fast_fill(world, item_pool, fill_locations):
|
||||
|
@ -251,7 +252,7 @@ def flood_items(world):
|
|||
random.shuffle(location_list)
|
||||
spot_to_fill = None
|
||||
for location in location_list:
|
||||
if world.state.can_reach(location) and location.can_fill(itempool[0]):
|
||||
if location.can_fill(world.state, itempool[0]):
|
||||
spot_to_fill = location
|
||||
break
|
||||
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
from Main import main, __version__ as ESVersion
|
||||
from AdjusterMain import adjust
|
||||
from Utils import is_bundled, local_path, output_path, open_file
|
||||
#!/usr/bin/env python3
|
||||
from argparse import Namespace
|
||||
from glob import glob
|
||||
import json
|
||||
import random
|
||||
import subprocess
|
||||
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, ttk
|
||||
import shutil
|
||||
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Entry, Spinbox, Button, filedialog, messagebox, ttk
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from AdjusterMain import adjust
|
||||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||
from Main import main, __version__ as ESVersion
|
||||
from Rom import Sprite
|
||||
from Utils import is_bundled, local_path, output_path, open_file
|
||||
|
||||
|
||||
def guiMain(args=None):
|
||||
|
@ -22,6 +29,25 @@ def guiMain(args=None):
|
|||
notebook.add(adjustWindow, text='Adjust')
|
||||
notebook.pack()
|
||||
|
||||
# Shared Controls
|
||||
|
||||
farBottomFrame = Frame(mainWindow)
|
||||
|
||||
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)
|
||||
|
||||
farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||
|
||||
# randomizer controls
|
||||
|
||||
topFrame = Frame(randomizerWindow)
|
||||
rightHalfFrame = Frame(topFrame)
|
||||
checkBoxFrame = Frame(rightHalfFrame)
|
||||
|
@ -72,15 +98,27 @@ def guiMain(args=None):
|
|||
romSelectButton.pack(side=LEFT)
|
||||
|
||||
spriteDialogFrame = Frame(fileDialogFrame)
|
||||
baseSpriteLabel = Label(spriteDialogFrame, text='Link Sprite')
|
||||
spriteVar = StringVar()
|
||||
spriteEntry = Entry(spriteDialogFrame, textvariable=spriteVar)
|
||||
baseSpriteLabel = Label(spriteDialogFrame, text='Link Sprite:')
|
||||
|
||||
spriteNameVar = StringVar()
|
||||
sprite = None
|
||||
def set_sprite(sprite_param):
|
||||
nonlocal sprite
|
||||
if sprite_param is None or not sprite_param.valid:
|
||||
sprite = None
|
||||
spriteNameVar.set('(default)')
|
||||
else:
|
||||
sprite = sprite_param
|
||||
spriteNameVar.set(sprite.name)
|
||||
|
||||
set_sprite(None)
|
||||
spriteNameVar.set('(default)')
|
||||
spriteEntry = Label(spriteDialogFrame, textvariable=spriteNameVar)
|
||||
|
||||
def SpriteSelect():
|
||||
sprite = filedialog.askopenfilename()
|
||||
spriteVar.set(sprite)
|
||||
SpriteSelector(mainWindow, set_sprite)
|
||||
|
||||
spriteSelectButton = Button(spriteDialogFrame, text='Select Sprite', command=SpriteSelect)
|
||||
spriteSelectButton = Button(spriteDialogFrame, text='Open Sprite Picker', command=SpriteSelect)
|
||||
|
||||
baseSpriteLabel.pack(side=LEFT)
|
||||
spriteEntry.pack(side=LEFT)
|
||||
|
@ -177,7 +215,6 @@ def guiMain(args=None):
|
|||
heartbeepFrame.pack(expand=True, anchor=E)
|
||||
|
||||
bottomFrame = Frame(randomizerWindow)
|
||||
farBottomFrame = Frame(randomizerWindow)
|
||||
|
||||
seedLabel = Label(bottomFrame, text='Seed #')
|
||||
seedVar = StringVar()
|
||||
|
@ -210,11 +247,11 @@ def guiMain(args=None):
|
|||
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
||||
guiargs.rom = romVar.get()
|
||||
guiargs.jsonout = None
|
||||
guiargs.sprite = spriteVar.get() if spriteVar.get() else None
|
||||
guiargs.sprite = sprite
|
||||
try:
|
||||
if guiargs.count is not None:
|
||||
seed = guiargs.seed
|
||||
for i in range(guiargs.count):
|
||||
for _ in range(guiargs.count):
|
||||
main(seed=seed, args=guiargs)
|
||||
seed = random.randint(0, 999999999)
|
||||
else:
|
||||
|
@ -226,17 +263,6 @@ def guiMain(args=None):
|
|||
|
||||
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)
|
||||
seedEntry.pack(side=LEFT)
|
||||
countLabel.pack(side=LEFT, padx=(5, 0))
|
||||
|
@ -248,19 +274,17 @@ def guiMain(args=None):
|
|||
drowDownFrame.pack(side=LEFT)
|
||||
rightHalfFrame.pack(side=RIGHT)
|
||||
topFrame.pack(side=TOP)
|
||||
farBottomFrame.pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||
bottomFrame.pack(side=BOTTOM)
|
||||
|
||||
# Adjuster Controls
|
||||
|
||||
topFrame2 = Frame(adjustWindow)
|
||||
rightHalfFrame2 = Frame(topFrame2)
|
||||
checkBoxFrame2 = Frame(rightHalfFrame2)
|
||||
|
||||
quickSwapVar2 = IntVar()
|
||||
quickSwapCheckbutton2 = Checkbutton(checkBoxFrame2, text="Enabled L/R Item quickswapping", variable=quickSwapVar2)
|
||||
fastMenuVar2 = IntVar()
|
||||
fastMenuCheckbutton2 = Checkbutton(checkBoxFrame2, text="Enable instant menu", variable=fastMenuVar2)
|
||||
disableMusicVar2 = IntVar()
|
||||
disableMusicCheckbutton2 = Checkbutton(checkBoxFrame2, text="Disable game music", variable=disableMusicVar2)
|
||||
quickSwapCheckbutton2 = Checkbutton(checkBoxFrame2, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
|
||||
fastMenuCheckbutton2 = Checkbutton(checkBoxFrame2, text="Enable instant menu", variable=fastMenuVar)
|
||||
disableMusicCheckbutton2 = Checkbutton(checkBoxFrame2, text="Disable game music", variable=disableMusicVar)
|
||||
|
||||
quickSwapCheckbutton2.pack(expand=True, anchor=W)
|
||||
fastMenuCheckbutton2.pack(expand=True, anchor=W)
|
||||
|
@ -284,14 +308,9 @@ def guiMain(args=None):
|
|||
|
||||
spriteDialogFrame2 = Frame(fileDialogFrame2)
|
||||
baseSpriteLabel2 = Label(spriteDialogFrame2, text='Link Sprite')
|
||||
spriteVar2 = StringVar()
|
||||
spriteEntry2 = Entry(spriteDialogFrame2, textvariable=spriteVar2)
|
||||
spriteEntry2 = Label(spriteDialogFrame2, textvariable=spriteNameVar)
|
||||
|
||||
def SpriteSelect2():
|
||||
sprite = filedialog.askopenfilename()
|
||||
spriteVar2.set(sprite)
|
||||
|
||||
spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelect2)
|
||||
spriteSelectButton2 = Button(spriteDialogFrame2, text='Select Sprite', command=SpriteSelect)
|
||||
|
||||
baseSpriteLabel2.pack(side=LEFT)
|
||||
spriteEntry2.pack(side=LEFT)
|
||||
|
@ -304,11 +323,8 @@ def guiMain(args=None):
|
|||
fileDialogFrame2.pack()
|
||||
|
||||
drowDownFrame2 = Frame(topFrame2)
|
||||
|
||||
heartbeepFrame2 = Frame(drowDownFrame2)
|
||||
heartbeepVar2 = StringVar()
|
||||
heartbeepVar2.set('normal')
|
||||
heartbeepOptionMenu2 = OptionMenu(heartbeepFrame2, heartbeepVar2, 'normal', 'half', 'quarter', 'off')
|
||||
heartbeepOptionMenu2 = OptionMenu(heartbeepFrame2, heartbeepVar, 'normal', 'half', 'quarter', 'off')
|
||||
heartbeepOptionMenu2.pack(side=RIGHT)
|
||||
heartbeepLabel2 = Label(heartbeepFrame2, text='Heartbeep sound rate')
|
||||
heartbeepLabel2.pack(side=LEFT)
|
||||
|
@ -319,12 +335,12 @@ def guiMain(args=None):
|
|||
|
||||
def adjustRom():
|
||||
guiargs = Namespace
|
||||
guiargs.heartbeep = heartbeepVar2.get()
|
||||
guiargs.fastmenu = bool(fastMenuVar2.get())
|
||||
guiargs.quickswap = bool(quickSwapVar2.get())
|
||||
guiargs.disablemusic = bool(disableMusicVar2.get())
|
||||
guiargs.heartbeep = heartbeepVar.get()
|
||||
guiargs.fastmenu = bool(fastMenuVar.get())
|
||||
guiargs.quickswap = bool(quickSwapVar.get())
|
||||
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||
guiargs.rom = romVar2.get()
|
||||
guiargs.sprite = spriteVar2.get() if spriteVar2.get() else None
|
||||
guiargs.sprite = sprite
|
||||
try:
|
||||
adjust(args=guiargs)
|
||||
except Exception as e:
|
||||
|
@ -368,15 +384,289 @@ def guiMain(args=None):
|
|||
romVar.set(args.rom)
|
||||
shuffleGanonVar.set(args.shuffleganon)
|
||||
if args.sprite is not None:
|
||||
spriteVar.set(args.sprite)
|
||||
set_sprite(Sprite(args.sprite))
|
||||
|
||||
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)
|
||||
class SpriteSelector(object):
|
||||
def __init__(self, parent, callback):
|
||||
if is_bundled():
|
||||
self.deploy_icons()
|
||||
self.parent = parent
|
||||
self.window = Toplevel(parent)
|
||||
self.callback = callback
|
||||
|
||||
self.window.wm_title("TAKE ANY ONE YOU WANT")
|
||||
self.window['padx'] = 5
|
||||
self.window['pady'] = 5
|
||||
|
||||
def open_unofficial_sprite_dir(_evt):
|
||||
open_file(self.unofficial_sprite_dir)
|
||||
|
||||
official_frametitle = Label(self.window, text='Official Sprites')
|
||||
|
||||
unofficial_frametitle = Frame(self.window)
|
||||
title_text = Label(unofficial_frametitle, text="Unofficial Sprites")
|
||||
title_link = Label(unofficial_frametitle, text="(open)", fg="blue", cursor="hand2")
|
||||
title_text.pack(side=LEFT)
|
||||
title_link.pack(side=LEFT)
|
||||
title_link.bind("<Button-1>", open_unofficial_sprite_dir)
|
||||
|
||||
self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official Sprites not found. Click "Update Official Sprites" to download them.')
|
||||
self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.')
|
||||
|
||||
frame = Frame(self.window)
|
||||
frame.pack(side=BOTTOM, fill=X, pady=5)
|
||||
|
||||
button = Button(frame, text="Browse for file...", command=self.browse_for_sprite)
|
||||
button.pack(side=RIGHT, padx=(5, 0))
|
||||
|
||||
button = Button(frame, text="Update Official Sprites", command=self.update_official_sprites)
|
||||
button.pack(side=RIGHT, padx=(5, 0))
|
||||
|
||||
button = Button(frame, text="Use Default Sprite", command=self.use_default_sprite)
|
||||
button.pack(side=LEFT)
|
||||
|
||||
set_icon(self.window)
|
||||
self.window.focus()
|
||||
|
||||
def icon_section(self, frame_label, path, no_results_label):
|
||||
frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5)
|
||||
frame.pack(side=TOP, fill=X)
|
||||
|
||||
i = 0
|
||||
for file in glob(output_path(path)):
|
||||
sprite = Sprite(file)
|
||||
image = get_image_for_sprite(sprite)
|
||||
if image is None:
|
||||
continue
|
||||
button = Button(frame, image=image, command=lambda spr=sprite: self.select_sprite(spr))
|
||||
ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name is not None else ""))
|
||||
button.image = image
|
||||
button.grid(row=i // 16, column=i % 16)
|
||||
i += 1
|
||||
|
||||
if i == 0:
|
||||
label = Label(frame, text=no_results_label)
|
||||
label.pack()
|
||||
|
||||
|
||||
def update_official_sprites(self):
|
||||
# need to wrap in try catch. We don't want errors getting the json or downloading the files to break us.
|
||||
self.window.destroy()
|
||||
self.parent.update()
|
||||
def work(task):
|
||||
resultmessage = ""
|
||||
successful = True
|
||||
|
||||
def finished():
|
||||
task.close_window()
|
||||
if successful:
|
||||
messagebox.showinfo("Sprite Updater", resultmessage)
|
||||
else:
|
||||
messagebox.showerror("Sprite Updater", resultmessage)
|
||||
SpriteSelector(self.parent, self.callback)
|
||||
|
||||
try:
|
||||
task.update_status("Downloading official sprites list")
|
||||
with urlopen('http://vt.alttp.run/sprites') as response:
|
||||
sprites_arr = json.loads(response.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
try:
|
||||
task.update_status("Determining needed sprites")
|
||||
current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')]
|
||||
official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr]
|
||||
needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites]
|
||||
bundled_sprites = [os.path.basename(file) for file in glob(self.local_official_sprite_dir+'/*')]
|
||||
# todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle.
|
||||
|
||||
official_filenames = [filename for (_, filename) in official_sprites]
|
||||
obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames]
|
||||
except Exception as e:
|
||||
resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
updated = 0
|
||||
for (sprite_url, filename) in needed_sprites:
|
||||
try:
|
||||
task.update_status("Downloading needed sprite %g/%g" % (updated + 1, len(needed_sprites)))
|
||||
target = os.path.join(self.official_sprite_dir, filename)
|
||||
with urlopen(sprite_url) as response, open(target, 'wb') as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
except Exception as e:
|
||||
resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
updated += 1
|
||||
|
||||
deleted = 0
|
||||
for sprite in obsolete_sprites:
|
||||
try:
|
||||
task.update_status("Removing obsolete sprite %g/%g" % (deleted + 1, len(obsolete_sprites)))
|
||||
os.remove(os.path.join(self.official_sprite_dir, sprite))
|
||||
except Exception as e:
|
||||
resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
deleted += 1
|
||||
|
||||
if successful:
|
||||
resultmessage = "official sprites updated sucessfully"
|
||||
|
||||
task.queue_event(finished)
|
||||
|
||||
BackgroundTaskProgress(self.parent, work, "Updating Sprites")
|
||||
|
||||
|
||||
def browse_for_sprite(self):
|
||||
sprite = filedialog.askopenfilename()
|
||||
try:
|
||||
self.callback(Sprite(sprite))
|
||||
except Exception:
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
def use_default_sprite(self):
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
def select_sprite(self, spritename):
|
||||
self.callback(spritename)
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
def deploy_icons(self):
|
||||
if not os.path.exists(self.unofficial_sprite_dir):
|
||||
os.makedirs(self.unofficial_sprite_dir)
|
||||
if not os.path.exists(self.official_sprite_dir):
|
||||
shutil.copytree(self.local_official_sprite_dir, self.official_sprite_dir)
|
||||
|
||||
@property
|
||||
def official_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/official")
|
||||
return self.local_official_sprite_dir
|
||||
|
||||
@property
|
||||
def local_official_sprite_dir(self):
|
||||
return local_path("data/sprites/official")
|
||||
|
||||
@property
|
||||
def unofficial_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/unofficial")
|
||||
return self.local_unofficial_sprite_dir
|
||||
|
||||
@property
|
||||
def local_unofficial_sprite_dir(self):
|
||||
return local_path("data/sprites/unofficial")
|
||||
|
||||
|
||||
def get_image_for_sprite(sprite):
|
||||
if not sprite.valid:
|
||||
return None
|
||||
height = 24
|
||||
width = 16
|
||||
|
||||
def draw_sprite_into_gif(add_palette_color, set_pixel_color_index):
|
||||
|
||||
def drawsprite(spr, pal_as_colors, offset):
|
||||
for y, row in enumerate(spr):
|
||||
for x, pal_index in enumerate(row):
|
||||
if pal_index:
|
||||
color = pal_as_colors[pal_index - 1]
|
||||
set_pixel_color_index(x + offset[0], y + offset[1], color)
|
||||
|
||||
add_palette_color(16, (40, 40, 40))
|
||||
shadow = [
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
]
|
||||
|
||||
drawsprite(shadow, [16], (2, 17))
|
||||
|
||||
palettes = sprite.decode_palette()
|
||||
for i in range(15):
|
||||
add_palette_color(i + 1, palettes[0][i])
|
||||
|
||||
body = sprite.decode16(0x4C0)
|
||||
drawsprite(body, list(range(1, 16)), (0, 8))
|
||||
head = sprite.decode16(0x40)
|
||||
drawsprite(head, list(range(1, 16)), (0, 0))
|
||||
|
||||
def make_gif(callback):
|
||||
gif_header = b'GIF89a'
|
||||
|
||||
gif_lsd = bytearray(7)
|
||||
gif_lsd[0] = width
|
||||
gif_lsd[2] = height
|
||||
gif_lsd[4] = 0xF4 # 32 color palette follows. transparant + 15 for sprite + 1 for shadow=17 which rounds up to 32 as nearest power of 2
|
||||
gif_lsd[5] = 0 # background color is zero
|
||||
gif_lsd[6] = 0 # aspect raio not specified
|
||||
gif_gct = bytearray(3 * 32)
|
||||
|
||||
gif_gce = bytearray(8)
|
||||
gif_gce[0] = 0x21 # start of extention blocked
|
||||
gif_gce[1] = 0xF9 # identifies this as the Graphics Control extension
|
||||
gif_gce[2] = 4 # we are suppling only the 4 four bytes
|
||||
gif_gce[3] = 0x01 # this gif includes transparency
|
||||
gif_gce[4] = gif_gce[5] = 0 # animation frrame delay (unused)
|
||||
gif_gce[6] = 0 # transparent color is index 0
|
||||
gif_gce[7] = 0 # end of gif_gce
|
||||
gif_id = bytearray(10)
|
||||
gif_id[0] = 0x2c
|
||||
# byte 1,2 are image left. 3,4 are image top both are left as zerosuitsamus
|
||||
gif_id[5] = width
|
||||
gif_id[7] = height
|
||||
gif_id[9] = 0 # no local color table
|
||||
|
||||
gif_img_minimum_code_size = bytes([7]) # we choose 7 bits, so that each pixel is represented by a byte, for conviennce.
|
||||
|
||||
clear = 0x80
|
||||
stop = 0x81
|
||||
|
||||
unchunked_image_data = bytearray(height * (width + 1) + 1)
|
||||
# we technically need a Clear code once every 125 bytes, but we do it at the start of every row for simplicity
|
||||
for row in range(height):
|
||||
unchunked_image_data[row * (width + 1)] = clear
|
||||
unchunked_image_data[-1] = stop
|
||||
|
||||
def add_palette_color(index, color):
|
||||
gif_gct[3 * index] = color[0]
|
||||
gif_gct[3 * index + 1] = color[1]
|
||||
gif_gct[3 * index + 2] = color[2]
|
||||
|
||||
def set_pixel_color_index(x, y, color):
|
||||
unchunked_image_data[y * (width + 1) + x + 1] = color
|
||||
|
||||
callback(add_palette_color, set_pixel_color_index)
|
||||
|
||||
def chunk_image(img):
|
||||
for i in range(0, len(img), 255):
|
||||
chunk = img[i:i + 255]
|
||||
yield bytes([len(chunk)])
|
||||
yield chunk
|
||||
|
||||
gif_img = b''.join([gif_img_minimum_code_size] + list(chunk_image(unchunked_image_data)) + [b'\x00'])
|
||||
|
||||
gif = b''.join([gif_header, gif_lsd, gif_gct, gif_gce, gif_id, gif_img, b'\x3b'])
|
||||
|
||||
return gif
|
||||
|
||||
gif_data = make_gif(draw_sprite_into_gif)
|
||||
image = PhotoImage(data=gif_data)
|
||||
|
||||
return image.zoom(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
guiMain()
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
import queue
|
||||
import threading
|
||||
import tkinter as tk
|
||||
|
||||
from Utils import local_path
|
||||
|
||||
def set_icon(window):
|
||||
er16 = tk.PhotoImage(file=local_path('data/ER16.gif'))
|
||||
er32 = tk.PhotoImage(file=local_path('data/ER32.gif'))
|
||||
er48 = tk.PhotoImage(file=local_path('data/ER32.gif'))
|
||||
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access
|
||||
|
||||
# Although tkinter is intended to be thread safe, there are many reports of issues
|
||||
# some which may be platform specific, or depend on if the TCL library was compiled without
|
||||
# multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems
|
||||
class BackgroundTask(object):
|
||||
def __init__(self, window, code_to_run):
|
||||
self.window = window
|
||||
self.queue = queue.Queue()
|
||||
self.running = True
|
||||
self.process_queue()
|
||||
self.task = threading.Thread(target=code_to_run, args=(self,))
|
||||
self.task.start()
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
#safe to call from worker
|
||||
def queue_event(self, event):
|
||||
self.queue.put(event)
|
||||
|
||||
def process_queue(self):
|
||||
try:
|
||||
while True:
|
||||
if not self.running:
|
||||
return
|
||||
event = self.queue.get_nowait()
|
||||
event()
|
||||
if self.running:
|
||||
#if self is no longer running self.window may no longer be valid
|
||||
self.window.update_idletasks()
|
||||
except queue.Empty:
|
||||
pass
|
||||
if self.running:
|
||||
self.window.after(100, self.process_queue)
|
||||
|
||||
class BackgroundTaskProgress(BackgroundTask):
|
||||
def __init__(self, parent, code_to_run, title):
|
||||
self.parent = parent
|
||||
self.window = tk.Toplevel(parent)
|
||||
self.window['padx'] = 5
|
||||
self.window['pady'] = 5
|
||||
|
||||
try:
|
||||
self.window.attributes("-toolwindow", 1)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
self.window.wm_title(title)
|
||||
self.label_var = tk.StringVar()
|
||||
self.label_var.set("")
|
||||
self.label = tk.Label(self.window, textvariable=self.label_var, width=50)
|
||||
self.label.pack()
|
||||
self.window.resizable(width=False, height=False)
|
||||
|
||||
set_icon(self.window)
|
||||
self.window.focus()
|
||||
super().__init__(self.window, code_to_run)
|
||||
|
||||
#safe to call from worker thread
|
||||
def update_status(self, text):
|
||||
self.queue_event(lambda: self.label_var.set(text))
|
||||
|
||||
# only call this in an event callback
|
||||
def close_window(self):
|
||||
self.stop()
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
|
||||
class ToolTips(object):
|
||||
# This class derived from wckToolTips which is available under the following license:
|
||||
|
||||
# Copyright (c) 1998-2007 by Secret Labs AB
|
||||
# Copyright (c) 1998-2007 by Fredrik Lundh
|
||||
#
|
||||
# By obtaining, using, and/or copying this software and/or its
|
||||
# associated documentation, you agree that you have read, understood,
|
||||
# and will comply with the following terms and conditions:
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# associated documentation for any purpose and without fee is hereby
|
||||
# granted, provided that the above copyright notice appears in all
|
||||
# copies, and that both that copyright notice and this permission notice
|
||||
# appear in supporting documentation, and that the name of Secret Labs
|
||||
# AB or the author not be used in advertising or publicity pertaining to
|
||||
# distribution of the software without specific, written prior
|
||||
# permission.
|
||||
#
|
||||
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
# FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
|
||||
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
label = None
|
||||
window = None
|
||||
active = 0
|
||||
tag = None
|
||||
after_id = None
|
||||
|
||||
@classmethod
|
||||
def getcontroller(cls, widget):
|
||||
if cls.tag is None:
|
||||
|
||||
cls.tag = "ui_tooltip_%d" % id(cls)
|
||||
widget.bind_class(cls.tag, "<Enter>", cls.enter)
|
||||
widget.bind_class(cls.tag, "<Leave>", cls.leave)
|
||||
widget.bind_class(cls.tag, "<Motion>", cls.motion)
|
||||
widget.bind_class(cls.tag, "<Destroy>", cls.leave)
|
||||
|
||||
# pick suitable colors for tooltips
|
||||
try:
|
||||
cls.bg = "systeminfobackground"
|
||||
cls.fg = "systeminfotext"
|
||||
widget.winfo_rgb(cls.fg) # make sure system colors exist
|
||||
widget.winfo_rgb(cls.bg)
|
||||
except Exception:
|
||||
cls.bg = "#ffffe0"
|
||||
cls.fg = "black"
|
||||
|
||||
return cls.tag
|
||||
|
||||
@classmethod
|
||||
def register(cls, widget, text):
|
||||
widget.ui_tooltip_text = text
|
||||
tags = list(widget.bindtags())
|
||||
tags.append(cls.getcontroller(widget))
|
||||
widget.bindtags(tuple(tags))
|
||||
|
||||
@classmethod
|
||||
def unregister(cls, widget):
|
||||
tags = list(widget.bindtags())
|
||||
tags.remove(cls.getcontroller(widget))
|
||||
widget.bindtags(tuple(tags))
|
||||
|
||||
# event handlers
|
||||
@classmethod
|
||||
def enter(cls, event):
|
||||
widget = event.widget
|
||||
if not cls.label:
|
||||
# create and hide balloon help window
|
||||
cls.popup = tk.Toplevel(bg=cls.fg, bd=1)
|
||||
cls.popup.overrideredirect(1)
|
||||
cls.popup.withdraw()
|
||||
cls.label = tk.Label(
|
||||
cls.popup, fg=cls.fg, bg=cls.bg, bd=0, padx=2, justify=tk.LEFT
|
||||
)
|
||||
cls.label.pack()
|
||||
cls.active = 0
|
||||
cls.xy = event.x_root + 16, event.y_root + 10
|
||||
cls.event_xy = event.x, event.y
|
||||
cls.after_id = widget.after(200, cls.display, widget)
|
||||
|
||||
@classmethod
|
||||
def motion(cls, event):
|
||||
cls.xy = event.x_root + 16, event.y_root + 10
|
||||
cls.event_xy = event.x, event.y
|
||||
|
||||
@classmethod
|
||||
def display(cls, widget):
|
||||
if not cls.active:
|
||||
# display balloon help window
|
||||
text = widget.ui_tooltip_text
|
||||
if callable(text):
|
||||
text = text(widget, cls.event_xy)
|
||||
cls.label.config(text=text)
|
||||
cls.popup.deiconify()
|
||||
cls.popup.lift()
|
||||
cls.popup.geometry("+%d+%d" % cls.xy)
|
||||
cls.active = 1
|
||||
cls.after_id = None
|
||||
|
||||
@classmethod
|
||||
def leave(cls, event):
|
||||
widget = event.widget
|
||||
if cls.active:
|
||||
cls.popup.withdraw()
|
||||
cls.active = 0
|
||||
if cls.after_id:
|
||||
widget.after_cancel(cls.after_id)
|
||||
cls.after_id = None
|
49
ItemList.py
|
@ -1,8 +1,10 @@
|
|||
from Items import ItemFactory
|
||||
from Fill import fill_restrictive
|
||||
from collections import namedtuple
|
||||
import random
|
||||
|
||||
from Items import ItemFactory
|
||||
from Fill import fill_restrictive
|
||||
from Dungeons import get_dungeon_item_pool
|
||||
|
||||
#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.
|
||||
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
|
||||
|
@ -62,22 +64,20 @@ Difficulty = namedtuple('Difficulty',
|
|||
'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
|
||||
'extras'])
|
||||
|
||||
TotalItemsToPlace = 153
|
||||
total_items_to_place = 153
|
||||
|
||||
def easy_conditional_extras(timer, goal, mode, pool, placed_items):
|
||||
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
|
||||
def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
|
||||
extraitems = total_items_to_place - 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):
|
||||
def no_conditonal_extras(*_args):
|
||||
return []
|
||||
|
||||
#def Difficulty(**kwargs):
|
||||
# protodifficulty._replace(**kwargs)
|
||||
|
||||
# pylint: disable=
|
||||
difficulties = {
|
||||
'normal': Difficulty(
|
||||
baseitems = normalbaseitems,
|
||||
|
@ -181,6 +181,9 @@ def generate_itempool(world):
|
|||
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')
|
||||
|
||||
if world.timer in ['ohko', 'timed-ohko']:
|
||||
world.can_take_damage = False
|
||||
|
||||
world.push_item('Ganon', ItemFactory('Triforce'), False)
|
||||
world.get_location('Ganon').event = True
|
||||
world.push_item('Agahnim 1', ItemFactory('Beat Agahnim 1'), False)
|
||||
|
@ -201,6 +204,9 @@ def generate_itempool(world):
|
|||
if treasure_hunt_icon is not None:
|
||||
world.treasure_hunt_icon = treasure_hunt_icon
|
||||
|
||||
if world.keysanity:
|
||||
world.itempool.extend(get_dungeon_item_pool(world))
|
||||
|
||||
# shuffle medallions
|
||||
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||
|
@ -226,10 +232,10 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
|||
|
||||
pool.extend(alwaysitems)
|
||||
|
||||
def wantProgressives():
|
||||
def want_progressives():
|
||||
return random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
||||
|
||||
if wantProgressives():
|
||||
if want_progressives():
|
||||
pool.extend(progressivegloves)
|
||||
else:
|
||||
pool.extend(basicgloves)
|
||||
|
@ -253,17 +259,17 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
|||
# all bottles, since only one bottle is available
|
||||
if diff.same_bottle:
|
||||
thisbottle = random.choice(diff.bottles)
|
||||
for i in range (diff.bottle_count):
|
||||
for _ in range(diff.bottle_count):
|
||||
if not diff.same_bottle:
|
||||
thisbottle = random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
if wantProgressives():
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressiveshield)
|
||||
else:
|
||||
pool.extend(diff.basicshield)
|
||||
|
||||
if wantProgressives():
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressivearmor)
|
||||
else:
|
||||
pool.extend(diff.basicarmor)
|
||||
|
@ -271,21 +277,21 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
|||
if mode == 'swordless':
|
||||
pool.extend(diff.swordless)
|
||||
elif mode == 'standard':
|
||||
if wantProgressives():
|
||||
if want_progressives():
|
||||
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():
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressivesword)
|
||||
pool.extend(['Progressive Sword'])
|
||||
else:
|
||||
pool.extend(diff.basicsword)
|
||||
pool.extend(['Fighter Sword'])
|
||||
|
||||
extraitems = TotalItemsToPlace - len(pool) - len(placed_items)
|
||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
||||
|
||||
if timer in ['timed', 'timed-countdown']:
|
||||
pool.extend(diff.timedother)
|
||||
|
@ -306,7 +312,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
|||
extraitems -= len(cond_extras)
|
||||
|
||||
for extra in diff.extras:
|
||||
if(extraitems > 0):
|
||||
if extraitems > 0:
|
||||
pool.extend(extra)
|
||||
extraitems -= len(extra)
|
||||
|
||||
|
@ -315,7 +321,7 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
|||
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__':
|
||||
def test():
|
||||
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']:
|
||||
|
@ -325,9 +331,12 @@ if __name__ == '__main__':
|
|||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
|
||||
count = len(out[0]) + len(out[1])
|
||||
|
||||
correct_count = TotalItemsToPlace
|
||||
correct_count = total_items_to_place
|
||||
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))
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
|
253
Items.py
|
@ -1,7 +1,7 @@
|
|||
from BaseClasses import World, Item
|
||||
import random
|
||||
import logging
|
||||
|
||||
from BaseClasses import Item
|
||||
|
||||
|
||||
def ItemFactory(items):
|
||||
ret = []
|
||||
|
@ -14,58 +14,57 @@ def ItemFactory(items):
|
|||
advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit = item_table[item]
|
||||
ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit))
|
||||
else:
|
||||
logging.getLogger('').warning('Unknown Item: %s' % item)
|
||||
logging.getLogger('').warning('Unknown Item: %s', item)
|
||||
return None
|
||||
|
||||
if singleton:
|
||||
return ret[0]
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text)
|
||||
item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'boy shoots again'),
|
||||
'Book of Mudora': (True, False, None, 0x1D, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'boy can read again'),
|
||||
item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again'),
|
||||
'Book of Mudora': (True, False, None, 0x1D, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again'),
|
||||
'Hammer': (True, False, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time'),
|
||||
'Hookshot': (True, False, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'boy tickles again'),
|
||||
'Magic Mirror': (True, False, None, 0x1A, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'boy sees himself again'),
|
||||
'Hookshot': (True, False, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again'),
|
||||
'Magic Mirror': (True, False, None, 0x1A, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again'),
|
||||
'Ocarina': (True, False, None, 0x14, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again'),
|
||||
'Pegasus Boots': (True, False, None, 0x4B, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'boy runs again'),
|
||||
'Power Glove': (True, False, None, 0x1B, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'boy lifts again'),
|
||||
'Cape': (True, False, None, 0x19, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'boy hides again'),
|
||||
'Mushroom': (True, False, None, 0x29, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'boy sells drugs again'),
|
||||
'Shovel': (True, False, None, 0x13, 'Can\n You\n Dig it?', 'and the fetch quest', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'boy digs again'),
|
||||
'Lamp': (True, False, None, 0x12, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'boy sees at night again'),
|
||||
'Magic Powder': (True, False, None, 0x0D, 'you can turn\nanti-faeries\ninto fairies', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'boy plays marbles again'),
|
||||
'Moon Pearl': (True, False, None, 0x1F, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'boy plays ball again'),
|
||||
'Cane of Somaria': (True, False, None, 0x15, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'boy makes blocks again'),
|
||||
'Fire Rod': (True, False, None, 0x07, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'boy burns again'),
|
||||
'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'boy swims again'),
|
||||
'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'boy freezes again'),
|
||||
'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'boy has bling again'),
|
||||
'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-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'),
|
||||
'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 (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red 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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'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'),
|
||||
'Golden Sword': (True, False, None, 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'boy fights again'),
|
||||
'Progressive Sword': (True, False, None, 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'boy fights again'),
|
||||
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'boy lifts again'),
|
||||
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'boy eats pork chops'),
|
||||
'Pegasus Boots': (True, False, None, 0x4B, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again'),
|
||||
'Power Glove': (True, False, None, 0x1B, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again'),
|
||||
'Cape': (True, False, None, 0x19, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again'),
|
||||
'Mushroom': (True, False, None, 0x29, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again'),
|
||||
'Shovel': (True, False, None, 0x13, 'Can\n You\n Dig it?', 'and the fetch quest', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again'),
|
||||
'Lamp': (True, False, None, 0x12, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again'),
|
||||
'Magic Powder': (True, False, None, 0x0D, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again'),
|
||||
'Moon Pearl': (True, False, None, 0x1F, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again'),
|
||||
'Cane of Somaria': (True, False, None, 0x15, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again'),
|
||||
'Fire Rod': (True, False, None, 0x07, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again'),
|
||||
'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims agai'),
|
||||
'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again'),
|
||||
'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again'),
|
||||
'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor 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', 'medallion boy melts room 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', 'medallion boy shakes dirt again'),
|
||||
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again'),
|
||||
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again'),
|
||||
'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again'),
|
||||
'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo 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', 'bottle boy has friend 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', 'bottle boy has mad bee 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', 'bottle boy has beetor 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', 'sword 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', 'sword 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', 'sword boy fights again'),
|
||||
'Golden Sword': (True, False, None, 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'),
|
||||
'Progressive Sword': (True, False, None, 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again'),
|
||||
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again'),
|
||||
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again'),
|
||||
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None),
|
||||
'Red Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], None, None, None, None, None, None),
|
||||
'Blue Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], None, None, None, None, None, None),
|
||||
'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'boy wins again'),
|
||||
'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'boy sees stars again'),
|
||||
'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'boy triangulates again'),
|
||||
'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again'),
|
||||
'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again'),
|
||||
'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again'),
|
||||
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], None, None, None, None, None, None),
|
||||
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], None, None, None, None, None, None),
|
||||
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], None, None, None, None, None, None),
|
||||
|
@ -73,91 +72,91 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
|||
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], None, None, None, None, None, None),
|
||||
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], None, None, None, None, None, None),
|
||||
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], None, None, None, None, None, None),
|
||||
'Single Arrow': (False, False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrow', 'boy sews again'),
|
||||
'Arrows (10)': (False, False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'boy sews again'),
|
||||
'Arrow Upgrade (+10)': (False, False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'boy sews more again'),
|
||||
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'boy sews more again'),
|
||||
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', 'boy explodes again'),
|
||||
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', 'boy explodes again'),
|
||||
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'boy explodes more again'),
|
||||
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'boy explodes more again'),
|
||||
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'boy fears little again'),
|
||||
'Red Mail': (False, True, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'boy fears nothing again'),
|
||||
'Progressive Armor': (False, True, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'boy fears less again'),
|
||||
'Blue Boomerang': (False, True, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'boy plays fetch again'),
|
||||
'Red Boomerang': (False, True, None, 0x2A, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'boy plays fetch again'),
|
||||
'Blue Shield': (False, True, None, 0x04, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'boy defends again'),
|
||||
'Red Shield': (False, True, None, 0x05, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'boy defends again'),
|
||||
'Mirror Shield': (True, False, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'boy defends again'),
|
||||
'Progressive Shield': (True, False, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'boy defends again'),
|
||||
'Bug Catching Net': (True, False, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'boy catches bees again'),
|
||||
'Cane of Byrna': (True, False, None, 0x18, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'boy encircles again'),
|
||||
'Boss Heart Container': (False, False, None, 0x3E, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'boy feels love again'),
|
||||
'Sanctuary Heart Container': (False, False, None, 0x3F, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'boy feels love again'),
|
||||
'Piece of Heart': (False, False, None, 0x17, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'boy feels love again'),
|
||||
'Rupee (1)': (False, False, None, 0x34, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'boy has snack again'),
|
||||
'Rupees (5)': (False, False, None, 0x35, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'boy has snack again'),
|
||||
'Rupees (20)': (False, False, None, 0x36, 'Just couch\ncash. Move\nright along.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'boy has lunch again'),
|
||||
'Rupees (50)': (False, False, None, 0x41, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'boy has dinner again'),
|
||||
'Rupees (100)': (False, False, None, 0x40, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'boy has buffet again'),
|
||||
'Rupees (300)': (False, False, None, 0x46, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'boy is rich again'),
|
||||
'Rupoor': (False, False, None, 0x59, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'boy steals rupees again'),
|
||||
'Red Clock': (False, True, None, 0x5B, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'boy adjusts time again'),
|
||||
'Blue Clock': (False, True, None, 0x5C, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'boy adjusts time again'),
|
||||
'Green Clock': (False, True, None, 0x5D, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'boy adjusts time again'),
|
||||
'Single RNG': (False, True, None, 0x62, 'something you don\'t yet have', None, None, None, None, None),
|
||||
'Multi RNG': (False, True, None, 0x63, 'something you may already have', None, None, None, None, None),
|
||||
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'boy saves magic again'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'boy saves magic again'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Desert Palace)': (False, True, 'Map', 0x7C, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Tower of Hera)': (False, True, 'Map', 0x75, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Escape)': (False, True, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Escape)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Thieves Town)': (False, True, 'Map', 0x74, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Skull Woods)': (False, True, 'Map', 0x77, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Ice Palace)': (False, True, 'Map', 0x76, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Misery Mire)': (False, True, 'Map', 0x78, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Turtle Rock)': (False, True, 'Map', 0x73, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'boy opens door again'),
|
||||
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'boy opens chest again'),
|
||||
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'boy points north again'),
|
||||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'boy draws again'),
|
||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'boy is bored again'),
|
||||
'Single Arrow': (False, False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrow', 'archer boy sews again'),
|
||||
'Arrows (10)': (False, False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again'),
|
||||
'Arrow Upgrade (+10)': (False, False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again'),
|
||||
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again'),
|
||||
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again'),
|
||||
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again'),
|
||||
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
||||
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
||||
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again'),
|
||||
'Red Mail': (False, True, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again'),
|
||||
'Progressive Armor': (False, True, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again'),
|
||||
'Blue Boomerang': (False, True, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again'),
|
||||
'Red Boomerang': (False, True, None, 0x2A, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again'),
|
||||
'Blue Shield': (False, True, None, 0x04, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again'),
|
||||
'Red Shield': (False, True, None, 0x05, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again'),
|
||||
'Mirror Shield': (True, False, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again'),
|
||||
'Progressive Shield': (True, False, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again'),
|
||||
'Bug Catching Net': (True, False, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again'),
|
||||
'Cane of Byrna': (True, False, None, 0x18, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again'),
|
||||
'Boss Heart Container': (False, False, None, 0x3E, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again'),
|
||||
'Sanctuary Heart Container': (False, False, None, 0x3F, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again'),
|
||||
'Piece of Heart': (False, False, None, 0x17, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again"'),
|
||||
'Rupee (1)': (False, False, None, 0x34, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again'),
|
||||
'Rupees (5)': (False, False, None, 0x35, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again'),
|
||||
'Rupees (20)': (False, False, None, 0x36, 'Just couch\ncash. Move\nright along.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again'),
|
||||
'Rupees (50)': (False, False, None, 0x41, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again'),
|
||||
'Rupees (100)': (False, False, None, 0x40, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again'),
|
||||
'Rupees (300)': (False, False, None, 0x46, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again'),
|
||||
'Rupoor': (False, False, None, 0x59, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees'),
|
||||
'Red Clock': (False, True, None, 0x5B, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again'),
|
||||
'Blue Clock': (False, True, None, 0x5C, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again'),
|
||||
'Green Clock': (False, True, None, 0x5D, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again'),
|
||||
'Single RNG': (False, True, None, 0x62, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again'),
|
||||
'Multi RNG': (False, True, None, 0x63, 'something you may already have', None, None, None, None, 'unknown boy somethings again'),
|
||||
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Desert Palace)': (False, True, 'Map', 0x7C, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Tower of Hera)': (False, True, 'Map', 0x75, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Escape)': (False, True, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Escape)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Thieves Town)': (False, True, 'Map', 0x74, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Skull Woods)': (False, True, 'Map', 0x77, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Ice Palace)': (False, True, 'Map', 0x76, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Misery Mire)': (False, True, 'Map', 0x78, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Turtle Rock)': (False, True, 'Map', 0x73, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'),
|
||||
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None)}
|
||||
|
|
37
Main.py
|
@ -1,18 +1,18 @@
|
|||
from collections import OrderedDict
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from BaseClasses import World, CollectionState, Item
|
||||
from Regions import create_regions, mark_light_world_regions
|
||||
from EntranceShuffle import link_entrances
|
||||
from Rom import patch_rom, LocalRom, JsonRom
|
||||
from Rom import patch_rom, Sprite, LocalRom, JsonRom
|
||||
from Rules import set_rules
|
||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
from Items import ItemFactory
|
||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items
|
||||
from collections import OrderedDict
|
||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
|
||||
from ItemList import generate_itempool
|
||||
from Utils import output_path
|
||||
import random
|
||||
import time
|
||||
import logging
|
||||
import json
|
||||
|
||||
__version__ = '0.5.1-dev'
|
||||
|
||||
|
@ -39,7 +39,7 @@ def main(args, seed=None):
|
|||
world.seed = int(seed)
|
||||
random.seed(world.seed)
|
||||
|
||||
logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (__version__, world.seed))
|
||||
logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
|
||||
|
||||
create_regions(world)
|
||||
|
||||
|
@ -61,7 +61,7 @@ def main(args, seed=None):
|
|||
logger.info('Placing Dungeon Items.')
|
||||
|
||||
shuffled_locations = None
|
||||
if args.algorithm == 'vt26' or args.keysanity:
|
||||
if args.algorithm in ['balanced', 'vt26'] or args.keysanity:
|
||||
shuffled_locations = world.get_unfilled_locations()
|
||||
random.shuffle(shuffled_locations)
|
||||
fill_dungeons_restrictive(world, shuffled_locations)
|
||||
|
@ -92,7 +92,10 @@ def main(args, seed=None):
|
|||
logger.info('Patching ROM.')
|
||||
|
||||
if args.sprite is not None:
|
||||
sprite = bytearray(open(args.sprite, 'rb').read())
|
||||
if isinstance(args.sprite, Sprite):
|
||||
sprite = args.sprite
|
||||
else:
|
||||
sprite = Sprite(args.sprite)
|
||||
else:
|
||||
sprite = None
|
||||
|
||||
|
@ -113,7 +116,7 @@ def main(args, seed=None):
|
|||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
||||
logger.info('Done. Enjoy.')
|
||||
logger.debug('Total Time: %s' % (time.clock() - start))
|
||||
logger.debug('Total Time: %s', time.clock() - start)
|
||||
|
||||
return world
|
||||
|
||||
|
@ -130,6 +133,7 @@ def copy_world(world):
|
|||
ret.dark_world_light_cone = world.dark_world_light_cone
|
||||
ret.seed = world.seed
|
||||
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge
|
||||
ret.can_take_damage = world.can_take_damage
|
||||
create_regions(ret)
|
||||
create_dungeons(ret)
|
||||
|
||||
|
@ -174,7 +178,7 @@ def create_playthrough(world):
|
|||
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
||||
|
||||
# get locations containing progress items
|
||||
prog_locations = [location for location in world.get_locations() if location.item is not None and (location.item.advancement or (location.item.key and world.keysanity))]
|
||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
||||
|
||||
collection_spheres = []
|
||||
state = CollectionState(world)
|
||||
|
@ -196,10 +200,10 @@ def create_playthrough(world):
|
|||
|
||||
collection_spheres.append(sphere)
|
||||
|
||||
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.' % (len(collection_spheres), len(sphere), len(prog_locations)))
|
||||
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
|
||||
|
||||
if not sphere:
|
||||
logging.getLogger('').debug('The following items could not be reached: %s' % ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
|
||||
logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
|
||||
if not world.check_beatable_only:
|
||||
raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.')
|
||||
else:
|
||||
|
@ -210,11 +214,10 @@ def create_playthrough(world):
|
|||
to_delete = []
|
||||
for location in sphere:
|
||||
# we remove the item at location and check if game is still beatable
|
||||
logging.getLogger('').debug('Checking if %s is required to beat the game.' % location.item.name)
|
||||
logging.getLogger('').debug('Checking if %s is required to beat the game.', location.item.name)
|
||||
old_item = location.item
|
||||
location.item = None
|
||||
state.remove(old_item)
|
||||
world._item_cache = {} # need to invalidate
|
||||
if world.can_beat_game():
|
||||
to_delete.append(location)
|
||||
else:
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import sys
|
||||
|
||||
from BaseClasses import World
|
||||
from Regions import create_regions
|
||||
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
|
||||
from Rom import patch_rom, LocalRom, write_string_to_rom
|
||||
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
|
||||
from Rules import set_rules
|
||||
from Dungeons import create_dungeons
|
||||
from Items import ItemFactory
|
||||
from Main import create_playthrough
|
||||
import random
|
||||
import time
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
import hashlib
|
||||
import sys
|
||||
|
||||
__version__ = '0.2-dev'
|
||||
|
||||
|
@ -26,8 +28,8 @@ logic_hash = [182, 244, 144, 92, 149, 200, 93, 183, 124, 169, 226, 46, 111, 163,
|
|||
153, 217, 252, 158, 25, 205, 22, 133, 254, 138, 203, 118, 210, 204, 82, 97, 52, 164, 68, 139, 120, 109, 54, 3, 41, 179, 212, 42]
|
||||
|
||||
|
||||
def main(args, seed=None):
|
||||
start = time.clock()
|
||||
def main(args):
|
||||
start_time = time.clock()
|
||||
|
||||
# initialize the world
|
||||
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False)
|
||||
|
@ -41,7 +43,7 @@ def main(args, seed=None):
|
|||
|
||||
random.seed(world.seed)
|
||||
|
||||
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n' % (__version__, args.plando))
|
||||
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando)
|
||||
|
||||
create_regions(world)
|
||||
create_dungeons(world)
|
||||
|
@ -74,7 +76,7 @@ def main(args, seed=None):
|
|||
logger.info('Patching ROM.')
|
||||
|
||||
if args.sprite is not None:
|
||||
sprite = bytearray(open(args.sprite, 'rb').read())
|
||||
sprite = Sprite(args.sprite)
|
||||
else:
|
||||
sprite = None
|
||||
|
||||
|
@ -84,8 +86,8 @@ def main(args, seed=None):
|
|||
for textname, texttype, text in text_patches:
|
||||
if texttype == 'text':
|
||||
write_string_to_rom(rom, textname, text)
|
||||
elif texttype == 'credit':
|
||||
write_credits_string_to_rom(rom, textname, text)
|
||||
#elif texttype == 'credit':
|
||||
# write_credits_string_to_rom(rom, textname, text)
|
||||
|
||||
outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed)
|
||||
|
||||
|
@ -94,7 +96,7 @@ def main(args, seed=None):
|
|||
world.spoiler.to_file('%s_Spoiler.txt' % outfilebase)
|
||||
|
||||
logger.info('Done. Enjoy.')
|
||||
logger.debug('Total Time: %s' % (time.clock() - start))
|
||||
logger.debug('Total Time: %s', time.clock() - start_time)
|
||||
|
||||
return world
|
||||
|
||||
|
@ -171,7 +173,7 @@ def fill_world(world, plando, text_patches):
|
|||
locationstr, itemstr = line.split(':', 1)
|
||||
location = world.get_location(locationstr.strip())
|
||||
if location is None:
|
||||
logger.warn('Unknown location: %s' % locationstr)
|
||||
logger.warning('Unknown location: %s', locationstr)
|
||||
continue
|
||||
else:
|
||||
item = ItemFactory(itemstr.strip())
|
||||
|
@ -198,7 +200,7 @@ def fill_world(world, plando, text_patches):
|
|||
world.get_location('Agahnim 2').item = ItemFactory('Beat Agahnim 2')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def start():
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
|
||||
parser.add_argument('--ignore_unsolvable', help='Do not abort if seed is deemed unsolvable.', action='store_true')
|
||||
|
@ -230,3 +232,6 @@ if __name__ == '__main__':
|
|||
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||
|
||||
main(args=args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
||||
|
|
209
Rom.py
|
@ -1,12 +1,17 @@
|
|||
from Dungeons import dungeon_music_addresses
|
||||
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 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 io
|
||||
import json
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
import random
|
||||
|
||||
from Dungeons import dungeon_music_addresses
|
||||
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 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, Sahasrahla_names
|
||||
from Utils import local_path
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '1deebb05eccefd2ab68297c6e9c0d25f'
|
||||
|
@ -30,13 +35,15 @@ class JsonRom(object):
|
|||
self.write_bytes(address, int32_as_bytes(value))
|
||||
|
||||
def write_to_file(self, file):
|
||||
json.dump([self.patches], open(file, 'w'))
|
||||
with open(file, 'w') as stream:
|
||||
json.dump([self.patches], stream)
|
||||
|
||||
|
||||
class LocalRom(object):
|
||||
|
||||
def __init__(self, file, patch=True):
|
||||
self.buffer = bytearray(open(file, 'rb').read())
|
||||
with open(file, 'rb') as stream:
|
||||
self.buffer = bytearray(stream.read())
|
||||
if patch:
|
||||
self.patch_base_rom()
|
||||
|
||||
|
@ -61,14 +68,15 @@ class LocalRom(object):
|
|||
# verify correct checksum of baserom
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(self.buffer)
|
||||
if not JAP10HASH == basemd5.hexdigest():
|
||||
if JAP10HASH != basemd5.hexdigest():
|
||||
logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
|
||||
|
||||
# extend to 2MB
|
||||
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
|
||||
|
||||
# load randomizer patches
|
||||
patches = json.load(open(local_path('data/base2current.json'), 'r'))
|
||||
with open(local_path('data/base2current.json'), 'r') as stream:
|
||||
patches = json.load(stream)
|
||||
for patch in patches:
|
||||
if isinstance(patch, dict):
|
||||
for baseaddress, values in patch.items():
|
||||
|
@ -77,7 +85,7 @@ class LocalRom(object):
|
|||
# verify md5
|
||||
patchedmd5 = hashlib.md5()
|
||||
patchedmd5.update(self.buffer)
|
||||
if not RANDOMIZERBASEHASH == patchedmd5.hexdigest():
|
||||
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
|
||||
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
|
||||
|
||||
def write_crc(self):
|
||||
|
@ -85,6 +93,161 @@ class LocalRom(object):
|
|||
inv = crc ^ 0xFFFF
|
||||
self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF])
|
||||
|
||||
class Sprite(object):
|
||||
default_palette = [255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
|
||||
89, 71, 54, 104, 59, 74, 10, 239, 18, 92, 42, 113, 21, 24, 122,
|
||||
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
|
||||
89, 128, 105, 145, 118, 184, 38, 127, 67, 92, 42, 153, 17, 24, 122,
|
||||
255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157,
|
||||
89, 87, 16, 126, 69, 243, 109, 185, 126, 92, 42, 39, 34, 24, 122,
|
||||
255, 127, 126, 35, 218, 17, 158, 54, 165, 20, 255, 1, 120, 16, 151,
|
||||
61, 71, 54, 104, 59, 74, 10, 239, 18, 126, 86, 114, 24, 24, 122]
|
||||
|
||||
default_glove_palette = [246, 82, 118, 3]
|
||||
|
||||
def __init__(self, filename):
|
||||
with open(filename, 'rb') as file:
|
||||
filedata = bytearray(file.read())
|
||||
self.name = os.path.basename(filename)
|
||||
self.author_name = None
|
||||
self.valid = True
|
||||
if len(filedata) == 0x7000:
|
||||
# sprite file with graphics and without palette data
|
||||
self.sprite = filedata[:0x7000]
|
||||
self.palette = list(self.default_palette)
|
||||
self.glove_palette = list(self.default_glove_palette)
|
||||
elif len(filedata) == 0x7078:
|
||||
# sprite file with graphics and palette data
|
||||
self.sprite = filedata[:0x7000]
|
||||
self.palette = filedata[0x7000:]
|
||||
self.glove_palette = filedata[0x7036:0x7038] + filedata[0x7054:0x7056]
|
||||
elif len(filedata) == 0x707C:
|
||||
# sprite file with graphics and palette data including gloves
|
||||
self.sprite = filedata[:0x7000]
|
||||
self.palette = filedata[0x7000:0x7078]
|
||||
self.glove_palette = filedata[0x7078:]
|
||||
elif len(filedata) in [0x100000, 0x200000]:
|
||||
# full rom with patched sprite, extract it
|
||||
self.sprite = filedata[0x80000:0x87000]
|
||||
self.palette = filedata[0xDD308:0xDD380]
|
||||
self.glove_palette = filedata[0xDEDF5:0xDEDF9]
|
||||
elif filedata.startswith(b'ZSPR'):
|
||||
result = self.parse_zspr(filedata, 1)
|
||||
if result is None:
|
||||
self.valid = False
|
||||
return
|
||||
(sprite, palette, self.name, self.author_name) = result
|
||||
if len(sprite) != 0x7000:
|
||||
self.valid = False
|
||||
return
|
||||
self.sprite = sprite
|
||||
if len(palette) == 0:
|
||||
self.palette = list(self.default_palette)
|
||||
self.glove_palette = list(self.default_glove_palette)
|
||||
elif len(palette) == 0x78:
|
||||
self.palette = palette
|
||||
self.glove_palette = list(self.default_glove_palette)
|
||||
elif len(palette) == 0x7C:
|
||||
self.palette = palette[:0x78]
|
||||
self.glove_palette = palette[0x78:]
|
||||
else:
|
||||
self.valid = False
|
||||
else:
|
||||
self.valid = False
|
||||
|
||||
def decode8(self, pos):
|
||||
arr = [[0 for _ in range(8)] for _ in range(8)]
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
position = 1<<(7-x)
|
||||
val = 0
|
||||
if self.sprite[pos+2*y] & position:
|
||||
val += 1
|
||||
if self.sprite[pos+2*y+1] & position:
|
||||
val += 2
|
||||
if self.sprite[pos+2*y+16] & position:
|
||||
val += 4
|
||||
if self.sprite[pos+2*y+17] & position:
|
||||
val += 8
|
||||
arr[y][x] = val
|
||||
return arr
|
||||
|
||||
def decode16(self, pos):
|
||||
arr = [[0 for _ in range(16)] for _ in range(16)]
|
||||
top_left = self.decode8(pos)
|
||||
top_right = self.decode8(pos+0x20)
|
||||
bottom_left = self.decode8(pos+0x200)
|
||||
bottom_right = self.decode8(pos+0x220)
|
||||
for x in range(8):
|
||||
for y in range(8):
|
||||
arr[y][x] = top_left[y][x]
|
||||
arr[y][x+8] = top_right[y][x]
|
||||
arr[y+8][x] = bottom_left[y][x]
|
||||
arr[y+8][x+8] = bottom_right[y][x]
|
||||
return arr
|
||||
|
||||
def parse_zspr(self, filedata, expected_kind):
|
||||
logger = logging.getLogger('')
|
||||
headerstr = "<4xBHHIHIHH6x"
|
||||
headersize = struct.calcsize(headerstr)
|
||||
if len(filedata) < headersize:
|
||||
return None
|
||||
(version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr, filedata)
|
||||
if version not in [1]:
|
||||
logger.error('Error parsing ZSPR file: Version %g not supported', version)
|
||||
return None
|
||||
if kind != expected_kind:
|
||||
return None
|
||||
|
||||
stream = io.BytesIO(filedata)
|
||||
stream.seek(headersize)
|
||||
|
||||
def read_utf16le(stream):
|
||||
"Decodes a null-terminated UTF-16_LE string of unknown size from a stream"
|
||||
raw = bytearray()
|
||||
while True:
|
||||
char = stream.read(2)
|
||||
if char in [b'', b'\x00\x00']:
|
||||
break
|
||||
raw += char
|
||||
return raw.decode('utf-16_le')
|
||||
|
||||
sprite_name = read_utf16le(stream)
|
||||
author_name = read_utf16le(stream)
|
||||
|
||||
# Ignoring the Author Rom name for the time being.
|
||||
|
||||
real_csum = sum(filedata) % 0x10000
|
||||
if real_csum != csum or real_csum ^ 0xFFFF != icsum:
|
||||
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
|
||||
|
||||
sprite = filedata[sprite_offset:sprite_offset + sprite_size]
|
||||
palette = filedata[palette_offset:palette_offset + palette_size]
|
||||
|
||||
if len(sprite) != sprite_size or len(palette) != palette_size:
|
||||
logger.error('Error parsing ZSPR file: Unexpected end of file')
|
||||
return None
|
||||
|
||||
return (sprite, palette, sprite_name, author_name)
|
||||
|
||||
def decode_palette(self):
|
||||
"Returns the palettes as an array of arrays of 15 colors"
|
||||
def array_chunk(arr, size):
|
||||
return list(zip(*[iter(arr)] * size))
|
||||
def make_int16(pair):
|
||||
return pair[1]<<8 | pair[0]
|
||||
def expand_color(i):
|
||||
return ((i & 0x1F) * 8, (i>>5 & 0x1F) * 8, (i>>10 & 0x1F) * 8)
|
||||
raw_palette = self.palette
|
||||
if raw_palette is None:
|
||||
raw_palette = Sprite.default_palette
|
||||
# turn palette data into a list of RGB tuples with 8 bit values
|
||||
palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(raw_palette, 2)]
|
||||
|
||||
# split into palettes of 15 colors
|
||||
return array_chunk(palette_as_colors, 15)
|
||||
|
||||
|
||||
def int16_as_bytes(value):
|
||||
value = value & 0xFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||
|
@ -309,8 +472,6 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
|
|||
else:
|
||||
rom.write_bytes(0x180090, [0x04, 0x47, 0x03, 0x47, 0x02, 0x47, 0x04, 0x47])
|
||||
|
||||
# TODO: FIXME: need to set capcity upgrade fills (2,3,0,0) for easy, (0,0,0,0) otherwise
|
||||
|
||||
# set up game internal RNG seed
|
||||
for i in range(1024):
|
||||
rom.write_byte(0x178000 + i, random.randint(0, 255))
|
||||
|
@ -683,20 +844,11 @@ def apply_rom_settings(rom, beep, quickswap, fastmenu, disable_music, sprite):
|
|||
|
||||
|
||||
def write_sprite(rom, sprite):
|
||||
if len(sprite) == 0x7000:
|
||||
# sprite file with graphics and without palette data
|
||||
rom.write_bytes(0x80000, sprite[:0x7000])
|
||||
elif len(sprite) == 0x7078:
|
||||
# sprite file with graphics and palette data
|
||||
rom.write_bytes(0x80000, sprite[:0x7000])
|
||||
rom.write_bytes(0xDD308, sprite[0x7000:])
|
||||
rom.write_bytes(0xDEDF5, sprite[0x7036:0x7038])
|
||||
rom.write_bytes(0xDEDF7, sprite[0x7054:0x7056])
|
||||
elif len(sprite) in [0x100000, 0x200000]:
|
||||
# full rom with patched sprite, extract it
|
||||
rom.write_bytes(0x80000, sprite[0x80000:0x87000])
|
||||
rom.write_bytes(0xDD308, sprite[0xDD308:0xDD380])
|
||||
rom.write_bytes(0xDEDF5, sprite[0xDEDF5:0xDEDF9])
|
||||
if not sprite.valid:
|
||||
return
|
||||
rom.write_bytes(0x80000, sprite.sprite)
|
||||
rom.write_bytes(0xDD308, sprite.palette)
|
||||
rom.write_bytes(0xDEDF5, sprite.glove_palette)
|
||||
|
||||
|
||||
def write_string_to_rom(rom, target, string):
|
||||
|
@ -759,7 +911,8 @@ def write_strings(rom, world):
|
|||
|
||||
credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts))
|
||||
credits.update_credits_line('sancturary', 0, random.choice(Sanctuary_texts))
|
||||
credits.update_credits_line('kakariko', 0, random.choice(Kakariko_texts))
|
||||
|
||||
credits.update_credits_line('kakariko', 0, random.choice(Kakariko_texts).format(random.choice(Sahasrahla_names)))
|
||||
credits.update_credits_line('desert', 0, random.choice(DesertPalace_texts))
|
||||
credits.update_credits_line('hera', 0, random.choice(MountainTower_texts))
|
||||
credits.update_credits_line('house', 0, random.choice(LinksHouse_texts))
|
||||
|
|
97
Rules.py
|
@ -39,6 +39,9 @@ def set_rules(world):
|
|||
def set_rule(spot, rule):
|
||||
spot.access_rule = rule
|
||||
|
||||
def set_always_allow(spot, rule):
|
||||
spot.always_allow = rule
|
||||
|
||||
|
||||
def add_rule(spot, rule, combine='and'):
|
||||
old_rule = spot.access_rule
|
||||
|
@ -60,10 +63,15 @@ def forbid_item(location, item):
|
|||
def item_in_locations(state, item, locations):
|
||||
for location in locations:
|
||||
loc = state.world.get_location(location)
|
||||
if loc.item is not None and loc.item.name == item:
|
||||
if item_name(loc) == item:
|
||||
return True
|
||||
return False
|
||||
|
||||
def item_name(location):
|
||||
if location.item is None:
|
||||
return None
|
||||
return location.item.name
|
||||
|
||||
|
||||
def global_rules(world):
|
||||
# ganon can only carry triforce
|
||||
|
@ -111,6 +119,7 @@ def global_rules(world):
|
|||
set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
|
||||
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
|
||||
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
||||
# FIXME: VT has a can_kill_most_things(8) call on Aga Tower's entrance. I think this is supposed to reflect that a better weapon than 10 bombs is needed to reach the two chests in this tower
|
||||
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has('Small Key (Agahnims Tower)', 2))
|
||||
set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has('Small Key (Agahnims Tower)'))
|
||||
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
|
||||
|
@ -162,6 +171,7 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Skull Woods Final Section'), lambda state: state.has('Fire Rod') and state.has_Pearl()) # bunny cannot use fire rod
|
||||
set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_sword() and state.has_misery_mire_medallion()) # sword required to cast magic (!)
|
||||
set_rule(world.get_entrance('Desert Ledge (West) Mirror Spot'), lambda state: state.has_Mirror())
|
||||
|
||||
set_rule(world.get_entrance('Desert Ledge Mirror Spot'), lambda state: state.has_Mirror())
|
||||
set_rule(world.get_entrance('Desert Palace Stairs Mirror Spot'), lambda state: state.has_Mirror())
|
||||
set_rule(world.get_entrance('Desert Palace Entrance (North) Mirror Spot'), lambda state: state.has_Mirror())
|
||||
|
@ -175,7 +185,9 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Fairy Ascension Mirror Spot'), lambda state: state.has_Mirror() and state.has_Pearl()) # need to lift flowers
|
||||
set_rule(world.get_entrance('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror())
|
||||
set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling
|
||||
set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and (state.has_bottle() or state.has('Half Magic') or state.has('Quarter Magic')))
|
||||
set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and state.can_extend_magic())
|
||||
# TODO: Current-VT logic is: hammer and lift_rocks and ((cape and extend) or (byrna and (can-take-damage OR canextend)))
|
||||
# Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probnably but seems to requires tas-like timing.)
|
||||
set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot'))
|
||||
set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot'))
|
||||
set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots'))
|
||||
|
@ -206,10 +218,11 @@ def global_rules(world):
|
|||
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']:
|
||||
forbid_item(world.get_location(location), 'Small Key (Desert Palace)')
|
||||
|
||||
set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)'))
|
||||
set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)') or item_name(world.get_location('Tower of Hera - Big Key Chest')) == 'Small Key (Tower of Hera)')
|
||||
set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)'))
|
||||
set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)'))
|
||||
set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source())
|
||||
set_always_allow(world.get_location('Tower of Hera - Big Key Chest'), lambda state, item: item.name == 'Small Key (Tower of Hera)')
|
||||
set_rule(world.get_location('Tower of Hera - Moldorm'), lambda state: state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Tower of Hera - Prize'), lambda state: state.has_blunt_weapon())
|
||||
for location in ['Tower of Hera - Moldorm', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']:
|
||||
|
@ -220,44 +233,45 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam'))
|
||||
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)'))
|
||||
set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer'))
|
||||
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)'))
|
||||
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(world.get_location('Swamp Palace - Big Chest')) == 'Big Key (Swamp Palace)')
|
||||
set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)')
|
||||
set_rule(world.get_entrance('Swamp Palace (North)'), lambda state: state.has('Hookshot'))
|
||||
set_rule(world.get_location('Swamp Palace - Arrghus'), lambda state: state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Swamp Palace - Prize'), lambda state: state.has_blunt_weapon())
|
||||
for location in ['Swamp Palace - Big Chest', 'Swamp Palace - Entrance']:
|
||||
for location in ['Swamp Palace - Entrance']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
|
||||
|
||||
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
|
||||
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna')))
|
||||
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: state.has('Small Key (Thieves Town)') and state.has('Hammer'))
|
||||
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(world.get_location('Thieves\' Town - Big Chest')) == 'Small Key (Thieves Town)') and state.has('Hammer'))
|
||||
set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)')
|
||||
set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)'))
|
||||
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
|
||||
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves Town - Blind']:
|
||||
for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']:
|
||||
forbid_item(world.get_location(location), 'Small Key (Thieves Town)')
|
||||
|
||||
set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has('Small Key (Skull Woods)'))
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has('Small Key (Skull Woods)'))
|
||||
set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has('Small Key (Skull Woods)', 2))
|
||||
set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)'))
|
||||
set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(world.get_location('Skull Woods - Big Chest')) == 'Big Key (Skull Woods)')
|
||||
set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)')
|
||||
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
|
||||
for location in ['Skull Woods - Big Chest']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Skull Woods)')
|
||||
for location in ['Skull Woods - Mothula']:
|
||||
forbid_item(world.get_location(location), 'Small Key (Skull Woods)')
|
||||
|
||||
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword()))
|
||||
set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1))))
|
||||
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)')) or state.has('Small Key (Ice Palace)', 2)) and (state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
|
||||
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)')) or state.has('Small Key (Ice Palace)', 2)) and (world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
|
||||
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
||||
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
||||
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
||||
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: state.has('Cane of Byrna') or state.has('Cape'))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: world.can_take_damage or state.has('Cane of Byrna') or state.has('Cape'))
|
||||
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
||||
# big key gives backdoor access to that from the teleporter in the north west
|
||||
|
@ -265,8 +279,8 @@ def global_rules(world):
|
|||
# in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key...
|
||||
set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has('Small Key (Misery Mire)', 2) or state.has('Big Key (Misery Mire)'))
|
||||
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
|
||||
set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((state.world.get_location('Misery Mire - Compass Chest').item is not None and state.world.get_location('Misery Mire - Compass Chest').item.name in ['Big Key (Misery Mire)']) or
|
||||
(state.world.get_location('Misery Mire - Big Key Chest').item is not None and state.world.get_location('Misery Mire - Big Key Chest').item.name in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3))
|
||||
set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((item_name(state.world.get_location('Misery Mire - Compass Chest')) in ['Big Key (Misery Mire)']) or
|
||||
(item_name(state.world.get_location('Misery Mire - Big Key Chest')) in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3))
|
||||
set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon()))
|
||||
|
@ -284,29 +298,31 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('Small Key (Turtle Rock)', 3))
|
||||
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria'))
|
||||
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria'))
|
||||
# FIXME: should shield overflow count check to the progrssive logic stuff, so we don't get false mirror shields which would cause problems here
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and
|
||||
(state.has('Hammer') or state.has_beam_sword() or state.has_bottle() or state.has('Half Magic') or state.has('Quarter Magic')))
|
||||
(state.has('Hammer') or state.has_beam_sword() or state.can_extend_magic()))
|
||||
# TODO: Per VT, possibly allow a regular sword with 4x extended magic (ie. quater magic, or half magic+bottle or 3 bottles)
|
||||
set_trock_key_rules(world)
|
||||
|
||||
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer'))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)'])))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
|
||||
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4))
|
||||
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
|
||||
|
||||
if world.keysanity:
|
||||
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)'])))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)'])))
|
||||
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)']))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
|
||||
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6))
|
||||
else:
|
||||
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Harmless Hellway').item is not None and (state.world.get_location('Palace of Darkness - Harmless Hellway').item.name in ['Small Key (Palace of Darkness)'])))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (state.world.get_location('Palace of Darkness - Big Key Chest').item is not None and (state.world.get_location('Palace of Darkness - Big Key Chest').item.name in ['Small Key (Palace of Darkness)'])))
|
||||
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)']))
|
||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)']))
|
||||
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5))
|
||||
|
||||
for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']:
|
||||
|
@ -322,13 +338,12 @@ def global_rules(world):
|
|||
set_rule(world.get_location('Ganons Tower - Bob\'s Torch'), lambda state: state.has_Boots())
|
||||
set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria'))
|
||||
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer'))
|
||||
if world.keysanity:
|
||||
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Big Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3)) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)'))
|
||||
else:
|
||||
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (state.world.get_location('Ganons Tower - Map Chest').item is not None and state.world.get_location('Ganons Tower - Map Chest').item.name == 'Small Key (Ganons Tower)'))
|
||||
|
||||
# It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere We reflect this in the chest requirements.
|
||||
# However we need to leave these at the lower values derive that with 3 keys it is always possible to reach Bob and Ice Armos.
|
||||
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_name(state.world.get_location('Ganons Tower - Map Chest')) in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has('Small Key (Ganons Tower)', 3)))
|
||||
set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3))
|
||||
|
||||
# It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements.
|
||||
# However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos.
|
||||
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Small Key (Ganons Tower)', 2))
|
||||
# It is possible to need more than 3 keys ....
|
||||
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3))
|
||||
|
@ -352,7 +367,7 @@ def global_rules(world):
|
|||
set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source())
|
||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has('Small Key (Ganons Tower)', 3))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has('Small Key (Ganons Tower)', 4))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot'))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and state.has_blunt_weapon())
|
||||
set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net'))
|
||||
set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2'))
|
||||
for location in ['Ganons Tower - Big Chest', 'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
|
@ -361,7 +376,7 @@ def global_rules(world):
|
|||
|
||||
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2')
|
||||
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')
|
||||
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.has('Bow')) or state.has('Lamp') or state.has_bottle() or state.has('Half Magic') or state.has('Quarter Magic'))) # need to light torch a sufficient amount of times
|
||||
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.has('Bow')) or state.has('Lamp') or state.can_extend_magic())) # need to light torch a sufficient amount of times
|
||||
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
|
||||
|
||||
|
||||
|
@ -426,9 +441,6 @@ def open_rules(world):
|
|||
forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)')
|
||||
forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)')
|
||||
|
||||
# to prevent key-lock in keysanity we need to prevent these chests from having an item that
|
||||
# blocks the small key
|
||||
if (world.keysanity):
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has('Small Key (Escape)'))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has('Small Key (Escape)'))
|
||||
|
||||
|
@ -455,10 +467,13 @@ def swordless_rules(world):
|
|||
|
||||
def standard_rules(world):
|
||||
# easiest way to enforce key placement not relevant for open
|
||||
forbid_item(world.get_location('Sewers - Secret Room - Left'), 'Small Key (Escape)')
|
||||
forbid_item(world.get_location('Sewers - Secret Room - Middle'), 'Small Key (Escape)')
|
||||
forbid_item(world.get_location('Sewers - Secret Room - Right'), 'Small Key (Escape)')
|
||||
forbid_item(world.get_location('Sanctuary'), 'Small Key (Escape)')
|
||||
set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things())
|
||||
add_rule(world.get_entrance('Sewers Door'), lambda state: state.can_kill_most_things())
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.can_kill_most_things())
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.can_kill_most_things())
|
||||
|
||||
|
||||
|
||||
|
||||
def set_trock_key_rules(world):
|
||||
|
@ -483,7 +498,10 @@ def set_trock_key_rules(world):
|
|||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has('Small Key (Turtle Rock)', 4))
|
||||
|
||||
# this is just the pokey room with one more key
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2)) if not can_reach_back else set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
|
||||
if not can_reach_back:
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2))
|
||||
else:
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
|
||||
|
||||
# the most complicated one
|
||||
# if we have back entrance access, we could waste all keys before touching this
|
||||
|
@ -492,11 +510,12 @@ def set_trock_key_rules(world):
|
|||
# however in keysanity being able to reach all other chests while only having three keys does not imply this contains
|
||||
# a key, so we again need all four keys unless it contains the big key
|
||||
if can_reach_back:
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)'])))
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 4) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)']))
|
||||
elif world.keysanity:
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 4) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)'])))
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Big Key (Turtle Rock)']) else state.has('Small Key (Turtle Rock)', 4) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)']))
|
||||
else:
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Big Key (Turtle Rock)'])) else state.has('Small Key (Turtle Rock)', 3) or (state.world.get_location('Turtle Rock - Big Key Chest').item is not None and (state.world.get_location('Turtle Rock - Big Key Chest').item.name in ['Small Key (Turtle Rock)'])))
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', 2) if (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Big Key (Turtle Rock)']) else state.has('Small Key (Turtle Rock)', 3) or (item_name(state.world.get_location('Turtle Rock - Big Key Chest')) in ['Small Key (Turtle Rock)']))
|
||||
# TODO add key-for-key logic to the above mess via always_allow rules. Ugh!
|
||||
|
||||
# set big key restrictions
|
||||
non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx']
|
||||
|
|
266
Text.py
|
@ -16,31 +16,250 @@ text_addresses = {'Pedestal': (0x180300, 256),
|
|||
'Ganon2Invincible': (0x181200, 256)}
|
||||
|
||||
|
||||
Uncle_texts = ['Good Luck!\nYou will need it.', 'Forward this message to 10 other people or this seed will be awful.', 'I hope you like your seeds bootless and fluteless.',
|
||||
'10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!', 'I have crippling depression.', 'I\'m off to visit cousin Fritzl.']
|
||||
Triforce_texts = ['Product has Hole in center. Bad seller, 0 out of 5.', 'Who stole the fourth triangle?', 'Trifource?\nMore Like Tritrice, am I right?'
|
||||
'\n Well Done!', 'You just wasted 2 hours of your life.', 'This was meant to be a trapezoid']
|
||||
Uncle_texts = [
|
||||
'Good Luck!\nYou will need it.',
|
||||
'Forward this message to 10 other people or this seed will be awful.',
|
||||
'I hope you like your seeds bootless and fluteless.',
|
||||
'10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!',
|
||||
'I\'m off to visit cousin Fritzl.'
|
||||
] * 2 + [
|
||||
"We're out of\nWeetabix. To\nthe store!",
|
||||
"This seed is\nbootless\nuntil boots.",
|
||||
"Why do we only\nhave one bed?",
|
||||
"This is the\nonly textbox.",
|
||||
"I'm going to\ngo watch the\nMoth tutorial.",
|
||||
"This seed is\nthe worst.",
|
||||
"Chasing tail.\nFly ladies.\nDo not follow.",
|
||||
"I feel like\nI've done this\nbefore...",
|
||||
"Magic cape can\npass through\nthe barrier!",
|
||||
"If this is a\nKanzeon seed,\nI'm quitting.",
|
||||
"I am not your\nreal uncle.",
|
||||
"You're going\nto have a very\nbad time.",
|
||||
"Today you\nwill have\nbad luck.",
|
||||
"I am leaving\nforever.\nGoodbye.",
|
||||
"Don't worry.\nI got this\ncovered.",
|
||||
"Race you to\nthe castle!",
|
||||
"\n hi",
|
||||
"I'M JUST GOING\nOUT FOR A\nPACK OF SMOKES",
|
||||
"It's dangerous\nto go alone.\nSee ya!",
|
||||
"ARE YOU A BAD\nENOUGH DUDE TO\nRESCUE ZELDA?",
|
||||
"\n\n I AM ERROR",
|
||||
"This seed is\nsub 2 hours,\nguaranteed.",
|
||||
"The chest is\na secret to\neverybody.",
|
||||
"I'm off to\nfind the\nwind fish.",
|
||||
"The shortcut\nto Ganon\nis this way!",
|
||||
"THE MOON IS\nCRASHING! RUN\nFOR YOUR LIFE!",
|
||||
"Time to fight\nhe who must\nnot be named.",
|
||||
"RED MAIL\nIS FOR\nCOWARDS.",
|
||||
"HEY!\n\nLISTEN!",
|
||||
"Well\nexcuuuuuse me,\nprincess!",
|
||||
"5,000 Rupee\nreward for >\nYou're boned",
|
||||
"Welcome to\nStoops Lonk's\nHoose",
|
||||
"Erreur de\ntraduction.\nsvp reessayer",
|
||||
"I could beat\nit in an hour\nand one life",
|
||||
"I thought this\nwas open mode?",
|
||||
]
|
||||
Triforce_texts = [
|
||||
'Product has Hole in center. Bad seller, 0 out of 5.',
|
||||
'Who stole the fourth triangle?',
|
||||
'Trifource?\nMore Like Tritrice, am I right?'
|
||||
'\n Well Done!',
|
||||
'You just wasted 2 hours of your life.',
|
||||
'This was meant to be a trapezoid'
|
||||
] * 2 + [
|
||||
"\n G G",
|
||||
"All your base\nare belong\nto us.",
|
||||
"You have ended\nthe domination\nof dr. wily",
|
||||
" thanks for\n playing!!!",
|
||||
"\n You Win!",
|
||||
" Thank you!\n your quest\n is over.",
|
||||
" A winner\n is\n you!",
|
||||
"\n WINNER!!",
|
||||
"\n I'm sorry\n\nbut your\nprincess is in\nanother castle",
|
||||
"\n success!",
|
||||
" Whelp…\n that just\n happened",
|
||||
" Oh hey…\n it's you",
|
||||
"\n Wheeeeee!!",
|
||||
" Time for\n another one?",
|
||||
"and\n\n scene",
|
||||
"\n GOT EM!!",
|
||||
"\nTHE VALUUUE!!!",
|
||||
"Cool seed,\n\nright?",
|
||||
"\n We did it!",
|
||||
" Spam those\n emotes in\n wilds chat",
|
||||
"\n O M G",
|
||||
" Hello. Will\n you be my\n friend?",
|
||||
" Beetorp\n was\n here!",
|
||||
"The Wind Fish\nwill wake\nsoon. Hoot!",
|
||||
"meow meow meow\nmeow meow meow\n oh my god!",
|
||||
"Ahhhhhhhhh\nYa ya yaaaah\nYa ya yaaah",
|
||||
".done\n\n.comment lol",
|
||||
]
|
||||
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
||||
PyramidFairy_texts = ['May I talk to you about our lord and savior, Ganon?']
|
||||
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
||||
Blind_texts = ['I bet you expected a vision related pun?\n\nNot Today.\n Didn\'t see that coming, did you?', 'What do you call a blind dinosaur?\n A Doyouthinkhe-saurus',
|
||||
'A blind man walks into a bar...\n\n\n and a table\n\n\n and a door.',
|
||||
'Why can\'t blind people eat fish?\n Because it\'s see food']
|
||||
Ganon1_texts = ['\n\n\n\n\n\n\n\n\nWhy are you reading an empty textbox?', 'Hi', 'Hey, can you turn off the lights?', 'Oink Oink',
|
||||
'Uncle: How do you like my Ganon cosplay?', 'I\'ll try spinning - that\'s a good trick!', 'Did you ever hear the tragedy of Darth Plagueis the Wise?']
|
||||
TavernMan_texts = ['Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.']
|
||||
Blind_texts = [
|
||||
"I hate insect\npuns, they\nreally bug me.",
|
||||
"I haven't seen\nthe eye doctor\nin years",
|
||||
"I don't see\nyou having a\nbright future",
|
||||
"Are you doing\na blind run\nof this game?",
|
||||
"pizza joke? no\nI think it's a\nbit too cheesy",
|
||||
"A novice skier\noften jumps to\ncontusions.",
|
||||
"the beach?\nI'm not shore\nI can make it.",
|
||||
"Rental agents\noffer quarters\nfor dollars.",
|
||||
"I got my tires\nfixed for a\nflat rate.",
|
||||
"New lightbulb\ninvented?\nEnlighten me.",
|
||||
"A baker's job\nis a piece of\ncake.",
|
||||
"My optometrist\nsaid I have\nvision!",
|
||||
"when you're a\nbaker, don't\nloaf around",
|
||||
"mire requires\nether quake,\nor bombos",
|
||||
"Broken pencils\nare pointless.",
|
||||
"The food they\nserve guards\nlasts sentries",
|
||||
"being crushed\nby big objects\nis depressing.",
|
||||
"A tap dancer's\nroutine runs\nhot and cold.",
|
||||
"A weeknight is\na tiny\nnobleman",
|
||||
"The chimney\nsweep wore a\nsoot and tye.",
|
||||
"Gardeners like\nto spring into\naction.",
|
||||
"bad at nuclear\nphysics. I\nGot no fission",
|
||||
]
|
||||
Ganon1_texts = [
|
||||
"Start your day\nsmiling with a\ndelicious\nwholegrain\nbreakfast\ncreated for\nyour\nincredible\ninsides.",
|
||||
"You drove\naway my other\nself, Agahnim\ntwo times…\nBut, I won't\ngive you the\nTriforce.\nI'll defeat\nyou!",
|
||||
"Impa says that\nthe mark on\nyour hand\nmeans that you\nare the hero\nchosen to\nawaken Zelda.\nyour blood can\nresurrect me.",
|
||||
"Don't stand,\n\ndon't stand so\nDon't stand so\n\nclose to me\nDon't stand so\nclose to me\nback off buddy",
|
||||
"So ya\nThought ya\nMight like to\ngo to the show\nTo feel the\nwarm thrill of\nconfusion\nThat space\ncadet glow.",
|
||||
"Like other\npulmonate land\ngastropods,\nthe majority\nof land slugs\nhave two pairs\nof 'feelers'\nor tentacles\non their head.",
|
||||
"If you were a\nburrito, what\nkind of a\nburrito would\nyou be?\nMe, I fancy I\nwould be a\nspicy barbacoa\nburrito.",
|
||||
"I am your\nfather's\nbrother's\nnephew's\ncousin's\nformer\nroommate. What\ndoes that make\nus, you ask?",
|
||||
"I'll be more\neager about\nencouraging\nthinking\noutside the\nbox when there\nis evidence of\nany thinking\ninside it.",
|
||||
"If we're not\nmeant to have\nmidnight\nsnacks, then\nwhy is there\na light in the\nfridge?\n",
|
||||
"I feel like we\nkeep ending up\nhere.\n\nDon't you?\n\nIt's like\ndeja vu\nall over again",
|
||||
"Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.",
|
||||
"Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?",
|
||||
"Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products",
|
||||
]
|
||||
TavernMan_texts = [
|
||||
'Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.'
|
||||
] + [
|
||||
"What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n",
|
||||
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
|
||||
"What do ducks\nlike to eat?\n\nQuackers!\n",
|
||||
"How do you\nset up a party\nin space?\n\nYou planet!\n",
|
||||
"I'm glad I\nknow sign\nlanguage,\nit's pretty\nhandy.\n",
|
||||
"What did Zelda\nsay to Link at\na secure door?\n\nTRIFORCE!\n",
|
||||
"I am on a\nseafood diet.\n\nEvery time\nI see food,\nI eat it.",
|
||||
"I've decided\nto sell my\nvacuum.\nIt was just\ngathering\ndust.",
|
||||
"Whats the best\ntime to go to\nthe dentist?\n\nTooth-hurtie!\n",
|
||||
"Why can't a\nbike stand on\nits own?\n\nIt's two-tired!\n",
|
||||
"If you haven't\nfound Quake\nyet…\nit's not your\nfault.",
|
||||
"Why is Peter\nPan always\nflying?\nBecause he\nNeverlands!",
|
||||
"I once told a\njoke to Armos.\n\nBut he\nremained\nstone-faced!",
|
||||
"Lanmola was\nlate to our\ndinner party.\nHe just came\nfor the desert",
|
||||
"Moldorm is\nsuch a\nprankster.\nAnd I fall for\nit every time!",
|
||||
"Helmasaur is\nthrowing a\nparty.\nI hope it's\na masquerade!",
|
||||
"I'd like to\nknow Arrghus\nbetter.\nBut he won't\ncome out of\nhis shell!",
|
||||
"Mothula didn't\nhave much fun\nat the party.\nHe's immune to\nspiked punch!",
|
||||
"Don't set me\nup with that\nchick from\nSteve's Town.\n\n\nI'm not\ninterested in\na Blind date!",
|
||||
"Kholdstare is\nafraid to go\nto the circus.\nHungry kids\nthought he was\ncotton candy!",
|
||||
"I asked who\nVitreous' best\nfriends are.\nHe said,\n'Me, Myself,\nand Eye!'",
|
||||
"Trinexx can be\na hothead or\nhe can be an\nice guy. In\nthe end, he's\na solid\nindividual!",
|
||||
"Bari thought I\nhad moved out\nof town.\nHe was shocked\nto see me!",
|
||||
"I can only get\nWeetabix\naround here.\nI have to go\nto Steve's\nTown for Count\nChocula!",
|
||||
"Don't argue\nwith a frozen\nDeadrock.\nHe'll never\nchange his\nposition!",
|
||||
"I offered a\ndrink to a\nself-loathing\nGhini.\nHe said he\ndidn't like\nspirits!",
|
||||
"I was supposed\nto meet Gibdo\nfor lunch.\nBut he got\nwrapped up in\nsomething!",
|
||||
"Goriya sure\nhas changed\nin this game.\nI hope he\ncomes back\naround!",
|
||||
"Hinox actually\nwants to be a\nlawyer.\nToo bad he\nbombed the\nBar exam!",
|
||||
"I'm surprised\nMoblin's tusks\nare so gross.\nHe always has\nhis Trident\nwith him!",
|
||||
"Don’t tell\nStalfos I’m\nhere.\nHe has a bone\nto pick with\nme!",
|
||||
"I got\nWallmaster to\nhelp me move\nfurniture.\nHe was really\nhandy!",
|
||||
"Wizzrobe was\njust here.\nHe always\nvanishes right\nbefore we get\nthe check!",
|
||||
"I shouldn't\nhave picked up\nZora's tab.\nThat guy\ndrinks like\na fish!",
|
||||
"I was sharing\na drink with\nPoe.\nFor no reason,\nhe left in a\nheartbeat!",
|
||||
"Don’t trust\nhorsemen on\nDeath Mountain\nThey’re Lynel\nthe time!",
|
||||
"Today's\nspecial is\nbattered bat.\nGot slapped\nfor offering a\nlady a Keese!",
|
||||
"Don’t walk\nunder\npropellered\npineapples.\nYou may end up\nwearing\na pee hat!",
|
||||
"My girlfriend\nburrowed under\nthe sand.\nSo I decided\nto Leever!",
|
||||
"Geldman wants\nto be a\nBroadway star.\nHe’s always\npracticing\nJazz Hands!",
|
||||
"Octoballoon\nmust be mad\nat me.\nHe blows up\nat the sight\nof me!",
|
||||
"Toppo is a\ntotal pothead.\n\nHe hates it\nwhen you take\naway his grass",
|
||||
"I lost my\nshield by\nthat house.\nWhy did they\nput up a\nPikit fence?!",
|
||||
"Know that fox\nin Steve’s\nTown?\nHe’ll Pikku\npockets if you\naren't careful",
|
||||
"Dash through\nDark World\nbushes.\nYou’ll see\nGanon is tryin\nto Stal you!",
|
||||
"Eyegore!\n\nYou gore!\nWe all gore\nthose jerks\nwith arrows!",
|
||||
"I like my\nwhiskey neat.\n\nSome prefer it\nOctoroks!",
|
||||
"I consoled\nFreezor over a\ncup of coffee.\nHis problems\njust seemed to\nmelt away!",
|
||||
"Magic droplets\nof water don’t\nshut up.\nThey just\nKyameron!",
|
||||
"I bought hot\nwings for\nSluggula.\nThey gave him\nexplosive\ndiarrhea!",
|
||||
"Hardhat Beetle\nwon’t\nLet It Be?\nTell it to Get\nBack or give\nit a Ticket to\nRide down\na hole!",
|
||||
]
|
||||
|
||||
KingsReturn_texts = ['Who is this even', 'The Harem']
|
||||
Sanctuary_texts = ['A Priest\'s love']
|
||||
Kakariko_texts = ['Shasschahshahsahahrahsashsa', 'Schaschlik']
|
||||
Blacksmiths_texts = ['frogs for bread', 'That\'s not a sword', 'The Rupeesmiths']
|
||||
DeathMountain_texts = ['lost again', 'Alzheimer']
|
||||
LostWoods_texts = ['thieves\' stump', 'He\'s got wood', 'Dancing pickles']
|
||||
WishingWell_texts = ['Bottle for Bottle']
|
||||
DesertPalace_texts = ['literacy moves']
|
||||
MountainTower_texts = ['up up and away']
|
||||
LinksHouse_texts = ['Home Sweet Home', 'Only one bed']
|
||||
Lumberjacks_texts = ['Chop Chop', 'logfellas']
|
||||
KingsReturn_texts = [
|
||||
'Who is this even',
|
||||
'The Harem'
|
||||
] * 2 + [
|
||||
"the return of the king",
|
||||
"fellowship of the ring",
|
||||
"the two towers",
|
||||
]
|
||||
Sanctuary_texts = [
|
||||
'A Priest\'s love'
|
||||
] * 2 + [
|
||||
"the loyal priest",
|
||||
"read a book",
|
||||
"sits in own pew",
|
||||
]
|
||||
Sahasrahla_names = [
|
||||
"sahasralah", "sabotaging", "sacahuista", "sacahuiste", "saccharase", "saccharide", "saccharify",
|
||||
"saccharine", "saccharins", "sacerdotal", "sackcloths", "salmonella", "saltarelli", "saltarello",
|
||||
"saltations", "saltbushes", "saltcellar", "saltshaker", "salubrious", "sandgrouse", "sandlotter",
|
||||
"sandstorms", "sandwiched", "sauerkraut", "schipperke", "schismatic", "schizocarp", "schmalzier",
|
||||
"schmeering", "schmoosing", "shibboleth", "shovelnose", "sahananana", "sarararara", "salamander",
|
||||
"sharshalah", "shahabadoo", "sassafrass",
|
||||
]
|
||||
|
||||
Kakariko_texts = ["{}'s homecoming"]
|
||||
Blacksmiths_texts = [
|
||||
'frogs for bread',
|
||||
'That\'s not a sword',
|
||||
'The Rupeesmiths'
|
||||
] * 1 + [
|
||||
"the dwarven breadsmiths"
|
||||
]
|
||||
DeathMountain_texts = [
|
||||
"the lost old man",
|
||||
"gary the old man",
|
||||
"Your ad here"
|
||||
]
|
||||
LostWoods_texts = [
|
||||
'thieves\' stump',
|
||||
'He\'s got wood',
|
||||
] * 2 + [
|
||||
"the forest thief",
|
||||
"dancing pickles",
|
||||
"flying vultures",
|
||||
]
|
||||
WishingWell_texts = [
|
||||
"venus. queen of faeries",
|
||||
"Venus was her name",
|
||||
"I'm your Venus",
|
||||
"Yeah, baby, shes got it",
|
||||
"Venus, I'm your fire",
|
||||
"Venus, At your desire",
|
||||
]
|
||||
DesertPalace_texts = ['vultures rule the desert', 'literacy moves']
|
||||
MountainTower_texts = ['the bully makes a friend', 'up up and away']
|
||||
LinksHouse_texts = ['your uncle recovers', 'Home Sweet Home', 'Only one bed']
|
||||
Lumberjacks_texts = [
|
||||
'Chop Chop'
|
||||
] * 2 + [
|
||||
"twin lumberjacks",
|
||||
"fresh flapjacks",
|
||||
"two woodchoppers",
|
||||
"double lumberman",
|
||||
"lumberclones",
|
||||
"woodfellas",
|
||||
]
|
||||
SickKid_texts = ['Next Time Stay Down']
|
||||
Zora_texts = ['Splashes For Sale', 'Slippery when wet']
|
||||
MagicShop_texts = ['Drug deal', 'Shrooms for days']
|
||||
|
@ -120,7 +339,7 @@ class Credits(object):
|
|||
self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch',
|
||||
'lumberjacks', 'grove', 'well', 'smithy', 'kakariko2', 'bridge', 'woods', 'pedestal']
|
||||
|
||||
def update_credits_line(self, scene, line, text, align='center'):
|
||||
def update_credits_line(self, scene, line, text):
|
||||
scenes = self.credit_scenes
|
||||
|
||||
text = text[:32]
|
||||
|
@ -456,9 +675,10 @@ def char_to_alttp_char(char):
|
|||
|
||||
return char_map.get(char, 0xFF)
|
||||
|
||||
|
||||
class TextMapper(object):
|
||||
number_offset = None
|
||||
alpha_offset = 0
|
||||
char_map = {}
|
||||
@classmethod
|
||||
def map_char(cls, char):
|
||||
if cls.number_offset is not None:
|
||||
|
|
43
Utils.py
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def is_bundled():
|
||||
|
@ -10,7 +11,7 @@ def local_path(path):
|
|||
|
||||
if is_bundled():
|
||||
# we are running in a bundle
|
||||
local_path.cached_path = sys._MEIPASS
|
||||
local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member
|
||||
else:
|
||||
# we are running in a normal Python environment
|
||||
local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -40,7 +41,7 @@ def output_path(path):
|
|||
documents = buf.value
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
from AppKit import NSSearchPathForDirectoriesInDomains
|
||||
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
|
||||
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
|
||||
NSDocumentDirectory = 9
|
||||
NSUserDomainMask = 1
|
||||
|
@ -60,7 +61,7 @@ def open_file(filename):
|
|||
if sys.platform == 'win32':
|
||||
os.startfile(filename)
|
||||
else:
|
||||
open_Command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
||||
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
|
||||
subprocess.call([open_command, filename])
|
||||
|
||||
def close_console():
|
||||
|
@ -69,5 +70,39 @@ def close_console():
|
|||
import ctypes.wintypes
|
||||
try:
|
||||
ctypes.windll.kernel32.FreeConsole()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def new_logic_array():
|
||||
import random
|
||||
l = list(range(256))
|
||||
random.SystemRandom().shuffle(l)
|
||||
chunks = [l[i:i + 16] for i in range(0, len(l), 16)]
|
||||
lines = [", ".join([str(j) for j in i]) for i in chunks]
|
||||
print("logic_hash = ["+",\n ".join(lines)+"]")
|
||||
|
||||
def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'):
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
import hashlib
|
||||
with open(old_rom, 'rb') as stream:
|
||||
old_rom_data = bytearray(stream.read())
|
||||
with open(new_rom, 'rb') as stream:
|
||||
new_rom_data = bytearray(stream.read())
|
||||
# extend to 2 mb
|
||||
old_rom_data.extend(bytearray([0x00] * (2097152 - len(old_rom_data))))
|
||||
|
||||
out_data = OrderedDict()
|
||||
for idx, old in enumerate(old_rom_data):
|
||||
new = new_rom_data[idx]
|
||||
if old != new:
|
||||
out_data[idx] = [int(new)]
|
||||
for offset in reversed(list(out_data.keys())):
|
||||
if offset - 1 in out_data:
|
||||
out_data[offset-1].extend(out_data.pop(offset))
|
||||
with open('data/base2current.json', 'wt') as outfile:
|
||||
json.dump([{key:value} for key, value in out_data.items()], outfile, separators=(",", ":"))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(new_rom_data)
|
||||
return "New Rom Hash: " + basemd5.hexdigest()
|
||||
|
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|