Revert "Revert "Merge branch 'pr/151'""
This reverts commit ce23369b0b
.
This commit is contained in:
parent
ce23369b0b
commit
76cdabd2cb
|
@ -30,3 +30,5 @@ weights/
|
||||||
_persistent_storage.yaml
|
_persistent_storage.yaml
|
||||||
mystery_result_*.yaml
|
mystery_result_*.yaml
|
||||||
/db.db3
|
/db.db3
|
||||||
|
*-errors.txt
|
||||||
|
success.txt
|
||||||
|
|
|
@ -6,7 +6,7 @@ import textwrap
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from AdjusterMain import adjust
|
from AdjusterMain import adjust
|
||||||
from Rom import get_sprite_from_name
|
from Rom import Sprite
|
||||||
|
|
||||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def main():
|
||||||
input(
|
input(
|
||||||
'Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
'Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite):
|
if args.sprite is not None and not os.path.isfile(args.sprite) and not Sprite.get_sprite_from_name(args.sprite):
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -65,7 +65,10 @@ def main():
|
||||||
logging.basicConfig(format='%(message)s', level=loglevel)
|
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||||
args, path = adjust(args=args)
|
args, path = adjust(args=args)
|
||||||
from Utils import persistent_store
|
from Utils import persistent_store
|
||||||
persistent_store("adjuster", "last_settings", args)
|
from Rom import Sprite
|
||||||
|
if isinstance(args.sprite, Sprite):
|
||||||
|
args.sprite = args.sprite.name
|
||||||
|
persistent_store("adjuster", "last_settings_3", args)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -27,7 +27,7 @@ def adjust(args):
|
||||||
palettes_options['hud']=args.hud_palettes
|
palettes_options['hud']=args.hud_palettes
|
||||||
palettes_options['sword']=args.sword_palettes
|
palettes_options['sword']=args.sword_palettes
|
||||||
palettes_options['shield']=args.shield_palettes
|
palettes_options['shield']=args.shield_palettes
|
||||||
palettes_options['link']=args.link_palettes
|
# palettes_options['link']=args.link_palettesvera
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
|
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
|
||||||
args.sprite, palettes_options)
|
args.sprite, palettes_options)
|
||||||
|
|
|
@ -119,13 +119,19 @@ class World(object):
|
||||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||||
set_player_attr('treasure_hunt_count', 0)
|
set_player_attr('treasure_hunt_count', 0)
|
||||||
set_player_attr('clock_mode', False)
|
set_player_attr('clock_mode', False)
|
||||||
|
set_player_attr('countdown_start_time', 10)
|
||||||
|
set_player_attr('red_clock_time', -2)
|
||||||
|
set_player_attr('blue_clock_time', 2)
|
||||||
|
set_player_attr('green_clock_time', 4)
|
||||||
set_player_attr('can_take_damage', True)
|
set_player_attr('can_take_damage', True)
|
||||||
set_player_attr('glitch_boots', True)
|
set_player_attr('glitch_boots', True)
|
||||||
set_player_attr('progression_balancing', True)
|
set_player_attr('progression_balancing', True)
|
||||||
set_player_attr('local_items', set())
|
set_player_attr('local_items', set())
|
||||||
|
set_player_attr('non_local_items', set())
|
||||||
set_player_attr('triforce_pieces_available', 30)
|
set_player_attr('triforce_pieces_available', 30)
|
||||||
set_player_attr('triforce_pieces_required', 20)
|
set_player_attr('triforce_pieces_required', 20)
|
||||||
set_player_attr('shop_shuffle', 'off')
|
set_player_attr('shop_shuffle', 'off')
|
||||||
|
set_player_attr('shop_shuffle_slots', 0)
|
||||||
set_player_attr('shuffle_prizes', "g")
|
set_player_attr('shuffle_prizes', "g")
|
||||||
set_player_attr('sprite_pool', [])
|
set_player_attr('sprite_pool', [])
|
||||||
set_player_attr('dark_room_logic', "lamp")
|
set_player_attr('dark_room_logic', "lamp")
|
||||||
|
@ -335,6 +341,27 @@ class World(object):
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item, location.event, location)
|
self.state.collect(item, location.event, location)
|
||||||
|
|
||||||
|
# TODO: Prevents fast_filling certain items. Move this to a proper filter.
|
||||||
|
if location.parent_region.shop is not None and location.name != 'Potion Shop': # includes potion shop slots but not potion shop powder
|
||||||
|
slot_num = int(location.name[-1]) - 1
|
||||||
|
my_item = location.parent_region.shop.inventory[slot_num]
|
||||||
|
if (my_item is not None and my_item['item'] == item.name) or 'Rupee' in item.name or ('Bee' in item.name and 'Trap' not in item.name):
|
||||||
|
# this will filter items that match the item in the shop or Rupees, or single bees
|
||||||
|
# really not a way for the player to know a renewable item from a player pool item
|
||||||
|
# bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal
|
||||||
|
logging.debug('skipping item shop {}'.format(item.name))
|
||||||
|
else:
|
||||||
|
if my_item is None:
|
||||||
|
location.parent_region.shop.add_inventory(slot_num, 'None', 0)
|
||||||
|
my_item = location.parent_region.shop.inventory[slot_num]
|
||||||
|
else:
|
||||||
|
my_item['replacement'] = my_item['item']
|
||||||
|
my_item['replacement_price'] = my_item['price']
|
||||||
|
my_item['item'] = item.name
|
||||||
|
my_item['price'] = self.random.randrange(1, 61) * 5 # can probably replace this with a price chart
|
||||||
|
my_item['max'] = 1
|
||||||
|
my_item['player'] = item.player if item.player != location.player else 0
|
||||||
|
|
||||||
logging.debug('Placed %s at %s', item, location)
|
logging.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))
|
||||||
|
@ -1135,7 +1162,8 @@ class Shop():
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': replacement,
|
'replacement': replacement,
|
||||||
'replacement_price': replacement_price,
|
'replacement_price': replacement_price,
|
||||||
'create_location': create_location
|
'create_location': create_location,
|
||||||
|
'player': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
|
def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
|
||||||
|
@ -1148,7 +1176,8 @@ class Shop():
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': self.inventory[slot]["item"],
|
'replacement': self.inventory[slot]["item"],
|
||||||
'replacement_price': self.inventory[slot]["price"],
|
'replacement_price': self.inventory[slot]["price"],
|
||||||
'create_location': self.inventory[slot]["create_location"]
|
'create_location': self.inventory[slot]["create_location"],
|
||||||
|
'player': self.inventory[slot]["player"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1234,6 +1263,10 @@ class Spoiler(object):
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
||||||
|
|
||||||
|
if item['player'] > 0:
|
||||||
|
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('—', '(Player {}) — '.format(item['player']))
|
||||||
|
|
||||||
if item['max'] == 0:
|
if item['max'] == 0:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
||||||
|
@ -1307,6 +1340,7 @@ class Spoiler(object):
|
||||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||||
'shop_shuffle': self.world.shop_shuffle,
|
'shop_shuffle': self.world.shop_shuffle,
|
||||||
|
'shop_shuffle_slots': self.world.shop_shuffle_slots,
|
||||||
'shuffle_prizes': self.world.shuffle_prizes,
|
'shuffle_prizes': self.world.shuffle_prizes,
|
||||||
'sprite_pool': self.world.sprite_pool,
|
'sprite_pool': self.world.sprite_pool,
|
||||||
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
||||||
|
|
|
@ -8,7 +8,7 @@ import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from Main import main, get_seed
|
from Main import main, get_seed
|
||||||
from Rom import get_sprite_from_name
|
from Rom import Sprite
|
||||||
from Utils import is_bundled, close_console
|
from Utils import is_bundled, close_console
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,6 +129,14 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
Timed mode. If time runs out, you lose (but can
|
Timed mode. If time runs out, you lose (but can
|
||||||
still keep playing).
|
still keep playing).
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--countdown_start_time', default=defval(10), type=int,
|
||||||
|
help='''Set amount of time, in minutes, to start with in Timed Countdown and Timed OHKO modes''')
|
||||||
|
parser.add_argument('--red_clock_time', default=defval(-2), type=int,
|
||||||
|
help='''Set amount of time, in minutes, to add from picking up red clocks; negative removes time instead''')
|
||||||
|
parser.add_argument('--blue_clock_time', default=defval(2), type=int,
|
||||||
|
help='''Set amount of time, in minutes, to add from picking up blue clocks; negative removes time instead''')
|
||||||
|
parser.add_argument('--green_clock_time', default=defval(4), type=int,
|
||||||
|
help='''Set amount of time, in minutes, to add from picking up green clocks; negative removes time instead''')
|
||||||
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
|
parser.add_argument('--dungeon_counters', default=defval('default'), const='default', nargs='?', choices=['default', 'on', 'pickup', 'off'],
|
||||||
help='''\
|
help='''\
|
||||||
Select dungeon counter display settings. (default: %(default)s)
|
Select dungeon counter display settings. (default: %(default)s)
|
||||||
|
@ -173,7 +181,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
slightly biased to placing progression items with
|
slightly biased to placing progression items with
|
||||||
less restrictions.
|
less restrictions.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--shuffle', default=defval('full'), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'],
|
parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'],
|
||||||
help='''\
|
help='''\
|
||||||
Select Entrance Shuffling Algorithm. (default: %(default)s)
|
Select Entrance Shuffling Algorithm. (default: %(default)s)
|
||||||
Full: Mix cave and dungeon entrances freely while limiting
|
Full: Mix cave and dungeon entrances freely while limiting
|
||||||
|
@ -258,6 +266,8 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
||||||
parser.add_argument('--local_items', default=defval(''),
|
parser.add_argument('--local_items', default=defval(''),
|
||||||
help='Specifies a list of items that will not spread across the multiworld (separated by commas)')
|
help='Specifies a list of items that will not spread across the multiworld (separated by commas)')
|
||||||
|
parser.add_argument('--non_local_items', default=defval(''),
|
||||||
|
help='Specifies a list of items that will spread across the multiworld (separated by commas)')
|
||||||
parser.add_argument('--custom', default=defval(False), help='Not supported.')
|
parser.add_argument('--custom', default=defval(False), help='Not supported.')
|
||||||
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
|
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
|
||||||
parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
||||||
|
@ -320,6 +330,11 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
p: randomize the prices of the items in shop inventories
|
p: randomize the prices of the items in shop inventories
|
||||||
u: shuffle capacity upgrades into the item pool
|
u: shuffle capacity upgrades into the item pool
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--shop_shuffle_slots', default=defval(0),
|
||||||
|
type=lambda value: min(max(int(value), 1), 96),
|
||||||
|
help='''
|
||||||
|
Maximum amount of shop slots able to be filled by items from the item pool.
|
||||||
|
''')
|
||||||
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
||||||
parser.add_argument('--sprite_pool', help='''\
|
parser.add_argument('--sprite_pool', help='''\
|
||||||
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
Specifies a colon separated list of sprites used for random/randomonevent. If not specified, the full sprite pool is used.''')
|
||||||
|
@ -366,14 +381,16 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
|
|
||||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||||
'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer',
|
'shuffle', 'crystals_ganon', 'crystals_gt', 'open_pyramid', 'timer',
|
||||||
|
'countdown_start_time', 'red_clock_time', 'blue_clock_time', 'green_clock_time',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
'local_items', 'non_local_items', 'retro', 'accessibility', 'hints', 'beemizer',
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle",
|
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
|
||||||
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'restrict_dungeon_item_on_boss']:
|
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic', 'restrict_dungeon_item_on_boss',
|
||||||
|
'hud_palettes', 'sword_palettes', 'shield_palettes', 'link_palettes']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
|
@ -400,7 +417,7 @@ def start():
|
||||||
input(
|
input(
|
||||||
'Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
'Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if any([sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in
|
if any([sprite is not None and not os.path.isfile(sprite) and not Sprite.get_sprite_from_name(sprite) for sprite in
|
||||||
args.sprite.values()]):
|
args.sprite.values()]):
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
3
Fill.py
3
Fill.py
|
@ -54,8 +54,9 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
|
||||||
for location in region.locations:
|
for location in region.locations:
|
||||||
if location.item and not location.event:
|
if location.item and not location.event:
|
||||||
placements.append(location)
|
placements.append(location)
|
||||||
|
|
||||||
raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
|
raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid. '
|
||||||
f'Already placed {len(placements)}: {", ".join(placements)}')
|
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
|
|
|
@ -14,12 +14,12 @@ def set_icon(window):
|
||||||
# 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
|
||||||
# multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems
|
# multithreading support. Therefore I will assume it is not thread safe to avoid any possible problems
|
||||||
class BackgroundTask(object):
|
class BackgroundTask(object):
|
||||||
def __init__(self, window, code_to_run):
|
def __init__(self, window, code_to_run, *args):
|
||||||
self.window = window
|
self.window = window
|
||||||
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, *args))
|
||||||
self.task.start()
|
self.task.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -45,7 +45,7 @@ class BackgroundTask(object):
|
||||||
self.window.after(100, self.process_queue)
|
self.window.after(100, self.process_queue)
|
||||||
|
|
||||||
class BackgroundTaskProgress(BackgroundTask):
|
class BackgroundTaskProgress(BackgroundTask):
|
||||||
def __init__(self, parent, code_to_run, title):
|
def __init__(self, parent, code_to_run, title, *args):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.window = tk.Toplevel(parent)
|
self.window = tk.Toplevel(parent)
|
||||||
self.window['padx'] = 5
|
self.window['padx'] = 5
|
||||||
|
@ -65,7 +65,7 @@ class BackgroundTaskProgress(BackgroundTask):
|
||||||
|
|
||||||
set_icon(self.window)
|
set_icon(self.window)
|
||||||
self.window.focus()
|
self.window.focus()
|
||||||
super().__init__(self.window, code_to_run)
|
super().__init__(self.window, code_to_run, *args)
|
||||||
|
|
||||||
#safe to call from worker thread
|
#safe to call from worker thread
|
||||||
def update_status(self, text):
|
def update_status(self, text):
|
||||||
|
|
40
Main.py
40
Main.py
|
@ -10,7 +10,7 @@ import zlib
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
|
||||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory, item_table
|
||||||
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
|
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance
|
||||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||||
|
@ -72,6 +72,10 @@ def main(args, seed=None):
|
||||||
world.tile_shuffle = args.tile_shuffle.copy()
|
world.tile_shuffle = args.tile_shuffle.copy()
|
||||||
world.beemizer = args.beemizer.copy()
|
world.beemizer = args.beemizer.copy()
|
||||||
world.timer = args.timer.copy()
|
world.timer = args.timer.copy()
|
||||||
|
world.countdown_start_time = args.countdown_start_time.copy()
|
||||||
|
world.red_clock_time = args.red_clock_time.copy()
|
||||||
|
world.blue_clock_time = args.blue_clock_time.copy()
|
||||||
|
world.green_clock_time = args.green_clock_time.copy()
|
||||||
world.shufflepots = args.shufflepots.copy()
|
world.shufflepots = args.shufflepots.copy()
|
||||||
world.progressive = args.progressive.copy()
|
world.progressive = args.progressive.copy()
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
|
@ -79,6 +83,7 @@ def main(args, seed=None):
|
||||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||||
world.shop_shuffle = args.shop_shuffle.copy()
|
world.shop_shuffle = args.shop_shuffle.copy()
|
||||||
|
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
||||||
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
||||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||||
world.sprite_pool = args.sprite_pool.copy()
|
world.sprite_pool = args.sprite_pool.copy()
|
||||||
|
@ -106,7 +111,13 @@ def main(args, seed=None):
|
||||||
item = ItemFactory(tok.strip(), player)
|
item = ItemFactory(tok.strip(), player)
|
||||||
if item:
|
if item:
|
||||||
world.push_precollected(item)
|
world.push_precollected(item)
|
||||||
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',')}
|
# item in item_table gets checked in mystery, but not CLI - so we double-check here
|
||||||
|
world.local_items[player] = {item.strip() for item in args.local_items[player].split(',') if
|
||||||
|
item.strip() in item_table}
|
||||||
|
world.non_local_items[player] = {item.strip() for item in args.non_local_items[player].split(',') if
|
||||||
|
item.strip() in item_table}
|
||||||
|
# items can't be both local and non-local, prefer local
|
||||||
|
world.non_local_items[player] -= world.local_items[player]
|
||||||
|
|
||||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
|
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], world.triforce_pieces_required[player])
|
||||||
|
|
||||||
|
@ -297,6 +308,30 @@ def main(args, seed=None):
|
||||||
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
||||||
er_hint_data[region.player][location.address] = main_entrance.name
|
er_hint_data[region.player][location.address] = main_entrance.name
|
||||||
|
|
||||||
|
ordered_areas = ('Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
|
||||||
|
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
|
||||||
|
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total")
|
||||||
|
|
||||||
|
checks_in_area = {player: {area: list() for area in ordered_areas}
|
||||||
|
for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
checks_in_area[player]["Total"] = 0
|
||||||
|
|
||||||
|
for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]:
|
||||||
|
main_entrance = get_entrance_to_region(location.parent_region)
|
||||||
|
if location.parent_region.dungeon:
|
||||||
|
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
|
||||||
|
'Inverted Ganons Tower': 'Ganons Tower'}\
|
||||||
|
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
|
||||||
|
checks_in_area[location.player][dungeonname].append(location.address)
|
||||||
|
elif main_entrance.parent_region.type == RegionType.LightWorld:
|
||||||
|
checks_in_area[location.player]["Light World"].append(location.address)
|
||||||
|
elif main_entrance.parent_region.type == RegionType.DarkWorld:
|
||||||
|
checks_in_area[location.player]["Dark World"].append(location.address)
|
||||||
|
checks_in_area[location.player]["Total"] += 1
|
||||||
|
|
||||||
|
|
||||||
precollected_items = [[] for player in range(world.players)]
|
precollected_items = [[] for player in range(world.players)]
|
||||||
for item in world.precollected_items:
|
for item in world.precollected_items:
|
||||||
precollected_items[item.player - 1].append(item.code)
|
precollected_items[item.player - 1].append(item.code)
|
||||||
|
@ -323,6 +358,7 @@ def main(args, seed=None):
|
||||||
(location.item.code, location.item.player))
|
(location.item.code, location.item.player))
|
||||||
for location in world.get_filled_locations() if
|
for location in world.get_filled_locations() if
|
||||||
type(location.address) is int],
|
type(location.address) is int],
|
||||||
|
"checks_in_area": checks_in_area,
|
||||||
"server_options": get_options()["server_options"],
|
"server_options": get_options()["server_options"],
|
||||||
"er_hint_data": er_hint_data,
|
"er_hint_data": er_hint_data,
|
||||||
"precollected_items": precollected_items,
|
"precollected_items": precollected_items,
|
||||||
|
|
24
Mystery.py
24
Mystery.py
|
@ -11,7 +11,7 @@ import ModuleUpdate
|
||||||
ModuleUpdate.update()
|
ModuleUpdate.update()
|
||||||
|
|
||||||
from Utils import parse_yaml
|
from Utils import parse_yaml
|
||||||
from Rom import get_sprite_from_name
|
from Rom import Sprite
|
||||||
from EntranceRandomizer import parse_arguments
|
from EntranceRandomizer import parse_arguments
|
||||||
from Main import main as ERmain
|
from Main import main as ERmain
|
||||||
from Main import get_seed, seeddigits
|
from Main import get_seed, seeddigits
|
||||||
|
@ -167,7 +167,7 @@ def main(args=None, callback=ERmain):
|
||||||
if path:
|
if path:
|
||||||
try:
|
try:
|
||||||
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
||||||
if settings.sprite and not os.path.isfile(settings.sprite) and not get_sprite_from_name(
|
if settings.sprite and not os.path.isfile(settings.sprite) and not Sprite.get_sprite_from_name(
|
||||||
settings.sprite):
|
settings.sprite):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Warning: The chosen sprite, \"{settings.sprite}\", for yaml \"{path}\", does not exist.")
|
f"Warning: The chosen sprite, \"{settings.sprite}\", for yaml \"{path}\", does not exist.")
|
||||||
|
@ -238,6 +238,8 @@ def convert_to_on_off(value):
|
||||||
def get_choice(option, root, value=None) -> typing.Any:
|
def get_choice(option, root, value=None) -> typing.Any:
|
||||||
if option not in root:
|
if option not in root:
|
||||||
return value
|
return value
|
||||||
|
if type(root[option]) is list:
|
||||||
|
return interpret_on_off(random.choices(root[option])[0])
|
||||||
if type(root[option]) is not dict:
|
if type(root[option]) is not dict:
|
||||||
return interpret_on_off(root[option])
|
return interpret_on_off(root[option])
|
||||||
if not root[option]:
|
if not root[option]:
|
||||||
|
@ -360,6 +362,8 @@ def roll_settings(weights):
|
||||||
# change minimum to required pieces to avoid problems
|
# change minimum to required pieces to avoid problems
|
||||||
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
||||||
|
|
||||||
|
ret.shop_shuffle_slots = int(get_choice('shop_shuffle_slots', weights, '0'))
|
||||||
|
|
||||||
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
||||||
if not ret.shop_shuffle:
|
if not ret.shop_shuffle:
|
||||||
ret.shop_shuffle = ''
|
ret.shop_shuffle = ''
|
||||||
|
@ -448,6 +452,11 @@ def roll_settings(weights):
|
||||||
'timed_countdown': 'timed-countdown',
|
'timed_countdown': 'timed-countdown',
|
||||||
'display': 'display'}[get_choice('timer', weights, False)]
|
'display': 'display'}[get_choice('timer', weights, False)]
|
||||||
|
|
||||||
|
ret.countdown_start_time = int(get_choice('countdown_start_time', weights, 10))
|
||||||
|
ret.red_clock_time = int(get_choice('red_clock_time', weights, -2))
|
||||||
|
ret.blue_clock_time = int(get_choice('blue_clock_time', weights, 2))
|
||||||
|
ret.green_clock_time = int(get_choice('green_clock_time', weights, 4))
|
||||||
|
|
||||||
ret.dungeon_counters = get_choice('dungeon_counters', weights, 'default')
|
ret.dungeon_counters = get_choice('dungeon_counters', weights, 'default')
|
||||||
|
|
||||||
ret.progressive = convert_to_on_off(get_choice('progressive', weights, 'on'))
|
ret.progressive = convert_to_on_off(get_choice('progressive', weights, 'on'))
|
||||||
|
@ -487,6 +496,17 @@ def roll_settings(weights):
|
||||||
|
|
||||||
ret.local_items = ",".join(ret.local_items)
|
ret.local_items = ",".join(ret.local_items)
|
||||||
|
|
||||||
|
ret.non_local_items = set()
|
||||||
|
for item_name in weights.get('non_local_items', []):
|
||||||
|
items = item_name_groups.get(item_name, {item_name})
|
||||||
|
for item in items:
|
||||||
|
if item in item_table:
|
||||||
|
ret.non_local_items.add(item)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Could not force item {item} to be world-non-local, as it was not recognized.")
|
||||||
|
|
||||||
|
ret.non_local_items = ",".join(ret.non_local_items)
|
||||||
|
|
||||||
if 'rom' in weights:
|
if 'rom' in weights:
|
||||||
romweights = weights['rom']
|
romweights = weights['rom']
|
||||||
|
|
||||||
|
|
107
Regions.py
107
Regions.py
|
@ -368,7 +368,17 @@ def create_shops(world, player: int):
|
||||||
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||||
ShopType.Shop: Shop,
|
ShopType.Shop: Shop,
|
||||||
ShopType.TakeAny: TakeAny}
|
ShopType.TakeAny: TakeAny}
|
||||||
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items():
|
option = world.shop_shuffle[player]
|
||||||
|
my_shop_table = dict(shop_table)
|
||||||
|
|
||||||
|
num_slots = int(world.shop_shuffle_slots[player])
|
||||||
|
|
||||||
|
my_shop_slots = ([True] * num_slots + [False] * (len(shop_table) * 3))[:len(shop_table)*3 - 2]
|
||||||
|
|
||||||
|
world.random.shuffle(my_shop_slots)
|
||||||
|
|
||||||
|
from Items import ItemFactory
|
||||||
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items():
|
||||||
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
||||||
locked = True
|
locked = True
|
||||||
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
@ -378,6 +388,19 @@ def create_shops(world, player: int):
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, item in enumerate(inventory):
|
for index, item in enumerate(inventory):
|
||||||
shop.add_inventory(index, *item)
|
shop.add_inventory(index, *item)
|
||||||
|
if region_name == 'Potion Shop':
|
||||||
|
pass
|
||||||
|
elif region_name == 'Capacity Upgrade':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if my_shop_slots.pop():
|
||||||
|
additional_item = world.random.choice(['Rupees (20)', 'Rupees (50)', 'Rupees (100)'])
|
||||||
|
world.itempool.append(ItemFactory(additional_item, player))
|
||||||
|
loc = Location(player, "{} Slot Item {}".format(shop.region.name, index+1), parent=shop.region)
|
||||||
|
shop.region.locations.append(loc)
|
||||||
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
|
world.clear_location_cache()
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
|
@ -393,10 +416,63 @@ shop_table = {
|
||||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
'Potion Shop': (0x0109, ShopType.Shop, 0xA0, True, False, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
||||||
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
old_location_address_to_new_location_address = {
|
||||||
|
0x2eb18: 0x18001b, # Bottle Merchant
|
||||||
|
0x33d68: 0x18001a, # Purple Chest
|
||||||
|
0x2df45: 0x18001d, # Link's Uncle
|
||||||
|
0x2f1fc: 0x180008, # Sahasrahla
|
||||||
|
0x18002a: 0x18001c, # Black Smith
|
||||||
|
0x339cf: 0x180009, # Sick Kid
|
||||||
|
0x33e7d: 0x180019, # Hobo
|
||||||
|
0x180160: 0x18000b, # Desert Palace - Desert Torch
|
||||||
|
0x289b0: 0x180018, # Master Sword Pedestal
|
||||||
|
0xf69fa: 0x180007, # Old Man
|
||||||
|
0x180162: 0x18000d, # Tower of Hera - Basement Cage
|
||||||
|
0x330c7: 0x18000a, # Stumpy
|
||||||
|
0x180161: 0x18000c # Ganons Tower - Bob's Torch
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
key_drop_data = {
|
||||||
|
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037],
|
||||||
|
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034],
|
||||||
|
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d],
|
||||||
|
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d],
|
||||||
|
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b],
|
||||||
|
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049],
|
||||||
|
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031],
|
||||||
|
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b],
|
||||||
|
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028],
|
||||||
|
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061],
|
||||||
|
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052],
|
||||||
|
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019],
|
||||||
|
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016],
|
||||||
|
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013],
|
||||||
|
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010],
|
||||||
|
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a],
|
||||||
|
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e],
|
||||||
|
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c],
|
||||||
|
'Thieves\' Town - Hallway Pot Key': [0x14005d, 0x14005e],
|
||||||
|
'Thieves\' Town - Spike Switch Pot Key': [0x14004e, 0x14004f],
|
||||||
|
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004],
|
||||||
|
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022],
|
||||||
|
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025],
|
||||||
|
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046],
|
||||||
|
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055],
|
||||||
|
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c],
|
||||||
|
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064],
|
||||||
|
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058],
|
||||||
|
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007],
|
||||||
|
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040],
|
||||||
|
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043],
|
||||||
|
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a],
|
||||||
|
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f]
|
||||||
|
}
|
||||||
|
|
||||||
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||||
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
||||||
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
||||||
|
@ -640,7 +716,9 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||||
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||||
|
|
||||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_id_to_name[-1] = "cheat console"
|
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
|
||||||
|
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
||||||
|
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
|
||||||
|
|
||||||
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
||||||
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
||||||
|
@ -745,7 +823,28 @@ lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 19125
|
||||||
60103: 'Ganons Tower', 60106: 'Ganons Tower', 60109: 'Ganons Tower',
|
60103: 'Ganons Tower', 60106: 'Ganons Tower', 60109: 'Ganons Tower',
|
||||||
60127: 'Ganons Tower', 60118: 'Ganons Tower', 60148: 'Ganons Tower',
|
60127: 'Ganons Tower', 60118: 'Ganons Tower', 60148: 'Ganons Tower',
|
||||||
60151: 'Ganons Tower', 60145: 'Ganons Tower', 60157: 'Ganons Tower',
|
60151: 'Ganons Tower', 60145: 'Ganons Tower', 60157: 'Ganons Tower',
|
||||||
60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower'}
|
60160: 'Ganons Tower', 60163: 'Ganons Tower', 60166: 'Ganons Tower',
|
||||||
|
0x140037: 'Hyrule Castle Entrance (South)',
|
||||||
|
0x140034: 'Hyrule Castle Entrance (South)',
|
||||||
|
0x14000d: 'Hyrule Castle Entrance (South)',
|
||||||
|
0x14003d: 'Hyrule Castle Entrance (South)',
|
||||||
|
0x14005b: 'Eastern Palace', 0x140049: 'Eastern Palace',
|
||||||
|
0x140031: 'Desert Palace Entrance (North)',
|
||||||
|
0x14002b: 'Desert Palace Entrance (North)',
|
||||||
|
0x140028: 'Desert Palace Entrance (North)',
|
||||||
|
0x140061: 'Agahnims Tower', 0x140052: 'Agahnims Tower',
|
||||||
|
0x140019: 'Swamp Palace', 0x140016: 'Swamp Palace', 0x140013: 'Swamp Palace',
|
||||||
|
0x140010: 'Swamp Palace', 0x14000a: 'Swamp Palace',
|
||||||
|
0x14002e: 'Skull Woods Second Section Door (East)',
|
||||||
|
0x14001c: 'Skull Woods Final Section',
|
||||||
|
0x14005e: 'Thieves Town', 0x14004f: 'Thieves Town',
|
||||||
|
0x140004: 'Ice Palace', 0x140022: 'Ice Palace',
|
||||||
|
0x140025: 'Ice Palace', 0x140046: 'Ice Palace',
|
||||||
|
0x140055: 'Misery Mire', 0x14004c: 'Misery Mire',
|
||||||
|
0x140064: 'Misery Mire',
|
||||||
|
0x140058: 'Turtle Rock', 0x140007: 'Dark Death Mountain Ledge (West)',
|
||||||
|
0x140040: 'Ganons Tower', 0x140043: 'Ganons Tower',
|
||||||
|
0x14003a: 'Ganons Tower', 0x14001f: 'Ganons Tower'}
|
||||||
|
|
||||||
lookup_prizes = {location for location in location_table if location.endswith(" - Prize")}
|
lookup_prizes = {location for location in location_table if location.endswith(" - Prize")}
|
||||||
lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")}
|
lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")}
|
6
Rules.py
6
Rules.py
|
@ -104,7 +104,7 @@ def mirrorless_path_to_castle_courtyard(world, player):
|
||||||
else:
|
else:
|
||||||
queue.append((entrance.connected_region, new_path))
|
queue.append((entrance.connected_region, new_path))
|
||||||
|
|
||||||
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player}")
|
raise Exception(f"Could not find mirrorless path to castle courtyard for Player {player} ({world.get_player_names(player)})")
|
||||||
|
|
||||||
def set_rule(spot, rule):
|
def set_rule(spot, rule):
|
||||||
spot.access_rule = rule
|
spot.access_rule = rule
|
||||||
|
@ -179,6 +179,10 @@ def locality_rules(world, player):
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.player != player:
|
if location.player != player:
|
||||||
forbid_items_for_player(location, world.local_items[player], player)
|
forbid_items_for_player(location, world.local_items[player], player)
|
||||||
|
if world.non_local_items[player]:
|
||||||
|
for location in world.get_locations():
|
||||||
|
if location.player == player:
|
||||||
|
forbid_items_for_player(location, world.non_local_items[player], player)
|
||||||
|
|
||||||
|
|
||||||
non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - {
|
non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - {
|
||||||
|
|
5
Text.py
5
Text.py
|
@ -266,7 +266,7 @@ junk_texts = [
|
||||||
"{C:GREEN}\n>Secret power\nis said to be\nin the arrow.",
|
"{C:GREEN}\n>Secret power\nis said to be\nin the arrow.",
|
||||||
"{C:GREEN}\nAim at the\neyes of Gohma.\n >",
|
"{C:GREEN}\nAim at the\neyes of Gohma.\n >",
|
||||||
"{C:GREEN}\nGrumble,\ngrumble…\n >",
|
"{C:GREEN}\nGrumble,\ngrumble…\n >",
|
||||||
"{C:GREEN}\n10th enemy\nhas the bomb.\n >",
|
# "{C:GREEN}\n10th enemy\nhas the bomb.\n >", removed as people may assume it applies to this game
|
||||||
"{C:GREEN}\nGo to the\nnext room.\n >",
|
"{C:GREEN}\nGo to the\nnext room.\n >",
|
||||||
"{C:GREEN}\n>Thanks, @\nYou’re the\nhero of Hyrule",
|
"{C:GREEN}\n>Thanks, @\nYou’re the\nhero of Hyrule",
|
||||||
"{C:GREEN}\nThere’s always\nmoney in the\nBanana Stand>",
|
"{C:GREEN}\nThere’s always\nmoney in the\nBanana Stand>",
|
||||||
|
@ -1228,7 +1228,8 @@ class GoldCreditMapper(CharTextMapper):
|
||||||
|
|
||||||
class GreenCreditMapper(CharTextMapper):
|
class GreenCreditMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F,
|
char_map = {' ': 0x9F,
|
||||||
'·': 0x52}
|
'·': 0x52,
|
||||||
|
'.': 0x52}
|
||||||
alpha_offset = -0x29
|
alpha_offset = -0x29
|
||||||
|
|
||||||
class RedCreditMapper(CharTextMapper):
|
class RedCreditMapper(CharTextMapper):
|
||||||
|
|
|
@ -47,6 +47,8 @@ app.config["PONY"] = {
|
||||||
}
|
}
|
||||||
app.config["MAX_ROLL"] = 20
|
app.config["MAX_ROLL"] = 20
|
||||||
app.config["CACHE_TYPE"] = "simple"
|
app.config["CACHE_TYPE"] = "simple"
|
||||||
|
app.config["JSON_AS_ASCII"] = False
|
||||||
|
|
||||||
app.autoversion = True
|
app.autoversion = True
|
||||||
av = Autoversion(app)
|
av = Autoversion(app)
|
||||||
cache = Cache(app)
|
cache = Cache(app)
|
||||||
|
|
|
@ -37,9 +37,10 @@ def download_raw_patch(seed_id, player_id):
|
||||||
return "Patch not found"
|
return "Patch not found"
|
||||||
else:
|
else:
|
||||||
import io
|
import io
|
||||||
|
if patch.seed.multidata:
|
||||||
pname = patch.seed.multidata["names"][0][patch.player - 1]
|
pname = patch.seed.multidata["names"][0][patch.player - 1]
|
||||||
|
else:
|
||||||
|
pname = "unknown"
|
||||||
patch_data = update_patch_data(patch.data, server="")
|
patch_data = update_patch_data(patch.data, server="")
|
||||||
patch_data = io.BytesIO(patch_data)
|
patch_data = io.BytesIO(patch_data)
|
||||||
|
|
||||||
|
|
|
@ -50,5 +50,5 @@ class Generation(db.Entity):
|
||||||
id = PrimaryKey(UUID, default=uuid4)
|
id = PrimaryKey(UUID, default=uuid4)
|
||||||
owner = Required(UUID)
|
owner = Required(UUID)
|
||||||
options = Required(bytes, lazy=True) # these didn't work as JSON on mariaDB, so they're getting pickled now
|
options = Required(bytes, lazy=True) # these didn't work as JSON on mariaDB, so they're getting pickled now
|
||||||
meta = Required(bytes, lazy=True)
|
meta = Required(bytes, lazy=True) # if state is -1 (error) this will contain an utf-8 encoded error message
|
||||||
state = Required(int, default=0, index=True)
|
state = Required(int, default=0, index=True)
|
||||||
|
|
|
@ -3,5 +3,5 @@ pony>=0.7.14
|
||||||
waitress>=1.4.4
|
waitress>=1.4.4
|
||||||
flask-caching>=1.9.0
|
flask-caching>=1.9.0
|
||||||
Flask-Autoversion>=0.2.0
|
Flask-Autoversion>=0.2.0
|
||||||
Flask-Compress>=1.7.0
|
Flask-Compress>=1.8.0
|
||||||
Flask-Limiter>=1.4
|
Flask-Limiter>=1.4
|
||||||
|
|
|
@ -165,7 +165,6 @@ item_pool:
|
||||||
normal: 50 # Item availability remains unchanged from vanilla game
|
normal: 50 # Item availability remains unchanged from vanilla game
|
||||||
hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless)
|
hard: 0 # Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless)
|
||||||
expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless)
|
expert: 0 # Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless)
|
||||||
crowd_control: 0 # Sets up the item pool for the crowd control extension. Do not use it without crowd control
|
|
||||||
item_functionality:
|
item_functionality:
|
||||||
easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere.
|
easy: 0 # Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere.
|
||||||
normal: 50 # Vanilla item functionality
|
normal: 50 # Vanilla item functionality
|
||||||
|
@ -232,6 +231,22 @@ timer:
|
||||||
ohko: 0 # Timer always at zero. Permanent OHKO.
|
ohko: 0 # Timer always at zero. Permanent OHKO.
|
||||||
timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.
|
timed_countdown: 0 # Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.
|
||||||
display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool.
|
display: 0 # Displays a timer, but otherwise does not affect gameplay or the item pool.
|
||||||
|
countdown_start_time: # For timed_ohko and timed_countdown timer modes, the amount of time in minutes to start with
|
||||||
|
0: 0 # For timed_ohko, starts in OHKO mode when starting the game
|
||||||
|
10: 50
|
||||||
|
20: 0
|
||||||
|
30: 0
|
||||||
|
60: 0
|
||||||
|
red_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock
|
||||||
|
-2: 50
|
||||||
|
1: 0
|
||||||
|
blue_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock
|
||||||
|
1: 0
|
||||||
|
2: 50
|
||||||
|
green_clock_time: # For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock
|
||||||
|
4: 50
|
||||||
|
10: 0
|
||||||
|
15: 0
|
||||||
# Can be uncommented to use it
|
# Can be uncommented to use it
|
||||||
# local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords"
|
# local_items: # Force certain items to appear in your world only, not across the multiworld. Recognizes some group names, like "Swords"
|
||||||
# - "Moon Pearl"
|
# - "Moon Pearl"
|
||||||
|
@ -289,6 +304,9 @@ intensity: # Only available if the host uses the doors branch, it is ignored oth
|
||||||
2: 0 # And shuffles open edges and straight staircases
|
2: 0 # And shuffles open edges and straight staircases
|
||||||
3: 0 # And shuffles dungeon lobbies
|
3: 0 # And shuffles dungeon lobbies
|
||||||
random: 0 # Picks one of those at random
|
random: 0 # Picks one of those at random
|
||||||
|
key_drop_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise
|
||||||
|
on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249.
|
||||||
|
off: 50
|
||||||
experimental: # Only available if the host uses the doors branch, it is ignored otherwise
|
experimental: # Only available if the host uses the doors branch, it is ignored otherwise
|
||||||
on: 0 # Enables experimental features. Currently, this is just the dungeon keys in chest counter.
|
on: 0 # Enables experimental features. Currently, this is just the dungeon keys in chest counter.
|
||||||
off: 50
|
off: 50
|
||||||
|
@ -391,13 +409,3 @@ rom:
|
||||||
dizzy: 0
|
dizzy: 0
|
||||||
sick: 0
|
sick: 0
|
||||||
puke: 0
|
puke: 0
|
||||||
uw_palettes: # Change the colors of shields
|
|
||||||
default: 50 # No changes
|
|
||||||
random: 0 # Shuffle the colors
|
|
||||||
blackout: 0 # Never use this
|
|
||||||
grayscale: 0
|
|
||||||
negative: 0
|
|
||||||
classic: 0
|
|
||||||
dizzy: 0
|
|
||||||
sick: 0
|
|
||||||
puke: 0
|
|
|
@ -124,7 +124,7 @@
|
||||||
<td>{{ player_names[(team, loop.index)]|e }}</td>
|
<td>{{ player_names[(team, loop.index)]|e }}</td>
|
||||||
{%- for area in ordered_areas -%}
|
{%- for area in ordered_areas -%}
|
||||||
{%- set checks_done = checks[area] -%}
|
{%- set checks_done = checks[area] -%}
|
||||||
{%- set checks_total = checks_in_area[area] -%}
|
{%- set checks_total = checks_in_area[player][area] -%}
|
||||||
{%- if checks_done == checks_total -%}
|
{%- if checks_done == checks_total -%}
|
||||||
<td class="item-acquired center-column">
|
<td class="item-acquired center-column">
|
||||||
{{ checks_done }}/{{ checks_total }}</td>
|
{{ checks_done }}/{{ checks_total }}</td>
|
||||||
|
|
|
@ -180,6 +180,25 @@ default_locations = {
|
||||||
60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157},
|
60121, 60124, 60127, 1573217, 60130, 60133, 60136, 60139, 60142, 60145, 60148, 60151, 60157},
|
||||||
'Total': set()}
|
'Total': set()}
|
||||||
|
|
||||||
|
key_only_locations = {
|
||||||
|
'Light World': set(),
|
||||||
|
'Dark World': set(),
|
||||||
|
'Desert Palace': {0x140031, 0x14002b, 0x140061, 0x140028},
|
||||||
|
'Eastern Palace': {0x14005b, 0x140049},
|
||||||
|
'Hyrule Castle': {0x140037, 0x140034, 0x14000d, 0x14003d},
|
||||||
|
'Agahnims Tower': {0x140061, 0x140052},
|
||||||
|
'Tower of Hera': set(),
|
||||||
|
'Swamp Palace': {0x140019, 0x140016, 0x140013, 0x140010, 0x14000a},
|
||||||
|
'Thieves Town': {0x14005e, 0x14004f},
|
||||||
|
'Skull Woods': {0x14002e, 0x14001c},
|
||||||
|
'Ice Palace': {0x140004, 0x140022, 0x140025, 0x140046},
|
||||||
|
'Misery Mire': {0x140055, 0x14004c, 0x140064},
|
||||||
|
'Turtle Rock': {0x140058, 0x140007},
|
||||||
|
'Palace of Darkness': set(),
|
||||||
|
'Ganons Tower': {0x140040, 0x140043, 0x14003a, 0x14001f},
|
||||||
|
'Total': set()
|
||||||
|
}
|
||||||
|
|
||||||
key_locations = {"Desert Palace", "Eastern Palace", "Hyrule Castle", "Agahnims Tower", "Tower of Hera", "Swamp Palace",
|
key_locations = {"Desert Palace", "Eastern Palace", "Hyrule Castle", "Agahnims Tower", "Tower of Hera", "Swamp Palace",
|
||||||
"Thieves Town", "Skull Woods", "Ice Palace", "Misery Mire", "Turtle Rock", "Palace of Darkness",
|
"Thieves Town", "Skull Woods", "Ice Palace", "Misery Mire", "Turtle Rock", "Palace of Darkness",
|
||||||
"Ganons Tower"}
|
"Ganons Tower"}
|
||||||
|
@ -191,6 +210,10 @@ for area, locations in default_locations.items():
|
||||||
for location in locations:
|
for location in locations:
|
||||||
location_to_area[location] = area
|
location_to_area[location] = area
|
||||||
|
|
||||||
|
for area, locations in key_only_locations.items():
|
||||||
|
for location in locations:
|
||||||
|
location_to_area[location] = area
|
||||||
|
|
||||||
checks_in_area = {area: len(checks) for area, checks in default_locations.items()}
|
checks_in_area = {area: len(checks) for area, checks in default_locations.items()}
|
||||||
checks_in_area["Total"] = 216
|
checks_in_area["Total"] = 216
|
||||||
|
|
||||||
|
@ -235,6 +258,14 @@ def render_timedelta(delta: datetime.timedelta):
|
||||||
|
|
||||||
_multidata_cache = {}
|
_multidata_cache = {}
|
||||||
|
|
||||||
|
def get_location_table(checks_table: dict) -> dict:
|
||||||
|
loc_to_area = {}
|
||||||
|
for area, locations in checks_table.items():
|
||||||
|
if area == "Total":
|
||||||
|
continue
|
||||||
|
for location in locations:
|
||||||
|
loc_to_area[location] = area
|
||||||
|
return loc_to_area
|
||||||
|
|
||||||
def get_static_room_data(room: Room):
|
def get_static_room_data(room: Room):
|
||||||
result = _multidata_cache.get(room.seed.id, None)
|
result = _multidata_cache.get(room.seed.id, None)
|
||||||
|
@ -244,11 +275,30 @@ def get_static_room_data(room: Room):
|
||||||
# in > 100 players this can take a bit of time and is the main reason for the cache
|
# in > 100 players this can take a bit of time and is the main reason for the cache
|
||||||
locations = {tuple(k): tuple(v) for k, v in multidata['locations']}
|
locations = {tuple(k): tuple(v) for k, v in multidata['locations']}
|
||||||
names = multidata["names"]
|
names = multidata["names"]
|
||||||
|
seed_checks_in_area = checks_in_area.copy()
|
||||||
|
|
||||||
use_door_tracker = False
|
use_door_tracker = False
|
||||||
if "tags" in multidata:
|
if "tags" in multidata:
|
||||||
use_door_tracker = "DR" in multidata["tags"]
|
use_door_tracker = "DR" in multidata["tags"]
|
||||||
result = locations, names, use_door_tracker
|
if use_door_tracker:
|
||||||
|
for area, checks in key_only_locations.items():
|
||||||
|
seed_checks_in_area[area] += len(checks)
|
||||||
|
seed_checks_in_area["Total"] = 249
|
||||||
|
if "checks_in_area" not in multidata:
|
||||||
|
player_checks_in_area = {playernumber: (seed_checks_in_area if use_door_tracker and
|
||||||
|
(0x140031, playernumber) in locations else checks_in_area)
|
||||||
|
for playernumber in range(1, len(names[0]) + 1)}
|
||||||
|
player_location_to_area = {playernumber: location_to_area
|
||||||
|
for playernumber in range(1, len(names[0]) + 1)}
|
||||||
|
|
||||||
|
else:
|
||||||
|
player_checks_in_area = {playernumber: {areaname: len(multidata["checks_in_area"][f'{playernumber}'][areaname])
|
||||||
|
if areaname != "Total" else multidata["checks_in_area"][f'{playernumber}']["Total"]
|
||||||
|
for areaname in ordered_areas}
|
||||||
|
for playernumber in range(1, len(names[0]) + 1)}
|
||||||
|
player_location_to_area = {playernumber: get_location_table(multidata["checks_in_area"][f'{playernumber}'])
|
||||||
|
for playernumber in range(1, len(names[0]) + 1)}
|
||||||
|
result = locations, names, use_door_tracker, player_checks_in_area, player_location_to_area
|
||||||
_multidata_cache[room.seed.id] = result
|
_multidata_cache[room.seed.id] = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -259,7 +309,7 @@ def getTracker(tracker: UUID):
|
||||||
room = Room.get(tracker=tracker)
|
room = Room.get(tracker=tracker)
|
||||||
if not room:
|
if not room:
|
||||||
abort(404)
|
abort(404)
|
||||||
locations, names, use_door_tracker = get_static_room_data(room)
|
locations, names, use_door_tracker, seed_checks_in_area, player_location_to_area = get_static_room_data(room)
|
||||||
|
|
||||||
inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in range(1, len(team) + 1)}
|
inventory = {teamnumber: {playernumber: collections.Counter() for playernumber in range(1, len(team) + 1)}
|
||||||
for teamnumber, team in enumerate(names)}
|
for teamnumber, team in enumerate(names)}
|
||||||
|
@ -280,9 +330,12 @@ def getTracker(tracker: UUID):
|
||||||
for item_id in precollected:
|
for item_id in precollected:
|
||||||
attribute_item(inventory, team, player, item_id)
|
attribute_item(inventory, team, player, item_id)
|
||||||
for location in locations_checked:
|
for location in locations_checked:
|
||||||
|
if (location, player) not in locations or location not in player_location_to_area[player]:
|
||||||
|
continue
|
||||||
|
|
||||||
item, recipient = locations[location, player]
|
item, recipient = locations[location, player]
|
||||||
attribute_item(inventory, team, recipient, item)
|
attribute_item(inventory, team, recipient, item)
|
||||||
checks_done[team][player][location_to_area[location]] += 1
|
checks_done[team][player][player_location_to_area[player][location]] += 1
|
||||||
checks_done[team][player]["Total"] += 1
|
checks_done[team][player]["Total"] += 1
|
||||||
|
|
||||||
for (team, player), game_state in room.multisave.get("client_game_state", []):
|
for (team, player), game_state in room.multisave.get("client_game_state", []):
|
||||||
|
@ -311,7 +364,7 @@ def getTracker(tracker: UUID):
|
||||||
lookup_id_to_name=Items.lookup_id_to_name, player_names=player_names,
|
lookup_id_to_name=Items.lookup_id_to_name, player_names=player_names,
|
||||||
tracking_names=tracking_names, tracking_ids=tracking_ids, room=room, icons=icons,
|
tracking_names=tracking_names, tracking_ids=tracking_ids, room=room, icons=icons,
|
||||||
multi_items=multi_items, checks_done=checks_done, ordered_areas=ordered_areas,
|
multi_items=multi_items, checks_done=checks_done, ordered_areas=ordered_areas,
|
||||||
checks_in_area=checks_in_area, activity_timers=activity_timers,
|
checks_in_area=seed_checks_in_area, activity_timers=activity_timers,
|
||||||
key_locations=key_locations, small_key_ids=small_key_ids, big_key_ids=big_key_ids,
|
key_locations=key_locations, small_key_ids=small_key_ids, big_key_ids=big_key_ids,
|
||||||
video=video, big_key_locations=key_locations if use_door_tracker else big_key_locations,
|
video=video, big_key_locations=key_locations if use_door_tracker else big_key_locations,
|
||||||
hints=hints, long_player_names = long_player_names)
|
hints=hints, long_player_names = long_player_names)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue