Style fixes

A bunch of style fixes. Mostly white space and style import order
tweaks, but a few other stylistic changes are present too.
This commit is contained in:
Kevin Cathcart 2017-12-17 00:25:46 -05:00
parent 8b20e39588
commit 823657bc26
18 changed files with 224 additions and 212 deletions

View File

@ -11,8 +11,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action): def _get_help_string(self, action):
return textwrap.dedent(action.help) return textwrap.dedent(action.help)
def main():
if __name__ == '__main__':
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) 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.') parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
@ -46,4 +45,7 @@ if __name__ == '__main__':
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel] loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel) logging.basicConfig(format='%(message)s', level=loglevel)
adjust(args=args) adjust(args=args)
if __name__ == '__main__':
main()

View File

@ -1,10 +1,9 @@
from collections import OrderedDict
from Utils import output_path
import os import os
import time import time
import logging 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): def adjust(args):
@ -13,7 +12,7 @@ def adjust(args):
logger.info('Patching ROM.') logger.info('Patching ROM.')
if args.sprite is not None: if args.sprite is not None:
if isinstance(args.sprite,Sprite): if isinstance(args.sprite, Sprite):
sprite = args.sprite sprite = args.sprite
else: else:
sprite = Sprite(args.sprite) sprite = Sprite(args.sprite)
@ -22,7 +21,7 @@ def adjust(args):
outfilebase = 'ER_adjusted' 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) rom = LocalRom(args.rom, False)
else: else:
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
@ -32,6 +31,6 @@ def adjust(args):
rom.write_to_file(output_path('%s.sfc' % outfilebase)) rom.write_to_file(output_path('%s.sfc' % outfilebase))
logger.info('Done. Enjoy.') logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start)) logger.debug('Total Time: %s', time.clock() - start)
return args return args

View File

@ -26,7 +26,6 @@ class World(object):
self._region_cache = {} self._region_cache = {}
self._entrance_cache = {} self._entrance_cache = {}
self._location_cache = {} self._location_cache = {}
self._item_cache = {}
self.required_locations = [] self.required_locations = []
self.place_dungeon_items = place_dungeon_items # configurable in future self.place_dungeon_items = place_dungeon_items # configurable in future
self.shuffle_bonk_prizes = False self.shuffle_bonk_prizes = False
@ -142,7 +141,7 @@ 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): '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) soft_collect(item)
ret.sweep_for_events() ret.sweep_for_events()
ret._clear_cache() ret.clear_cached_unreachable()
return ret return ret
def find_items(self, item): def find_items(self, item):
@ -158,7 +157,7 @@ class World(object):
if collect: if collect:
self.state.collect(item, location.event) 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: else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
@ -187,7 +186,7 @@ class World(object):
def unlocks_new_location(self, item): def unlocks_new_location(self, item):
temp_state = self.state.copy() temp_state = self.state.copy()
temp_state._clear_cache() temp_state.clear_cached_unreachable()
temp_state.collect(item, True) temp_state.collect(item, True)
for location in self.get_unfilled_locations(): for location in self.get_unfilled_locations():
@ -197,9 +196,10 @@ class World(object):
return False return False
def has_beaten_game(self, state): def has_beaten_game(self, state):
if state.has('Triforce'): return True if state.has('Triforce'):
return True
if self.goal in ['triforcehunt']: if self.goal in ['triforcehunt']:
if state.item_count('Triforce Piece')+state.item_count('Power Star')> self.treasure_hunt_count: if state.item_count('Triforce Piece') + state.item_count('Power Star') > self.treasure_hunt_count:
return True return True
return False return False
@ -242,7 +242,7 @@ class World(object):
goal = ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'].index(self.goal) goal = ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'].index(self.goal)
shuffle = ['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'].index(self.shuffle) shuffle = ['vanilla', 'simple', 'restricted', 'full', 'madness', 'insanity', 'dungeonsfull', 'dungeonssimple'].index(self.shuffle)
difficulty = ['easy', 'normal', 'hard', 'expert', 'insane'].index(self.difficulty) difficulty = ['easy', 'normal', 'hard', 'expert', 'insane'].index(self.difficulty)
timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown','ohko'].index(self.timer) timer = ['none', 'display', 'timed', 'timed-ohko', 'timed-countdown', 'ohko'].index(self.timer)
progressive = ['on', 'off', 'random'].index(self.progressive) progressive = ['on', 'off', 'random'].index(self.progressive)
algorithm = ['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'].index(self.algorithm) algorithm = ['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'].index(self.algorithm)
beatableonly = 1 if self.check_beatable_only else 0 beatableonly = 1 if self.check_beatable_only else 0
@ -262,7 +262,7 @@ class CollectionState(object):
self.recursion_count = 0 self.recursion_count = 0
self.events = [] 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 # 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.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} self.location_cache = {k: v for k, v in self.location_cache.items() if v}
@ -337,8 +337,7 @@ class CollectionState(object):
def has(self, item, count=1): def has(self, item, count=1):
if count == 1: if count == 1:
return item in self.prog_items return item in self.prog_items
else: return self.item_count(item) >= count
return self.item_count(item) >= count
def item_count(self, item): def item_count(self, item):
return len([pritem for pritem in self.prog_items if pritem == item]) return len([pritem for pritem in self.prog_items if pritem == item])
@ -424,10 +423,10 @@ class CollectionState(object):
changed = True changed = True
if changed: if changed:
self._clear_cache() self.clear_cached_unreachable()
if not event: if not event:
self.sweep_for_events() self.sweep_for_events()
self._clear_cache() self.clear_cached_unreachable()
def remove(self, item): def remove(self, item):
if item.advancement: if item.advancement:
@ -497,10 +496,7 @@ class Region(object):
is_dungeon_item = item.key or item.map or item.compass is_dungeon_item = item.key or item.map or item.compass
sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)' 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 sewer_hack or (is_dungeon_item and not self.world.keysanity):
if self.dungeon and self.dungeon.is_dungeon_item(item): return self.dungeon and self.dungeon.is_dungeon_item(item)
return True
else:
return False
return True return True
@ -522,9 +518,7 @@ class Entrance(object):
self.spot_type = 'Entrance' self.spot_type = 'Entrance'
self.recursion_count = 0 self.recursion_count = 0
self.vanilla = None self.vanilla = None
self.access_rule = lambda state: True
def access_rule(self, state):
return True
def can_reach(self, state): def can_reach(self, state):
if self.access_rule(state) and state.can_reach(self.parent_region): if self.access_rule(state) and state.can_reach(self.parent_region):
@ -586,12 +580,8 @@ class Location(object):
self.recursion_count = 0 self.recursion_count = 0
self.staleness_count = 0 self.staleness_count = 0
self.event = False self.event = False
self.access_rule = lambda state: True
def access_rule(self, state): self.item_rule = lambda state: True
return True
def item_rule(self, item):
return True
def can_fill(self, item): def can_fill(self, item):
return self.parent_region.can_fill(item) and self.item_rule(item) return self.parent_region.can_fill(item) and self.item_rule(item)

View File

@ -1,7 +1,8 @@
from Items import ItemFactory import random
from BaseClasses import Dungeon from BaseClasses import Dungeon
from Fill import fill_restrictive from Fill import fill_restrictive
import random from Items import ItemFactory
def create_dungeons(world): def create_dungeons(world):
@ -62,7 +63,7 @@ def fill_dungeons(world):
world.push_item(bk_location, big_key, False) world.push_item(bk_location, big_key, False)
bk_location.event = True bk_location.event = True
dungeon_locations.remove(bk_location) dungeon_locations.remove(bk_location)
all_state._clear_cache() all_state.clear_cached_unreachable()
big_key = None big_key = None
# next place small keys # next place small keys
@ -88,7 +89,7 @@ def fill_dungeons(world):
world.push_item(sk_location, small_key, False) world.push_item(sk_location, small_key, False)
sk_location.event = True sk_location.event = True
dungeon_locations.remove(sk_location) dungeon_locations.remove(sk_location)
all_state._clear_cache() all_state.clear_cached_unreachable()
if small_keys: if small_keys:
# key placement not finished, loop again # key placement not finished, loop again
@ -100,7 +101,7 @@ def fill_dungeons(world):
di_location = dungeon_locations.pop() di_location = dungeon_locations.pop()
world.push_item(di_location, dungeon_item, False) world.push_item(di_location, dungeon_item, False)
world.state._clear_cache() world.state.clear_cached_unreachable()
def fill_dungeons_restrictive(world, shuffled_locations): def fill_dungeons_restrictive(world, shuffled_locations):
@ -119,7 +120,7 @@ def fill_dungeons_restrictive(world, shuffled_locations):
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items) 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], dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],

View File

@ -5,8 +5,8 @@ import random
import textwrap import textwrap
import sys import sys
from Main import main
from Gui import guiMain from Gui import guiMain
from Main import main
from Utils import is_bundled, close_console from Utils import is_bundled, close_console
@ -16,7 +16,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
return textwrap.dedent(action.help) return textwrap.dedent(action.help)
if __name__ == '__main__': def start():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') 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'], parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'],
@ -191,7 +191,7 @@ if __name__ == '__main__':
''') ''')
args = parser.parse_args() args = parser.parse_args()
if is_bundled() and len(sys.argv) == 1 : if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user # for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line # probably wants the gui. Users of the bundled build who want the command line
# interface shouuld specify at least one option, possibly setting a value to a # interface shouuld specify at least one option, possibly setting a value to a
@ -219,8 +219,11 @@ if __name__ == '__main__':
guiMain(args) guiMain(args)
elif args.count is not None: elif args.count is not None:
seed = args.seed seed = args.seed
for i in range(args.count): for _ in range(args.count):
main(seed=seed, args=args) main(seed=seed, args=args)
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)
else: else:
main(seed=args.seed, args=args) main(seed=args.seed, args=args)
if __name__ == '__main__':
start()

View File

@ -1298,7 +1298,7 @@ mandatory_connections = [('Links House', 'Links House'), # unshuffled. For now
('Ganons Tower Moldorm Gap', 'Agahnim 2'), ('Ganons Tower Moldorm Gap', 'Agahnim 2'),
('Ganon Drop', 'Bottom of Pyramid'), ('Ganon Drop', 'Bottom of Pyramid'),
('Pyramid Drop', 'East Dark World') ('Pyramid Drop', 'East Dark World')
] ]
# non-shuffled entrance links # non-shuffled entrance links
default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'), default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
@ -1450,7 +1450,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
('Pyramid Hole', 'Pyramid'), ('Pyramid Hole', 'Pyramid'),
('Pyramid Exit', 'Pyramid Ledge'), ('Pyramid Exit', 'Pyramid Ledge'),
('Pyramid Entrance', 'Bottom of Pyramid') ('Pyramid Entrance', 'Bottom of Pyramid')
] ]
# non shuffled dungeons # non shuffled dungeons
default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace Main'), default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace Main'),
@ -1510,7 +1510,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace
('Ganons Tower', 'Ganons Tower (Entrance)'), ('Ganons Tower', 'Ganons Tower (Entrance)'),
('Ganons Tower Exit', 'Dark Death Mountain (Top)') ('Ganons Tower Exit', 'Dark Death Mountain (Top)')
] ]
# ToDo somehow merge this with creation of the locations # ToDo somehow merge this with creation of the locations

View File

@ -56,7 +56,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
raise RuntimeError('No more progress items left to place.') raise RuntimeError('No more progress items left to place.')
spot_to_fill = None spot_to_fill = None
for location in (fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations)): 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): if world.state.can_reach(location) and location.can_fill(item_to_place):
spot_to_fill = location spot_to_fill = location
break break
@ -72,7 +72,7 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
itempool.remove(item_to_place) itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill) 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): def distribute_items_staleness(world):
@ -153,7 +153,7 @@ def distribute_items_staleness(world):
itempool.remove(item_to_place) itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill) 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): def fill_restrictive(world, base_state, locations, itempool):
@ -226,7 +226,7 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
fast_fill(world, restitempool, fill_locations) 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): def fast_fill(world, item_pool, fill_locations):

44
Gui.py
View File

@ -4,7 +4,7 @@ import json
import random import random
import os import os
import shutil import shutil
from tkinter import Checkbutton, OptionMenu, Toplevel, LabelFrame, PhotoImage, Tk, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, Y, Entry, Spinbox, Button, filedialog, messagebox, ttk 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.parse import urlparse
from urllib.request import urlopen from urllib.request import urlopen
@ -100,7 +100,7 @@ def guiMain(args=None):
baseSpriteLabel = Label(spriteDialogFrame, text='Link Sprite:') baseSpriteLabel = Label(spriteDialogFrame, text='Link Sprite:')
spriteNameVar = StringVar() spriteNameVar = StringVar()
sprite=None sprite = None
def set_sprite(sprite_param): def set_sprite(sprite_param):
nonlocal sprite nonlocal sprite
if sprite_param is None or not sprite_param.valid: if sprite_param is None or not sprite_param.valid:
@ -250,7 +250,7 @@ def guiMain(args=None):
try: try:
if guiargs.count is not None: if guiargs.count is not None:
seed = guiargs.seed seed = guiargs.seed
for i in range(guiargs.count): for _ in range(guiargs.count):
main(seed=seed, args=guiargs) main(seed=seed, args=guiargs)
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)
else: else:
@ -264,9 +264,9 @@ def guiMain(args=None):
seedLabel.pack(side=LEFT) seedLabel.pack(side=LEFT)
seedEntry.pack(side=LEFT) seedEntry.pack(side=LEFT)
countLabel.pack(side=LEFT, padx=(5,0)) countLabel.pack(side=LEFT, padx=(5, 0))
countSpinbox.pack(side=LEFT) countSpinbox.pack(side=LEFT)
generateButton.pack(side=LEFT, padx=(5,0)) generateButton.pack(side=LEFT, padx=(5, 0))
openOutputButton.pack(side=RIGHT) openOutputButton.pack(side=RIGHT)
@ -349,12 +349,12 @@ def guiMain(args=None):
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom) adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
adjustButton.pack(side=LEFT, padx=(5,0)) adjustButton.pack(side=LEFT, padx=(5, 0))
drowDownFrame2.pack(side=LEFT, pady=(0,40)) drowDownFrame2.pack(side=LEFT, pady=(0, 40))
rightHalfFrame2.pack(side=RIGHT) rightHalfFrame2.pack(side=RIGHT)
topFrame2.pack(side=TOP, pady=30) topFrame2.pack(side=TOP, pady=30)
bottomFrame2.pack(side=BOTTOM, pady=(180,0)) bottomFrame2.pack(side=BOTTOM, pady=(180, 0))
if args is not None: if args is not None:
# load values from commandline args # load values from commandline args
@ -425,15 +425,16 @@ class SpriteSelector(object):
for file in glob(output_path(path)): for file in glob(output_path(path)):
sprite = Sprite(file) sprite = Sprite(file)
image = get_image_for_sprite(sprite) image = get_image_for_sprite(sprite)
if image is None: continue if image is None:
button = Button(frame, image=image, command=lambda sprite=sprite: self.select_sprite(sprite)) 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 "")) ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name is not None else ""))
button.image = image button.image = image
button.grid(row=i // 16, column=i % 16) button.grid(row=i // 16, column=i % 16)
i += 1 i += 1
if i == 0: if i == 0:
label = Label(frame, text="Put sprites in the Sprites/Unoffical folder to have them appear here.") label = Label(frame, text=no_results_label)
label.pack() label.pack()
@ -442,7 +443,7 @@ class SpriteSelector(object):
self.window.destroy() self.window.destroy()
self.parent.update() self.parent.update()
def work(task): def work(task):
resultmessage="" resultmessage = ""
successful = True successful = True
def finished(): def finished():
@ -467,7 +468,7 @@ class SpriteSelector(object):
current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')] 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] 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] needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites]
bundled_sprites=[] bundled_sprites = []
official_filenames = [filename for (_, filename) in official_sprites] official_filenames = [filename for (_, filename) in official_sprites]
obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames] obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames]
@ -511,7 +512,7 @@ class SpriteSelector(object):
sprite = filedialog.askopenfilename() sprite = filedialog.askopenfilename()
try: try:
self.callback(Sprite(sprite)) self.callback(Sprite(sprite))
except: except Exception:
self.callback(None) self.callback(None)
self.window.destroy() self.window.destroy()
@ -535,22 +536,20 @@ class SpriteSelector(object):
def official_sprite_dir(self): def official_sprite_dir(self):
if is_bundled(): if is_bundled():
return output_path("sprites/official") return output_path("sprites/official")
else: return self.local_official_sprite_dir
return self.local_official_sprite_dir
@property @property
def local_official_sprite_dir(site): def local_official_sprite_dir(self):
return local_path("data/sprites/official") return local_path("data/sprites/official")
@property @property
def unofficial_sprite_dir(self): def unofficial_sprite_dir(self):
if is_bundled(): if is_bundled():
return output_path("sprites/unofficial") return output_path("sprites/unofficial")
else: return self.local_unofficial_sprite_dir
return self.local_unofficial_sprite_dir
@property @property
def local_unofficial_sprite_dir(site): def local_unofficial_sprite_dir(self):
return local_path("data/sprites/unofficial") return local_path("data/sprites/unofficial")
@ -563,9 +562,8 @@ def get_image_for_sprite(sprite):
def draw_sprite_into_gif(add_palette_color, set_pixel_color_index): def draw_sprite_into_gif(add_palette_color, set_pixel_color_index):
def drawsprite(spr, pal_as_colors, offset): def drawsprite(spr, pal_as_colors, offset):
for y in range(len(spr)): for y, row in enumerate(spr):
for x in range(len(spr[y])): for x, pal_index in enumerate(row):
pal_index = spr[y][x]
if pal_index: if pal_index:
color = pal_as_colors[pal_index - 1] color = pal_as_colors[pal_index - 1]
set_pixel_color_index(x + offset[0], y + offset[1], color) set_pixel_color_index(x + offset[0], y + offset[1], color)

View File

@ -8,7 +8,7 @@ def set_icon(window):
er16 = tk.PhotoImage(file=local_path('data/ER16.gif')) er16 = tk.PhotoImage(file=local_path('data/ER16.gif'))
er32 = tk.PhotoImage(file=local_path('data/ER32.gif')) er32 = tk.PhotoImage(file=local_path('data/ER32.gif'))
er48 = 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) 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 # 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 # some which may be platform specific, or depend on if the TCL library was compiled without
@ -19,7 +19,7 @@ class BackgroundTask(object):
self.queue = queue.Queue() self.queue = queue.Queue()
self.running = True self.running = True
self.process_queue() self.process_queue()
self.task=threading.Thread(target=code_to_run , args=(self,)) self.task = threading.Thread(target=code_to_run, args=(self,))
self.task.start() self.task.start()
def stop(self): def stop(self):
@ -51,12 +51,12 @@ class BackgroundTaskProgress(BackgroundTask):
self.window['padx'] = 5 self.window['padx'] = 5
self.window['pady'] = 5 self.window['pady'] = 5
self.window.attributes("-toolwindow",1) self.window.attributes("-toolwindow", 1)
self.window.wm_title(title) self.window.wm_title(title)
self.labelVar = tk.StringVar() self.label_var = tk.StringVar()
self.labelVar.set("") self.label_var.set("")
self.label = tk.Label(self.window, textvariable = self.labelVar, width=50) self.label = tk.Label(self.window, textvariable=self.label_var, width=50)
self.label.pack() self.label.pack()
self.window.resizable(width=False, height=False) self.window.resizable(width=False, height=False)
@ -66,7 +66,7 @@ class BackgroundTaskProgress(BackgroundTask):
#safe to call from worker thread #safe to call from worker thread
def update_status(self, text): def update_status(self, text):
self.queue_event(lambda text=text: self.labelVar.set(text)) self.queue_event(lambda: self.label_var.set(text))
# only call this in an event callback # only call this in an event callback
def close_window(self): def close_window(self):
@ -123,7 +123,7 @@ class ToolTips(object):
cls.fg = "systeminfotext" cls.fg = "systeminfotext"
widget.winfo_rgb(cls.fg) # make sure system colors exist widget.winfo_rgb(cls.fg) # make sure system colors exist
widget.winfo_rgb(cls.bg) widget.winfo_rgb(cls.bg)
except: except Exception:
cls.bg = "#ffffe0" cls.bg = "#ffffe0"
cls.fg = "black" cls.fg = "black"
@ -162,7 +162,6 @@ class ToolTips(object):
@classmethod @classmethod
def motion(cls, event): def motion(cls, event):
widget = event.widget
cls.xy = event.x_root + 16, event.y_root + 10 cls.xy = event.x_root + 16, event.y_root + 10
cls.event_xy = event.x, event.y cls.event_xy = event.x, event.y

View File

@ -1,13 +1,14 @@
from Items import ItemFactory
from Fill import fill_restrictive
from collections import namedtuple from collections import namedtuple
import random import random
from Items import ItemFactory
from Fill import fill_restrictive
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. #This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. #Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna'] 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna']
progressivegloves = ['Progressive Glove'] * 2 progressivegloves = ['Progressive Glove'] * 2
basicgloves = ['Power Glove', 'Titans Mitts'] basicgloves = ['Power Glove', 'Titans Mitts']
@ -15,7 +16,7 @@ normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bott
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)'] hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 + normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 +
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24) ['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)'] normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
@ -24,7 +25,7 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 + easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 +
['Single Arrow', 'Sanctuary Heart Container'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12) ['Single Arrow', 'Sanctuary Heart Container'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)'] easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart
easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
@ -34,21 +35,21 @@ easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] +
easytimedotherextra = ['Red Clock'] * 5 easytimedotherextra = ['Red Clock'] * 5
hardbaseitems = (['Silver Arrows', 'Single Arrow'] + ['Rupees (300)'] + ['Rupees (100)'] * 2 + ['Rupees (50)'] + ['Bombs (3)'] + hardbaseitems = (['Silver Arrows', 'Single Arrow'] + ['Rupees (300)'] + ['Rupees (100)'] * 2 + ['Rupees (50)'] + ['Bombs (3)'] +
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24) ['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24)
hardfirst20extra = ['Bombs (3)'] * 4 + ['Single Bomb'] * 4 + ['Rupees (5)'] * 5 + ['Rupee (1)'] * 2 + ['Rupees (100)'] + ['Rupees (50)'] * 4 hardfirst20extra = ['Bombs (3)'] * 4 + ['Single Bomb'] * 4 + ['Rupees (5)'] * 5 + ['Rupee (1)'] * 2 + ['Rupees (100)'] + ['Rupees (50)'] * 4
hardsecond20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (20)'] * 2 + ['Rupee (1)'] * 3 + ['Arrows (10)'] hardsecond20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (20)'] * 2 + ['Rupee (1)'] * 3 + ['Arrows (10)']
hardthird20extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3 + ['Single Bomb'] * 5 + ['Single Arrow'] * 5 hardthird20extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3 + ['Single Bomb'] * 5 + ['Single Arrow'] * 5
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14 hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 + expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 +
['Rupees (20)'] + ['Single Bomb'] * 2 + ['Piece of Heart'] * 24) ['Rupees (20)'] + ['Single Bomb'] * 2 + ['Piece of Heart'] * 24)
expertfirst15extra = ['Single Bomb'] * 13 + ['Rupees (20)'] * 2 expertfirst15extra = ['Single Bomb'] * 13 + ['Rupees (20)'] * 2
expertsecond25extra = ['Single Bomb'] * 8 + ['Single Arrow'] * 9 + ['Rupees (20)'] * 3 + ['Rupee (1)'] * 5 expertsecond25extra = ['Single Bomb'] * 8 + ['Single Arrow'] * 9 + ['Rupees (20)'] * 3 + ['Rupee (1)'] * 5
expertthird15extra = ['Rupees (5)'] * 5 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 5 expertthird15extra = ['Rupees (5)'] * 5 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 5
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18 expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18
insanebaseitems = (['Single Arrow', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 3 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 4 + ['Rupees (100)'] * 3 + insanebaseitems = (['Single Arrow', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 3 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 4 + ['Rupees (100)'] * 3 +
['Rupee (1)'] * 4 + ['Single Bomb'] * 4) ['Rupee (1)'] * 4 + ['Single Bomb'] * 4)
insanefirst15extra = ['Single Bomb'] * 4 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 4 + ['Rupees (300)'] + ['Rupees (100)'] + ['Rupees (50)'] insanefirst15extra = ['Single Bomb'] * 4 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 4 + ['Rupees (300)'] + ['Rupees (100)'] + ['Rupees (50)']
insanesecond25extra = ['Single Bomb'] * 7 + ['Single Arrow'] * 7 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 4 insanesecond25extra = ['Single Bomb'] * 7 + ['Single Arrow'] * 7 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 4
insanethird10extra = ['Single Bomb'] * 3 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 + ['Rupees (20)'] insanethird10extra = ['Single Bomb'] * 3 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 + ['Rupees (20)']
@ -56,29 +57,27 @@ insanefourth15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)']
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6 insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
Difficulty = namedtuple('Difficulty', Difficulty = namedtuple('Difficulty',
['baseitems', 'bottles', 'bottle_count','same_bottle', 'progressiveshield', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
'progressivesword', 'basicsword', 'timedohko', 'timedother', 'progressivesword', 'basicsword', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'conditional_extras', 'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
'extras']) 'extras'])
TotalItemsToPlace = 153 total_items_to_place = 153
def easy_conditional_extras(timer, goal, mode, pool, placed_items): def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
extraitems = TotalItemsToPlace - len(pool) - len(placed_items) extraitems = total_items_to_place - len(pool) - len(placed_items)
if extraitems < len(easyextra): if extraitems < len(easyextra):
return easylimitedextra return easylimitedextra
if timer in ['timed', 'timed-countdown']: if timer in ['timed', 'timed-countdown']:
return easytimedotherextra return easytimedotherextra
return [] return []
def no_conditonal_extras(*args): def no_conditonal_extras(*_args):
return [] return []
#def Difficulty(**kwargs): # pylint: disable=
# protodifficulty._replace(**kwargs) difficulties = {
difficulties= {
'normal': Difficulty( 'normal': Difficulty(
baseitems = normalbaseitems, baseitems = normalbaseitems,
bottles = normalbottles, bottles = normalbottles,
@ -96,7 +95,7 @@ difficulties= {
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, conditional_extras = no_conditonal_extras,
extras = [normalfirst15extra,normalsecond15extra,normalthird10extra,normalfourth5extra,normalfinal25extra] extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra]
), ),
'easy': Difficulty( 'easy': Difficulty(
baseitems = easybaseitems, baseitems = easybaseitems,
@ -109,7 +108,7 @@ difficulties= {
basicarmor = ['Blue Mail', 'Red Mail'] * 2, basicarmor = ['Blue Mail', 'Red Mail'] * 2,
swordless = ['Rupees (20)'] * 8, swordless = ['Rupees (20)'] * 8,
progressivesword = ['Progressive Sword'] * 7, progressivesword = ['Progressive Sword'] * 7,
basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'] *2 + ['Fighter Sword'], basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'] *2 + ['Fighter Sword'],
timedohko = ['Green Clock'] * 25, timedohko = ['Green Clock'] * 25,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
@ -178,7 +177,7 @@ difficulties= {
def generate_itempool(world): def generate_itempool(world):
if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'] if (world.difficulty not in ['easy', 'normal', 'hard', 'expert', 'insane'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']): or world.mode not in ['open', 'standard', 'swordless'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
raise NotImplementedError('Not supported yet') raise NotImplementedError('Not supported yet')
world.push_item('Ganon', ItemFactory('Triforce'), False) world.push_item('Ganon', ItemFactory('Triforce'), False)
@ -217,19 +216,19 @@ def generate_itempool(world):
fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals) fill_restrictive(world, world.get_all_state(keys=True), crystal_locations, crystals)
def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode): def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
pool=[] pool = []
placed_items=[] placed_items = []
clock_mode=None clock_mode = None
treasure_hunt_count=None treasure_hunt_count = None
treasure_hunt_icon=None treasure_hunt_icon = None
pool.extend(alwaysitems) pool.extend(alwaysitems)
def wantProgressives(): def want_progressives():
return random.choice([True, False]) if progressive == 'random' else progressive=='on' return random.choice([True, False]) if progressive == 'random' else progressive == 'on'
if wantProgressives(): if want_progressives():
pool.extend(progressivegloves) pool.extend(progressivegloves)
else: else:
pool.extend(basicgloves) pool.extend(basicgloves)
@ -253,17 +252,17 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
# all bottles, since only one bottle is available # all bottles, since only one bottle is available
if diff.same_bottle: if diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = random.choice(diff.bottles)
for i in range (diff.bottle_count): for _ in range(diff.bottle_count):
if not diff.same_bottle: if not diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = random.choice(diff.bottles)
pool.append(thisbottle) pool.append(thisbottle)
if wantProgressives(): if want_progressives():
pool.extend(diff.progressiveshield) pool.extend(diff.progressiveshield)
else: else:
pool.extend(diff.basicshield) pool.extend(diff.basicshield)
if wantProgressives(): if want_progressives():
pool.extend(diff.progressivearmor) pool.extend(diff.progressivearmor)
else: else:
pool.extend(diff.basicarmor) pool.extend(diff.basicarmor)
@ -271,21 +270,21 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
if mode == 'swordless': if mode == 'swordless':
pool.extend(diff.swordless) pool.extend(diff.swordless)
elif mode == 'standard': elif mode == 'standard':
if wantProgressives(): if want_progressives():
placed_items.append(('Link\'s Uncle', 'Progressive Sword')) placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
pool.extend(diff.progressivesword) pool.extend(diff.progressivesword)
else: else:
placed_items.append(('Link\'s Uncle', 'Fighter Sword')) placed_items.append(('Link\'s Uncle', 'Fighter Sword'))
pool.extend(diff.basicsword) pool.extend(diff.basicsword)
else: else:
if wantProgressives(): if want_progressives():
pool.extend(diff.progressivesword) pool.extend(diff.progressivesword)
pool.extend(['Progressive Sword']) pool.extend(['Progressive Sword'])
else: else:
pool.extend(diff.basicsword) pool.extend(diff.basicsword)
pool.extend(['Fighter Sword']) 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']: if timer in ['timed', 'timed-countdown']:
pool.extend(diff.timedother) pool.extend(diff.timedother)
@ -306,8 +305,8 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
extraitems -= len(cond_extras) extraitems -= len(cond_extras)
for extra in diff.extras: for extra in diff.extras:
if(extraitems > 0): if extraitems > 0:
pool.extend(extra ) pool.extend(extra)
extraitems -= len(extra) extraitems -= len(extra)
if goal == 'pedestal': if goal == 'pedestal':
@ -315,19 +314,22 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon) 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. # 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 difficulty in ['easy', 'normal', 'hard', 'expert', 'insane']:
for goal in ['ganon', 'triforcehunt', 'pedestal']: for goal in ['ganon', 'triforcehunt', 'pedestal']:
for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']:
for mode in ['open', 'standard', 'swordless']: for mode in ['open', 'standard', 'swordless']:
for progressive in ['on','off']: for progressive in ['on', 'off']:
for shuffle in ['full','insane']: for shuffle in ['full', 'insane']:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode) out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
count = len(out[0]) + len(out[1]) count = len(out[0]) + len(out[1])
correct_count = TotalItemsToPlace correct_count = total_items_to_place
if goal in ['pedestal']: if goal in ['pedestal']:
# pedestal goals generate one extra item # pedestal goals generate one extra item
correct_count += 1 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)) 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()

View File

@ -1,7 +1,7 @@
from BaseClasses import World, Item
import random
import logging import logging
from BaseClasses import Item
def ItemFactory(items): def ItemFactory(items):
ret = [] ret = []
@ -14,13 +14,12 @@ def ItemFactory(items):
advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit = item_table[item] 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)) ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit))
else: else:
logging.getLogger('').warning('Unknown Item: %s' % item) logging.getLogger('').warning('Unknown Item: %s', item)
return None return None
if singleton: if singleton:
return ret[0] return ret[0]
else: return ret
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) # 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)

27
Main.py
View File

@ -1,18 +1,18 @@
from collections import OrderedDict
import json
import logging
import random
import time
from BaseClasses import World, CollectionState, Item from BaseClasses import World, CollectionState, Item
from Regions import create_regions, mark_light_world_regions from Regions import create_regions, mark_light_world_regions
from EntranceShuffle import link_entrances from EntranceShuffle import link_entrances
from Rom import patch_rom, Sprite, LocalRom, JsonRom from Rom import patch_rom, Sprite, LocalRom, JsonRom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive 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, flood_items
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, fill_restrictive, flood_items
from collections import OrderedDict
from ItemList import generate_itempool from ItemList import generate_itempool
from Utils import output_path from Utils import output_path
import random
import time
import logging
import json
__version__ = '0.5.1-dev' __version__ = '0.5.1-dev'
@ -39,7 +39,7 @@ def main(args, seed=None):
world.seed = int(seed) world.seed = int(seed)
random.seed(world.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) create_regions(world)
@ -92,7 +92,7 @@ def main(args, seed=None):
logger.info('Patching ROM.') logger.info('Patching ROM.')
if args.sprite is not None: if args.sprite is not None:
if isinstance(args.sprite,Sprite): if isinstance(args.sprite, Sprite):
sprite = args.sprite sprite = args.sprite
else: else:
sprite = Sprite(args.sprite) sprite = Sprite(args.sprite)
@ -116,7 +116,7 @@ def main(args, seed=None):
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
logger.info('Done. Enjoy.') logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start)) logger.debug('Total Time: %s', time.clock() - start)
return world return world
@ -199,10 +199,10 @@ def create_playthrough(world):
collection_spheres.append(sphere) 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: 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: if not world.check_beatable_only:
raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.') raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.')
else: else:
@ -213,11 +213,10 @@ def create_playthrough(world):
to_delete = [] to_delete = []
for location in sphere: for location in sphere:
# we remove the item at location and check if game is still beatable # 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 old_item = location.item
location.item = None location.item = None
state.remove(old_item) state.remove(old_item)
world._item_cache = {} # need to invalidate
if world.can_beat_game(): if world.can_beat_game():
to_delete.append(location) to_delete.append(location)
else: else:

View File

@ -1,18 +1,19 @@
import argparse
import hashlib
import logging
import os
import random
import time
import sys
from BaseClasses import World from BaseClasses import World
from Regions import create_regions from Regions import create_regions
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom from Rom import patch_rom, LocalRom, write_string_to_rom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons from Dungeons import create_dungeons
from Items import ItemFactory from Items import ItemFactory
from Main import create_playthrough from Main import create_playthrough
import random
import time
import logging
import argparse
import os
import hashlib
import sys
__version__ = '0.2-dev' __version__ = '0.2-dev'
@ -26,8 +27,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] 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): def main(args):
start = time.clock() start_time = time.clock()
# initialize the world # initialize the world
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False) world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False)
@ -41,7 +42,7 @@ def main(args, seed=None):
random.seed(world.seed) 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_regions(world)
create_dungeons(world) create_dungeons(world)
@ -74,7 +75,7 @@ def main(args, seed=None):
logger.info('Patching ROM.') logger.info('Patching ROM.')
if args.sprite is not None: if args.sprite is not None:
sprite = Sprite(args.sprite) sprite = bytearray(open(args.sprite, 'rb').read())
else: else:
sprite = None sprite = None
@ -84,8 +85,8 @@ def main(args, seed=None):
for textname, texttype, text in text_patches: for textname, texttype, text in text_patches:
if texttype == 'text': if texttype == 'text':
write_string_to_rom(rom, textname, text) write_string_to_rom(rom, textname, text)
elif texttype == 'credit': #elif texttype == 'credit':
write_credits_string_to_rom(rom, textname, text) # write_credits_string_to_rom(rom, textname, text)
outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed) outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed)
@ -94,7 +95,7 @@ def main(args, seed=None):
world.spoiler.to_file('%s_Spoiler.txt' % outfilebase) world.spoiler.to_file('%s_Spoiler.txt' % outfilebase)
logger.info('Done. Enjoy.') logger.info('Done. Enjoy.')
logger.debug('Total Time: %s' % (time.clock() - start)) logger.debug('Total Time: %s', time.clock() - start_time)
return world return world
@ -171,7 +172,7 @@ def fill_world(world, plando, text_patches):
locationstr, itemstr = line.split(':', 1) locationstr, itemstr = line.split(':', 1)
location = world.get_location(locationstr.strip()) location = world.get_location(locationstr.strip())
if location is None: if location is None:
logger.warn('Unknown location: %s' % locationstr) logger.warning('Unknown location: %s', locationstr)
continue continue
else: else:
item = ItemFactory(itemstr.strip()) item = ItemFactory(itemstr.strip())
@ -198,7 +199,7 @@ def fill_world(world, plando, text_patches):
world.get_location('Agahnim 2').item = ItemFactory('Beat Agahnim 2') world.get_location('Agahnim 2').item = ItemFactory('Beat Agahnim 2')
if __name__ == '__main__': def start():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') 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') parser.add_argument('--ignore_unsolvable', help='Do not abort if seed is deemed unsolvable.', action='store_true')
@ -230,3 +231,6 @@ if __name__ == '__main__':
logging.basicConfig(format='%(message)s', level=loglevel) logging.basicConfig(format='%(message)s', level=loglevel)
main(args=args) main(args=args)
if __name__ == '__main__':
start()

View File

@ -134,7 +134,7 @@ def create_regions(world):
create_region('Tower of Hera (Top)', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Moldorm', 'Tower of Hera - Prize']), create_region('Tower of Hera (Top)', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Moldorm', 'Tower of Hera - Prize']),
create_region('East Dark World', ['Pyramid', 'Catfish'], ['Pyramid Fairy', 'South Dark World Bridge', 'West Dark World Gap', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', 'Dark Lake Hylia Teleporter', create_region('East Dark World', ['Pyramid', 'Catfish'], ['Pyramid Fairy', 'South Dark World Bridge', 'West Dark World Gap', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)', 'Dark Lake Hylia Teleporter',
'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Dark World Potion Shop', 'Pyramid Hole']), 'Hyrule Castle Ledge Mirror Spot', 'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Dark World Potion Shop', 'Pyramid Hole']),
create_region('Palace of Darkness Hint'), create_region('Palace of Darkness Hint'),
create_region('East Dark World Hint'), create_region('East Dark World Hint'),
create_region('South Dark World', ['Stumpy', 'Digging Game', 'Bombos Tablet'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', create_region('South Dark World', ['Stumpy', 'Digging Game', 'Bombos Tablet'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock',
@ -147,7 +147,7 @@ def create_regions(world):
create_region('Dark Lake Hylia Ledge Hint'), create_region('Dark Lake Hylia Ledge Hint'),
create_region('Dark Lake Hylia Ledge Spike Cave'), create_region('Dark Lake Hylia Ledge Spike Cave'),
create_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', create_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']), 'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
create_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Cave', 'Bumper Cave (Bottom)', 'Skull Woods Forest', create_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Cave', 'Bumper Cave (Bottom)', 'Skull Woods Forest',
'Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop']), 'Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop']),
create_region('Fortune Teller (Dark)'), create_region('Fortune Teller (Dark)'),
@ -180,7 +180,7 @@ def create_regions(world):
create_region('Hookshot Cave', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], create_region('Hookshot Cave', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
create_region('Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']), create_region('Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']),
create_region('Death Mountain Floating Island (Light World)', ['Floating Island'] ), create_region('Death Mountain Floating Island (Light World)', ['Floating Island']),
create_region('Turtle Rock (Top)', None, ['Turtle Rock Drop']), create_region('Turtle Rock (Top)', None, ['Turtle Rock Drop']),
create_region('Mimic Cave', ['Mimic Cave']), create_region('Mimic Cave', ['Mimic Cave']),
@ -292,7 +292,7 @@ def mark_light_world_regions(world):
# Exclude entrances that represent connections from the light world to the dark world # Exclude entrances that represent connections from the light world to the dark world
excluded_entrances = set(['Top of Pyramid', 'Lake Hylia Central Island Teleporter', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter', 'Death Mountain Teleporter', 'East Death Mountain Teleporter', 'Turtle Rock Teleporter']) excluded_entrances = set(['Top of Pyramid', 'Lake Hylia Central Island Teleporter', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter', 'Death Mountain Teleporter', 'East Death Mountain Teleporter', 'Turtle Rock Teleporter'])
starting_regions = ['Links House', 'Cave 45', 'Graveyard Cave','Mimic Cave', 'Death Mountain Floating Island (Light World)','Desert Ledge (West)', 'Lake Hylia Island', 'Spectacle Rock'] starting_regions = ['Links House', 'Cave 45', 'Graveyard Cave', 'Mimic Cave', 'Death Mountain Floating Island (Light World)', 'Desert Ledge (West)', 'Lake Hylia Island', 'Spectacle Rock']
queue = collections.deque([world.get_region(region) for region in starting_regions]) queue = collections.deque([world.get_region(region) for region in starting_regions])
seen = set(queue) seen = set(queue)
while queue: while queue:

69
Rom.py
View File

@ -1,15 +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 io
import json import json
import hashlib import hashlib
import logging import logging
import os import os
import struct 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
from Utils import local_path
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '1deebb05eccefd2ab68297c6e9c0d25f' RANDOMIZERBASEHASH = '1deebb05eccefd2ab68297c6e9c0d25f'
@ -33,13 +35,15 @@ class JsonRom(object):
self.write_bytes(address, int32_as_bytes(value)) self.write_bytes(address, int32_as_bytes(value))
def write_to_file(self, file): 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): class LocalRom(object):
def __init__(self, file, patch=True): 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: if patch:
self.patch_base_rom() self.patch_base_rom()
@ -64,14 +68,15 @@ class LocalRom(object):
# verify correct checksum of baserom # verify correct checksum of baserom
basemd5 = hashlib.md5() basemd5 = hashlib.md5()
basemd5.update(self.buffer) 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.') logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
# extend to 2MB # extend to 2MB
self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer)))) self.buffer.extend(bytearray([0x00] * (2097152 - len(self.buffer))))
# load randomizer patches # load randomizer patches
patches = json.load(open(local_path('data/base2current.json'), 'r')) with open(local_path('data/base2current.json'), 'r') as stream:
patches = json.load(stream)
for patch in patches: for patch in patches:
if isinstance(patch, dict): if isinstance(patch, dict):
for baseaddress, values in patch.items(): for baseaddress, values in patch.items():
@ -80,7 +85,7 @@ class LocalRom(object):
# verify md5 # verify md5
patchedmd5 = hashlib.md5() patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer) 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.') 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): def write_crc(self):
@ -127,7 +132,7 @@ class Sprite(object):
self.palette = filedata[0xDD308:0xDD380] self.palette = filedata[0xDD308:0xDD380]
self.glove_palette = filedata[0xDEDF5:0xDEDF9] self.glove_palette = filedata[0xDEDF5:0xDEDF9]
elif filedata.startswith(b'ZSPR'): elif filedata.startswith(b'ZSPR'):
result = self.parse_zspr(filedata,1) result = self.parse_zspr(filedata, 1)
if result is None: if result is None:
self.valid = False self.valid = False
return return
@ -148,23 +153,27 @@ class Sprite(object):
else: else:
self.valid = False self.valid = False
else: else:
self.valid = False self.valid = False
def decode8(self, pos): def decode8(self, pos):
arr=[[0 for _ in range(8)] for _ in range(8)] arr = [[0 for _ in range(8)] for _ in range(8)]
for y in range(8): for y in range(8):
for x in range(8): for x in range(8):
position = 1<<(7-x) position = 1<<(7-x)
val=0; val = 0
if self.sprite[pos+2*y] & position: val += 1 if self.sprite[pos+2*y] & position:
if self.sprite[pos+2*y+1] & position: val += 2 val += 1
if self.sprite[pos+2*y+16] & position: val += 4 if self.sprite[pos+2*y+1] & position:
if self.sprite[pos+2*y+17] & position: val += 8 val += 2
arr[y][x]= val 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 return arr
def decode16(self, pos): def decode16(self, pos):
arr=[[0 for _ in range(16)] for _ in range(16)] arr = [[0 for _ in range(16)] for _ in range(16)]
top_left = self.decode8(pos) top_left = self.decode8(pos)
top_right = self.decode8(pos+0x20) top_right = self.decode8(pos+0x20)
bottom_left = self.decode8(pos+0x200) bottom_left = self.decode8(pos+0x200)
@ -183,7 +192,7 @@ class Sprite(object):
headersize = struct.calcsize(headerstr) headersize = struct.calcsize(headerstr)
if len(filedata) < headersize: if len(filedata) < headersize:
return None return None
(version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr,filedata) (version, csum, icsum, sprite_offset, sprite_size, palette_offset, palette_size, kind) = struct.unpack_from(headerstr, filedata)
if version not in [1]: if version not in [1]:
logger.error('Error parsing ZSPR file: Version %g not supported', version) logger.error('Error parsing ZSPR file: Version %g not supported', version)
return None return None
@ -211,7 +220,7 @@ class Sprite(object):
real_csum = sum(filedata) % 0x10000 real_csum = sum(filedata) % 0x10000
if real_csum != csum or real_csum ^ 0xFFFF != icsum: if real_csum != csum or real_csum ^ 0xFFFF != icsum:
logger.warning('ZSPR file has incorrect checksum. It may be corrupted.') logger.warning('ZSPR file has incorrect checksum. It may be corrupted.')
pass
sprite = filedata[sprite_offset:sprite_offset + sprite_size] sprite = filedata[sprite_offset:sprite_offset + sprite_size]
palette = filedata[palette_offset:palette_offset + palette_size] palette = filedata[palette_offset:palette_offset + palette_size]
@ -223,14 +232,15 @@ class Sprite(object):
def decode_palette(self): def decode_palette(self):
"Returns the palettes as an array of arrays of 15 colors" "Returns the palettes as an array of arrays of 15 colors"
def array_chunk(arr,size): def array_chunk(arr, size):
return list(zip(*[iter(arr)] * size)) return list(zip(*[iter(arr)] * size))
def make_int16(pair): def make_int16(pair):
return pair[1]<<8 | pair[0] return pair[1]<<8 | pair[0]
def expand_color(i): def expand_color(i):
return ( (i & 0x1F)*8, (i>>5 & 0x1F)*8, (i>>10 & 0x1F)*8) return ((i & 0x1F) * 8, (i>>5 & 0x1F) * 8, (i>>10 & 0x1F) * 8)
raw_palette = self.palette raw_palette = self.palette
if raw_palette is None: raw_palette = Sprite.default_palette if raw_palette is None:
raw_palette = Sprite.default_palette
# turn palette data into a list of RGB tuples with 8 bit values # 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)] palette_as_colors = [expand_color(make_int16(chnk)) for chnk in array_chunk(raw_palette, 2)]
@ -567,9 +577,9 @@ def patch_rom(world, rom, hashtable, beep='normal', sprite=None):
if world.shuffle == 'vanilla': if world.shuffle == 'vanilla':
ERtimeincrease = 0 ERtimeincrease = 0
elif world.shuffle in ['dungeonssimple', 'dungeonsfull']: elif world.shuffle in ['dungeonssimple', 'dungeonsfull']:
ERtimeincrease = 10 ERtimeincrease = 10
else: else:
ERtimeincrease = 20 ERtimeincrease = 20
if world.keysanity: if world.keysanity:
ERtimeincrease = ERtimeincrease + 15 ERtimeincrease = ERtimeincrease + 15
if world.clock_mode == 'off': if world.clock_mode == 'off':
@ -836,7 +846,8 @@ def apply_rom_settings(rom, beep, quickswap, fastmenu, disable_music, sprite):
def write_sprite(rom, sprite): def write_sprite(rom, sprite):
if not sprite.valid: return if not sprite.valid:
return
rom.write_bytes(0x80000, sprite.sprite) rom.write_bytes(0x80000, sprite.sprite)
if sprite.palette is not None: if sprite.palette is not None:
rom.write_bytes(0xDD308, sprite.palette) rom.write_bytes(0xDD308, sprite.palette)

View File

@ -428,17 +428,17 @@ def open_rules(world):
# to prevent key-lock in keysanity we need to prevent these chests from having an item that # to prevent key-lock in keysanity we need to prevent these chests from having an item that
# blocks the small key # blocks the small key
if (world.keysanity): 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 - 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)')) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has('Small Key (Escape)'))
def swordless_rules(world): def swordless_rules(world):
# for the time being swordless mode just inhierits all fixes from open mode. # for the time being swordless mode just inhierits all fixes from open mode.
# should there ever be fixes that apply to open mode but not swordless, this # should there ever be fixes that apply to open mode but not swordless, this
# can be revisited. # can be revisited.
open_rules(world) open_rules(world)
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has('Hammer') or state.has('Bug Catching Net') and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has('Hammer') or state.has('Bug Catching Net') and state.has('Small Key (Agahnims Tower)', 2))
@ -483,7 +483,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)) 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 # 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 # the most complicated one
# if we have back entrance access, we could waste all keys before touching this # if we have back entrance access, we could waste all keys before touching this

View File

@ -120,7 +120,7 @@ class Credits(object):
self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch', self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch',
'lumberjacks', 'grove', 'well', 'smithy', 'kakariko2', 'bridge', 'woods', 'pedestal'] '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 scenes = self.credit_scenes
text = text[:32] text = text[:32]
@ -162,7 +162,7 @@ class SceneCreditLine(CreditLine):
"""Base class for credit lines for the scene portion of the credits""" """Base class for credit lines for the scene portion of the credits"""
def __init__(self, y, text, align='center'): def __init__(self, y, text, align='center'):
self.y = y self.y = y
super().__init__(text,align) super().__init__(text, align)
def header(self, x=None, y=None, length=None): def header(self, x=None, y=None, length=None):
if x is None: if x is None:
@ -456,9 +456,10 @@ def char_to_alttp_char(char):
return char_map.get(char, 0xFF) return char_map.get(char, 0xFF)
class TextMapper(object): class TextMapper(object):
number_offset = None number_offset = None
alpha_offset = 0
char_map = {}
@classmethod @classmethod
def map_char(cls, char): def map_char(cls, char):
if cls.number_offset is not None: if cls.number_offset is not None:
@ -492,7 +493,7 @@ class GreenCreditMapper(TextMapper):
class RedCreditMapper(TextMapper): class RedCreditMapper(TextMapper):
char_map = {' ': 0x9F} #fixme char_map = {' ': 0x9F} #fixme
alpha_offset= -0x61 alpha_offset = -0x61
class LargeCreditTopMapper(TextMapper): class LargeCreditTopMapper(TextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,

View File

@ -1,4 +1,5 @@
import os import os
import subprocess
import sys import sys
def is_bundled(): def is_bundled():
@ -10,7 +11,7 @@ def local_path(path):
if is_bundled(): if is_bundled():
# we are running in a bundle # 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: else:
# we are running in a normal Python environment # we are running in a normal Python environment
local_path.cached_path = os.path.dirname(os.path.abspath(__file__)) local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
@ -40,7 +41,7 @@ def output_path(path):
documents = buf.value documents = buf.value
elif sys.platform == 'darwin': 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 # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
NSDocumentDirectory = 9 NSDocumentDirectory = 9
NSUserDomainMask = 1 NSUserDomainMask = 1
@ -60,7 +61,7 @@ def open_file(filename):
if sys.platform == 'win32': if sys.platform == 'win32':
os.startfile(filename) os.startfile(filename)
else: 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]) subprocess.call([open_command, filename])
def close_console(): def close_console():
@ -69,5 +70,5 @@ def close_console():
import ctypes.wintypes import ctypes.wintypes
try: try:
ctypes.windll.kernel32.FreeConsole() ctypes.windll.kernel32.FreeConsole()
except: except Exception:
pass pass