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:
parent
8b20e39588
commit
823657bc26
|
@ -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.')
|
||||||
|
@ -47,3 +46,6 @@ if __name__ == '__main__':
|
||||||
logging.basicConfig(format='%(message)s', level=loglevel)
|
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||||
|
|
||||||
adjust(args=args)
|
adjust(args=args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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,7 +196,8 @@ 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
|
||||||
|
@ -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,7 +337,6 @@ 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):
|
||||||
|
@ -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)
|
||||||
|
|
13
Dungeons.py
13
Dungeons.py
|
@ -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],
|
||||||
|
|
|
@ -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'],
|
||||||
|
@ -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()
|
||||||
|
|
8
Fill.py
8
Fill.py
|
@ -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):
|
||||||
|
|
24
Gui.py
24
Gui.py
|
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
13
GuiUtils.py
13
GuiUtils.py
|
@ -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
|
||||||
|
@ -54,9 +54,9 @@ class BackgroundTaskProgress(BackgroundTask):
|
||||||
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
|
||||||
|
|
||||||
|
|
42
ItemList.py
42
ItemList.py
|
@ -1,8 +1,9 @@
|
||||||
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.
|
||||||
|
|
||||||
|
@ -62,22 +63,20 @@ Difficulty = namedtuple('Difficulty',
|
||||||
'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,
|
||||||
|
@ -226,10 +225,10 @@ def get_pool_core(progressive,shuffle,difficulty,timer, goal, mode):
|
||||||
|
|
||||||
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,7 +305,7 @@ 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)
|
||||||
|
|
||||||
|
@ -315,7 +314,7 @@ 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']:
|
||||||
|
@ -325,9 +324,12 @@ if __name__ == '__main__':
|
||||||
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()
|
||||||
|
|
7
Items.py
7
Items.py
|
@ -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,12 +14,11 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
|
25
Main.py
25
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 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)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
|
38
Plando.py
38
Plando.py
|
@ -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()
|
||||||
|
|
49
Rom.py
49
Rom.py
|
@ -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):
|
||||||
|
@ -155,11 +160,15 @@ class Sprite(object):
|
||||||
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
|
||||||
|
if self.sprite[pos+2*y+16] & position:
|
||||||
|
val += 4
|
||||||
|
if self.sprite[pos+2*y+17] & position:
|
||||||
|
val += 8
|
||||||
arr[y][x] = val
|
arr[y][x] = val
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -230,7 +239,8 @@ class Sprite(object):
|
||||||
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)]
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
7
Rules.py
7
Rules.py
|
@ -428,7 +428,7 @@ 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)'))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
5
Text.py
5
Text.py
|
@ -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]
|
||||||
|
@ -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:
|
||||||
|
|
9
Utils.py
9
Utils.py
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue