Multiworld merge
This commit is contained in:
@@ -15,5 +15,8 @@ README.html
|
|||||||
*multisave
|
*multisave
|
||||||
EnemizerCLI/
|
EnemizerCLI/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
RaceRom.py
|
||||||
|
weights/
|
||||||
|
|
||||||
venv
|
venv
|
||||||
test
|
test
|
||||||
|
|||||||
+9
-4
@@ -6,6 +6,7 @@ import textwrap
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from AdjusterMain import adjust
|
from AdjusterMain import adjust
|
||||||
|
from Rom import get_sprite_from_name
|
||||||
|
|
||||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
|||||||
def main():
|
def 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 ALttPR rom to adjust.')
|
||||||
|
parser.add_argument('--baserom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||||
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||||
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
||||||
help='''\
|
help='''\
|
||||||
@@ -31,6 +33,8 @@ def main():
|
|||||||
''')
|
''')
|
||||||
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
||||||
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
||||||
|
parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||||
|
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||||
parser.add_argument('--sprite', help='''\
|
parser.add_argument('--sprite', help='''\
|
||||||
Path to a sprite sheet to use for Link. Needs to be in
|
Path to a sprite sheet to use for Link. Needs to be in
|
||||||
binary format and have a length of 0x7000 (28672) bytes,
|
binary format and have a length of 0x7000 (28672) bytes,
|
||||||
@@ -38,14 +42,15 @@ def main():
|
|||||||
Alternatively, can be a ALttP Rom patched with a Link
|
Alternatively, can be a ALttP Rom patched with a Link
|
||||||
sprite that will be extracted.
|
sprite that will be extracted.
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--names', default='', type=str)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# ToDo: Validate files further than mere existance
|
# ToDo: Validate files further than mere existance
|
||||||
if not os.path.isfile(args.rom):
|
if not os.path.isfile(args.rom):
|
||||||
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
input('Could not find valid 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):
|
if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite):
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# set up logger
|
# set up logger
|
||||||
|
|||||||
+6
-11
@@ -2,8 +2,8 @@ import os
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from Utils import output_path
|
from Utils import output_path, parse_names_string
|
||||||
from Rom import LocalRom, Sprite, apply_rom_settings
|
from Rom import LocalRom, apply_rom_settings
|
||||||
|
|
||||||
|
|
||||||
def adjust(args):
|
def adjust(args):
|
||||||
@@ -11,22 +11,17 @@ def adjust(args):
|
|||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
logger.info('Patching ROM.')
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
if args.sprite is not None:
|
|
||||||
if isinstance(args.sprite, Sprite):
|
|
||||||
sprite = args.sprite
|
|
||||||
else:
|
|
||||||
sprite = Sprite(args.sprite)
|
|
||||||
else:
|
|
||||||
sprite = None
|
|
||||||
|
|
||||||
outfilebase = os.path.basename(args.rom)[:-4] + '_adjusted'
|
outfilebase = os.path.basename(args.rom)[:-4] + '_adjusted'
|
||||||
|
|
||||||
if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc':
|
if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc':
|
||||||
rom = LocalRom(args.rom, False)
|
rom = LocalRom(args.rom, False)
|
||||||
|
if os.path.isfile(args.baserom):
|
||||||
|
baserom = LocalRom(args.baserom, True)
|
||||||
|
rom.orig_buffer = baserom.orig_buffer
|
||||||
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.')
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, sprite)
|
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, parse_names_string(args.names))
|
||||||
|
|
||||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||||
|
|
||||||
|
|||||||
+160
-98
@@ -12,18 +12,18 @@ from RoomData import Room
|
|||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints):
|
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
|
||||||
self.players = players
|
self.players = players
|
||||||
self.shuffle = shuffle
|
self.shuffle = shuffle.copy()
|
||||||
self.doorShuffle = doorShuffle
|
self.doorShuffle = doorShuffle.copy()
|
||||||
self.logic = logic
|
self.logic = logic.copy()
|
||||||
self.mode = mode
|
self.mode = mode.copy()
|
||||||
self.swords = swords
|
self.swords = swords.copy()
|
||||||
self.difficulty = difficulty
|
self.difficulty = difficulty.copy()
|
||||||
self.difficulty_adjustments = difficulty_adjustments
|
self.difficulty_adjustments = difficulty_adjustments.copy()
|
||||||
self.timer = timer
|
self.timer = timer
|
||||||
self.progressive = progressive
|
self.progressive = progressive
|
||||||
self.goal = goal
|
self.goal = goal.copy()
|
||||||
self.algorithm = algorithm
|
self.algorithm = algorithm
|
||||||
self.dungeons = []
|
self.dungeons = []
|
||||||
self.regions = []
|
self.regions = []
|
||||||
@@ -32,55 +32,30 @@ class World(object):
|
|||||||
self.seed = None
|
self.seed = None
|
||||||
self.precollected_items = []
|
self.precollected_items = []
|
||||||
self.state = CollectionState(self)
|
self.state = CollectionState(self)
|
||||||
self.required_medallions = dict([(player, ['Ether', 'Quake']) for player in range(1, players + 1)])
|
|
||||||
self._cached_entrances = None
|
self._cached_entrances = None
|
||||||
self._cached_locations = None
|
self._cached_locations = None
|
||||||
self._entrance_cache = {}
|
self._entrance_cache = {}
|
||||||
self._region_cache = {}
|
|
||||||
self._entrance_cache = {}
|
|
||||||
self._location_cache = {}
|
self._location_cache = {}
|
||||||
self.required_locations = []
|
self.required_locations = []
|
||||||
self.place_dungeon_items = place_dungeon_items # configurable in future
|
|
||||||
self.shuffle_bonk_prizes = False
|
self.shuffle_bonk_prizes = False
|
||||||
self.swamp_patch_required = {player: False for player in range(1, players + 1)}
|
|
||||||
self.powder_patch_required = {player: False for player in range(1, players + 1)}
|
|
||||||
self.ganon_at_pyramid = {player: True for player in range(1, players + 1)}
|
|
||||||
self.ganonstower_vanilla = {player: True for player in range(1, players + 1)}
|
|
||||||
self.sewer_light_cone = mode == 'standard'
|
|
||||||
self.light_world_light_cone = False
|
self.light_world_light_cone = False
|
||||||
self.dark_world_light_cone = False
|
self.dark_world_light_cone = False
|
||||||
self.treasure_hunt_count = 0
|
|
||||||
self.treasure_hunt_icon = 'Triforce Piece'
|
|
||||||
self.clock_mode = 'off'
|
self.clock_mode = 'off'
|
||||||
self.rupoor_cost = 10
|
self.rupoor_cost = 10
|
||||||
self.aga_randomness = True
|
self.aga_randomness = True
|
||||||
self.lock_aga_door_in_escape = False
|
self.lock_aga_door_in_escape = False
|
||||||
self.fix_trock_doors = self.shuffle != 'vanilla' or self.mode == 'inverted'
|
|
||||||
self.save_and_quit_from_boss = True
|
self.save_and_quit_from_boss = True
|
||||||
self.accessibility = accessibility
|
self.accessibility = accessibility.copy()
|
||||||
self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle not in ['vanilla']
|
self.fix_skullwoods_exit = {}
|
||||||
self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
self.fix_palaceofdarkness_exit = {}
|
||||||
self.fix_trock_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
self.fix_trock_exit = {}
|
||||||
self.shuffle_ganon = shuffle_ganon
|
self.shuffle_ganon = shuffle_ganon
|
||||||
self.fix_gtower_exit = self.shuffle_ganon
|
self.fix_gtower_exit = self.shuffle_ganon
|
||||||
self.can_access_trock_eyebridge = None
|
self.retro = retro.copy()
|
||||||
self.can_access_trock_front = None
|
|
||||||
self.can_access_trock_big_chest = None
|
|
||||||
self.can_access_trock_middle = None
|
|
||||||
self.quickswap = quickswap
|
|
||||||
self.fastmenu = fastmenu
|
|
||||||
self.disable_music = disable_music
|
|
||||||
self.keysanity = keysanity
|
|
||||||
self.retro = retro
|
|
||||||
self.custom = custom
|
self.custom = custom
|
||||||
self.customitemarray = customitemarray
|
self.customitemarray = customitemarray
|
||||||
self.can_take_damage = True
|
self.can_take_damage = True
|
||||||
self.difficulty_requirements = None
|
self.hints = hints.copy()
|
||||||
self.fix_fake_world = True
|
|
||||||
self.boss_shuffle = boss_shuffle
|
|
||||||
self.hints = hints
|
|
||||||
self.crystals_needed_for_ganon = 7
|
|
||||||
self.crystals_needed_for_gt = 7
|
|
||||||
self.dynamic_regions = []
|
self.dynamic_regions = []
|
||||||
self.dynamic_locations = []
|
self.dynamic_locations = []
|
||||||
self.spoiler = Spoiler(self)
|
self.spoiler = Spoiler(self)
|
||||||
@@ -94,19 +69,59 @@ class World(object):
|
|||||||
self.inaccessible_regions = {}
|
self.inaccessible_regions = {}
|
||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
|
|
||||||
def intialize_regions(self):
|
for player in range(1, players + 1):
|
||||||
for region in self.regions:
|
def set_player_attr(attr, val):
|
||||||
|
self.__dict__.setdefault(attr, {})[player] = val
|
||||||
|
set_player_attr('_region_cache', {})
|
||||||
|
set_player_attr('required_medallions', ['Ether', 'Quake'])
|
||||||
|
set_player_attr('swamp_patch_required', False)
|
||||||
|
set_player_attr('powder_patch_required', False)
|
||||||
|
set_player_attr('ganon_at_pyramid', True)
|
||||||
|
set_player_attr('ganonstower_vanilla', True)
|
||||||
|
set_player_attr('sewer_light_cone', self.mode[player] == 'standard')
|
||||||
|
set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted')
|
||||||
|
set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||||
|
set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||||
|
set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||||
|
set_player_attr('can_access_trock_eyebridge', None)
|
||||||
|
set_player_attr('can_access_trock_front', None)
|
||||||
|
set_player_attr('can_access_trock_big_chest', None)
|
||||||
|
set_player_attr('can_access_trock_middle', None)
|
||||||
|
set_player_attr('fix_fake_world', True)
|
||||||
|
set_player_attr('mapshuffle', False)
|
||||||
|
set_player_attr('compassshuffle', False)
|
||||||
|
set_player_attr('keyshuffle', False)
|
||||||
|
set_player_attr('bigkeyshuffle', False)
|
||||||
|
set_player_attr('difficulty_requirements', None)
|
||||||
|
set_player_attr('boss_shuffle', 'none')
|
||||||
|
set_player_attr('enemy_shuffle', 'none')
|
||||||
|
set_player_attr('enemy_health', 'default')
|
||||||
|
set_player_attr('enemy_damage', 'default')
|
||||||
|
set_player_attr('beemizer', 0)
|
||||||
|
set_player_attr('escape_assist', [])
|
||||||
|
set_player_attr('crystals_needed_for_ganon', 7)
|
||||||
|
set_player_attr('crystals_needed_for_gt', 7)
|
||||||
|
set_player_attr('open_pyramid', False)
|
||||||
|
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||||
|
set_player_attr('treasure_hunt_count', 0)
|
||||||
|
|
||||||
|
def initialize_regions(self, regions=None):
|
||||||
|
for region in regions if regions else self.regions:
|
||||||
region.world = self
|
region.world = self
|
||||||
|
self._region_cache[region.player][region.name] = region
|
||||||
|
|
||||||
|
def get_regions(self, player=None):
|
||||||
|
return self.regions if player is None else self._region_cache[player].values()
|
||||||
|
|
||||||
def get_region(self, regionname, player):
|
def get_region(self, regionname, player):
|
||||||
if isinstance(regionname, Region):
|
if isinstance(regionname, Region):
|
||||||
return regionname
|
return regionname
|
||||||
try:
|
try:
|
||||||
return self._region_cache[(regionname, player)]
|
return self._region_cache[player][regionname]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
for region in self.regions:
|
for region in self.regions:
|
||||||
if region.name == regionname and region.player == player:
|
if region.name == regionname and region.player == player:
|
||||||
self._region_cache[(regionname, player)] = region
|
assert not region.world # this should only happen before initialization
|
||||||
return region
|
return region
|
||||||
raise RuntimeError('No such region %s for player %d' % (regionname, player))
|
raise RuntimeError('No such region %s for player %d' % (regionname, player))
|
||||||
|
|
||||||
@@ -189,13 +204,13 @@ class World(object):
|
|||||||
if 'Sword' in item.name:
|
if 'Sword' in item.name:
|
||||||
if ret.has('Golden Sword', item.player):
|
if ret.has('Golden Sword', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 4:
|
elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
||||||
ret.prog_items.add(('Golden Sword', item.player))
|
ret.prog_items.add(('Golden Sword', item.player))
|
||||||
elif ret.has('Master Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 3:
|
elif ret.has('Master Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 3:
|
||||||
ret.prog_items.add(('Tempered Sword', item.player))
|
ret.prog_items.add(('Tempered Sword', item.player))
|
||||||
elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements.progressive_sword_limit >= 2:
|
elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||||
ret.prog_items.add(('Master Sword', item.player))
|
ret.prog_items.add(('Master Sword', item.player))
|
||||||
elif self.difficulty_requirements.progressive_sword_limit >= 1:
|
elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||||
ret.prog_items.add(('Fighter Sword', item.player))
|
ret.prog_items.add(('Fighter Sword', item.player))
|
||||||
elif 'Glove' in item.name:
|
elif 'Glove' in item.name:
|
||||||
if ret.has('Titans Mitts', item.player):
|
if ret.has('Titans Mitts', item.player):
|
||||||
@@ -207,23 +222,23 @@ class World(object):
|
|||||||
elif 'Shield' in item.name:
|
elif 'Shield' in item.name:
|
||||||
if ret.has('Mirror Shield', item.player):
|
if ret.has('Mirror Shield', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Red Shield', item.player) and self.difficulty_requirements.progressive_shield_limit >= 3:
|
elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||||
ret.prog_items.add(('Mirror Shield', item.player))
|
ret.prog_items.add(('Mirror Shield', item.player))
|
||||||
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements.progressive_shield_limit >= 2:
|
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||||
ret.prog_items.add(('Red Shield', item.player))
|
ret.prog_items.add(('Red Shield', item.player))
|
||||||
elif self.difficulty_requirements.progressive_shield_limit >= 1:
|
elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||||
ret.prog_items.add(('Blue Shield', item.player))
|
ret.prog_items.add(('Blue Shield', item.player))
|
||||||
elif 'Bow' in item.name:
|
elif 'Bow' in item.name:
|
||||||
if ret.has('Silver Arrows', item.player):
|
if ret.has('Silver Arrows', item.player):
|
||||||
pass
|
pass
|
||||||
elif ret.has('Bow', item.player) and self.difficulty_requirements.progressive_bow_limit >= 2:
|
elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2:
|
||||||
ret.prog_items.add(('Silver Arrows', item.player))
|
ret.prog_items.add(('Silver Arrows', item.player))
|
||||||
elif self.difficulty_requirements.progressive_bow_limit >= 1:
|
elif self.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||||
ret.prog_items.add(('Bow', item.player))
|
ret.prog_items.add(('Bow', item.player))
|
||||||
elif item.name.startswith('Bottle'):
|
elif item.name.startswith('Bottle'):
|
||||||
if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit:
|
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||||
ret.prog_items.add((item.name, item.player))
|
ret.prog_items.add((item.name, item.player))
|
||||||
elif item.advancement or item.key:
|
elif item.advancement or item.smallkey or item.bigkey:
|
||||||
ret.prog_items.add((item.name, item.player))
|
ret.prog_items.add((item.name, item.player))
|
||||||
|
|
||||||
for item in self.itempool:
|
for item in self.itempool:
|
||||||
@@ -251,6 +266,8 @@ class World(object):
|
|||||||
return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player]
|
return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player]
|
||||||
|
|
||||||
def push_precollected(self, item):
|
def push_precollected(self, item):
|
||||||
|
if (item.smallkey and self.keyshuffle[item.player]) or (item.bigkey and self.bigkeyshuffle[item.player]):
|
||||||
|
item.advancement = True
|
||||||
self.precollected_items.append(item)
|
self.precollected_items.append(item)
|
||||||
self.state.collect(item, True)
|
self.state.collect(item, True)
|
||||||
|
|
||||||
@@ -366,15 +383,15 @@ class CollectionState(object):
|
|||||||
self.collect(item, True)
|
self.collect(item, True)
|
||||||
|
|
||||||
def update_reachable_regions(self, player):
|
def update_reachable_regions(self, player):
|
||||||
player_regions = [region for region in self.world.regions if region.player == player]
|
player_regions = self.world.get_regions(player)
|
||||||
self.stale[player] = False
|
self.stale[player] = False
|
||||||
rrp = self.reachable_regions[player]
|
rrp = self.reachable_regions[player]
|
||||||
ccr = self.colored_regions[player]
|
ccr = self.colored_regions[player]
|
||||||
new_regions = True
|
new_regions = True
|
||||||
reachable_regions_count = len(rrp)
|
reachable_regions_count = len(rrp)
|
||||||
while new_regions:
|
while new_regions:
|
||||||
possible = [region for region in player_regions if region not in rrp]
|
player_regions = [region for region in player_regions if region not in rrp]
|
||||||
for candidate in possible:
|
for candidate in player_regions:
|
||||||
if candidate.can_reach_private(self):
|
if candidate.can_reach_private(self):
|
||||||
rrp.add(candidate)
|
rrp.add(candidate)
|
||||||
if candidate.type == RegionType.Dungeon:
|
if candidate.type == RegionType.Dungeon:
|
||||||
@@ -455,12 +472,14 @@ class CollectionState(object):
|
|||||||
|
|
||||||
def sweep_for_events(self, key_only=False, locations=None):
|
def sweep_for_events(self, key_only=False, locations=None):
|
||||||
# this may need improvement
|
# this may need improvement
|
||||||
|
if locations is None:
|
||||||
|
locations = self.world.get_filled_locations()
|
||||||
new_locations = True
|
new_locations = True
|
||||||
checked_locations = 0
|
checked_locations = 0
|
||||||
while new_locations:
|
while new_locations:
|
||||||
if locations is None:
|
reachable_events = [location for location in locations if location.event and
|
||||||
locations = self.world.get_filled_locations()
|
(not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey))
|
||||||
reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)]
|
and location.can_reach(self)]
|
||||||
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
||||||
for event in reachable_events:
|
for event in reachable_events:
|
||||||
if (event.name, event.player) not in self.events:
|
if (event.name, event.player) not in self.events:
|
||||||
@@ -501,7 +520,7 @@ class CollectionState(object):
|
|||||||
return self.prog_items.count((item, player)) >= count
|
return self.prog_items.count((item, player)) >= count
|
||||||
|
|
||||||
def has_key(self, item, player, count=1):
|
def has_key(self, item, player, count=1):
|
||||||
if self.world.retro:
|
if self.world.retro[player]:
|
||||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return (item, player) in self.prog_items
|
return (item, player) in self.prog_items
|
||||||
@@ -535,7 +554,7 @@ class CollectionState(object):
|
|||||||
|
|
||||||
def heart_count(self, player):
|
def heart_count(self, player):
|
||||||
# Warning: This only considers items that are marked as advancement items
|
# Warning: This only considers items that are marked as advancement items
|
||||||
diff = self.world.difficulty_requirements
|
diff = self.world.difficulty_requirements[player]
|
||||||
return (
|
return (
|
||||||
min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit)
|
min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit)
|
||||||
+ self.item_count('Sanctuary Heart Container', player)
|
+ self.item_count('Sanctuary Heart Container', player)
|
||||||
@@ -553,9 +572,9 @@ class CollectionState(object):
|
|||||||
elif self.has('Half Magic', player):
|
elif self.has('Half Magic', player):
|
||||||
basemagic = 16
|
basemagic = 16
|
||||||
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
|
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
|
||||||
if self.world.difficulty_adjustments == 'hard' and not fullrefill:
|
if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill:
|
||||||
basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player))
|
basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player))
|
||||||
elif self.world.difficulty_adjustments == 'expert' and not fullrefill:
|
elif self.world.difficulty_adjustments[player] == 'expert' and not fullrefill:
|
||||||
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player))
|
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player))
|
||||||
else:
|
else:
|
||||||
basemagic = basemagic + basemagic * self.bottle_count(player)
|
basemagic = basemagic + basemagic * self.bottle_count(player)
|
||||||
@@ -570,7 +589,7 @@ class CollectionState(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def can_shoot_arrows(self, player):
|
def can_shoot_arrows(self, player):
|
||||||
if self.world.retro:
|
if self.world.retro[player]:
|
||||||
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
|
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
|
||||||
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
|
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
|
||||||
return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player))
|
return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player))
|
||||||
@@ -621,7 +640,7 @@ class CollectionState(object):
|
|||||||
if self.has_Pearl(player):
|
if self.has_Pearl(player):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return region.is_light_world if self.world.mode != 'inverted' else region.is_dark_world
|
return region.is_light_world if self.world.mode[player] != 'inverted' else region.is_dark_world
|
||||||
|
|
||||||
def can_reach_light_world(self, player):
|
def can_reach_light_world(self, player):
|
||||||
if True in [i.is_light_world for i in self.reachable_regions[player]]:
|
if True in [i.is_light_world for i in self.reachable_regions[player]]:
|
||||||
@@ -647,16 +666,16 @@ class CollectionState(object):
|
|||||||
if 'Sword' in item.name:
|
if 'Sword' in item.name:
|
||||||
if self.has('Golden Sword', item.player):
|
if self.has('Golden Sword', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 4:
|
elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 4:
|
||||||
self.prog_items.add(('Golden Sword', item.player))
|
self.prog_items.add(('Golden Sword', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif self.has('Master Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 3:
|
elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3:
|
||||||
self.prog_items.add(('Tempered Sword', item.player))
|
self.prog_items.add(('Tempered Sword', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements.progressive_sword_limit >= 2:
|
elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||||
self.prog_items.add(('Master Sword', item.player))
|
self.prog_items.add(('Master Sword', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif self.world.difficulty_requirements.progressive_sword_limit >= 1:
|
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||||
self.prog_items.add(('Fighter Sword', item.player))
|
self.prog_items.add(('Fighter Sword', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif 'Glove' in item.name:
|
elif 'Glove' in item.name:
|
||||||
@@ -671,13 +690,13 @@ class CollectionState(object):
|
|||||||
elif 'Shield' in item.name:
|
elif 'Shield' in item.name:
|
||||||
if self.has('Mirror Shield', item.player):
|
if self.has('Mirror Shield', item.player):
|
||||||
pass
|
pass
|
||||||
elif self.has('Red Shield', item.player) and self.world.difficulty_requirements.progressive_shield_limit >= 3:
|
elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||||
self.prog_items.add(('Mirror Shield', item.player))
|
self.prog_items.add(('Mirror Shield', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements.progressive_shield_limit >= 2:
|
elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||||
self.prog_items.add(('Red Shield', item.player))
|
self.prog_items.add(('Red Shield', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif self.world.difficulty_requirements.progressive_shield_limit >= 1:
|
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||||
self.prog_items.add(('Blue Shield', item.player))
|
self.prog_items.add(('Blue Shield', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif 'Bow' in item.name:
|
elif 'Bow' in item.name:
|
||||||
@@ -690,7 +709,7 @@ class CollectionState(object):
|
|||||||
self.prog_items.add(('Bow', item.player))
|
self.prog_items.add(('Bow', item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif item.name.startswith('Bottle'):
|
elif item.name.startswith('Bottle'):
|
||||||
if self.bottle_count(item.player) < self.world.difficulty_requirements.progressive_bottle_limit:
|
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||||
self.prog_items.add((item.name, item.player))
|
self.prog_items.add((item.name, item.player))
|
||||||
changed = True
|
changed = True
|
||||||
elif event or item.advancement:
|
elif event or item.advancement:
|
||||||
@@ -757,6 +776,8 @@ class CollectionState(object):
|
|||||||
return self.can_reach(item[10])
|
return self.can_reach(item[10])
|
||||||
#elif item.startswith('has_'):
|
#elif item.startswith('has_'):
|
||||||
# return self.has(item[4])
|
# return self.has(item[4])
|
||||||
|
if item == '__len__':
|
||||||
|
return
|
||||||
|
|
||||||
raise RuntimeError('Cannot parse %s.' % item)
|
raise RuntimeError('Cannot parse %s.' % item)
|
||||||
|
|
||||||
@@ -805,9 +826,12 @@ class Region(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def can_fill(self, item):
|
def can_fill(self, item):
|
||||||
is_dungeon_item = item.key or item.map or item.compass
|
inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player])
|
||||||
sewer_hack = self.world.mode == 'standard' and item.name == 'Small Key (Escape)'
|
or (item.bigkey and not self.world.bigkeyshuffle[item.player])
|
||||||
if sewer_hack or (is_dungeon_item and not self.world.keysanity):
|
or (item.map and not self.world.mapshuffle[item.player])
|
||||||
|
or (item.compass and not self.world.compassshuffle[item.player]))
|
||||||
|
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)'
|
||||||
|
if sewer_hack or inside_dungeon_item:
|
||||||
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
|
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -1211,7 +1235,7 @@ class Boss(object):
|
|||||||
return self.defeat_rule(state, self.player)
|
return self.defeat_rule(state, self.player)
|
||||||
|
|
||||||
class Location(object):
|
class Location(object):
|
||||||
def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, forced_item=None):
|
def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, forced_item=None, player_address=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.parent_region = parent
|
self.parent_region = parent
|
||||||
if forced_item is not None:
|
if forced_item is not None:
|
||||||
@@ -1226,11 +1250,12 @@ class Location(object):
|
|||||||
self.event = False
|
self.event = False
|
||||||
self.crystal = crystal
|
self.crystal = crystal
|
||||||
self.address = address
|
self.address = address
|
||||||
|
self.player_address = player_address
|
||||||
self.spot_type = 'Location'
|
self.spot_type = 'Location'
|
||||||
self.hint_text = hint_text if hint_text is not None else 'Hyrule'
|
self.hint_text = hint_text if hint_text is not None else 'Hyrule'
|
||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.staleness_count = 0
|
self.staleness_count = 0
|
||||||
self.locked = True
|
self.locked = False
|
||||||
self.always_allow = lambda item, state: False
|
self.always_allow = lambda item, state: False
|
||||||
self.access_rule = lambda state: True
|
self.access_rule = lambda state: True
|
||||||
self.item_rule = lambda item: True
|
self.item_rule = lambda item: True
|
||||||
@@ -1272,14 +1297,18 @@ class Item(object):
|
|||||||
self.location = None
|
self.location = None
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
@property
|
|
||||||
def key(self):
|
|
||||||
return self.type == 'SmallKey' or self.type == 'BigKey'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def crystal(self):
|
def crystal(self):
|
||||||
return self.type == 'Crystal'
|
return self.type == 'Crystal'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def smallkey(self):
|
||||||
|
return self.type == 'SmallKey'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bigkey(self):
|
||||||
|
return self.type == 'BigKey'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map(self):
|
def map(self):
|
||||||
return self.type == 'Map'
|
return self.type == 'Map'
|
||||||
@@ -1309,14 +1338,14 @@ class ShopType(Enum):
|
|||||||
UpgradeShop = 2
|
UpgradeShop = 2
|
||||||
|
|
||||||
class Shop(object):
|
class Shop(object):
|
||||||
def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
|
def __init__(self, region, room_id, type, shopkeeper_config, custom, locked):
|
||||||
self.region = region
|
self.region = region
|
||||||
self.room_id = room_id
|
self.room_id = room_id
|
||||||
self.type = type
|
self.type = type
|
||||||
self.inventory = [None, None, None]
|
self.inventory = [None, None, None]
|
||||||
self.shopkeeper_config = shopkeeper_config
|
self.shopkeeper_config = shopkeeper_config
|
||||||
self.replaceable = replaceable
|
self.custom = custom
|
||||||
self.active = False
|
self.locked = locked
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def item_count(self):
|
def item_count(self):
|
||||||
@@ -1373,6 +1402,8 @@ class Spoiler(object):
|
|||||||
self.doorTypes = OrderedDict()
|
self.doorTypes = OrderedDict()
|
||||||
self.medallions = {}
|
self.medallions = {}
|
||||||
self.playthrough = {}
|
self.playthrough = {}
|
||||||
|
self.unreachables = []
|
||||||
|
self.startinventory = []
|
||||||
self.locations = {}
|
self.locations = {}
|
||||||
self.paths = {}
|
self.paths = {}
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
@@ -1407,6 +1438,8 @@ class Spoiler(object):
|
|||||||
self.medallions['Misery Mire (Player %d)' % player] = self.world.required_medallions[player][0]
|
self.medallions['Misery Mire (Player %d)' % player] = self.world.required_medallions[player][0]
|
||||||
self.medallions['Turtle Rock (Player %d)' % player] = self.world.required_medallions[player][1]
|
self.medallions['Turtle Rock (Player %d)' % player] = self.world.required_medallions[player][1]
|
||||||
|
|
||||||
|
self.startinventory = list(map(str, self.world.precollected_items))
|
||||||
|
|
||||||
self.locations = OrderedDict()
|
self.locations = OrderedDict()
|
||||||
listed_locations = set()
|
listed_locations = set()
|
||||||
|
|
||||||
@@ -1434,7 +1467,7 @@ class Spoiler(object):
|
|||||||
|
|
||||||
self.shops = []
|
self.shops = []
|
||||||
for shop in self.world.shops:
|
for shop in self.world.shops:
|
||||||
if not shop.active:
|
if not shop.custom:
|
||||||
continue
|
continue
|
||||||
shopdata = {'location': str(shop.region),
|
shopdata = {'location': str(shop.region),
|
||||||
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
|
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
|
||||||
@@ -1472,14 +1505,27 @@ class Spoiler(object):
|
|||||||
self.metadata = {'version': ERVersion,
|
self.metadata = {'version': ERVersion,
|
||||||
'logic': self.world.logic,
|
'logic': self.world.logic,
|
||||||
'mode': self.world.mode,
|
'mode': self.world.mode,
|
||||||
|
'retro': self.world.retro,
|
||||||
'weapons': self.world.swords,
|
'weapons': self.world.swords,
|
||||||
'goal': self.world.goal,
|
'goal': self.world.goal,
|
||||||
'shuffle': self.world.shuffle,
|
'shuffle': self.world.shuffle,
|
||||||
|
'door_shuffle': self.world.doorShuffle,
|
||||||
'item_pool': self.world.difficulty,
|
'item_pool': self.world.difficulty,
|
||||||
'item_functionality': self.world.difficulty_adjustments,
|
'item_functionality': self.world.difficulty_adjustments,
|
||||||
|
'gt_crystals': self.world.crystals_needed_for_gt,
|
||||||
|
'ganon_crystals': self.world.crystals_needed_for_ganon,
|
||||||
|
'open_pyramid': self.world.open_pyramid,
|
||||||
'accessibility': self.world.accessibility,
|
'accessibility': self.world.accessibility,
|
||||||
'hints': self.world.hints,
|
'hints': self.world.hints,
|
||||||
'keysanity': self.world.keysanity,
|
'mapshuffle': self.world.mapshuffle,
|
||||||
|
'compassshuffle': self.world.compassshuffle,
|
||||||
|
'keyshuffle': self.world.keyshuffle,
|
||||||
|
'bigkeyshuffle': self.world.bigkeyshuffle,
|
||||||
|
'boss_shuffle': self.world.boss_shuffle,
|
||||||
|
'enemy_shuffle': self.world.enemy_shuffle,
|
||||||
|
'enemy_health': self.world.enemy_health,
|
||||||
|
'enemy_damage': self.world.enemy_damage,
|
||||||
|
'players': self.world.players
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
@@ -1489,13 +1535,13 @@ class Spoiler(object):
|
|||||||
out['Doors'] = list(self.doors.values())
|
out['Doors'] = list(self.doors.values())
|
||||||
out['DoorTypes'] = list(self.doorTypes.values())
|
out['DoorTypes'] = list(self.doorTypes.values())
|
||||||
out.update(self.locations)
|
out.update(self.locations)
|
||||||
|
out['Starting Inventory'] = self.startinventory
|
||||||
out['Special'] = self.medallions
|
out['Special'] = self.medallions
|
||||||
if self.shops:
|
if self.shops:
|
||||||
out['Shops'] = self.shops
|
out['Shops'] = self.shops
|
||||||
out['playthrough'] = self.playthrough
|
out['playthrough'] = self.playthrough
|
||||||
out['paths'] = self.paths
|
out['paths'] = self.paths
|
||||||
if self.world.boss_shuffle != 'none':
|
out['Bosses'] = self.bosses
|
||||||
out['Bosses'] = self.bosses
|
|
||||||
out['meta'] = self.metadata
|
out['meta'] = self.metadata
|
||||||
|
|
||||||
return json.dumps(out)
|
return json.dumps(out)
|
||||||
@@ -1504,19 +1550,30 @@ class Spoiler(object):
|
|||||||
self.parse_data()
|
self.parse_data()
|
||||||
with open(filename, 'w') as outfile:
|
with open(filename, 'w') as outfile:
|
||||||
outfile.write('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
|
outfile.write('ALttP Entrance Randomizer Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed))
|
||||||
|
outfile.write('Players: %d\n' % self.world.players)
|
||||||
|
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
||||||
outfile.write('Logic: %s\n' % self.metadata['logic'])
|
outfile.write('Logic: %s\n' % self.metadata['logic'])
|
||||||
outfile.write('Mode: %s\n' % self.metadata['mode'])
|
outfile.write('Mode: %s\n' % self.metadata['mode'])
|
||||||
|
outfile.write('Retro: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['retro'].items()})
|
||||||
|
outfile.write('Swords: %s\n' % self.metadata['weapons'])
|
||||||
outfile.write('Goal: %s\n' % self.metadata['goal'])
|
outfile.write('Goal: %s\n' % self.metadata['goal'])
|
||||||
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'])
|
outfile.write('Difficulty: %s\n' % self.metadata['item_pool'])
|
||||||
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'])
|
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'])
|
||||||
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
|
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
|
||||||
outfile.write('Filling Algorithm: %s\n' % self.world.algorithm)
|
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'])
|
||||||
|
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'])
|
||||||
|
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'])
|
||||||
|
outfile.write('Pyramid hole pre-opened: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['open_pyramid'].items()})
|
||||||
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'])
|
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'])
|
||||||
outfile.write('Maps and Compasses in Dungeons: %s\n' % ('Yes' if self.world.place_dungeon_items else 'No'))
|
outfile.write('Map shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['mapshuffle'].items()})
|
||||||
outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No'))
|
outfile.write('Compass shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['compassshuffle'].items()})
|
||||||
outfile.write('Menu speed: %s\n' % self.world.fastmenu)
|
outfile.write('Small Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['keyshuffle'].items()})
|
||||||
outfile.write('Keysanity enabled: %s\n' % ('Yes' if self.metadata['keysanity'] else 'No'))
|
outfile.write('Big Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['bigkeyshuffle'].items()})
|
||||||
outfile.write('Players: %d' % self.world.players)
|
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'])
|
||||||
|
outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'])
|
||||||
|
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'])
|
||||||
|
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'])
|
||||||
|
outfile.write('Hints: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['hints'].items()})
|
||||||
if self.doors:
|
if self.doors:
|
||||||
outfile.write('\n\nDoors:\n\n')
|
outfile.write('\n\nDoors:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.doors.values()]))
|
outfile.write('\n'.join(['%s%s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.doors.values()]))
|
||||||
@@ -1534,12 +1591,17 @@ class Spoiler(object):
|
|||||||
for player in range(1, self.world.players + 1):
|
for player in range(1, self.world.players + 1):
|
||||||
outfile.write('\nMisery Mire Medallion (Player %d): %s' % (player, self.medallions['Misery Mire (Player %d)' % player]))
|
outfile.write('\nMisery Mire Medallion (Player %d): %s' % (player, self.medallions['Misery Mire (Player %d)' % player]))
|
||||||
outfile.write('\nTurtle Rock Medallion (Player %d): %s' % (player, self.medallions['Turtle Rock (Player %d)' % player]))
|
outfile.write('\nTurtle Rock Medallion (Player %d): %s' % (player, self.medallions['Turtle Rock (Player %d)' % player]))
|
||||||
|
outfile.write('\n\nStarting Inventory:\n\n')
|
||||||
|
outfile.write('\n'.join(self.startinventory))
|
||||||
outfile.write('\n\nLocations:\n\n')
|
outfile.write('\n\nLocations:\n\n')
|
||||||
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||||
outfile.write('\n\nShops:\n\n')
|
outfile.write('\n\nShops:\n\n')
|
||||||
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
||||||
outfile.write('\n\nPlaythrough:\n\n')
|
outfile.write('\n\nPlaythrough:\n\n')
|
||||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||||
|
if self.unreachables:
|
||||||
|
outfile.write('\n\nUnreachable Items:\n\n')
|
||||||
|
outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
|
||||||
outfile.write('\n\nPaths:\n\n')
|
outfile.write('\n\nPaths:\n\n')
|
||||||
|
|
||||||
path_listings = []
|
path_listings = []
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ def KholdstareDefeatRule(state, player):
|
|||||||
(
|
(
|
||||||
state.has('Bombos', player) and
|
state.has('Bombos', player) and
|
||||||
# FIXME: the following only actually works for the vanilla location for swordless
|
# FIXME: the following only actually works for the vanilla location for swordless
|
||||||
(state.has_sword(player) or state.world.swords == 'swordless')
|
(state.has_sword(player) or state.world.swords[player] == 'swordless')
|
||||||
)
|
)
|
||||||
) and
|
) and
|
||||||
(
|
(
|
||||||
@@ -83,7 +83,7 @@ def KholdstareDefeatRule(state, player):
|
|||||||
(
|
(
|
||||||
state.has('Fire Rod', player) and
|
state.has('Fire Rod', player) and
|
||||||
state.has('Bombos', player) and
|
state.has('Bombos', player) and
|
||||||
state.world.swords == 'swordless' and
|
state.world.swords[player] == 'swordless' and
|
||||||
state.can_extend_magic(player, 16)
|
state.can_extend_magic(player, 16)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -115,8 +115,8 @@ boss_table = {
|
|||||||
'Agahnim2': ('Agahnim2', AgahnimDefeatRule)
|
'Agahnim2': ('Agahnim2', AgahnimDefeatRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
def can_place_boss(world, boss, dungeon_name, level=None):
|
def can_place_boss(world, player, boss, dungeon_name, level=None):
|
||||||
if world.swords in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace':
|
if world.swords[player] in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'top':
|
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'top':
|
||||||
@@ -138,10 +138,10 @@ def can_place_boss(world, boss, dungeon_name, level=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def place_bosses(world, player):
|
def place_bosses(world, player):
|
||||||
if world.boss_shuffle == 'none':
|
if world.boss_shuffle[player] == 'none':
|
||||||
return
|
return
|
||||||
# Most to least restrictive order
|
# Most to least restrictive order
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
boss_locations = [
|
boss_locations = [
|
||||||
['Ganons Tower', 'top'],
|
['Ganons Tower', 'top'],
|
||||||
['Tower of Hera', None],
|
['Tower of Hera', None],
|
||||||
@@ -177,15 +177,15 @@ def place_bosses(world, player):
|
|||||||
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
|
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
|
||||||
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
|
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
|
||||||
|
|
||||||
if world.boss_shuffle in ["basic", "normal"]:
|
if world.boss_shuffle[player] in ["basic", "normal"]:
|
||||||
# temporary hack for swordless kholdstare:
|
# temporary hack for swordless kholdstare:
|
||||||
if world.swords == 'swordless':
|
if world.swords[player] == 'swordless':
|
||||||
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
|
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
|
||||||
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
|
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
|
||||||
boss_locations.remove(['Ice Palace', None])
|
boss_locations.remove(['Ice Palace', None])
|
||||||
placeable_bosses.remove('Kholdstare')
|
placeable_bosses.remove('Kholdstare')
|
||||||
|
|
||||||
if world.boss_shuffle == "basic": # vanilla bosses shuffled
|
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
|
||||||
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
||||||
else: # all bosses present, the three duplicates chosen at random
|
else: # all bosses present, the three duplicates chosen at random
|
||||||
bosses = all_bosses + [random.choice(placeable_bosses) for _ in range(3)]
|
bosses = all_bosses + [random.choice(placeable_bosses) for _ in range(3)]
|
||||||
@@ -195,18 +195,18 @@ def place_bosses(world, player):
|
|||||||
random.shuffle(bosses)
|
random.shuffle(bosses)
|
||||||
for [loc, level] in boss_locations:
|
for [loc, level] in boss_locations:
|
||||||
loc_text = loc + (' ('+level+')' if level else '')
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
boss = next((b for b in bosses if can_place_boss(world, b, loc, level)), None)
|
boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None)
|
||||||
if not boss:
|
if not boss:
|
||||||
raise FillError('Could not place boss for location %s' % loc_text)
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
bosses.remove(boss)
|
bosses.remove(boss)
|
||||||
|
|
||||||
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
|
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
|
||||||
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
|
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
|
||||||
elif world.boss_shuffle == "chaos": #all bosses chosen at random
|
elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random
|
||||||
for [loc, level] in boss_locations:
|
for [loc, level] in boss_locations:
|
||||||
loc_text = loc + (' ('+level+')' if level else '')
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
try:
|
try:
|
||||||
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, b, loc, level)])
|
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise FillError('Could not place boss for location %s' % loc_text)
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -39,7 +39,7 @@ def link_doors(world, player):
|
|||||||
for ent, ext in ladders:
|
for ent, ext in ladders:
|
||||||
connect_two_way(world, ent, ext, player)
|
connect_two_way(world, ent, ext, player)
|
||||||
|
|
||||||
if world.doorShuffle == 'vanilla':
|
if world.doorShuffle[player] == 'vanilla':
|
||||||
for exitName, regionName in vanilla_logical_connections:
|
for exitName, regionName in vanilla_logical_connections:
|
||||||
connect_simple_door(world, exitName, regionName, player)
|
connect_simple_door(world, exitName, regionName, player)
|
||||||
for entrance, ext in spiral_staircases:
|
for entrance, ext in spiral_staircases:
|
||||||
@@ -49,14 +49,14 @@ def link_doors(world, player):
|
|||||||
for ent, ext in default_one_way_connections:
|
for ent, ext in default_one_way_connections:
|
||||||
connect_one_way(world, ent, ext, player)
|
connect_one_way(world, ent, ext, player)
|
||||||
vanilla_key_logic(world, player)
|
vanilla_key_logic(world, player)
|
||||||
elif world.doorShuffle == 'basic':
|
elif world.doorShuffle[player] == 'basic':
|
||||||
within_dungeon(world, player)
|
within_dungeon(world, player)
|
||||||
elif world.doorShuffle == 'crossed':
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
cross_dungeon(world, player)
|
cross_dungeon(world, player)
|
||||||
elif world.doorShuffle == 'experimental':
|
elif world.doorShuffle[player] == 'experimental':
|
||||||
experiment(world, player)
|
experiment(world, player)
|
||||||
|
|
||||||
if world.doorShuffle != 'vanilla':
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
create_door_spoiler(world, player)
|
create_door_spoiler(world, player)
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ def mark_regions(world, player):
|
|||||||
|
|
||||||
def create_door_spoiler(world, player):
|
def create_door_spoiler(world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
queue = collections.deque(world.doors)
|
queue = collections.deque((door for door in world.doors if door.player == player))
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
door_a = queue.popleft()
|
door_a = queue.popleft()
|
||||||
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
||||||
@@ -283,7 +283,7 @@ def within_dungeon(world, player):
|
|||||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||||
|
|
||||||
paths = determine_required_paths(world)
|
paths = determine_required_paths(world, player)
|
||||||
check_required_paths(paths, world, player)
|
check_required_paths(paths, world, player)
|
||||||
|
|
||||||
# shuffle_key_doors for dungeons
|
# shuffle_key_doors for dungeons
|
||||||
@@ -637,7 +637,7 @@ def cross_dungeon(world, player):
|
|||||||
|
|
||||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||||
|
|
||||||
paths = determine_required_paths(world)
|
paths = determine_required_paths(world, player)
|
||||||
check_required_paths(paths, world, player)
|
check_required_paths(paths, world, player)
|
||||||
|
|
||||||
hc = world.get_dungeon('Hyrule Castle', player)
|
hc = world.get_dungeon('Hyrule Castle', player)
|
||||||
@@ -1106,7 +1106,7 @@ def change_door_to_small_key(d, world, player):
|
|||||||
room.change(d.doorListPos, DoorKind.SmallKey)
|
room.change(d.doorListPos, DoorKind.SmallKey)
|
||||||
|
|
||||||
|
|
||||||
def determine_required_paths(world):
|
def determine_required_paths(world, player):
|
||||||
paths = {
|
paths = {
|
||||||
'Hyrule Castle': [],
|
'Hyrule Castle': [],
|
||||||
'Eastern Palace': ['Eastern Boss'],
|
'Eastern Palace': ['Eastern Boss'],
|
||||||
@@ -1122,7 +1122,7 @@ def determine_required_paths(world):
|
|||||||
'Turtle Rock': ['TR Boss'],
|
'Turtle Rock': ['TR Boss'],
|
||||||
'Ganons Tower': ['GT Agahnim 2']
|
'Ganons Tower': ['GT Agahnim 2']
|
||||||
}
|
}
|
||||||
if world.shuffle == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
paths['Skull Woods'].insert(0, 'Skull 2 West Lobby')
|
paths['Skull Woods'].insert(0, 'Skull 2 West Lobby')
|
||||||
paths['Turtle Rock'].insert(0, 'TR Eye Bridge')
|
paths['Turtle Rock'].insert(0, 'TR Eye Bridge')
|
||||||
paths['Turtle Rock'].insert(0, 'TR Big Chest Entrance')
|
paths['Turtle Rock'].insert(0, 'TR Big Chest Entrance')
|
||||||
@@ -1131,7 +1131,7 @@ def determine_required_paths(world):
|
|||||||
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
|
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
||||||
if world.doorShuffle in ['basic']:
|
if world.doorShuffle[player] in ['basic']:
|
||||||
paths['Thieves Town'].append('Thieves Attic Window')
|
paths['Thieves Town'].append('Thieves Attic Window')
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
@@ -1194,7 +1194,7 @@ def add_inaccessible_doors(world, player):
|
|||||||
create_door(world, player, 'Death Mountain Return Cave (West)', 'Death Mountain Return Ledge')
|
create_door(world, player, 'Death Mountain Return Cave (West)', 'Death Mountain Return Ledge')
|
||||||
if 'Desert Palace Lone Stairs' in world.inaccessible_regions[player]:
|
if 'Desert Palace Lone Stairs' in world.inaccessible_regions[player]:
|
||||||
create_door(world, player, 'Desert Palace Entrance (East)', 'Desert Palace Lone Stairs')
|
create_door(world, player, 'Desert Palace Entrance (East)', 'Desert Palace Lone Stairs')
|
||||||
if world.mode == 'standard' and 'Hyrule Castle Ledge' in world.inaccessible_regions[player]:
|
if world.mode[player] == 'standard' and 'Hyrule Castle Ledge' in world.inaccessible_regions[player]:
|
||||||
create_door(world, player, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Ledge')
|
create_door(world, player, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Ledge')
|
||||||
create_door(world, player, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Ledge')
|
create_door(world, player, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Ledge')
|
||||||
|
|
||||||
|
|||||||
+90
-50
@@ -1,13 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from Main import main
|
from Main import main
|
||||||
from Utils import is_bundled, close_console, output_path
|
from Rom import get_sprite_from_name
|
||||||
|
from Utils import is_bundled, close_console
|
||||||
from Fill import FillError
|
from Fill import FillError
|
||||||
|
|
||||||
|
|
||||||
@@ -16,11 +19,18 @@ 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 parse_arguments(argv, no_defaults=False):
|
||||||
|
def defval(value):
|
||||||
|
return value if not no_defaults else None
|
||||||
|
|
||||||
|
# we need to know how many players we have first
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255))
|
||||||
|
multiargs, _ = parser.parse_known_args(argv)
|
||||||
|
|
||||||
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', 'nologic'],
|
parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
|
||||||
help='''\
|
help='''\
|
||||||
Select Enforcement of Item Requirements. (default: %(default)s)
|
Select Enforcement of Item Requirements. (default: %(default)s)
|
||||||
No Glitches:
|
No Glitches:
|
||||||
@@ -29,7 +39,7 @@ def start():
|
|||||||
No Logic: Distribute items without regard for
|
No Logic: Distribute items without regard for
|
||||||
item requirements.
|
item requirements.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'inverted'],
|
parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'],
|
||||||
help='''\
|
help='''\
|
||||||
Select game mode. (default: %(default)s)
|
Select game mode. (default: %(default)s)
|
||||||
Open: World starts with Zelda rescued.
|
Open: World starts with Zelda rescued.
|
||||||
@@ -42,7 +52,7 @@ def start():
|
|||||||
Requires the moon pearl to be Link in the Light World
|
Requires the moon pearl to be Link in the Light World
|
||||||
instead of a bunny.
|
instead of a bunny.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--swords', default='random', const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'],
|
parser.add_argument('--swords', default=defval('random'), const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'],
|
||||||
help='''\
|
help='''\
|
||||||
Select sword placement. (default: %(default)s)
|
Select sword placement. (default: %(default)s)
|
||||||
Random: All swords placed randomly.
|
Random: All swords placed randomly.
|
||||||
@@ -56,7 +66,7 @@ def start():
|
|||||||
Palace, to allow for an alternative to firerod.
|
Palace, to allow for an alternative to firerod.
|
||||||
Vanilla: Swords are in vanilla locations.
|
Vanilla: Swords are in vanilla locations.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'],
|
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'],
|
||||||
help='''\
|
help='''\
|
||||||
Select completion goal. (default: %(default)s)
|
Select completion goal. (default: %(default)s)
|
||||||
Ganon: Collect all crystals, beat Agahnim 2 then
|
Ganon: Collect all crystals, beat Agahnim 2 then
|
||||||
@@ -68,21 +78,21 @@ def start():
|
|||||||
Triforce Hunt: Places 30 Triforce Pieces in the world, collect
|
Triforce Hunt: Places 30 Triforce Pieces in the world, collect
|
||||||
20 of them to beat the game.
|
20 of them to beat the game.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
parser.add_argument('--difficulty', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
||||||
help='''\
|
help='''\
|
||||||
Select game difficulty. Affects available itempool. (default: %(default)s)
|
Select game difficulty. Affects available itempool. (default: %(default)s)
|
||||||
Normal: Normal difficulty.
|
Normal: Normal difficulty.
|
||||||
Hard: A harder setting with less equipment and reduced health.
|
Hard: A harder setting with less equipment and reduced health.
|
||||||
Expert: A harder yet setting with minimum equipment and health.
|
Expert: A harder yet setting with minimum equipment and health.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--item_functionality', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
parser.add_argument('--item_functionality', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
||||||
help='''\
|
help='''\
|
||||||
Select limits on item functionality to increase difficulty. (default: %(default)s)
|
Select limits on item functionality to increase difficulty. (default: %(default)s)
|
||||||
Normal: Normal functionality.
|
Normal: Normal functionality.
|
||||||
Hard: Reduced functionality.
|
Hard: Reduced functionality.
|
||||||
Expert: Greatly reduced functionality.
|
Expert: Greatly reduced functionality.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
|
parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
|
||||||
help='''\
|
help='''\
|
||||||
Select game timer setting. Affects available itempool. (default: %(default)s)
|
Select game timer setting. Affects available itempool. (default: %(default)s)
|
||||||
None: No timer.
|
None: No timer.
|
||||||
@@ -102,7 +112,7 @@ def start():
|
|||||||
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('--progressive', default='on', const='normal', nargs='?', choices=['on', 'off', 'random'],
|
parser.add_argument('--progressive', default=defval('on'), const='normal', nargs='?', choices=['on', 'off', 'random'],
|
||||||
help='''\
|
help='''\
|
||||||
Select progressive equipment setting. Affects available itempool. (default: %(default)s)
|
Select progressive equipment setting. Affects available itempool. (default: %(default)s)
|
||||||
On: Swords, Shields, Armor, and Gloves will
|
On: Swords, Shields, Armor, and Gloves will
|
||||||
@@ -116,7 +126,7 @@ def start():
|
|||||||
category, be randomly progressive or not.
|
category, be randomly progressive or not.
|
||||||
Link will die in one hit.
|
Link will die in one hit.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--algorithm', default='balanced', const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'],
|
parser.add_argument('--algorithm', default=defval('balanced'), const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'],
|
||||||
help='''\
|
help='''\
|
||||||
Select item filling algorithm. (default: %(default)s
|
Select item filling algorithm. (default: %(default)s
|
||||||
balanced: vt26 derivative that aims to strike a balance between
|
balanced: vt26 derivative that aims to strike a balance between
|
||||||
@@ -139,7 +149,7 @@ def start():
|
|||||||
slightly biased to placing progression items with
|
slightly biased to placing progression items with
|
||||||
less restrictions.
|
less restrictions.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--shuffle', default='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='full', 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
|
||||||
@@ -163,7 +173,7 @@ def start():
|
|||||||
The dungeon variants only mix up dungeons and keep the rest of
|
The dungeon variants only mix up dungeons and keep the rest of
|
||||||
the overworld vanilla.
|
the overworld vanilla.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--door_shuffle', default='vanilla', const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed', 'experimental'],
|
parser.add_argument('--door_shuffle', default=defval('basic'), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed', 'experimental'],
|
||||||
help='''\
|
help='''\
|
||||||
Select Door Shuffling Algorithm. (default: %(default)s)
|
Select Door Shuffling Algorithm. (default: %(default)s)
|
||||||
Basic: Doors are mixed within a single dungeon.
|
Basic: Doors are mixed within a single dungeon.
|
||||||
@@ -174,7 +184,7 @@ def start():
|
|||||||
base game.
|
base game.
|
||||||
Experimental: Experimental mixes live here. Use at your own risk.
|
Experimental: Experimental mixes live here. Use at your own risk.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--crystals_ganon', default='7', const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||||
help='''\
|
help='''\
|
||||||
How many crystals are needed to defeat ganon. Any other
|
How many crystals are needed to defeat ganon. Any other
|
||||||
requirements for ganon for the selected goal still apply.
|
requirements for ganon for the selected goal still apply.
|
||||||
@@ -183,16 +193,18 @@ def start():
|
|||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
Random: Picks a random value between 0 and 7 (inclusive).
|
||||||
0-7: Number of crystals needed
|
0-7: Number of crystals needed
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--crystals_gt', default='7', const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
parser.add_argument('--crystals_gt', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||||
help='''\
|
help='''\
|
||||||
How many crystals are needed to open GT. For inverted mode
|
How many crystals are needed to open GT. For inverted mode
|
||||||
this applies to the castle tower door instead. (default: %(default)s)
|
this applies to the castle tower door instead. (default: %(default)s)
|
||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
Random: Picks a random value between 0 and 7 (inclusive).
|
||||||
0-7: Number of crystals needed
|
0-7: Number of crystals needed
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--openpyramid', default=defval(False), help='''\
|
||||||
parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it
|
||||||
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
''', action='store_true')
|
||||||
|
parser.add_argument('--rom', default=defval('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'), help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||||
|
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||||
parser.add_argument('--count', help='''\
|
parser.add_argument('--count', help='''\
|
||||||
Use to batch generate multiple seeds with same settings.
|
Use to batch generate multiple seeds with same settings.
|
||||||
@@ -201,50 +213,50 @@ def start():
|
|||||||
--seed given will produce the same 10 (different) roms each
|
--seed given will produce the same 10 (different) roms each
|
||||||
time).
|
time).
|
||||||
''', type=int)
|
''', type=int)
|
||||||
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
parser.add_argument('--fastmenu', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
||||||
help='''\
|
help='''\
|
||||||
Select the rate at which the menu opens and closes.
|
Select the rate at which the menu opens and closes.
|
||||||
(default: %(default)s)
|
(default: %(default)s)
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
|
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
|
||||||
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
|
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
|
||||||
parser.add_argument('--keysanity', help='''\
|
parser.add_argument('--mapshuffle', default=defval(False), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
Keys (and other dungeon items) are no longer restricted to
|
parser.add_argument('--compassshuffle', default=defval(False), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
their dungeons, but can be anywhere
|
parser.add_argument('--keyshuffle', default=defval(False), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
''', action='store_true')
|
parser.add_argument('--bigkeyshuffle', default=defval(False), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||||
parser.add_argument('--retro', help='''\
|
parser.add_argument('--keysanity', default=defval(False), help=argparse.SUPPRESS, action='store_true')
|
||||||
|
parser.add_argument('--retro', default=defval(False), help='''\
|
||||||
Keys are universal, shooting arrows costs rupees,
|
Keys are universal, shooting arrows costs rupees,
|
||||||
and a few other little things make this more like Zelda-1.
|
and a few other little things make this more like Zelda-1.
|
||||||
''', action='store_true')
|
''', action='store_true')
|
||||||
parser.add_argument('--custom', default=False, help='Not supported.')
|
parser.add_argument('--startinventory', default=defval(''), help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
||||||
parser.add_argument('--customitemarray', default=False, help='Not supported.')
|
parser.add_argument('--custom', default=defval(False), help='Not supported.')
|
||||||
parser.add_argument('--nodungeonitems', help='''\
|
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
|
||||||
Remove Maps and Compasses from Itempool, replacing them by
|
parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
||||||
empty slots.
|
|
||||||
''', action='store_true')
|
|
||||||
parser.add_argument('--accessibility', default='items', const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
|
||||||
Select Item/Location Accessibility. (default: %(default)s)
|
Select Item/Location Accessibility. (default: %(default)s)
|
||||||
Items: You can reach all unique inventory items. No guarantees about
|
Items: You can reach all unique inventory items. No guarantees about
|
||||||
reaching all locations or all keys.
|
reaching all locations or all keys.
|
||||||
Locations: You will be able to reach every location in the game.
|
Locations: You will be able to reach every location in the game.
|
||||||
None: You will be able to reach enough locations to beat the game.
|
None: You will be able to reach enough locations to beat the game.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--hints', help='''\
|
parser.add_argument('--hints', default=defval(False), help='''\
|
||||||
Make telepathic tiles and storytellers give helpful hints.
|
Make telepathic tiles and storytellers give helpful hints.
|
||||||
''', action='store_true')
|
''', action='store_true')
|
||||||
# included for backwards compatibility
|
# included for backwards compatibility
|
||||||
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=True)
|
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True))
|
||||||
parser.add_argument('--no-shuffleganon', help='''\
|
parser.add_argument('--no-shuffleganon', help='''\
|
||||||
If set, the Pyramid Hole and Ganon's Tower are not
|
If set, the Pyramid Hole and Ganon's Tower are not
|
||||||
included entrance shuffle pool.
|
included entrance shuffle pool.
|
||||||
''', action='store_false', dest='shuffleganon')
|
''', action='store_false', dest='shuffleganon')
|
||||||
parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
|
parser.add_argument('--heartbeep', default=defval('normal'), const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
|
||||||
help='''\
|
help='''\
|
||||||
Select the rate at which the heart beep sound is played at
|
Select the rate at which the heart beep sound is played at
|
||||||
low health. (default: %(default)s)
|
low health. (default: %(default)s)
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
||||||
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
||||||
|
parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
|
||||||
|
parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
|
||||||
parser.add_argument('--sprite', help='''\
|
parser.add_argument('--sprite', help='''\
|
||||||
Path to a sprite sheet to use for Link. Needs to be in
|
Path to a sprite sheet to use for Link. Needs to be in
|
||||||
binary format and have a length of 0x7000 (28672) bytes,
|
binary format and have a length of 0x7000 (28672) bytes,
|
||||||
@@ -258,21 +270,49 @@ def start():
|
|||||||
Output .json patch to stdout instead of a patched rom. Used
|
Output .json patch to stdout instead of a patched rom. Used
|
||||||
for VT site integration, do not use otherwise.
|
for VT site integration, do not use otherwise.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--skip_playthrough', action='store_true', default=False)
|
parser.add_argument('--skip_playthrough', action='store_true', default=defval(False))
|
||||||
parser.add_argument('--enemizercli', default='')
|
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
||||||
parser.add_argument('--shufflebosses', default='none', choices=['none', 'basic', 'normal', 'chaos'])
|
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos'])
|
||||||
parser.add_argument('--shuffleenemies', default=False, action='store_true')
|
parser.add_argument('--shuffleenemies', default=defval('none'), choices=['none', 'shuffled', 'chaos'])
|
||||||
parser.add_argument('--enemy_health', default='default', choices=['default', 'easy', 'normal', 'hard', 'expert'])
|
parser.add_argument('--enemy_health', default=defval('default'), choices=['default', 'easy', 'normal', 'hard', 'expert'])
|
||||||
parser.add_argument('--enemy_damage', default='default', choices=['default', 'shuffled', 'chaos'])
|
parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos'])
|
||||||
parser.add_argument('--shufflepalette', default=False, action='store_true')
|
parser.add_argument('--shufflepots', default=defval(False), action='store_true')
|
||||||
parser.add_argument('--shufflepots', default=False, action='store_true')
|
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
||||||
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
|
parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255))
|
||||||
|
parser.add_argument('--names', default=defval(''))
|
||||||
parser.add_argument('--outputpath')
|
parser.add_argument('--outputpath')
|
||||||
args = parser.parse_args()
|
parser.add_argument('--race', default=defval(False), action='store_true')
|
||||||
|
parser.add_argument('--outputname')
|
||||||
|
|
||||||
if args.outputpath and os.path.isdir(args.outputpath):
|
if multiargs.multi:
|
||||||
output_path.cached_path = args.outputpath
|
for player in range(1, multiargs.multi + 1):
|
||||||
|
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
|
||||||
|
|
||||||
|
ret = parser.parse_args(argv)
|
||||||
|
if ret.keysanity:
|
||||||
|
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
||||||
|
|
||||||
|
if multiargs.multi:
|
||||||
|
defaults = copy.deepcopy(ret)
|
||||||
|
for player in range(1, multiargs.multi + 1):
|
||||||
|
playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True)
|
||||||
|
|
||||||
|
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||||
|
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
|
'retro', 'accessibility', 'hints', 'beemizer',
|
||||||
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep']:
|
||||||
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
|
if player == 1:
|
||||||
|
setattr(ret, name, {1: value})
|
||||||
|
else:
|
||||||
|
getattr(ret, name)[player] = value
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def start():
|
||||||
|
args = parse_arguments(None)
|
||||||
|
|
||||||
if is_bundled() and len(sys.argv) == 1:
|
if is_bundled() and len(sys.argv) == 1:
|
||||||
# for the bundled builds, if we have no arguments, the user
|
# for the bundled builds, if we have no arguments, the user
|
||||||
@@ -288,9 +328,9 @@ def start():
|
|||||||
if not args.jsonout and not os.path.isfile(args.rom):
|
if not args.jsonout and not os.path.isfile(args.rom):
|
||||||
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if args.sprite is not None and not os.path.isfile(args.sprite):
|
if any([sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in args.sprite.values()]):
|
||||||
if not args.jsonout:
|
if not args.jsonout:
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
raise IOError('Cannot find sprite file at %s' % args.sprite)
|
raise IOError('Cannot find sprite file at %s' % args.sprite)
|
||||||
|
|||||||
+20
-19
@@ -8,7 +8,7 @@ from Items import ItemFactory
|
|||||||
|
|
||||||
def create_dungeons(world, player):
|
def create_dungeons(world, player):
|
||||||
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
|
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
|
||||||
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items, player)
|
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys, dungeon_items, player)
|
||||||
dungeon.boss = BossFactory(default_boss, player)
|
dungeon.boss = BossFactory(default_boss, player)
|
||||||
for region in dungeon.regions:
|
for region in dungeon.regions:
|
||||||
world.get_region(region, player).dungeon = dungeon
|
world.get_region(region, player).dungeon = dungeon
|
||||||
@@ -27,7 +27,7 @@ def create_dungeons(world, player):
|
|||||||
MM = make_dungeon('Misery Mire', 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
|
MM = make_dungeon('Misery Mire', 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
|
||||||
TR = make_dungeon('Turtle Rock', 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
|
TR = make_dungeon('Turtle Rock', 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
AT = make_dungeon('Agahnims Tower', 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
|
AT = make_dungeon('Agahnims Tower', 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
|
||||||
GT = make_dungeon('Ganons Tower', 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
|
GT = make_dungeon('Ganons Tower', 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
|
||||||
else:
|
else:
|
||||||
@@ -47,7 +47,7 @@ def fill_dungeons(world):
|
|||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
||||||
if world.retro:
|
if world.retro[player]:
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
||||||
else:
|
else:
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
||||||
@@ -113,21 +113,20 @@ def fill_dungeons(world):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# next place dungeon items
|
# next place dungeon items
|
||||||
if world.place_dungeon_items:
|
for dungeon_item in dungeon_items:
|
||||||
for dungeon_item in dungeon_items:
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
def get_dungeon_item_pool(world):
|
||||||
return [item for dungeon in world.dungeons for item in dungeon.all_items if item.key or world.place_dungeon_items]
|
return [item for dungeon in world.dungeons for item in dungeon.all_items]
|
||||||
|
|
||||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
all_state_base = world.get_all_state()
|
all_state_base = world.get_all_state()
|
||||||
|
|
||||||
# for player in range(1, world.players + 1):
|
# for player in range(1, world.players + 1):
|
||||||
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
||||||
# if world.retro:
|
# if world.retro[player]:
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
||||||
# else:
|
# else:
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
||||||
@@ -135,22 +134,24 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||||||
# pinball_room.locked = True
|
# pinball_room.locked = True
|
||||||
# shuffled_locations.remove(pinball_room)
|
# shuffled_locations.remove(pinball_room)
|
||||||
|
|
||||||
if world.keysanity:
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
#in keysanity dungeon items are distributed as part of the normal item pool
|
for item in world.get_items():
|
||||||
for item in world.get_items():
|
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
||||||
if item.key:
|
all_state_base.collect(item, True)
|
||||||
item.advancement = True
|
item.advancement = True
|
||||||
elif item.map or item.compass:
|
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
||||||
item.priority = True
|
item.priority = True
|
||||||
return
|
|
||||||
|
|
||||||
dungeon_items = get_dungeon_item_pool(world)
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
|
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||||
|
or (item.map and not world.mapshuffle[item.player])
|
||||||
|
or (item.compass and not world.compassshuffle[item.player]))]
|
||||||
|
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items)
|
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, True)
|
||||||
|
|
||||||
|
|
||||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||||
|
|||||||
+23
-3
@@ -8,6 +8,21 @@ Hints will appear in the following ratios across the 15 telepathic tiles that ha
|
|||||||
5 hints for valuable items.
|
5 hints for valuable items.
|
||||||
4 junk hints.
|
4 junk hints.
|
||||||
|
|
||||||
|
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following ratios will be used instead:
|
||||||
|
|
||||||
|
5 hints for inconvenient item locations.
|
||||||
|
8 hints for valuable items.
|
||||||
|
7 junk hints.
|
||||||
|
|
||||||
|
In the simple, restricted, and restricted legacy shuffles, these are the ratios:
|
||||||
|
|
||||||
|
2 hints for inconvenient entrances.
|
||||||
|
1 hint for an inconvenient dungeon entrance.
|
||||||
|
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
|
||||||
|
3 hints for inconvenient item locations.
|
||||||
|
5 hints for valuable items.
|
||||||
|
5 junk hints.
|
||||||
|
|
||||||
These hints will use the following format:
|
These hints will use the following format:
|
||||||
|
|
||||||
Entrance hints go "[Entrance on overworld] leads to [interior]".
|
Entrance hints go "[Entrance on overworld] leads to [interior]".
|
||||||
@@ -65,7 +80,12 @@ Spike Cave
|
|||||||
Magic Bat
|
Magic Bat
|
||||||
Sahasrahla (Green Pendant)
|
Sahasrahla (Green Pendant)
|
||||||
|
|
||||||
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If keysanity is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
|
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two locations are added to the inconvenient locations list:
|
||||||
|
|
||||||
|
Graveyard Cave
|
||||||
|
Mimic Cave
|
||||||
|
|
||||||
|
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If key shuffle is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
|
||||||
|
|
||||||
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
|
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
|
||||||
|
|
||||||
@@ -103,8 +123,8 @@ Death Mountain Return Cave (East): The westmost cave on west DM
|
|||||||
Spectacle Rock Cave Peak: The highest cave on west DM
|
Spectacle Rock Cave Peak: The highest cave on west DM
|
||||||
Spectacle Rock Cave: The right ledge on west DM
|
Spectacle Rock Cave: The right ledge on west DM
|
||||||
Spectacle Rock Cave (Bottom): The left ledge on west DM
|
Spectacle Rock Cave (Bottom): The left ledge on west DM
|
||||||
Paradox Cave (Bottom): The southmost cave on east DM
|
Paradox Cave (Bottom): The right paired cave on east DM
|
||||||
Paradox Cave (Middle): The right paired cave on east DM
|
Paradox Cave (Middle): The southmost cave on east DM
|
||||||
Paradox Cave (Top): The east DM summit cave
|
Paradox Cave (Top): The east DM summit cave
|
||||||
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
|
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
|
||||||
Fairy Ascension Cave (Top): The central ledge on east DM
|
Fairy Ascension Cave (Top): The central ledge on east DM
|
||||||
|
|||||||
+58
-88
@@ -19,17 +19,17 @@ def link_entrances(world, player):
|
|||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
# if we do not shuffle, set default connections
|
# if we do not shuffle, set default connections
|
||||||
if world.shuffle == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
for exitname, regionname in default_dungeon_connections:
|
for exitname, regionname in default_dungeon_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
elif world.shuffle == 'dungeonssimple':
|
elif world.shuffle[player] == 'dungeonssimple':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
elif world.shuffle == 'dungeonsfull':
|
elif world.shuffle[player] == 'dungeonsfull':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ def link_entrances(world, player):
|
|||||||
lw_entrances = list(LW_Dungeon_Entrances)
|
lw_entrances = list(LW_Dungeon_Entrances)
|
||||||
dw_entrances = list(DW_Dungeon_Entrances)
|
dw_entrances = list(DW_Dungeon_Entrances)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -52,14 +52,14 @@ def link_entrances(world, player):
|
|||||||
dw_entrances.append('Ganons Tower')
|
dw_entrances.append('Ganons Tower')
|
||||||
dungeon_exits.append('Ganons Tower Exit')
|
dungeon_exits.append('Ganons Tower Exit')
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
|
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
|
||||||
connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player)
|
||||||
else:
|
else:
|
||||||
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
|
||||||
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
|
||||||
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
|
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
|
||||||
elif world.shuffle == 'simple':
|
elif world.shuffle[player] == 'simple':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
old_man_entrances = list(Old_Man_Entrances)
|
old_man_entrances = list(Old_Man_Entrances)
|
||||||
@@ -130,7 +130,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'restricted':
|
elif world.shuffle[player] == 'restricted':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
||||||
@@ -201,7 +201,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'restricted_legacy':
|
elif world.shuffle[player] == 'restricted_legacy':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances)
|
lw_entrances = list(LW_Entrances)
|
||||||
@@ -256,7 +256,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'full':
|
elif world.shuffle[player] == 'full':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
||||||
@@ -273,7 +273,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -309,7 +309,7 @@ def link_entrances(world, player):
|
|||||||
pass
|
pass
|
||||||
else: #if the cave wasn't placed we get here
|
else: #if the cave wasn't placed we get here
|
||||||
connect_caves(world, lw_entrances, [], old_man_house, player)
|
connect_caves(world, lw_entrances, [], old_man_house, player)
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world
|
# rest of hyrule castle must be in light world
|
||||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'crossed':
|
elif world.shuffle[player] == 'crossed':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
|
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
|
||||||
@@ -376,7 +376,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -392,7 +392,7 @@ def link_entrances(world, player):
|
|||||||
#place must-exit caves
|
#place must-exit caves
|
||||||
connect_mandatory_exits(world, entrances, caves, must_exits, player)
|
connect_mandatory_exits(world, entrances, caves, must_exits, player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be dealt with
|
# rest of hyrule castle must be dealt with
|
||||||
connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, entrances, door_targets, player)
|
connect_doors(world, entrances, door_targets, player)
|
||||||
elif world.shuffle == 'full_legacy':
|
elif world.shuffle[player] == 'full_legacy':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
||||||
@@ -451,7 +451,7 @@ def link_entrances(world, player):
|
|||||||
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
||||||
door_targets = list(Single_Cave_Targets)
|
door_targets = list(Single_Cave_Targets)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -471,7 +471,7 @@ def link_entrances(world, player):
|
|||||||
else:
|
else:
|
||||||
connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
|
connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
|
||||||
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
|
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world
|
# rest of hyrule castle must be in light world
|
||||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -513,7 +513,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'madness_legacy':
|
elif world.shuffle[player] == 'madness_legacy':
|
||||||
# here lie dragons, connections are no longer two way
|
# here lie dragons, connections are no longer two way
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
||||||
dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
|
dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
|
||||||
@@ -552,7 +552,7 @@ def link_entrances(world, player):
|
|||||||
('Lumberjack Tree Exit', 'Lumberjack Tree (top)'),
|
('Lumberjack Tree Exit', 'Lumberjack Tree (top)'),
|
||||||
(('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')]
|
(('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')]
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -606,7 +606,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, target, player)
|
connect_entrance(world, hole, target, player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -755,7 +755,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'insanity':
|
elif world.shuffle[player] == 'insanity':
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
||||||
@@ -792,7 +792,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -825,7 +825,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, hole_targets.pop(), player)
|
connect_entrance(world, hole, hole_targets.pop(), player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -902,8 +902,8 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'insanity_legacy':
|
elif world.shuffle[player] == 'insanity_legacy':
|
||||||
world.fix_fake_world = False
|
world.fix_fake_world[player] = False
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
||||||
@@ -927,7 +927,7 @@ def link_entrances(world, player):
|
|||||||
hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)',
|
hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)',
|
||||||
'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
|
'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -960,7 +960,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, hole_targets.pop(), player)
|
connect_entrance(world, hole, hole_targets.pop(), player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -1079,17 +1079,17 @@ def link_inverted_entrances(world, player):
|
|||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
# if we do not shuffle, set default connections
|
# if we do not shuffle, set default connections
|
||||||
if world.shuffle == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
for exitname, regionname in inverted_default_dungeon_connections:
|
for exitname, regionname in inverted_default_dungeon_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
elif world.shuffle == 'dungeonssimple':
|
elif world.shuffle[player] == 'dungeonssimple':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
elif world.shuffle == 'dungeonsfull':
|
elif world.shuffle[player] == 'dungeonsfull':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
@@ -1151,7 +1151,7 @@ def link_inverted_entrances(world, player):
|
|||||||
remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances]
|
remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances]
|
||||||
connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player)
|
connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player)
|
||||||
|
|
||||||
elif world.shuffle == 'simple':
|
elif world.shuffle[player] == 'simple':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
old_man_entrances = list(Inverted_Old_Man_Entrances)
|
old_man_entrances = list(Inverted_Old_Man_Entrances)
|
||||||
@@ -1160,7 +1160,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
single_doors = list(Single_Cave_Doors)
|
single_doors = list(Single_Cave_Doors)
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# we shuffle all 2 entrance caves as pairs as a start
|
# we shuffle all 2 entrance caves as pairs as a start
|
||||||
@@ -1191,6 +1191,8 @@ def link_inverted_entrances(world, player):
|
|||||||
bomb_shop_doors.remove(links_house)
|
bomb_shop_doors.remove(links_house)
|
||||||
if links_house in blacksmith_doors:
|
if links_house in blacksmith_doors:
|
||||||
blacksmith_doors.remove(links_house)
|
blacksmith_doors.remove(links_house)
|
||||||
|
if links_house in old_man_entrances:
|
||||||
|
old_man_entrances.remove(links_house)
|
||||||
|
|
||||||
# place dark sanc
|
# place dark sanc
|
||||||
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
|
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
|
||||||
@@ -1243,7 +1245,7 @@ def link_inverted_entrances(world, player):
|
|||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
|
|
||||||
elif world.shuffle == 'restricted':
|
elif world.shuffle[player] == 'restricted':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
|
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
|
||||||
@@ -1253,7 +1255,7 @@ def link_inverted_entrances(world, player):
|
|||||||
caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House)
|
caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House)
|
||||||
single_doors = list(Single_Cave_Doors)
|
single_doors = list(Single_Cave_Doors)
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# place links house
|
# place links house
|
||||||
@@ -1326,7 +1328,7 @@ def link_inverted_entrances(world, player):
|
|||||||
doors = lw_entrances + dw_entrances
|
doors = lw_entrances + dw_entrances
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'full':
|
elif world.shuffle[player] == 'full':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
|
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
|
||||||
@@ -1335,7 +1337,7 @@ def link_inverted_entrances(world, player):
|
|||||||
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
||||||
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
old_man_house = list(Old_Man_House)
|
old_man_house = list(Old_Man_House)
|
||||||
|
|
||||||
@@ -1477,7 +1479,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'crossed':
|
elif world.shuffle[player] == 'crossed':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
|
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
|
||||||
@@ -1486,7 +1488,7 @@ def link_inverted_entrances(world, player):
|
|||||||
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
||||||
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# randomize which desert ledge door is a must-exit
|
# randomize which desert ledge door is a must-exit
|
||||||
@@ -1587,7 +1589,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, entrances, door_targets, player)
|
connect_doors(world, entrances, door_targets, player)
|
||||||
elif world.shuffle == 'insanity':
|
elif world.shuffle[player] == 'insanity':
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
|
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
|
||||||
@@ -1609,8 +1611,8 @@ def link_inverted_entrances(world, player):
|
|||||||
# and rentering to find bomb shop. However appended list here is all those that we currently have
|
# and rentering to find bomb shop. However appended list here is all those that we currently have
|
||||||
# bomb shop logic for.
|
# bomb shop logic for.
|
||||||
# Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits
|
# Specifically we could potentially add: 'Dark Death Mountain Ledge (East)' and doors associated with pits
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Desert Palace Entrance (East)', 'Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance'])
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors + ['Turtle Rock Isolated Ledge Entrance', 'Bumper Cave (Top)', 'Hookshot Cave Back Entrance'])
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
random.shuffle(doors)
|
random.shuffle(doors)
|
||||||
@@ -1831,7 +1833,7 @@ def scramble_holes(world, player):
|
|||||||
else:
|
else:
|
||||||
hole_targets.append(('Pyramid Exit', 'Pyramid'))
|
hole_targets.append(('Pyramid Exit', 'Pyramid'))
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player)
|
connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player)
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
@@ -1840,14 +1842,14 @@ def scramble_holes(world, player):
|
|||||||
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
|
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
|
||||||
|
|
||||||
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
|
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
|
||||||
if world.shuffle == 'crossed':
|
if world.shuffle[player] == 'crossed':
|
||||||
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
||||||
if world.shuffle_ganon:
|
if world.shuffle_ganon:
|
||||||
random.shuffle(hole_targets)
|
random.shuffle(hole_targets)
|
||||||
exit, target = hole_targets.pop()
|
exit, target = hole_targets.pop()
|
||||||
connect_two_way(world, 'Pyramid Entrance', exit, player)
|
connect_two_way(world, 'Pyramid Entrance', exit, player)
|
||||||
connect_entrance(world, 'Pyramid Hole', target, player)
|
connect_entrance(world, 'Pyramid Hole', target, player)
|
||||||
if world.shuffle != 'crossed':
|
if world.shuffle[player] != 'crossed':
|
||||||
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
||||||
|
|
||||||
random.shuffle(hole_targets)
|
random.shuffle(hole_targets)
|
||||||
@@ -1882,14 +1884,14 @@ def scramble_inverted_holes(world, player):
|
|||||||
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
|
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
|
||||||
|
|
||||||
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
|
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
|
||||||
if world.shuffle == 'crossed':
|
if world.shuffle[player] == 'crossed':
|
||||||
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
||||||
if world.shuffle_ganon:
|
if world.shuffle_ganon:
|
||||||
random.shuffle(hole_targets)
|
random.shuffle(hole_targets)
|
||||||
exit, target = hole_targets.pop()
|
exit, target = hole_targets.pop()
|
||||||
connect_two_way(world, 'Inverted Pyramid Entrance', exit, player)
|
connect_two_way(world, 'Inverted Pyramid Entrance', exit, player)
|
||||||
connect_entrance(world, 'Inverted Pyramid Hole', target, player)
|
connect_entrance(world, 'Inverted Pyramid Hole', target, player)
|
||||||
if world.shuffle != 'crossed':
|
if world.shuffle[player] != 'crossed':
|
||||||
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
|
||||||
|
|
||||||
random.shuffle(hole_targets)
|
random.shuffle(hole_targets)
|
||||||
@@ -1931,11 +1933,11 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, dp_m
|
|||||||
if len(cave) == 2:
|
if len(cave) == 2:
|
||||||
entrance = entrances.pop()
|
entrance = entrances.pop()
|
||||||
# ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other
|
# ToDo Better solution, this is a hot fix. Do not connect both sides of trock/desert ledge only to each other
|
||||||
if world.mode != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)':
|
if world.mode[player] != 'inverted' and entrance == 'Dark Death Mountain Ledge (West)':
|
||||||
new_entrance = entrances.pop()
|
new_entrance = entrances.pop()
|
||||||
entrances.append(entrance)
|
entrances.append(entrance)
|
||||||
entrance = new_entrance
|
entrance = new_entrance
|
||||||
if world.mode == 'inverted' and entrance == dp_must_exit:
|
if world.mode[player] == 'inverted' and entrance == dp_must_exit:
|
||||||
new_entrance = entrances.pop()
|
new_entrance = entrances.pop()
|
||||||
entrances.append(entrance)
|
entrances.append(entrance)
|
||||||
entrance = new_entrance
|
entrance = new_entrance
|
||||||
@@ -2006,7 +2008,7 @@ def simple_shuffle_dungeons(world, player):
|
|||||||
dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace']
|
dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace']
|
||||||
dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit']
|
dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit']
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
if not world.shuffle_ganon:
|
if not world.shuffle_ganon:
|
||||||
connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
|
connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
|
||||||
else:
|
else:
|
||||||
@@ -2021,13 +2023,13 @@ def simple_shuffle_dungeons(world, player):
|
|||||||
|
|
||||||
# mix up 4 door dungeons
|
# mix up 4 door dungeons
|
||||||
multi_dungeons = ['Desert', 'Turtle Rock']
|
multi_dungeons = ['Desert', 'Turtle Rock']
|
||||||
if world.mode == 'open' or (world.mode == 'inverted' and world.shuffle_ganon):
|
if world.mode[player] == 'open' or (world.mode[player] == 'inverted' and world.shuffle_ganon):
|
||||||
multi_dungeons.append('Hyrule Castle')
|
multi_dungeons.append('Hyrule Castle')
|
||||||
random.shuffle(multi_dungeons)
|
random.shuffle(multi_dungeons)
|
||||||
|
|
||||||
dp_target = multi_dungeons[0]
|
dp_target = multi_dungeons[0]
|
||||||
tr_target = multi_dungeons[1]
|
tr_target = multi_dungeons[1]
|
||||||
if world.mode not in ['open', 'inverted'] or (world.mode == 'inverted' and world.shuffle_ganon is False):
|
if world.mode[player] not in ['open', 'inverted'] or (world.mode[player] == 'inverted' and world.shuffle_ganon is False):
|
||||||
# place hyrule castle as intended
|
# place hyrule castle as intended
|
||||||
hc_target = 'Hyrule Castle'
|
hc_target = 'Hyrule Castle'
|
||||||
else:
|
else:
|
||||||
@@ -2035,7 +2037,7 @@ def simple_shuffle_dungeons(world, player):
|
|||||||
|
|
||||||
# ToDo improve this?
|
# ToDo improve this?
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
if hc_target == 'Hyrule Castle':
|
if hc_target == 'Hyrule Castle':
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player)
|
||||||
@@ -2648,8 +2650,6 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
|||||||
'Desert Palace Entrance (West)',
|
'Desert Palace Entrance (West)',
|
||||||
'Desert Palace Entrance (North)']
|
'Desert Palace Entrance (North)']
|
||||||
|
|
||||||
Inverted_Blacksmith_Multi_Cave_Doors = [] # same as non-inverted
|
|
||||||
|
|
||||||
Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop']
|
Inverted_LW_Single_Cave_Doors = LW_Single_Cave_Doors + ['Inverted Big Bomb Shop']
|
||||||
|
|
||||||
Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)',
|
Inverted_DW_Single_Cave_Doors = ['Bonk Fairy (Dark)',
|
||||||
@@ -2717,39 +2717,8 @@ Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing',
|
|||||||
'Bumper Cave (Top)',
|
'Bumper Cave (Top)',
|
||||||
'Mimic Cave',
|
'Mimic Cave',
|
||||||
'Dark Lake Hylia Shop',
|
'Dark Lake Hylia Shop',
|
||||||
'Inverted Links House']
|
'Inverted Links House',
|
||||||
|
'Inverted Big Bomb Shop']
|
||||||
Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout',
|
|
||||||
'Lake Hylia Fairy',
|
|
||||||
'Light Hype Fairy',
|
|
||||||
'Desert Fairy',
|
|
||||||
'Chicken House',
|
|
||||||
'Aginahs Cave',
|
|
||||||
'Sahasrahlas Hut',
|
|
||||||
'Cave Shop (Lake Hylia)',
|
|
||||||
'Blacksmiths Hut',
|
|
||||||
'Sick Kids House',
|
|
||||||
'Lost Woods Gamble',
|
|
||||||
'Fortune Teller (Light)',
|
|
||||||
'Snitch Lady (East)',
|
|
||||||
'Snitch Lady (West)',
|
|
||||||
'Bush Covered House',
|
|
||||||
'Tavern (Front)',
|
|
||||||
'Light World Bomb Hut',
|
|
||||||
'Kakariko Shop',
|
|
||||||
'Mini Moldorm Cave',
|
|
||||||
'Long Fairy Cave',
|
|
||||||
'Good Bee Cave',
|
|
||||||
'20 Rupee Cave',
|
|
||||||
'50 Rupee Cave',
|
|
||||||
'Ice Rod Cave',
|
|
||||||
'Library',
|
|
||||||
'Potion Shop',
|
|
||||||
'Dam',
|
|
||||||
'Lumberjack House',
|
|
||||||
'Lake Hylia Fortune Teller',
|
|
||||||
'Kakariko Gamble Game',
|
|
||||||
'Inverted Big Bomb Shop']
|
|
||||||
|
|
||||||
|
|
||||||
Inverted_Single_Cave_Targets = ['Blinds Hideout',
|
Inverted_Single_Cave_Targets = ['Blinds Hideout',
|
||||||
@@ -2950,7 +2919,8 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
|
|||||||
('Pyramid Drop', 'East Dark World')
|
('Pyramid Drop', 'East Dark World')
|
||||||
]
|
]
|
||||||
|
|
||||||
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
|
||||||
|
('Lake Hylia Island', 'Lake Hylia Island'),
|
||||||
('Zoras River', 'Zoras River'),
|
('Zoras River', 'Zoras River'),
|
||||||
('Kings Grave Outer Rocks', 'Kings Grave Area'),
|
('Kings Grave Outer Rocks', 'Kings Grave Area'),
|
||||||
('Kings Grave Inner Rocks', 'Light World'),
|
('Kings Grave Inner Rocks', 'Light World'),
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ def distribute_items_staleness(world):
|
|||||||
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, single_player_placement = False):
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
for item in itempool:
|
for item in itempool:
|
||||||
@@ -169,53 +169,60 @@ def fill_restrictive(world, base_state, locations, itempool):
|
|||||||
new_state.sweep_for_events()
|
new_state.sweep_for_events()
|
||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
while itempool and locations:
|
unplaced_items = []
|
||||||
items_to_place = []
|
|
||||||
nextpool = []
|
|
||||||
placing_players = set()
|
|
||||||
for item in reversed(itempool):
|
|
||||||
if item.player not in placing_players:
|
|
||||||
placing_players.add(item.player)
|
|
||||||
items_to_place.append(item)
|
|
||||||
else:
|
|
||||||
nextpool.insert(0, item)
|
|
||||||
itempool = nextpool
|
|
||||||
|
|
||||||
maximum_exploration_state = sweep_from_pool()
|
no_access_checks = {}
|
||||||
|
reachable_items = {}
|
||||||
|
for item in itempool:
|
||||||
|
if world.accessibility[item.player] == 'none':
|
||||||
|
no_access_checks.setdefault(item.player, []).append(item)
|
||||||
|
else:
|
||||||
|
reachable_items.setdefault(item.player, []).append(item)
|
||||||
|
|
||||||
perform_access_check = True
|
for player_items in [no_access_checks, reachable_items]:
|
||||||
if world.accessibility == 'none':
|
while any(player_items.values()) and locations:
|
||||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state)
|
items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items]
|
||||||
|
|
||||||
for item_to_place in items_to_place:
|
maximum_exploration_state = sweep_from_pool()
|
||||||
spot_to_fill = None
|
has_beaten_game = world.has_beaten_game(maximum_exploration_state)
|
||||||
for location in locations:
|
|
||||||
if item_to_place.key: # a better test to see if a key can go there
|
|
||||||
location.item = item_to_place
|
|
||||||
test_state = maximum_exploration_state.copy()
|
|
||||||
test_state.stale[item_to_place.player] = True
|
|
||||||
else:
|
|
||||||
test_state = maximum_exploration_state
|
|
||||||
if location.can_fill(test_state, item_to_place, perform_access_check):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
elif item_to_place.key:
|
|
||||||
location.item = None
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
for item_to_place in items_to_place:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
perform_access_check = True
|
||||||
if world.can_beat_game():
|
if world.accessibility[item_to_place.player] == 'none':
|
||||||
if world.accessibility != 'none':
|
perform_access_check = not world.has_beaten_game(maximum_exploration_state, item_to_place.player) if single_player_placement else not has_beaten_game
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
|
||||||
continue
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
spot_to_fill = None
|
||||||
locations.remove(spot_to_fill)
|
|
||||||
spot_to_fill.event = True
|
|
||||||
|
|
||||||
|
for location in locations:
|
||||||
|
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
||||||
|
location.item = item_to_place
|
||||||
|
test_state = maximum_exploration_state.copy()
|
||||||
|
test_state.stale[item_to_place.player] = True
|
||||||
|
else:
|
||||||
|
test_state = maximum_exploration_state
|
||||||
|
if (not single_player_placement or location.player == item_to_place.player)\
|
||||||
|
and location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
|
spot_to_fill = location
|
||||||
|
break
|
||||||
|
elif item_to_place.smallkey or item_to_place.bigkey:
|
||||||
|
location.item = None
|
||||||
|
|
||||||
def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=None):
|
if spot_to_fill is None:
|
||||||
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
|
unplaced_items.insert(0, item_to_place)
|
||||||
|
if world.can_beat_game():
|
||||||
|
if world.accessibility[item_to_place.player] != 'none':
|
||||||
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
||||||
|
continue
|
||||||
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
locations.remove(spot_to_fill)
|
||||||
|
spot_to_fill.event = True
|
||||||
|
|
||||||
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
fill_locations = world.get_unfilled_locations()
|
fill_locations = world.get_unfilled_locations()
|
||||||
@@ -229,23 +236,26 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
|
|||||||
|
|
||||||
# fill in gtower locations with trash first
|
# fill in gtower locations with trash first
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.ganonstower_vanilla[player]:
|
if not gftower_trash or not world.ganonstower_vanilla[player]:
|
||||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
continue
|
||||||
random.shuffle(gtower_locations)
|
|
||||||
trashcnt = 0
|
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
|
||||||
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
|
||||||
spot_to_fill = gtower_locations.pop()
|
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
||||||
item_to_place = restitempool.pop()
|
random.shuffle(gtower_locations)
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
trashcnt = 0
|
||||||
fill_locations.remove(spot_to_fill)
|
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
||||||
trashcnt += 1
|
spot_to_fill = gtower_locations.pop()
|
||||||
|
item_to_place = restitempool.pop()
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
trashcnt += 1
|
||||||
|
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
fill_locations.reverse()
|
fill_locations.reverse()
|
||||||
|
|
||||||
# Make sure the escape small key is placed first in standard keysanity to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
if world.keysanity and world.mode == 'standard':
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.mode[item.player] == 'standard' and world.keyshuffle[item.player] else 0)
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0)
|
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
|
||||||
@@ -315,7 +325,7 @@ def flood_items(world):
|
|||||||
location_list = world.get_reachable_locations()
|
location_list = world.get_reachable_locations()
|
||||||
random.shuffle(location_list)
|
random.shuffle(location_list)
|
||||||
for location in location_list:
|
for location in location_list:
|
||||||
if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.key:
|
if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey:
|
||||||
# safe to replace
|
# safe to replace
|
||||||
replace_item = location.item
|
replace_item = location.item
|
||||||
replace_item.location = None
|
replace_item.location = None
|
||||||
@@ -335,8 +345,7 @@ def balance_multiworld_progression(world):
|
|||||||
reachable_locations_count[player] = 0
|
reachable_locations_count[player] = 0
|
||||||
|
|
||||||
def get_sphere_locations(sphere_state, locations):
|
def get_sphere_locations(sphere_state, locations):
|
||||||
if not world.keysanity:
|
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
|
||||||
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -346,8 +355,7 @@ def balance_multiworld_progression(world):
|
|||||||
reachable_locations_count[location.player] += 1
|
reachable_locations_count[location.player] += 1
|
||||||
|
|
||||||
if checked_locations:
|
if checked_locations:
|
||||||
average_reachable_locations = sum(reachable_locations_count.values()) / world.players
|
threshold = max(reachable_locations_count.values()) - 20
|
||||||
threshold = ((average_reachable_locations + max(reachable_locations_count.values())) / 2) * 0.8 #todo: probably needs some tweaking
|
|
||||||
|
|
||||||
balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold]
|
balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold]
|
||||||
if balancing_players:
|
if balancing_players:
|
||||||
@@ -358,9 +366,9 @@ def balance_multiworld_progression(world):
|
|||||||
candidate_items = []
|
candidate_items = []
|
||||||
while True:
|
while True:
|
||||||
for location in balancing_sphere:
|
for location in balancing_sphere:
|
||||||
if location.event:
|
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
||||||
balancing_state.collect(location.item, True, location)
|
balancing_state.collect(location.item, True, location)
|
||||||
if location.item.player in balancing_players:
|
if location.item.player in balancing_players and not location.locked:
|
||||||
candidate_items.append(location)
|
candidate_items.append(location)
|
||||||
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
||||||
for location in balancing_sphere:
|
for location in balancing_sphere:
|
||||||
@@ -368,11 +376,14 @@ def balance_multiworld_progression(world):
|
|||||||
balancing_reachables[location.player] += 1
|
balancing_reachables[location.player] += 1
|
||||||
if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]):
|
if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]):
|
||||||
break
|
break
|
||||||
|
elif not balancing_sphere:
|
||||||
|
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||||
|
|
||||||
unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations]
|
unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations]
|
||||||
items_to_replace = []
|
items_to_replace = []
|
||||||
for player in balancing_players:
|
for player in balancing_players:
|
||||||
locations_to_test = [l for l in unlocked_locations if l.player == player]
|
locations_to_test = [l for l in unlocked_locations if l.player == player]
|
||||||
|
# only replace items that end up in another player's world
|
||||||
items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player]
|
items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player]
|
||||||
while items_to_test:
|
while items_to_test:
|
||||||
testing = items_to_test.pop()
|
testing = items_to_test.pop()
|
||||||
@@ -382,9 +393,6 @@ def balance_multiworld_progression(world):
|
|||||||
|
|
||||||
reducing_state.sweep_for_events(locations=locations_to_test)
|
reducing_state.sweep_for_events(locations=locations_to_test)
|
||||||
|
|
||||||
if testing.locked:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if world.has_beaten_game(balancing_state):
|
if world.has_beaten_game(balancing_state):
|
||||||
if not world.has_beaten_game(reducing_state):
|
if not world.has_beaten_game(reducing_state):
|
||||||
items_to_replace.append(testing)
|
items_to_replace.append(testing)
|
||||||
@@ -394,13 +402,17 @@ def balance_multiworld_progression(world):
|
|||||||
items_to_replace.append(testing)
|
items_to_replace.append(testing)
|
||||||
|
|
||||||
replaced_items = False
|
replaced_items = False
|
||||||
locations_for_replacing = [l for l in checked_locations if not l.event and not l.locked]
|
replacement_locations = [l for l in checked_locations if not l.event and not l.locked]
|
||||||
while locations_for_replacing and items_to_replace:
|
while replacement_locations and items_to_replace:
|
||||||
new_location = locations_for_replacing.pop()
|
new_location = replacement_locations.pop()
|
||||||
old_location = items_to_replace.pop()
|
old_location = items_to_replace.pop()
|
||||||
|
|
||||||
|
while not new_location.can_fill(state, old_location.item, False) or (new_location.item and not old_location.can_fill(state, new_location.item, False)):
|
||||||
|
replacement_locations.insert(0, new_location)
|
||||||
|
new_location = replacement_locations.pop()
|
||||||
|
|
||||||
new_location.item, old_location.item = old_location.item, new_location.item
|
new_location.item, old_location.item = old_location.item, new_location.item
|
||||||
new_location.event = True
|
new_location.event, old_location.event = True, False
|
||||||
old_location.event = False
|
|
||||||
state.collect(new_location.item, True, new_location)
|
state.collect(new_location.item, True, new_location)
|
||||||
replaced_items = True
|
replaced_items = True
|
||||||
if replaced_items:
|
if replaced_items:
|
||||||
@@ -410,9 +422,11 @@ def balance_multiworld_progression(world):
|
|||||||
sphere_locations.append(location)
|
sphere_locations.append(location)
|
||||||
|
|
||||||
for location in sphere_locations:
|
for location in sphere_locations:
|
||||||
if location.event:
|
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
||||||
state.collect(location.item, True, location)
|
state.collect(location.item, True, location)
|
||||||
checked_locations.extend(sphere_locations)
|
checked_locations.extend(sphere_locations)
|
||||||
|
|
||||||
if world.has_beaten_game(state):
|
if world.has_beaten_game(state):
|
||||||
break
|
break
|
||||||
|
elif not sphere_locations:
|
||||||
|
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from glob import glob
|
from glob import glob
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -10,10 +11,11 @@ from urllib.parse import urlparse
|
|||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from AdjusterMain import adjust
|
from AdjusterMain import adjust
|
||||||
|
from DungeonRandomizer import parse_arguments
|
||||||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||||
from Main import main, __version__ as ESVersion
|
from Main import main, __version__ as ESVersion
|
||||||
from Rom import Sprite
|
from Rom import Sprite
|
||||||
from Utils import is_bundled, local_path, output_path, open_file
|
from Utils import is_bundled, local_path, output_path, open_file, parse_names_string
|
||||||
|
|
||||||
|
|
||||||
def guiMain(args=None):
|
def guiMain(args=None):
|
||||||
@@ -58,16 +60,20 @@ def guiMain(args=None):
|
|||||||
createSpoilerCheckbutton = Checkbutton(checkBoxFrame, text="Create Spoiler Log", variable=createSpoilerVar)
|
createSpoilerCheckbutton = Checkbutton(checkBoxFrame, text="Create Spoiler Log", variable=createSpoilerVar)
|
||||||
suppressRomVar = IntVar()
|
suppressRomVar = IntVar()
|
||||||
suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar)
|
suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar)
|
||||||
quickSwapVar = IntVar()
|
openpyramidVar = IntVar()
|
||||||
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
|
openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar)
|
||||||
keysanityVar = IntVar()
|
mcsbshuffleFrame = Frame(checkBoxFrame)
|
||||||
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
|
mcsbLabel = Label(mcsbshuffleFrame, text="Shuffle: ")
|
||||||
|
mapshuffleVar = IntVar()
|
||||||
|
mapshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Maps", variable=mapshuffleVar)
|
||||||
|
compassshuffleVar = IntVar()
|
||||||
|
compassshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Compasses", variable=compassshuffleVar)
|
||||||
|
keyshuffleVar = IntVar()
|
||||||
|
keyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="Keys", variable=keyshuffleVar)
|
||||||
|
bigkeyshuffleVar = IntVar()
|
||||||
|
bigkeyshuffleCheckbutton = Checkbutton(mcsbshuffleFrame, text="BigKeys", variable=bigkeyshuffleVar)
|
||||||
retroVar = IntVar()
|
retroVar = IntVar()
|
||||||
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
|
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
|
||||||
dungeonItemsVar = IntVar()
|
|
||||||
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
|
|
||||||
disableMusicVar = IntVar()
|
|
||||||
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
|
|
||||||
shuffleGanonVar = IntVar()
|
shuffleGanonVar = IntVar()
|
||||||
shuffleGanonVar.set(1) #set default
|
shuffleGanonVar.set(1) #set default
|
||||||
shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar)
|
shuffleGanonCheckbutton = Checkbutton(checkBoxFrame, text="Include Ganon's Tower and Pyramid Hole in shuffle pool", variable=shuffleGanonVar)
|
||||||
@@ -79,61 +85,31 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
||||||
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
||||||
quickSwapCheckbutton.pack(expand=True, anchor=W)
|
openpyramidCheckbutton.pack(expand=True, anchor=W)
|
||||||
keysanityCheckbutton.pack(expand=True, anchor=W)
|
mcsbshuffleFrame.pack(expand=True, anchor=W)
|
||||||
|
mcsbLabel.grid(row=0, column=0)
|
||||||
|
mapshuffleCheckbutton.grid(row=0, column=1)
|
||||||
|
compassshuffleCheckbutton.grid(row=0, column=2)
|
||||||
|
keyshuffleCheckbutton.grid(row=0, column=3)
|
||||||
|
bigkeyshuffleCheckbutton.grid(row=0, column=4)
|
||||||
retroCheckbutton.pack(expand=True, anchor=W)
|
retroCheckbutton.pack(expand=True, anchor=W)
|
||||||
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
|
||||||
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
|
||||||
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
shuffleGanonCheckbutton.pack(expand=True, anchor=W)
|
||||||
hintsCheckbutton.pack(expand=True, anchor=W)
|
hintsCheckbutton.pack(expand=True, anchor=W)
|
||||||
customCheckbutton.pack(expand=True, anchor=W)
|
customCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
|
||||||
fileDialogFrame = Frame(rightHalfFrame)
|
romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options")
|
||||||
|
romOptionsFrame.columnconfigure(0, weight=1)
|
||||||
|
romOptionsFrame.columnconfigure(1, weight=1)
|
||||||
|
for i in range(5):
|
||||||
|
romOptionsFrame.rowconfigure(i, weight=1)
|
||||||
|
|
||||||
heartbeepFrame = Frame(fileDialogFrame)
|
disableMusicVar = IntVar()
|
||||||
heartbeepVar = StringVar()
|
disableMusicCheckbutton = Checkbutton(romOptionsFrame, text="Disable music", variable=disableMusicVar)
|
||||||
heartbeepVar.set('normal')
|
disableMusicCheckbutton.grid(row=0, column=0, sticky=E)
|
||||||
heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
|
|
||||||
heartbeepOptionMenu.pack(side=RIGHT)
|
|
||||||
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep sound rate')
|
|
||||||
heartbeepLabel.pack(side=LEFT, padx=(0,52))
|
|
||||||
|
|
||||||
heartcolorFrame = Frame(fileDialogFrame)
|
spriteDialogFrame = Frame(romOptionsFrame)
|
||||||
heartcolorVar = StringVar()
|
spriteDialogFrame.grid(row=0, column=1)
|
||||||
heartcolorVar.set('red')
|
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
|
||||||
heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
|
|
||||||
heartcolorOptionMenu.pack(side=RIGHT)
|
|
||||||
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
|
|
||||||
heartcolorLabel.pack(side=LEFT, padx=(0,127))
|
|
||||||
|
|
||||||
fastMenuFrame = Frame(fileDialogFrame)
|
|
||||||
fastMenuVar = StringVar()
|
|
||||||
fastMenuVar.set('normal')
|
|
||||||
fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
|
||||||
fastMenuOptionMenu.pack(side=RIGHT)
|
|
||||||
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
|
||||||
fastMenuLabel.pack(side=LEFT, padx=(0,100))
|
|
||||||
|
|
||||||
heartbeepFrame.pack(expand=True, anchor=E)
|
|
||||||
heartcolorFrame.pack(expand=True, anchor=E)
|
|
||||||
fastMenuFrame.pack(expand=True, anchor=E)
|
|
||||||
|
|
||||||
romDialogFrame = Frame(fileDialogFrame)
|
|
||||||
baseRomLabel = Label(romDialogFrame, text='Base Rom')
|
|
||||||
romVar = StringVar()
|
|
||||||
romEntry = Entry(romDialogFrame, textvariable=romVar)
|
|
||||||
|
|
||||||
def RomSelect():
|
|
||||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
|
||||||
romVar.set(rom)
|
|
||||||
romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect)
|
|
||||||
|
|
||||||
baseRomLabel.pack(side=LEFT)
|
|
||||||
romEntry.pack(side=LEFT)
|
|
||||||
romSelectButton.pack(side=LEFT)
|
|
||||||
|
|
||||||
spriteDialogFrame = Frame(fileDialogFrame)
|
|
||||||
baseSpriteLabel = Label(spriteDialogFrame, text='Link Sprite:')
|
|
||||||
|
|
||||||
spriteNameVar = StringVar()
|
spriteNameVar = StringVar()
|
||||||
sprite = None
|
sprite = None
|
||||||
@@ -153,17 +129,79 @@ def guiMain(args=None):
|
|||||||
def SpriteSelect():
|
def SpriteSelect():
|
||||||
SpriteSelector(mainWindow, set_sprite)
|
SpriteSelector(mainWindow, set_sprite)
|
||||||
|
|
||||||
spriteSelectButton = Button(spriteDialogFrame, text='Open Sprite Picker', command=SpriteSelect)
|
spriteSelectButton = Button(spriteDialogFrame, text='...', command=SpriteSelect)
|
||||||
|
|
||||||
baseSpriteLabel.pack(side=LEFT)
|
baseSpriteLabel.pack(side=LEFT)
|
||||||
spriteEntry.pack(side=LEFT)
|
spriteEntry.pack(side=LEFT)
|
||||||
spriteSelectButton.pack(side=LEFT)
|
spriteSelectButton.pack(side=LEFT)
|
||||||
|
|
||||||
romDialogFrame.pack()
|
quickSwapVar = IntVar()
|
||||||
spriteDialogFrame.pack()
|
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=quickSwapVar)
|
||||||
|
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
|
||||||
|
|
||||||
checkBoxFrame.pack()
|
fastMenuFrame = Frame(romOptionsFrame)
|
||||||
fileDialogFrame.pack()
|
fastMenuFrame.grid(row=1, column=1, sticky=E)
|
||||||
|
fastMenuLabel = Label(fastMenuFrame, text='Menu speed')
|
||||||
|
fastMenuLabel.pack(side=LEFT)
|
||||||
|
fastMenuVar = StringVar()
|
||||||
|
fastMenuVar.set('normal')
|
||||||
|
fastMenuOptionMenu = OptionMenu(fastMenuFrame, fastMenuVar, 'normal', 'instant', 'double', 'triple', 'quadruple', 'half')
|
||||||
|
fastMenuOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
|
heartcolorFrame = Frame(romOptionsFrame)
|
||||||
|
heartcolorFrame.grid(row=2, column=0, sticky=E)
|
||||||
|
heartcolorLabel = Label(heartcolorFrame, text='Heart color')
|
||||||
|
heartcolorLabel.pack(side=LEFT)
|
||||||
|
heartcolorVar = StringVar()
|
||||||
|
heartcolorVar.set('red')
|
||||||
|
heartcolorOptionMenu = OptionMenu(heartcolorFrame, heartcolorVar, 'red', 'blue', 'green', 'yellow', 'random')
|
||||||
|
heartcolorOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
|
heartbeepFrame = Frame(romOptionsFrame)
|
||||||
|
heartbeepFrame.grid(row=2, column=1, sticky=E)
|
||||||
|
heartbeepLabel = Label(heartbeepFrame, text='Heartbeep')
|
||||||
|
heartbeepLabel.pack(side=LEFT)
|
||||||
|
heartbeepVar = StringVar()
|
||||||
|
heartbeepVar.set('normal')
|
||||||
|
heartbeepOptionMenu = OptionMenu(heartbeepFrame, heartbeepVar, 'double', 'normal', 'half', 'quarter', 'off')
|
||||||
|
heartbeepOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
|
owPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
owPalettesFrame.grid(row=3, column=0, sticky=E)
|
||||||
|
owPalettesLabel = Label(owPalettesFrame, text='Overworld palettes')
|
||||||
|
owPalettesLabel.pack(side=LEFT)
|
||||||
|
owPalettesVar = StringVar()
|
||||||
|
owPalettesVar.set('default')
|
||||||
|
owPalettesOptionMenu = OptionMenu(owPalettesFrame, owPalettesVar, 'default', 'random', 'blackout')
|
||||||
|
owPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
|
uwPalettesFrame = Frame(romOptionsFrame)
|
||||||
|
uwPalettesFrame.grid(row=3, column=1, sticky=E)
|
||||||
|
uwPalettesLabel = Label(uwPalettesFrame, text='Dungeon palettes')
|
||||||
|
uwPalettesLabel.pack(side=LEFT)
|
||||||
|
uwPalettesVar = StringVar()
|
||||||
|
uwPalettesVar.set('default')
|
||||||
|
uwPalettesOptionMenu = OptionMenu(uwPalettesFrame, uwPalettesVar, 'default', 'random', 'blackout')
|
||||||
|
uwPalettesOptionMenu.pack(side=LEFT)
|
||||||
|
|
||||||
|
romDialogFrame = Frame(romOptionsFrame)
|
||||||
|
romDialogFrame.grid(row=4, column=0, columnspan=2, sticky=W+E)
|
||||||
|
|
||||||
|
baseRomLabel = Label(romDialogFrame, text='Base Rom: ')
|
||||||
|
romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc")
|
||||||
|
romEntry = Entry(romDialogFrame, textvariable=romVar)
|
||||||
|
|
||||||
|
def RomSelect():
|
||||||
|
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
||||||
|
romVar.set(rom)
|
||||||
|
romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect)
|
||||||
|
|
||||||
|
baseRomLabel.pack(side=LEFT)
|
||||||
|
romEntry.pack(side=LEFT, expand=True, fill=X)
|
||||||
|
romSelectButton.pack(side=LEFT)
|
||||||
|
|
||||||
|
checkBoxFrame.pack(side=TOP, anchor=W, padx=5, pady=10)
|
||||||
|
romOptionsFrame.pack(expand=True, fill=BOTH, padx=3)
|
||||||
|
|
||||||
drowDownFrame = Frame(topFrame)
|
drowDownFrame = Frame(topFrame)
|
||||||
|
|
||||||
@@ -183,46 +221,6 @@ def guiMain(args=None):
|
|||||||
logicLabel = Label(logicFrame, text='Game logic')
|
logicLabel = Label(logicFrame, text='Game logic')
|
||||||
logicLabel.pack(side=LEFT)
|
logicLabel.pack(side=LEFT)
|
||||||
|
|
||||||
swordsFrame = Frame(drowDownFrame)
|
|
||||||
swordsVar = StringVar()
|
|
||||||
swordsVar.set('random')
|
|
||||||
swordsOptionMenu = OptionMenu(swordsFrame, swordsVar, 'random', 'assured', 'swordless', 'vanilla')
|
|
||||||
swordsOptionMenu.pack(side=RIGHT)
|
|
||||||
swordsLabel = Label(swordsFrame, text='Swords')
|
|
||||||
swordsLabel.pack(side=LEFT)
|
|
||||||
|
|
||||||
itemFuncFrame = Frame(drowDownFrame)
|
|
||||||
itemFuncVar = StringVar()
|
|
||||||
itemFuncVar.set('normal')
|
|
||||||
itemFuncOptionMenu = OptionMenu(itemFuncFrame, itemFuncVar, 'normal', 'hard', 'expert')
|
|
||||||
itemFuncOptionMenu.pack(side=RIGHT)
|
|
||||||
itemFuncLabel = Label(itemFuncFrame, text='Item Functionality')
|
|
||||||
itemFuncLabel.pack(side=LEFT)
|
|
||||||
|
|
||||||
accessibilityFrame = Frame(drowDownFrame)
|
|
||||||
accessibilityVar = StringVar()
|
|
||||||
accessibilityVar.set('items')
|
|
||||||
accessibilityOptionMenu = OptionMenu(accessibilityFrame, accessibilityVar, 'items', 'locations', 'none')
|
|
||||||
accessibilityOptionMenu.pack(side=RIGHT)
|
|
||||||
accessibilityLabel = Label(accessibilityFrame, text='Accessibility')
|
|
||||||
accessibilityLabel.pack(side=LEFT)
|
|
||||||
|
|
||||||
crystalsGanonFrame = Frame(drowDownFrame)
|
|
||||||
crystalsGanonVar = StringVar()
|
|
||||||
crystalsGanonVar.set('7')
|
|
||||||
crystalsGanonOptionMenu = OptionMenu(crystalsGanonFrame, crystalsGanonVar, 'random', '0', '1', '2', '3', '4', '5', '6', '7')
|
|
||||||
crystalsGanonOptionMenu.pack(side=RIGHT)
|
|
||||||
crystalsGanonLabel = Label(crystalsGanonFrame, text='Ganon Vulnerable')
|
|
||||||
crystalsGanonLabel.pack(side=LEFT)
|
|
||||||
|
|
||||||
crystalsGTFrame = Frame(drowDownFrame)
|
|
||||||
crystalsGTVar = StringVar()
|
|
||||||
crystalsGTVar.set('7')
|
|
||||||
crystalsGTOptionMenu = OptionMenu(crystalsGTFrame, crystalsGTVar, 'random', '0', '1', '2', '3', '4', '5', '6', '7')
|
|
||||||
crystalsGTOptionMenu.pack(side=RIGHT)
|
|
||||||
crystalsGTLabel = Label(crystalsGTFrame, text='Open Tower')
|
|
||||||
crystalsGTLabel.pack(side=LEFT)
|
|
||||||
|
|
||||||
goalFrame = Frame(drowDownFrame)
|
goalFrame = Frame(drowDownFrame)
|
||||||
goalVar = StringVar()
|
goalVar = StringVar()
|
||||||
goalVar.set('ganon')
|
goalVar.set('ganon')
|
||||||
@@ -231,6 +229,30 @@ def guiMain(args=None):
|
|||||||
goalLabel = Label(goalFrame, text='Game goal')
|
goalLabel = Label(goalFrame, text='Game goal')
|
||||||
goalLabel.pack(side=LEFT)
|
goalLabel.pack(side=LEFT)
|
||||||
|
|
||||||
|
crystalsGTFrame = Frame(drowDownFrame)
|
||||||
|
crystalsGTVar = StringVar()
|
||||||
|
crystalsGTVar.set('7')
|
||||||
|
crystalsGTOptionMenu = OptionMenu(crystalsGTFrame, crystalsGTVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random')
|
||||||
|
crystalsGTOptionMenu.pack(side=RIGHT)
|
||||||
|
crystalsGTLabel = Label(crystalsGTFrame, text='Crystals to open Ganon\'s Tower')
|
||||||
|
crystalsGTLabel.pack(side=LEFT)
|
||||||
|
|
||||||
|
crystalsGanonFrame = Frame(drowDownFrame)
|
||||||
|
crystalsGanonVar = StringVar()
|
||||||
|
crystalsGanonVar.set('7')
|
||||||
|
crystalsGanonOptionMenu = OptionMenu(crystalsGanonFrame, crystalsGanonVar, '0', '1', '2', '3', '4', '5', '6', '7', 'random')
|
||||||
|
crystalsGanonOptionMenu.pack(side=RIGHT)
|
||||||
|
crystalsGanonLabel = Label(crystalsGanonFrame, text='Crystals to fight Ganon')
|
||||||
|
crystalsGanonLabel.pack(side=LEFT)
|
||||||
|
|
||||||
|
swordFrame = Frame(drowDownFrame)
|
||||||
|
swordVar = StringVar()
|
||||||
|
swordVar.set('random')
|
||||||
|
swordOptionMenu = OptionMenu(swordFrame, swordVar, 'random', 'assured', 'swordless', 'vanilla')
|
||||||
|
swordOptionMenu.pack(side=RIGHT)
|
||||||
|
swordLabel = Label(swordFrame, text='Sword availability')
|
||||||
|
swordLabel.pack(side=LEFT)
|
||||||
|
|
||||||
difficultyFrame = Frame(drowDownFrame)
|
difficultyFrame = Frame(drowDownFrame)
|
||||||
difficultyVar = StringVar()
|
difficultyVar = StringVar()
|
||||||
difficultyVar.set('normal')
|
difficultyVar.set('normal')
|
||||||
@@ -239,6 +261,14 @@ def guiMain(args=None):
|
|||||||
difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool')
|
difficultyLabel = Label(difficultyFrame, text='Difficulty: item pool')
|
||||||
difficultyLabel.pack(side=LEFT)
|
difficultyLabel.pack(side=LEFT)
|
||||||
|
|
||||||
|
itemfunctionFrame = Frame(drowDownFrame)
|
||||||
|
itemfunctionVar = StringVar()
|
||||||
|
itemfunctionVar.set('normal')
|
||||||
|
itemfunctionOptionMenu = OptionMenu(itemfunctionFrame, itemfunctionVar, 'normal', 'hard', 'expert')
|
||||||
|
itemfunctionOptionMenu.pack(side=RIGHT)
|
||||||
|
itemfunctionLabel = Label(itemfunctionFrame, text='Difficulty: item functionality')
|
||||||
|
itemfunctionLabel.pack(side=LEFT)
|
||||||
|
|
||||||
timerFrame = Frame(drowDownFrame)
|
timerFrame = Frame(drowDownFrame)
|
||||||
timerVar = StringVar()
|
timerVar = StringVar()
|
||||||
timerVar.set('none')
|
timerVar.set('none')
|
||||||
@@ -255,6 +285,14 @@ def guiMain(args=None):
|
|||||||
progressiveLabel = Label(progressiveFrame, text='Progressive equipment')
|
progressiveLabel = Label(progressiveFrame, text='Progressive equipment')
|
||||||
progressiveLabel.pack(side=LEFT)
|
progressiveLabel.pack(side=LEFT)
|
||||||
|
|
||||||
|
accessibilityFrame = Frame(drowDownFrame)
|
||||||
|
accessibilityVar = StringVar()
|
||||||
|
accessibilityVar.set('items')
|
||||||
|
accessibilityOptionMenu = OptionMenu(accessibilityFrame, accessibilityVar, 'items', 'locations', 'none')
|
||||||
|
accessibilityOptionMenu.pack(side=RIGHT)
|
||||||
|
accessibilityLabel = Label(accessibilityFrame, text='Item accessibility')
|
||||||
|
accessibilityLabel.pack(side=LEFT)
|
||||||
|
|
||||||
algorithmFrame = Frame(drowDownFrame)
|
algorithmFrame = Frame(drowDownFrame)
|
||||||
algorithmVar = StringVar()
|
algorithmVar = StringVar()
|
||||||
algorithmVar.set('balanced')
|
algorithmVar.set('balanced')
|
||||||
@@ -314,25 +352,26 @@ def guiMain(args=None):
|
|||||||
shuffleFrame.pack(expand=True, anchor=E)
|
shuffleFrame.pack(expand=True, anchor=E)
|
||||||
doorShuffleFrame.pack(expand=True, anchor=E)
|
doorShuffleFrame.pack(expand=True, anchor=E)
|
||||||
|
|
||||||
swordsFrame.pack(expand=True, anchor=E)
|
|
||||||
difficultyFrame.pack(expand=True, anchor=E)
|
difficultyFrame.pack(expand=True, anchor=E)
|
||||||
itemFuncFrame.pack(expand=True, anchor=E)
|
itemfunctionFrame.pack(expand=True, anchor=E)
|
||||||
timerFrame.pack(expand=True, anchor=E)
|
timerFrame.pack(expand=True, anchor=E)
|
||||||
progressiveFrame.pack(expand=True, anchor=E)
|
progressiveFrame.pack(expand=True, anchor=E)
|
||||||
|
accessibilityFrame.pack(expand=True, anchor=E)
|
||||||
algorithmFrame.pack(expand=True, anchor=E)
|
algorithmFrame.pack(expand=True, anchor=E)
|
||||||
|
|
||||||
enemizerFrame = LabelFrame(randomizerWindow, text="Enemizer", padx=5, pady=5)
|
enemizerFrame = LabelFrame(randomizerWindow, text="Enemizer", padx=5, pady=2)
|
||||||
enemizerFrame.columnconfigure(0, weight=1)
|
enemizerFrame.columnconfigure(0, weight=1)
|
||||||
enemizerFrame.columnconfigure(1, weight=1)
|
enemizerFrame.columnconfigure(1, weight=1)
|
||||||
enemizerFrame.columnconfigure(2, weight=1)
|
enemizerFrame.columnconfigure(2, weight=1)
|
||||||
|
enemizerFrame.columnconfigure(3, weight=1)
|
||||||
|
|
||||||
enemizerPathFrame = Frame(enemizerFrame)
|
enemizerPathFrame = Frame(enemizerFrame)
|
||||||
enemizerPathFrame.grid(row=0, column=0, columnspan=3, sticky=W)
|
enemizerPathFrame.grid(row=0, column=0, columnspan=3, sticky=W+E, padx=3)
|
||||||
enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ")
|
enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ")
|
||||||
enemizerCLIlabel.pack(side=LEFT)
|
enemizerCLIlabel.pack(side=LEFT)
|
||||||
enemizerCLIpathVar = StringVar()
|
enemizerCLIpathVar = StringVar(value="EnemizerCLI/EnemizerCLI.Core")
|
||||||
enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=enemizerCLIpathVar, width=80)
|
enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=enemizerCLIpathVar)
|
||||||
enemizerCLIpathEntry.pack(side=LEFT)
|
enemizerCLIpathEntry.pack(side=LEFT, expand=True, fill=X)
|
||||||
def EnemizerSelectPath():
|
def EnemizerSelectPath():
|
||||||
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")])
|
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")])
|
||||||
if path:
|
if path:
|
||||||
@@ -340,18 +379,21 @@ def guiMain(args=None):
|
|||||||
enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath)
|
enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath)
|
||||||
enemizerCLIbrowseButton.pack(side=LEFT)
|
enemizerCLIbrowseButton.pack(side=LEFT)
|
||||||
|
|
||||||
enemyShuffleVar = IntVar()
|
|
||||||
enemyShuffleButton = Checkbutton(enemizerFrame, text="Enemy shuffle", variable=enemyShuffleVar)
|
|
||||||
enemyShuffleButton.grid(row=1, column=0)
|
|
||||||
paletteShuffleVar = IntVar()
|
|
||||||
paletteShuffleButton = Checkbutton(enemizerFrame, text="Palette shuffle", variable=paletteShuffleVar)
|
|
||||||
paletteShuffleButton.grid(row=1, column=1)
|
|
||||||
potShuffleVar = IntVar()
|
potShuffleVar = IntVar()
|
||||||
potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar)
|
potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar)
|
||||||
potShuffleButton.grid(row=1, column=2)
|
potShuffleButton.grid(row=0, column=3)
|
||||||
|
|
||||||
|
enemizerEnemyFrame = Frame(enemizerFrame)
|
||||||
|
enemizerEnemyFrame.grid(row=1, column=0, pady=5)
|
||||||
|
enemizerEnemyLabel = Label(enemizerEnemyFrame, text='Enemy shuffle')
|
||||||
|
enemizerEnemyLabel.pack(side=LEFT)
|
||||||
|
enemyShuffleVar = StringVar()
|
||||||
|
enemyShuffleVar.set('none')
|
||||||
|
enemizerEnemyOption = OptionMenu(enemizerEnemyFrame, enemyShuffleVar, 'none', 'shuffled', 'chaos')
|
||||||
|
enemizerEnemyOption.pack(side=LEFT)
|
||||||
|
|
||||||
enemizerBossFrame = Frame(enemizerFrame)
|
enemizerBossFrame = Frame(enemizerFrame)
|
||||||
enemizerBossFrame.grid(row=2, column=0)
|
enemizerBossFrame.grid(row=1, column=1)
|
||||||
enemizerBossLabel = Label(enemizerBossFrame, text='Boss shuffle')
|
enemizerBossLabel = Label(enemizerBossFrame, text='Boss shuffle')
|
||||||
enemizerBossLabel.pack(side=LEFT)
|
enemizerBossLabel.pack(side=LEFT)
|
||||||
enemizerBossVar = StringVar()
|
enemizerBossVar = StringVar()
|
||||||
@@ -360,7 +402,7 @@ def guiMain(args=None):
|
|||||||
enemizerBossOption.pack(side=LEFT)
|
enemizerBossOption.pack(side=LEFT)
|
||||||
|
|
||||||
enemizerDamageFrame = Frame(enemizerFrame)
|
enemizerDamageFrame = Frame(enemizerFrame)
|
||||||
enemizerDamageFrame.grid(row=2, column=1)
|
enemizerDamageFrame.grid(row=1, column=2)
|
||||||
enemizerDamageLabel = Label(enemizerDamageFrame, text='Enemy damage')
|
enemizerDamageLabel = Label(enemizerDamageFrame, text='Enemy damage')
|
||||||
enemizerDamageLabel.pack(side=LEFT)
|
enemizerDamageLabel.pack(side=LEFT)
|
||||||
enemizerDamageVar = StringVar()
|
enemizerDamageVar = StringVar()
|
||||||
@@ -369,7 +411,7 @@ def guiMain(args=None):
|
|||||||
enemizerDamageOption.pack(side=LEFT)
|
enemizerDamageOption.pack(side=LEFT)
|
||||||
|
|
||||||
enemizerHealthFrame = Frame(enemizerFrame)
|
enemizerHealthFrame = Frame(enemizerFrame)
|
||||||
enemizerHealthFrame.grid(row=2, column=2)
|
enemizerHealthFrame.grid(row=1, column=3)
|
||||||
enemizerHealthLabel = Label(enemizerHealthFrame, text='Enemy health')
|
enemizerHealthLabel = Label(enemizerHealthFrame, text='Enemy health')
|
||||||
enemizerHealthLabel.pack(side=LEFT)
|
enemizerHealthLabel.pack(side=LEFT)
|
||||||
enemizerHealthVar = StringVar()
|
enemizerHealthVar = StringVar()
|
||||||
@@ -382,6 +424,9 @@ def guiMain(args=None):
|
|||||||
worldLabel = Label(bottomFrame, text='Worlds')
|
worldLabel = Label(bottomFrame, text='Worlds')
|
||||||
worldVar = StringVar()
|
worldVar = StringVar()
|
||||||
worldSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=worldVar)
|
worldSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=worldVar)
|
||||||
|
namesLabel = Label(bottomFrame, text='Player names')
|
||||||
|
namesVar = StringVar()
|
||||||
|
namesEntry = Entry(bottomFrame, textvariable=namesVar)
|
||||||
seedLabel = Label(bottomFrame, text='Seed #')
|
seedLabel = Label(bottomFrame, text='Seed #')
|
||||||
seedVar = StringVar()
|
seedVar = StringVar()
|
||||||
seedEntry = Entry(bottomFrame, width=15, textvariable=seedVar)
|
seedEntry = Entry(bottomFrame, width=15, textvariable=seedVar)
|
||||||
@@ -390,21 +435,23 @@ def guiMain(args=None):
|
|||||||
countSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=countVar)
|
countSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=countVar)
|
||||||
|
|
||||||
def generateRom():
|
def generateRom():
|
||||||
guiargs = Namespace
|
guiargs = Namespace()
|
||||||
guiargs.multi = int(worldVar.get())
|
guiargs.multi = int(worldVar.get())
|
||||||
|
guiargs.names = namesVar.get()
|
||||||
guiargs.seed = int(seedVar.get()) if seedVar.get() else None
|
guiargs.seed = int(seedVar.get()) if seedVar.get() else None
|
||||||
guiargs.count = int(countVar.get()) if countVar.get() != '1' else None
|
guiargs.count = int(countVar.get()) if countVar.get() != '1' else None
|
||||||
guiargs.mode = modeVar.get()
|
guiargs.mode = modeVar.get()
|
||||||
guiargs.logic = logicVar.get()
|
guiargs.logic = logicVar.get()
|
||||||
guiargs.swords = swordsVar.get()
|
|
||||||
guiargs.item_functionality = itemFuncVar.get()
|
|
||||||
guiargs.accessibility = accessibilityVar.get()
|
|
||||||
guiargs.crystals_ganon = crystalsGanonVar.get()
|
|
||||||
guiargs.crystals_gt = crystalsGTVar.get()
|
|
||||||
guiargs.goal = goalVar.get()
|
guiargs.goal = goalVar.get()
|
||||||
|
guiargs.crystals_gt = crystalsGTVar.get()
|
||||||
|
guiargs.crystals_ganon = crystalsGanonVar.get()
|
||||||
|
guiargs.swords = swordVar.get()
|
||||||
guiargs.difficulty = difficultyVar.get()
|
guiargs.difficulty = difficultyVar.get()
|
||||||
|
guiargs.item_functionality = itemfunctionVar.get()
|
||||||
guiargs.timer = timerVar.get()
|
guiargs.timer = timerVar.get()
|
||||||
guiargs.progressive = progressiveVar.get()
|
guiargs.progressive = progressiveVar.get()
|
||||||
|
guiargs.accessibility = accessibilityVar.get()
|
||||||
guiargs.algorithm = algorithmVar.get()
|
guiargs.algorithm = algorithmVar.get()
|
||||||
guiargs.shuffle = shuffleVar.get()
|
guiargs.shuffle = shuffleVar.get()
|
||||||
guiargs.door_shuffle = doorShuffleVar.get()
|
guiargs.door_shuffle = doorShuffleVar.get()
|
||||||
@@ -413,19 +460,23 @@ def guiMain(args=None):
|
|||||||
guiargs.fastmenu = fastMenuVar.get()
|
guiargs.fastmenu = fastMenuVar.get()
|
||||||
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
||||||
guiargs.suppress_rom = bool(suppressRomVar.get())
|
guiargs.suppress_rom = bool(suppressRomVar.get())
|
||||||
guiargs.keysanity = bool(keysanityVar.get())
|
guiargs.openpyramid = bool(openpyramidVar.get())
|
||||||
|
guiargs.mapshuffle = bool(mapshuffleVar.get())
|
||||||
|
guiargs.compassshuffle = bool(compassshuffleVar.get())
|
||||||
|
guiargs.keyshuffle = bool(keyshuffleVar.get())
|
||||||
|
guiargs.bigkeyshuffle = bool(bigkeyshuffleVar.get())
|
||||||
guiargs.retro = bool(retroVar.get())
|
guiargs.retro = bool(retroVar.get())
|
||||||
guiargs.nodungeonitems = bool(dungeonItemsVar.get())
|
|
||||||
guiargs.quickswap = bool(quickSwapVar.get())
|
guiargs.quickswap = bool(quickSwapVar.get())
|
||||||
guiargs.disablemusic = bool(disableMusicVar.get())
|
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||||
|
guiargs.ow_palettes = owPalettesVar.get()
|
||||||
|
guiargs.uw_palettes = uwPalettesVar.get()
|
||||||
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
guiargs.shuffleganon = bool(shuffleGanonVar.get())
|
||||||
guiargs.hints = bool(hintsVar.get())
|
guiargs.hints = bool(hintsVar.get())
|
||||||
guiargs.enemizercli = enemizerCLIpathVar.get()
|
guiargs.enemizercli = enemizerCLIpathVar.get()
|
||||||
guiargs.shufflebosses = enemizerBossVar.get()
|
guiargs.shufflebosses = enemizerBossVar.get()
|
||||||
guiargs.shuffleenemies = bool(enemyShuffleVar.get())
|
guiargs.shuffleenemies = enemyShuffleVar.get()
|
||||||
guiargs.enemy_health = enemizerHealthVar.get()
|
guiargs.enemy_health = enemizerHealthVar.get()
|
||||||
guiargs.enemy_damage = enemizerDamageVar.get()
|
guiargs.enemy_damage = enemizerDamageVar.get()
|
||||||
guiargs.shufflepalette = bool(paletteShuffleVar.get())
|
|
||||||
guiargs.shufflepots = bool(potShuffleVar.get())
|
guiargs.shufflepots = bool(potShuffleVar.get())
|
||||||
guiargs.custom = bool(customVar.get())
|
guiargs.custom = bool(customVar.get())
|
||||||
guiargs.customitemarray = [int(bowVar.get()), int(silverarrowVar.get()), int(boomerangVar.get()), int(magicboomerangVar.get()), int(hookshotVar.get()), int(mushroomVar.get()), int(magicpowderVar.get()), int(firerodVar.get()),
|
guiargs.customitemarray = [int(bowVar.get()), int(silverarrowVar.get()), int(boomerangVar.get()), int(magicboomerangVar.get()), int(hookshotVar.get()), int(mushroomVar.get()), int(magicpowderVar.get()), int(firerodVar.get()),
|
||||||
@@ -435,13 +486,16 @@ def guiMain(args=None):
|
|||||||
int(sword3Var.get()), int(sword4Var.get()), int(progswordVar.get()), int(shield1Var.get()), int(shield2Var.get()), int(shield3Var.get()), int(progshieldVar.get()), int(bluemailVar.get()),
|
int(sword3Var.get()), int(sword4Var.get()), int(progswordVar.get()), int(shield1Var.get()), int(shield2Var.get()), int(shield3Var.get()), int(progshieldVar.get()), int(bluemailVar.get()),
|
||||||
int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()),
|
int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()),
|
||||||
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
||||||
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(triforcepieceVar.get()), int(triforcecountVar.get()),
|
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(progbowVar.get()), int(bomb10Var.get()), int(triforcepieceVar.get()),
|
||||||
int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
|
int(triforcecountVar.get()), int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
|
||||||
guiargs.rom = romVar.get()
|
guiargs.rom = romVar.get()
|
||||||
guiargs.jsonout = None
|
|
||||||
guiargs.sprite = sprite
|
guiargs.sprite = sprite
|
||||||
guiargs.skip_playthrough = False
|
# get default values for missing parameters
|
||||||
guiargs.outputpath = None
|
for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items():
|
||||||
|
if k not in vars(guiargs):
|
||||||
|
setattr(guiargs, k, v)
|
||||||
|
elif type(v) is dict: # use same settings for every player
|
||||||
|
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)})
|
||||||
try:
|
try:
|
||||||
if guiargs.count is not None:
|
if guiargs.count is not None:
|
||||||
seed = guiargs.seed
|
seed = guiargs.seed
|
||||||
@@ -451,14 +505,21 @@ def guiMain(args=None):
|
|||||||
else:
|
else:
|
||||||
main(seed=guiargs.seed, args=guiargs)
|
main(seed=guiargs.seed, args=guiargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||||
else:
|
else:
|
||||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
msgtxt = "Rom patched successfully"
|
||||||
|
if guiargs.names:
|
||||||
|
for player, name in parse_names_string(guiargs.names).items():
|
||||||
|
msgtxt += "\nPlayer %d => %s" % (player, name)
|
||||||
|
messagebox.showinfo(title="Success", message=msgtxt)
|
||||||
|
|
||||||
generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom)
|
generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom)
|
||||||
|
|
||||||
worldLabel.pack(side=LEFT)
|
worldLabel.pack(side=LEFT)
|
||||||
worldSpinbox.pack(side=LEFT)
|
worldSpinbox.pack(side=LEFT)
|
||||||
|
namesLabel.pack(side=LEFT)
|
||||||
|
namesEntry.pack(side=LEFT)
|
||||||
seedLabel.pack(side=LEFT, padx=(5, 0))
|
seedLabel.pack(side=LEFT, padx=(5, 0))
|
||||||
seedEntry.pack(side=LEFT)
|
seedEntry.pack(side=LEFT)
|
||||||
countLabel.pack(side=LEFT, padx=(5, 0))
|
countLabel.pack(side=LEFT, padx=(5, 0))
|
||||||
@@ -539,28 +600,59 @@ def guiMain(args=None):
|
|||||||
fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed')
|
fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed')
|
||||||
fastMenuLabel2.pack(side=LEFT)
|
fastMenuLabel2.pack(side=LEFT)
|
||||||
|
|
||||||
|
owPalettesFrame2 = Frame(drowDownFrame2)
|
||||||
|
owPalettesOptionMenu2 = OptionMenu(owPalettesFrame2, owPalettesVar, 'default', 'random', 'blackout')
|
||||||
|
owPalettesOptionMenu2.pack(side=RIGHT)
|
||||||
|
owPalettesLabel2 = Label(owPalettesFrame2, text='Overworld palettes')
|
||||||
|
owPalettesLabel2.pack(side=LEFT)
|
||||||
|
|
||||||
|
uwPalettesFrame2 = Frame(drowDownFrame2)
|
||||||
|
uwPalettesOptionMenu2 = OptionMenu(uwPalettesFrame2, uwPalettesVar, 'default', 'random', 'blackout')
|
||||||
|
uwPalettesOptionMenu2.pack(side=RIGHT)
|
||||||
|
uwPalettesLabel2 = Label(uwPalettesFrame2, text='Dungeon palettes')
|
||||||
|
uwPalettesLabel2.pack(side=LEFT)
|
||||||
|
|
||||||
|
namesFrame2 = Frame(drowDownFrame2)
|
||||||
|
namesLabel2 = Label(namesFrame2, text='Player names')
|
||||||
|
namesVar2 = StringVar()
|
||||||
|
namesEntry2 = Entry(namesFrame2, textvariable=namesVar2)
|
||||||
|
|
||||||
|
namesLabel2.pack(side=LEFT)
|
||||||
|
namesEntry2.pack(side=LEFT)
|
||||||
|
|
||||||
heartbeepFrame2.pack(expand=True, anchor=E)
|
heartbeepFrame2.pack(expand=True, anchor=E)
|
||||||
heartcolorFrame2.pack(expand=True, anchor=E)
|
heartcolorFrame2.pack(expand=True, anchor=E)
|
||||||
fastMenuFrame2.pack(expand=True, anchor=E)
|
fastMenuFrame2.pack(expand=True, anchor=E)
|
||||||
|
owPalettesFrame2.pack(expand=True, anchor=E)
|
||||||
|
uwPalettesFrame2.pack(expand=True, anchor=E)
|
||||||
|
namesFrame2.pack(expand=True, anchor=E)
|
||||||
|
|
||||||
bottomFrame2 = Frame(topFrame2)
|
bottomFrame2 = Frame(topFrame2)
|
||||||
|
|
||||||
def adjustRom():
|
def adjustRom():
|
||||||
guiargs = Namespace
|
guiargs = Namespace()
|
||||||
guiargs.heartbeep = heartbeepVar.get()
|
guiargs.heartbeep = heartbeepVar.get()
|
||||||
guiargs.heartcolor = heartcolorVar.get()
|
guiargs.heartcolor = heartcolorVar.get()
|
||||||
guiargs.fastmenu = fastMenuVar.get()
|
guiargs.fastmenu = fastMenuVar.get()
|
||||||
|
guiargs.ow_palettes = owPalettesVar.get()
|
||||||
|
guiargs.uw_palettes = uwPalettesVar.get()
|
||||||
guiargs.quickswap = bool(quickSwapVar.get())
|
guiargs.quickswap = bool(quickSwapVar.get())
|
||||||
guiargs.disablemusic = bool(disableMusicVar.get())
|
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||||
guiargs.rom = romVar2.get()
|
guiargs.rom = romVar2.get()
|
||||||
|
guiargs.baserom = romVar.get()
|
||||||
guiargs.sprite = sprite
|
guiargs.sprite = sprite
|
||||||
|
guiargs.names = namesEntry2.get()
|
||||||
try:
|
try:
|
||||||
adjust(args=guiargs)
|
adjust(args=guiargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||||
else:
|
else:
|
||||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
msgtxt = "Rom patched successfully"
|
||||||
|
if guiargs.names:
|
||||||
|
for player, name in parse_names_string(guiargs.names).items():
|
||||||
|
msgtxt += "\nPlayer %d => %s" % (player, name)
|
||||||
|
messagebox.showinfo(title="Success", message=msgtxt)
|
||||||
|
|
||||||
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
|
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
|
||||||
|
|
||||||
@@ -590,19 +682,19 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
bowFrame = Frame(itemList1)
|
bowFrame = Frame(itemList1)
|
||||||
bowLabel = Label(bowFrame, text='Bow')
|
bowLabel = Label(bowFrame, text='Bow')
|
||||||
bowVar = StringVar(value='1')
|
bowVar = StringVar(value='0')
|
||||||
bowEntry = Entry(bowFrame, textvariable=bowVar, width=3, validate='all', vcmd=vcmd)
|
bowEntry = Entry(bowFrame, textvariable=bowVar, width=3, validate='all', vcmd=vcmd)
|
||||||
bowFrame.pack()
|
bowFrame.pack()
|
||||||
bowLabel.pack(anchor=W, side=LEFT, padx=(0,53))
|
bowLabel.pack(anchor=W, side=LEFT, padx=(0,53))
|
||||||
bowEntry.pack(anchor=E)
|
bowEntry.pack(anchor=E)
|
||||||
|
|
||||||
silverarrowFrame = Frame(itemList1)
|
progbowFrame = Frame(itemList1)
|
||||||
silverarrowLabel = Label(silverarrowFrame, text='Silver Arrow')
|
progbowLabel = Label(progbowFrame, text='Prog.Bow')
|
||||||
silverarrowVar = StringVar(value='1')
|
progbowVar = StringVar(value='2')
|
||||||
silverarrowEntry = Entry(silverarrowFrame, textvariable=silverarrowVar, width=3, validate='all', vcmd=vcmd)
|
progbowEntry = Entry(progbowFrame, textvariable=progbowVar, width=3, validate='all', vcmd=vcmd)
|
||||||
silverarrowFrame.pack()
|
progbowFrame.pack()
|
||||||
silverarrowLabel.pack(anchor=W, side=LEFT, padx=(0,13))
|
progbowLabel.pack(anchor=W, side=LEFT, padx=(0,25))
|
||||||
silverarrowEntry.pack(anchor=E)
|
progbowEntry.pack(anchor=E)
|
||||||
|
|
||||||
boomerangFrame = Frame(itemList1)
|
boomerangFrame = Frame(itemList1)
|
||||||
boomerangLabel = Label(boomerangFrame, text='Boomerang')
|
boomerangLabel = Label(boomerangFrame, text='Boomerang')
|
||||||
@@ -958,7 +1050,7 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
bcap5Frame = Frame(itemList3)
|
bcap5Frame = Frame(itemList3)
|
||||||
bcap5Label = Label(bcap5Frame, text='Bomb C.+5')
|
bcap5Label = Label(bcap5Frame, text='Bomb C.+5')
|
||||||
bcap5Var = StringVar(value='6')
|
bcap5Var = StringVar(value='0')
|
||||||
bcap5Entry = Entry(bcap5Frame, textvariable=bcap5Var, width=3, validate='all', vcmd=vcmd)
|
bcap5Entry = Entry(bcap5Frame, textvariable=bcap5Var, width=3, validate='all', vcmd=vcmd)
|
||||||
bcap5Frame.pack()
|
bcap5Frame.pack()
|
||||||
bcap5Label.pack(anchor=W, side=LEFT, padx=(0,16))
|
bcap5Label.pack(anchor=W, side=LEFT, padx=(0,16))
|
||||||
@@ -966,7 +1058,7 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
bcap10Frame = Frame(itemList3)
|
bcap10Frame = Frame(itemList3)
|
||||||
bcap10Label = Label(bcap10Frame, text='Bomb C.+10')
|
bcap10Label = Label(bcap10Frame, text='Bomb C.+10')
|
||||||
bcap10Var = StringVar(value='1')
|
bcap10Var = StringVar(value='0')
|
||||||
bcap10Entry = Entry(bcap10Frame, textvariable=bcap10Var, width=3, validate='all', vcmd=vcmd)
|
bcap10Entry = Entry(bcap10Frame, textvariable=bcap10Var, width=3, validate='all', vcmd=vcmd)
|
||||||
bcap10Frame.pack()
|
bcap10Frame.pack()
|
||||||
bcap10Label.pack(anchor=W, side=LEFT, padx=(0,10))
|
bcap10Label.pack(anchor=W, side=LEFT, padx=(0,10))
|
||||||
@@ -974,7 +1066,7 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
acap5Frame = Frame(itemList4)
|
acap5Frame = Frame(itemList4)
|
||||||
acap5Label = Label(acap5Frame, text='Arrow C.+5')
|
acap5Label = Label(acap5Frame, text='Arrow C.+5')
|
||||||
acap5Var = StringVar(value='6')
|
acap5Var = StringVar(value='0')
|
||||||
acap5Entry = Entry(acap5Frame, textvariable=acap5Var, width=3, validate='all', vcmd=vcmd)
|
acap5Entry = Entry(acap5Frame, textvariable=acap5Var, width=3, validate='all', vcmd=vcmd)
|
||||||
acap5Frame.pack()
|
acap5Frame.pack()
|
||||||
acap5Label.pack(anchor=W, side=LEFT, padx=(0,7))
|
acap5Label.pack(anchor=W, side=LEFT, padx=(0,7))
|
||||||
@@ -982,7 +1074,7 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
acap10Frame = Frame(itemList4)
|
acap10Frame = Frame(itemList4)
|
||||||
acap10Label = Label(acap10Frame, text='Arrow C.+10')
|
acap10Label = Label(acap10Frame, text='Arrow C.+10')
|
||||||
acap10Var = StringVar(value='1')
|
acap10Var = StringVar(value='0')
|
||||||
acap10Entry = Entry(acap10Frame, textvariable=acap10Var, width=3, validate='all', vcmd=vcmd)
|
acap10Entry = Entry(acap10Frame, textvariable=acap10Var, width=3, validate='all', vcmd=vcmd)
|
||||||
acap10Frame.pack()
|
acap10Frame.pack()
|
||||||
acap10Label.pack(anchor=W, side=LEFT, padx=(0,1))
|
acap10Label.pack(anchor=W, side=LEFT, padx=(0,1))
|
||||||
@@ -998,7 +1090,7 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
arrow10Frame = Frame(itemList4)
|
arrow10Frame = Frame(itemList4)
|
||||||
arrow10Label = Label(arrow10Frame, text='Arrows (10)')
|
arrow10Label = Label(arrow10Frame, text='Arrows (10)')
|
||||||
arrow10Var = StringVar(value='5')
|
arrow10Var = StringVar(value='12')
|
||||||
arrow10Entry = Entry(arrow10Frame, textvariable=arrow10Var, width=3, validate='all', vcmd=vcmd)
|
arrow10Entry = Entry(arrow10Frame, textvariable=arrow10Var, width=3, validate='all', vcmd=vcmd)
|
||||||
arrow10Frame.pack()
|
arrow10Frame.pack()
|
||||||
arrow10Label.pack(anchor=W, side=LEFT, padx=(0,7))
|
arrow10Label.pack(anchor=W, side=LEFT, padx=(0,7))
|
||||||
@@ -1014,12 +1106,20 @@ def guiMain(args=None):
|
|||||||
|
|
||||||
bomb3Frame = Frame(itemList4)
|
bomb3Frame = Frame(itemList4)
|
||||||
bomb3Label = Label(bomb3Frame, text='Bombs (3)')
|
bomb3Label = Label(bomb3Frame, text='Bombs (3)')
|
||||||
bomb3Var = StringVar(value='10')
|
bomb3Var = StringVar(value='16')
|
||||||
bomb3Entry = Entry(bomb3Frame, textvariable=bomb3Var, width=3, validate='all', vcmd=vcmd)
|
bomb3Entry = Entry(bomb3Frame, textvariable=bomb3Var, width=3, validate='all', vcmd=vcmd)
|
||||||
bomb3Frame.pack()
|
bomb3Frame.pack()
|
||||||
bomb3Label.pack(anchor=W, side=LEFT, padx=(0,13))
|
bomb3Label.pack(anchor=W, side=LEFT, padx=(0,13))
|
||||||
bomb3Entry.pack(anchor=E)
|
bomb3Entry.pack(anchor=E)
|
||||||
|
|
||||||
|
bomb10Frame = Frame(itemList4)
|
||||||
|
bomb10Label = Label(bomb10Frame, text='Bombs (10)')
|
||||||
|
bomb10Var = StringVar(value='1')
|
||||||
|
bomb10Entry = Entry(bomb10Frame, textvariable=bomb10Var, width=3, validate='all', vcmd=vcmd)
|
||||||
|
bomb10Frame.pack()
|
||||||
|
bomb10Label.pack(anchor=W, side=LEFT, padx=(0,7))
|
||||||
|
bomb10Entry.pack(anchor=E)
|
||||||
|
|
||||||
rupee1Frame = Frame(itemList4)
|
rupee1Frame = Frame(itemList4)
|
||||||
rupee1Label = Label(rupee1Frame, text='Rupee (1)')
|
rupee1Label = Label(rupee1Frame, text='Rupee (1)')
|
||||||
rupee1Var = StringVar(value='2')
|
rupee1Var = StringVar(value='2')
|
||||||
@@ -1068,14 +1168,6 @@ def guiMain(args=None):
|
|||||||
rupee300Label.pack(anchor=W, side=LEFT, padx=(0,0))
|
rupee300Label.pack(anchor=W, side=LEFT, padx=(0,0))
|
||||||
rupee300Entry.pack(anchor=E)
|
rupee300Entry.pack(anchor=E)
|
||||||
|
|
||||||
rupoorFrame = Frame(itemList4)
|
|
||||||
rupoorLabel = Label(rupoorFrame, text='Rupoor')
|
|
||||||
rupoorVar = StringVar(value='0')
|
|
||||||
rupoorEntry = Entry(rupoorFrame, textvariable=rupoorVar, width=3, validate='all', vcmd=vcmd)
|
|
||||||
rupoorFrame.pack()
|
|
||||||
rupoorLabel.pack(anchor=W, side=LEFT, padx=(0,28))
|
|
||||||
rupoorEntry.pack(anchor=E)
|
|
||||||
|
|
||||||
blueclockFrame = Frame(itemList4)
|
blueclockFrame = Frame(itemList4)
|
||||||
blueclockLabel = Label(blueclockFrame, text='Blue Clock')
|
blueclockLabel = Label(blueclockFrame, text='Blue Clock')
|
||||||
blueclockVar = StringVar(value='0')
|
blueclockVar = StringVar(value='0')
|
||||||
@@ -1100,6 +1192,14 @@ def guiMain(args=None):
|
|||||||
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
|
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
|
||||||
redclockEntry.pack(anchor=E)
|
redclockEntry.pack(anchor=E)
|
||||||
|
|
||||||
|
silverarrowFrame = Frame(itemList5)
|
||||||
|
silverarrowLabel = Label(silverarrowFrame, text='Silver Arrow')
|
||||||
|
silverarrowVar = StringVar(value='0')
|
||||||
|
silverarrowEntry = Entry(silverarrowFrame, textvariable=silverarrowVar, width=3, validate='all', vcmd=vcmd)
|
||||||
|
silverarrowFrame.pack()
|
||||||
|
silverarrowLabel.pack(anchor=W, side=LEFT, padx=(0,64))
|
||||||
|
silverarrowEntry.pack(anchor=E)
|
||||||
|
|
||||||
universalkeyFrame = Frame(itemList5)
|
universalkeyFrame = Frame(itemList5)
|
||||||
universalkeyLabel = Label(universalkeyFrame, text='Universal Key')
|
universalkeyLabel = Label(universalkeyFrame, text='Universal Key')
|
||||||
universalkeyVar = StringVar(value='0')
|
universalkeyVar = StringVar(value='0')
|
||||||
@@ -1132,6 +1232,14 @@ def guiMain(args=None):
|
|||||||
triforceLabel.pack(anchor=W, side=LEFT, padx=(0,23))
|
triforceLabel.pack(anchor=W, side=LEFT, padx=(0,23))
|
||||||
triforceEntry.pack(anchor=E)
|
triforceEntry.pack(anchor=E)
|
||||||
|
|
||||||
|
rupoorFrame = Frame(itemList5)
|
||||||
|
rupoorLabel = Label(rupoorFrame, text='Rupoor')
|
||||||
|
rupoorVar = StringVar(value='0')
|
||||||
|
rupoorEntry = Entry(rupoorFrame, textvariable=rupoorVar, width=3, validate='all', vcmd=vcmd)
|
||||||
|
rupoorFrame.pack()
|
||||||
|
rupoorLabel.pack(anchor=W, side=LEFT, padx=(0,87))
|
||||||
|
rupoorEntry.pack(anchor=E)
|
||||||
|
|
||||||
rupoorcostFrame = Frame(itemList5)
|
rupoorcostFrame = Frame(itemList5)
|
||||||
rupoorcostLabel = Label(rupoorcostFrame, text='Rupoor Cost')
|
rupoorcostLabel = Label(rupoorcostFrame, text='Rupoor Cost')
|
||||||
rupoorcostVar = StringVar(value='10')
|
rupoorcostVar = StringVar(value='10')
|
||||||
@@ -1148,13 +1256,17 @@ def guiMain(args=None):
|
|||||||
topFrame3.pack(side=TOP, pady=(17,0))
|
topFrame3.pack(side=TOP, pady=(17,0))
|
||||||
|
|
||||||
if args is not None:
|
if args is not None:
|
||||||
|
for k,v in vars(args).items():
|
||||||
|
if type(v) is dict:
|
||||||
|
setattr(args, k, v[1]) # only get values for player 1 for now
|
||||||
# load values from commandline args
|
# load values from commandline args
|
||||||
createSpoilerVar.set(int(args.create_spoiler))
|
createSpoilerVar.set(int(args.create_spoiler))
|
||||||
suppressRomVar.set(int(args.suppress_rom))
|
suppressRomVar.set(int(args.suppress_rom))
|
||||||
keysanityVar.set(args.keysanity)
|
mapshuffleVar.set(args.mapshuffle)
|
||||||
|
compassshuffleVar.set(args.compassshuffle)
|
||||||
|
keyshuffleVar.set(args.keyshuffle)
|
||||||
|
bigkeyshuffleVar.set(args.bigkeyshuffle)
|
||||||
retroVar.set(args.retro)
|
retroVar.set(args.retro)
|
||||||
if args.nodungeonitems:
|
|
||||||
dungeonItemsVar.set(int(not args.nodungeonitems))
|
|
||||||
quickSwapVar.set(int(args.quickswap))
|
quickSwapVar.set(int(args.quickswap))
|
||||||
disableMusicVar.set(int(args.disablemusic))
|
disableMusicVar.set(int(args.disablemusic))
|
||||||
if args.count:
|
if args.count:
|
||||||
@@ -1162,9 +1274,9 @@ def guiMain(args=None):
|
|||||||
if args.seed:
|
if args.seed:
|
||||||
seedVar.set(str(args.seed))
|
seedVar.set(str(args.seed))
|
||||||
modeVar.set(args.mode)
|
modeVar.set(args.mode)
|
||||||
swordsVar.set(args.swords)
|
swordVar.set(args.swords)
|
||||||
difficultyVar.set(args.difficulty)
|
difficultyVar.set(args.difficulty)
|
||||||
itemFuncVar.set(args.item_functionality)
|
itemfunctionVar.set(args.item_functionality)
|
||||||
timerVar.set(args.timer)
|
timerVar.set(args.timer)
|
||||||
progressiveVar.set(args.progressive)
|
progressiveVar.set(args.progressive)
|
||||||
accessibilityVar.set(args.accessibility)
|
accessibilityVar.set(args.accessibility)
|
||||||
|
|||||||
+239
-311
@@ -1,5 +1,6 @@
|
|||||||
import collections
|
import collections
|
||||||
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
|
from BaseClasses import RegionType
|
||||||
|
from Regions import create_lw_region, create_dw_region, create_cave_region, create_dungeon_region
|
||||||
|
|
||||||
|
|
||||||
def create_inverted_regions(world, player):
|
def create_inverted_regions(world, player):
|
||||||
@@ -9,7 +10,7 @@ def create_inverted_regions(world, player):
|
|||||||
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
|
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
|
||||||
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
|
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
|
||||||
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
|
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
|
||||||
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier',
|
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island',
|
||||||
'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
|
'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
|
||||||
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop',
|
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop',
|
||||||
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
|
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
|
||||||
@@ -302,52 +303,11 @@ def create_inverted_regions(world, player):
|
|||||||
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
|
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
|
||||||
]
|
]
|
||||||
|
|
||||||
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
|
||||||
region = world.get_region(region_name, player)
|
|
||||||
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
|
||||||
region.shop = shop
|
|
||||||
world.shops.append(shop)
|
|
||||||
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
|
||||||
shop.add_inventory(index, item, price)
|
|
||||||
|
|
||||||
region = world.get_region('Capacity Upgrade', player)
|
def mark_dark_world_regions(world, player):
|
||||||
shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
|
|
||||||
region.shop = shop
|
|
||||||
world.shops.append(shop)
|
|
||||||
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
|
||||||
shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7)
|
|
||||||
world.intialize_regions()
|
|
||||||
|
|
||||||
def create_lw_region(player, name, locations=None, exits=None):
|
|
||||||
return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
|
|
||||||
|
|
||||||
def create_dw_region(player, name, locations=None, exits=None):
|
|
||||||
return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits)
|
|
||||||
|
|
||||||
def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None):
|
|
||||||
return _create_region(player, name, RegionType.Cave, hint, locations, exits)
|
|
||||||
|
|
||||||
def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None):
|
|
||||||
return _create_region(player, name, RegionType.Dungeon, hint, locations, exits)
|
|
||||||
|
|
||||||
def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None):
|
|
||||||
ret = Region(name, type, hint, player)
|
|
||||||
if locations is None:
|
|
||||||
locations = []
|
|
||||||
if exits is None:
|
|
||||||
exits = []
|
|
||||||
|
|
||||||
for exit in exits:
|
|
||||||
ret.exits.append(Entrance(player, exit, ret))
|
|
||||||
for location in locations:
|
|
||||||
address, crystal, hint_text = location_table[location]
|
|
||||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def mark_dark_world_regions(world):
|
|
||||||
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
||||||
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
||||||
queue = collections.deque(region for region in world.regions if region.type == RegionType.DarkWorld)
|
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld)
|
||||||
seen = set(queue)
|
seen = set(queue)
|
||||||
while queue:
|
while queue:
|
||||||
current = queue.popleft()
|
current = queue.popleft()
|
||||||
@@ -360,7 +320,7 @@ def mark_dark_world_regions(world):
|
|||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
queue = collections.deque(region for region in world.regions if region.type == RegionType.LightWorld)
|
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld)
|
||||||
seen = set(queue)
|
seen = set(queue)
|
||||||
while queue:
|
while queue:
|
||||||
current = queue.popleft()
|
current = queue.popleft()
|
||||||
@@ -373,269 +333,237 @@ def mark_dark_world_regions(world):
|
|||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
# (room_id, shopkeeper, replaceable)
|
|
||||||
shop_table = {
|
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
|
|
||||||
'Red Shield Shop': (0x0110, 0xC1, True),
|
|
||||||
'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
|
|
||||||
'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
|
|
||||||
'Village of Outcasts Shop': (0x010F, 0xC1, True),
|
|
||||||
'Dark World Potion Shop': (0x010F, 0xC1, True),
|
|
||||||
'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
|
|
||||||
'Kakariko Shop': (0x011F, 0xA0, True),
|
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
|
|
||||||
'Potion Shop': (0x0109, 0xFF, False),
|
|
||||||
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
|
||||||
}
|
|
||||||
# region, [item]
|
|
||||||
# slot, item, price, max=0, replacement=None, replacement_price=0
|
|
||||||
# item = (item, price)
|
|
||||||
|
|
||||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||||
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
||||||
default_shop_contents = {
|
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
||||||
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
|
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
|
||||||
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
|
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
|
||||||
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
|
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
|
||||||
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
|
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
|
||||||
'Village of Outcasts Shop': _dark_world_shop_defaults,
|
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
|
||||||
'Dark World Potion Shop': _dark_world_shop_defaults,
|
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
|
||||||
'Light World Death Mountain Shop': _basic_shop_defaults,
|
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
|
||||||
'Kakariko Shop': _basic_shop_defaults,
|
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
|
||||||
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
|
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
|
||||||
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
|
||||||
}
|
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
|
||||||
|
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
|
||||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
|
||||||
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
|
||||||
'Flute Spot': (0x18014A, False, 'underground'),
|
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
|
||||||
'Sunken Treasure': (0x180145, False, 'underwater'),
|
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
|
||||||
'Purple Chest': (0x33D68, False, 'from a box'),
|
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
|
||||||
'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'),
|
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
|
||||||
'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'),
|
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
|
||||||
'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'),
|
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
|
||||||
'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'),
|
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
|
||||||
'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'),
|
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
|
||||||
'Link\'s Uncle': (0x2DF45, False, 'with your uncle'),
|
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
|
||||||
'Secret Passage': (0xE971, False, 'near your uncle'),
|
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
|
||||||
'King Zora': (0xEE1C3, False, 'at a high price'),
|
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
|
||||||
'Zora\'s Ledge': (0x180149, False, 'near Zora'),
|
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
|
||||||
'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'),
|
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
|
||||||
'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'),
|
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
|
||||||
'King\'s Tomb': (0xE97A, False, 'alone in a cave'),
|
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
|
||||||
'Floodgate Chest': (0xE98C, False, 'in the dam'),
|
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
|
||||||
'Link\'s House': (0xE9BC, False, 'in your home'),
|
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
|
||||||
'Kakariko Tavern': (0xE9CE, False, 'in the bar'),
|
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
|
||||||
'Chicken House': (0xE9E9, False, 'near poultry'),
|
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
|
||||||
'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'),
|
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
|
||||||
'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'),
|
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
|
||||||
'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'),
|
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
|
||||||
'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'),
|
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
|
||||||
'Sahasrahla': (0x2F1FC, False, 'with the elder'),
|
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
|
||||||
'Kakariko Well - Top': (0xEA8E, False, 'in a well'),
|
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
|
||||||
'Kakariko Well - Left': (0xEA91, False, 'in a well'),
|
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
|
||||||
'Kakariko Well - Middle': (0xEA94, False, 'in a well'),
|
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
|
||||||
'Kakariko Well - Right': (0xEA97, False, 'in a well'),
|
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
|
||||||
'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'),
|
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
|
||||||
'Blacksmith': (0x18002A, False, 'with the smith'),
|
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
|
||||||
'Magic Bat': (0x180015, False, 'with the bat'),
|
'Library': (0x180012, 0x18635c, False, 'near books'),
|
||||||
'Sick Kid': (0x339CF, False, 'with the sick'),
|
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
|
||||||
'Hobo': (0x33E7D, False, 'with the hobo'),
|
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
|
||||||
'Lost Woods Hideout': (0x180000, False, 'near a thief'),
|
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
|
||||||
'Lumberjack Tree': (0x180001, False, 'in a hole'),
|
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
|
||||||
'Cave 45': (0x180003, False, 'alone in a cave'),
|
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
|
||||||
'Graveyard Cave': (0x180004, False, 'alone in a cave'),
|
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
|
||||||
'Checkerboard Cave': (0x180005, False, 'alone in a cave'),
|
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
|
||||||
'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'),
|
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
|
||||||
'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'),
|
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
|
||||||
'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'),
|
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
|
||||||
'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'),
|
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
|
||||||
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'),
|
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
|
||||||
'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'),
|
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
|
||||||
'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'),
|
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
|
||||||
'Library': (0x180012, False, 'near books'),
|
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
|
||||||
'Potion Shop': (0x180014, False, 'near potions'),
|
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
|
||||||
'Lake Hylia Island': (0x180144, False, 'on an island'),
|
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
|
||||||
'Maze Race': (0x180142, False, 'at the race'),
|
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
|
||||||
'Desert Ledge': (0x180143, False, 'in the desert'),
|
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
|
||||||
'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'),
|
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
|
||||||
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'),
|
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
|
||||||
'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'),
|
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
|
||||||
'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'),
|
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
|
||||||
'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'),
|
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
|
||||||
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'),
|
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
|
||||||
'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'),
|
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
|
||||||
'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'),
|
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
|
||||||
'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'),
|
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
|
||||||
'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'),
|
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
|
||||||
'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'),
|
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
|
||||||
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'),
|
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
|
||||||
'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'),
|
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
|
||||||
'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'),
|
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
|
||||||
'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'),
|
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
|
||||||
'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'),
|
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
|
||||||
'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'),
|
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
|
||||||
'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'),
|
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
|
||||||
'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'),
|
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
|
||||||
'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'),
|
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
|
||||||
'Sanctuary': (0xEA79, False, 'in Sanctuary'),
|
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
|
||||||
'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'),
|
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
|
||||||
'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'),
|
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
|
||||||
'Old Man': (0xF69FA, False, 'with the old man'),
|
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
|
||||||
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'),
|
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
|
||||||
'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'),
|
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
|
||||||
'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'),
|
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
|
||||||
'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'),
|
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
|
||||||
'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'),
|
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
|
||||||
'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'),
|
'Digging Game': (0x180148, 0x186357, False, 'underground'),
|
||||||
'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'),
|
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
|
||||||
'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'),
|
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
|
||||||
'Spiral Cave': (0xE9BF, False, 'in spiral cave'),
|
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
|
||||||
'Ether Tablet': (0x180016, False, 'at a monolith'),
|
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
|
||||||
'Spectacle Rock': (0x180140, False, 'atop a rock'),
|
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
|
||||||
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'),
|
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
|
||||||
'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'),
|
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
|
||||||
'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'),
|
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
|
||||||
'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'),
|
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
|
||||||
'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'),
|
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
|
||||||
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'),
|
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
|
||||||
'Pyramid': (0x180147, False, 'on the pyramid'),
|
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
|
||||||
'Catfish': (0xEE185, False, 'with a catfish'),
|
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
|
||||||
'Stumpy': (0x330C7, False, 'with tree boy'),
|
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
|
||||||
'Digging Game': (0x180148, False, 'underground'),
|
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
|
||||||
'Bombos Tablet': (0x180017, False, 'at a monolith'),
|
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
|
||||||
'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'),
|
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
|
||||||
'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'),
|
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
|
||||||
'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'),
|
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
|
||||||
'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'),
|
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
|
||||||
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'),
|
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
|
||||||
'Peg Cave': (0x180006, False, 'alone in a cave'),
|
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
|
||||||
'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'),
|
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
|
||||||
'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'),
|
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
|
||||||
'Brewery': (0xE9EC, False, 'alone in a home'),
|
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
|
||||||
'C-Shaped House': (0xE9EF, False, 'alone in a home'),
|
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
|
||||||
'Chest Game': (0xEDA8, False, 'as a prize'),
|
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
|
||||||
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'),
|
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
|
||||||
'Mire Shed - Left': (0xEA73, False, 'near sparks'),
|
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
|
||||||
'Mire Shed - Right': (0xEA76, False, 'near sparks'),
|
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
|
||||||
'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'),
|
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
|
||||||
'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'),
|
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
|
||||||
'Spike Cave': (0xEA8B, False, 'beyond spikes'),
|
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
|
||||||
'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'),
|
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
|
||||||
'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'),
|
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
|
||||||
'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'),
|
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
|
||||||
'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'),
|
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
|
||||||
'Floating Island': (0x180141, False, 'on an island'),
|
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
|
||||||
'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'),
|
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
|
||||||
'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'),
|
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
|
||||||
'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'),
|
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
|
||||||
'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'),
|
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
|
||||||
'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'),
|
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'),
|
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'),
|
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'),
|
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'),
|
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'),
|
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
|
||||||
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'),
|
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
|
||||||
'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'),
|
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
|
||||||
'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'),
|
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'),
|
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'),
|
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'),
|
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'),
|
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'),
|
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
|
||||||
'Thieves\' Town - Boss': (0x180156, False, 'with Blind'),
|
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
|
||||||
'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'),
|
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
|
||||||
'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'),
|
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'),
|
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'),
|
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'),
|
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'),
|
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'),
|
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
|
||||||
'Skull Woods - Boss': (0x180155, False, 'with Mothula'),
|
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
|
||||||
'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'),
|
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
|
||||||
'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'),
|
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'),
|
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'),
|
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'),
|
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'),
|
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'),
|
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
|
||||||
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'),
|
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
|
||||||
'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'),
|
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
|
||||||
'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'),
|
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
|
||||||
'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'),
|
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
|
||||||
'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'),
|
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
|
||||||
'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'),
|
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
|
||||||
'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'),
|
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
|
||||||
'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'),
|
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
|
||||||
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'),
|
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'),
|
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'),
|
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'),
|
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'),
|
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'),
|
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'),
|
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'),
|
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'),
|
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'),
|
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'),
|
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
|
||||||
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'),
|
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
|
||||||
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'),
|
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'),
|
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'),
|
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'),
|
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'),
|
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'),
|
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'),
|
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'),
|
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'),
|
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'),
|
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'),
|
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'),
|
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'),
|
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'),
|
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
|
||||||
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'),
|
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'),
|
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'),
|
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'),
|
'Ganon': (None, None, False, 'from me'),
|
||||||
'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'),
|
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||||
'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'),
|
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||||
'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'),
|
'Floodgate': (None, None, False, None),
|
||||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'),
|
'Frog': (None, None, False, None),
|
||||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'),
|
'Missing Smith': (None, None, False, None),
|
||||||
'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'),
|
'Dark Blacksmith Ruins': (None, None, False, None),
|
||||||
'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'),
|
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
|
||||||
'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'),
|
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
|
||||||
'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'),
|
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
|
||||||
'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'),
|
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
|
||||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'),
|
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
|
||||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'),
|
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
|
||||||
'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'),
|
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
|
||||||
'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'),
|
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
|
||||||
'Ganon': (None, False, 'from me'),
|
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
|
||||||
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'),
|
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||||
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'),
|
|
||||||
'Floodgate': (None, False, None),
|
|
||||||
'Frog': (None, False, None),
|
|
||||||
'Missing Smith': (None, False, None),
|
|
||||||
'Dark Blacksmith Ruins': (None, False, None),
|
|
||||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
|
||||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
|
||||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
|
||||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'),
|
|
||||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'),
|
|
||||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'),
|
|
||||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'),
|
|
||||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'),
|
|
||||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'),
|
|
||||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')}
|
|
||||||
|
|||||||
+143
-115
@@ -35,7 +35,7 @@ Difficulty = namedtuple('Difficulty',
|
|||||||
'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother',
|
'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother',
|
||||||
'triforcehunt', 'triforce_pieces_required', 'retro',
|
'triforcehunt', 'triforce_pieces_required', 'retro',
|
||||||
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
||||||
'progressive_armor_limit', 'progressive_bottle_limit',
|
'progressive_armor_limit', 'progressive_bottle_limit',
|
||||||
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
|
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
|
||||||
|
|
||||||
total_items_to_place = 153
|
total_items_to_place = 153
|
||||||
@@ -51,8 +51,8 @@ difficulties = {
|
|||||||
progressivearmor = ['Progressive Armor'] * 2,
|
progressivearmor = ['Progressive Armor'] * 2,
|
||||||
basicarmor = ['Blue Mail', 'Red Mail'],
|
basicarmor = ['Blue Mail', 'Red Mail'],
|
||||||
swordless = ['Rupees (20)'] * 4,
|
swordless = ['Rupees (20)'] * 4,
|
||||||
progressivesword = ['Progressive Sword'] * 3,
|
progressivesword = ['Progressive Sword'] * 4,
|
||||||
basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'],
|
basicsword = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'],
|
||||||
basicbow = ['Bow', 'Silver Arrows'],
|
basicbow = ['Bow', 'Silver Arrows'],
|
||||||
timedohko = ['Green Clock'] * 25,
|
timedohko = ['Green Clock'] * 25,
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
@@ -78,8 +78,8 @@ difficulties = {
|
|||||||
progressivearmor = ['Progressive Armor'] * 2,
|
progressivearmor = ['Progressive Armor'] * 2,
|
||||||
basicarmor = ['Progressive Armor'] * 2, # neither will count
|
basicarmor = ['Progressive Armor'] * 2, # neither will count
|
||||||
swordless = ['Rupees (20)'] * 4,
|
swordless = ['Rupees (20)'] * 4,
|
||||||
progressivesword = ['Progressive Sword'] * 3,
|
progressivesword = ['Progressive Sword'] * 4,
|
||||||
basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'],
|
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword'],
|
||||||
basicbow = ['Bow'] * 2,
|
basicbow = ['Bow'] * 2,
|
||||||
timedohko = ['Green Clock'] * 25,
|
timedohko = ['Green Clock'] * 25,
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
@@ -105,8 +105,8 @@ difficulties = {
|
|||||||
progressivearmor = ['Progressive Armor'] * 2, # neither will count
|
progressivearmor = ['Progressive Armor'] * 2, # neither will count
|
||||||
basicarmor = ['Progressive Armor'] * 2, # neither will count
|
basicarmor = ['Progressive Armor'] * 2, # neither will count
|
||||||
swordless = ['Rupees (20)'] * 4,
|
swordless = ['Rupees (20)'] * 4,
|
||||||
progressivesword = ['Progressive Sword'] * 3,
|
progressivesword = ['Progressive Sword'] * 4,
|
||||||
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
basicsword = ['Fighter Sword', 'Fighter Sword', 'Master Sword', 'Master Sword'],
|
||||||
basicbow = ['Bow'] * 2,
|
basicbow = ['Bow'] * 2,
|
||||||
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
@@ -125,26 +125,23 @@ difficulties = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generate_itempool(world, player):
|
def generate_itempool(world, player):
|
||||||
if (world.difficulty not in ['normal', 'hard', 'expert'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
|
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
|
||||||
or world.mode not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
||||||
raise NotImplementedError('Not supported yet')
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
if world.timer in ['ohko', 'timed-ohko']:
|
if world.timer in ['ohko', 'timed-ohko']:
|
||||||
world.can_take_damage = False
|
world.can_take_damage = False
|
||||||
|
|
||||||
if world.goal in ['pedestal', 'triforcehunt']:
|
if world.goal[player] in ['pedestal', 'triforcehunt']:
|
||||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Nothing', player), False)
|
world.push_item(world.get_location('Ganon', player), ItemFactory('Nothing', player), False)
|
||||||
else:
|
else:
|
||||||
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
world.push_item(world.get_location('Ganon', player), ItemFactory('Triforce', player), False)
|
||||||
|
|
||||||
if world.goal in ['triforcehunt']:
|
if world.goal[player] in ['triforcehunt']:
|
||||||
if world.mode == 'inverted':
|
region = world.get_region('Light World',player)
|
||||||
region = world.get_region('Light World',player)
|
|
||||||
else:
|
|
||||||
region = world.get_region('Hyrule Castle Courtyard', player)
|
|
||||||
|
|
||||||
loc = Location(player, "Murahdahla", parent=region)
|
loc = Location(player, "Murahdahla", parent=region)
|
||||||
loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count
|
loc.access_rule = lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) > state.world.treasure_hunt_count[player]
|
||||||
region.locations.append(loc)
|
region.locations.append(loc)
|
||||||
world.dynamic_locations.append(loc)
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
@@ -153,7 +150,7 @@ def generate_itempool(world, player):
|
|||||||
world.push_item(loc, ItemFactory('Triforce', player), False)
|
world.push_item(loc, ItemFactory('Triforce', player), False)
|
||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
|
||||||
world.get_location('Ganon', player).event = True
|
world.get_location('Ganon', player).event = True
|
||||||
world.get_location('Ganon', player).locked = True
|
world.get_location('Ganon', player).locked = True
|
||||||
world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False)
|
world.push_item(world.get_location('Agahnim 1', player), ItemFactory('Beat Agahnim 1', player), False)
|
||||||
@@ -198,38 +195,82 @@ def generate_itempool(world, player):
|
|||||||
|
|
||||||
# set up item pool
|
# set up item pool
|
||||||
if world.custom:
|
if world.custom:
|
||||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro, world.customitemarray)
|
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
|
||||||
world.rupoor_cost = min(world.customitemarray[67], 9999)
|
world.rupoor_cost = min(world.customitemarray[69], 9999)
|
||||||
else:
|
else:
|
||||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro)
|
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player])
|
||||||
world.itempool += ItemFactory(pool, player)
|
|
||||||
for item in precollected_items:
|
for item in precollected_items:
|
||||||
world.push_precollected(ItemFactory(item, player))
|
world.push_precollected(ItemFactory(item, player))
|
||||||
for (location, item) in placed_items:
|
|
||||||
|
if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player):
|
||||||
|
if "Link's Uncle" not in placed_items:
|
||||||
|
found_sword = False
|
||||||
|
found_bow = False
|
||||||
|
possible_weapons = []
|
||||||
|
for item in pool:
|
||||||
|
if item in ['Progressive Sword', 'Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword']:
|
||||||
|
if not found_sword and world.swords[player] != 'swordless':
|
||||||
|
found_sword = True
|
||||||
|
possible_weapons.append(item)
|
||||||
|
if item in ['Progressive Bow', 'Bow'] and not found_bow:
|
||||||
|
found_bow = True
|
||||||
|
possible_weapons.append(item)
|
||||||
|
if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
|
||||||
|
if item not in possible_weapons:
|
||||||
|
possible_weapons.append(item)
|
||||||
|
starting_weapon = random.choice(possible_weapons)
|
||||||
|
placed_items["Link's Uncle"] = starting_weapon
|
||||||
|
pool.remove(starting_weapon)
|
||||||
|
if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and world.enemy_health[player] not in ['default', 'easy']:
|
||||||
|
world.escape_assist[player].append('bombs')
|
||||||
|
|
||||||
|
for (location, item) in placed_items.items():
|
||||||
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
||||||
world.get_location(location, player).event = True
|
world.get_location(location, player).event = True
|
||||||
world.get_location(location, player).locked = True
|
world.get_location(location, player).locked = True
|
||||||
|
|
||||||
|
items = ItemFactory(pool, player)
|
||||||
|
|
||||||
world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms
|
world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms
|
||||||
|
|
||||||
if clock_mode is not None:
|
if clock_mode is not None:
|
||||||
world.clock_mode = clock_mode
|
world.clock_mode = clock_mode
|
||||||
if treasure_hunt_count is not None:
|
|
||||||
world.treasure_hunt_count = treasure_hunt_count
|
|
||||||
if treasure_hunt_icon is not None:
|
|
||||||
world.treasure_hunt_icon = treasure_hunt_icon
|
|
||||||
|
|
||||||
if world.keysanity:
|
if treasure_hunt_count is not None:
|
||||||
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player])
|
world.treasure_hunt_count[player] = treasure_hunt_count
|
||||||
|
if treasure_hunt_icon is not None:
|
||||||
|
world.treasure_hunt_icon[player] = treasure_hunt_icon
|
||||||
|
|
||||||
|
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
|
||||||
|
and ((item.smallkey and world.keyshuffle[player])
|
||||||
|
or (item.bigkey and world.bigkeyshuffle[player])
|
||||||
|
or (item.map and world.mapshuffle[player])
|
||||||
|
or (item.compass and world.compassshuffle[player]))])
|
||||||
|
|
||||||
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
|
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
|
||||||
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
|
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
|
||||||
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
|
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
|
||||||
if world.difficulty in ['normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
|
if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray[30] == 0):
|
||||||
[item for item in world.itempool if item.name == 'Boss Heart Container' and item.player == player][0].advancement = True
|
[item for item in items if item.name == 'Boss Heart Container'][0].advancement = True
|
||||||
elif world.difficulty in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
|
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[29] < 4):
|
||||||
adv_heart_pieces = [item for item in world.itempool if item.name == 'Piece of Heart' and item.player == player][0:4]
|
adv_heart_pieces = [item for item in items if item.name == 'Piece of Heart'][0:4]
|
||||||
for hp in adv_heart_pieces:
|
for hp in adv_heart_pieces:
|
||||||
hp.advancement = True
|
hp.advancement = True
|
||||||
|
|
||||||
|
beeweights = {0: {None: 100},
|
||||||
|
1: {None: 75, 'trap': 25},
|
||||||
|
2: {None: 40, 'trap': 40, 'bee': 20},
|
||||||
|
3: {'trap': 50, 'bee': 50},
|
||||||
|
4: {'trap': 100}}
|
||||||
|
def beemizer(item):
|
||||||
|
if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type:
|
||||||
|
choice = random.choices(list(beeweights[world.beemizer[item.player]].keys()), weights=list(beeweights[world.beemizer[item.player]].values()))[0]
|
||||||
|
return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player)
|
||||||
|
return item
|
||||||
|
|
||||||
|
world.itempool += [beemizer(item) for item in items]
|
||||||
|
|
||||||
# shuffle medallions
|
# shuffle medallions
|
||||||
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||||
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||||
@@ -238,7 +279,7 @@ def generate_itempool(world, player):
|
|||||||
place_bosses(world, player)
|
place_bosses(world, player)
|
||||||
set_up_shops(world, player)
|
set_up_shops(world, player)
|
||||||
|
|
||||||
if world.retro:
|
if world.retro[player]:
|
||||||
set_up_take_anys(world, player)
|
set_up_take_anys(world, player)
|
||||||
|
|
||||||
create_dynamic_shop_locations(world, player)
|
create_dynamic_shop_locations(world, player)
|
||||||
@@ -254,9 +295,9 @@ take_any_locations = [
|
|||||||
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint']
|
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint']
|
||||||
|
|
||||||
def set_up_take_anys(world, player):
|
def set_up_take_anys(world, player):
|
||||||
if world.mode == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations:
|
if world.mode[player] == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations:
|
||||||
take_any_locations.remove('Dark Sanctuary Hint')
|
take_any_locations.remove('Dark Sanctuary Hint')
|
||||||
|
|
||||||
regions = random.sample(take_any_locations, 5)
|
regions = random.sample(take_any_locations, 5)
|
||||||
|
|
||||||
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
|
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
|
||||||
@@ -267,9 +308,8 @@ def set_up_take_anys(world, player):
|
|||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance, old_man_take_any, player)
|
connect_entrance(world, entrance, old_man_take_any, player)
|
||||||
entrance.target = 0x58
|
entrance.target = 0x58
|
||||||
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True)
|
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True, True)
|
||||||
world.shops.append(old_man_take_any.shop)
|
world.shops.append(old_man_take_any.shop)
|
||||||
old_man_take_any.shop.active = True
|
|
||||||
|
|
||||||
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
|
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
|
||||||
if swords:
|
if swords:
|
||||||
@@ -290,13 +330,12 @@ def set_up_take_anys(world, player):
|
|||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance, take_any, player)
|
connect_entrance(world, entrance, take_any, player)
|
||||||
entrance.target = target
|
entrance.target = target
|
||||||
take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True)
|
take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True, True)
|
||||||
world.shops.append(take_any.shop)
|
world.shops.append(take_any.shop)
|
||||||
take_any.shop.active = True
|
|
||||||
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
||||||
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
||||||
|
|
||||||
world.intialize_regions()
|
world.initialize_regions()
|
||||||
|
|
||||||
def create_dynamic_shop_locations(world, player):
|
def create_dynamic_shop_locations(world, player):
|
||||||
for shop in world.shops:
|
for shop in world.shops:
|
||||||
@@ -333,9 +372,9 @@ def fill_prizes(world, attempts=15):
|
|||||||
prize_locs = list(empty_crystal_locations)
|
prize_locs = list(empty_crystal_locations)
|
||||||
random.shuffle(prizepool)
|
random.shuffle(prizepool)
|
||||||
random.shuffle(prize_locs)
|
random.shuffle(prize_locs)
|
||||||
fill_restrictive(world, all_state, prize_locs, prizepool)
|
fill_restrictive(world, all_state, prize_locs, prizepool, True)
|
||||||
except FillError as e:
|
except FillError as e:
|
||||||
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt)
|
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt - 1)
|
||||||
for location in empty_crystal_locations:
|
for location in empty_crystal_locations:
|
||||||
location.item = None
|
location.item = None
|
||||||
continue
|
continue
|
||||||
@@ -345,30 +384,23 @@ def fill_prizes(world, attempts=15):
|
|||||||
|
|
||||||
|
|
||||||
def set_up_shops(world, player):
|
def set_up_shops(world, player):
|
||||||
# Changes to basic Shops
|
|
||||||
# TODO: move hard+ mode changes for sheilds here, utilizing the new shops
|
# TODO: move hard+ mode changes for sheilds here, utilizing the new shops
|
||||||
|
|
||||||
for shop in world.shops:
|
if world.retro[player]:
|
||||||
shop.active = True
|
|
||||||
|
|
||||||
if world.retro:
|
|
||||||
rss = world.get_region('Red Shield Shop', player).shop
|
rss = world.get_region('Red Shield Shop', player).shop
|
||||||
rss.active = True
|
if not rss.locked:
|
||||||
rss.add_inventory(2, 'Single Arrow', 80)
|
rss.add_inventory(2, 'Single Arrow', 80)
|
||||||
|
for shop in random.sample([s for s in world.shops if s.custom and not s.locked and s.region.player == player], 5):
|
||||||
# Randomized changes to Shops
|
shop.locked = True
|
||||||
if world.retro:
|
|
||||||
for shop in random.sample([s for s in world.shops if s.replaceable and s.type == ShopType.Shop and s.region.player == player], 5):
|
|
||||||
shop.active = True
|
|
||||||
shop.add_inventory(0, 'Single Arrow', 80)
|
shop.add_inventory(0, 'Single Arrow', 80)
|
||||||
shop.add_inventory(1, 'Small Key (Universal)', 100)
|
shop.add_inventory(1, 'Small Key (Universal)', 100)
|
||||||
shop.add_inventory(2, 'Bombs (10)', 50)
|
shop.add_inventory(2, 'Bombs (10)', 50)
|
||||||
|
rss.locked = True
|
||||||
|
|
||||||
#special shop types
|
|
||||||
|
|
||||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
|
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = []
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
treasure_hunt_count = None
|
treasure_hunt_count = None
|
||||||
@@ -376,6 +408,10 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
|
|
||||||
pool.extend(alwaysitems)
|
pool.extend(alwaysitems)
|
||||||
|
|
||||||
|
def place_item(loc, item):
|
||||||
|
assert loc not in placed_items
|
||||||
|
placed_items[loc] = item
|
||||||
|
|
||||||
def want_progressives():
|
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'
|
||||||
|
|
||||||
@@ -388,8 +424,8 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
|
|
||||||
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
|
# insanity shuffle doesn't have fake LW/DW logic so for now guaranteed Mirror and Moon Pearl at the start
|
||||||
if shuffle == 'insanity_legacy':
|
if shuffle == 'insanity_legacy':
|
||||||
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
place_item('Link\'s House', 'Magic Mirror')
|
||||||
placed_items.append(('Sanctuary', 'Moon Pearl'))
|
place_item('Sanctuary', 'Moon Pearl')
|
||||||
else:
|
else:
|
||||||
pool.extend(['Magic Mirror', 'Moon Pearl'])
|
pool.extend(['Magic Mirror', 'Moon Pearl'])
|
||||||
|
|
||||||
@@ -420,50 +456,36 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
else:
|
else:
|
||||||
pool.extend(diff.basicarmor)
|
pool.extend(diff.basicarmor)
|
||||||
|
|
||||||
if swords != 'swordless':
|
if want_progressives():
|
||||||
if want_progressives():
|
pool.extend(['Progressive Bow'] * 2)
|
||||||
pool.extend(['Progressive Bow'] * 2)
|
elif swords != 'swordless':
|
||||||
else:
|
pool.extend(diff.basicbow)
|
||||||
pool.extend(diff.basicbow)
|
else:
|
||||||
|
pool.extend(['Bow', 'Silver Arrows'])
|
||||||
|
|
||||||
if swords == 'swordless':
|
if swords == 'swordless':
|
||||||
pool.extend(diff.swordless)
|
pool.extend(diff.swordless)
|
||||||
if want_progressives():
|
|
||||||
pool.extend(['Progressive Bow'] * 2)
|
|
||||||
else:
|
|
||||||
pool.extend(['Bow', 'Silver Arrows'])
|
|
||||||
elif swords == 'assured':
|
|
||||||
precollected_items.append('Fighter Sword')
|
|
||||||
if want_progressives():
|
|
||||||
pool.extend(diff.progressivesword)
|
|
||||||
pool.extend(['Rupees (100)'])
|
|
||||||
else:
|
|
||||||
pool.extend(diff.basicsword)
|
|
||||||
pool.extend(['Rupees (100)'])
|
|
||||||
elif swords == 'vanilla':
|
elif swords == 'vanilla':
|
||||||
swords_to_use = []
|
swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy()
|
||||||
if want_progressives():
|
|
||||||
swords_to_use.extend(diff.progressivesword)
|
|
||||||
swords_to_use.extend(['Progressive Sword'])
|
|
||||||
else:
|
|
||||||
swords_to_use.extend(diff.basicsword)
|
|
||||||
swords_to_use.extend(['Fighter Sword'])
|
|
||||||
random.shuffle(swords_to_use)
|
random.shuffle(swords_to_use)
|
||||||
|
|
||||||
placed_items.append(('Link\'s Uncle', swords_to_use.pop()))
|
place_item('Link\'s Uncle', swords_to_use.pop())
|
||||||
placed_items.append(('Blacksmith', swords_to_use.pop()))
|
place_item('Blacksmith', swords_to_use.pop())
|
||||||
placed_items.append(('Pyramid Fairy - Left', swords_to_use.pop()))
|
place_item('Pyramid Fairy - Left', swords_to_use.pop())
|
||||||
if goal != 'pedestal':
|
if goal != 'pedestal':
|
||||||
placed_items.append(('Master Sword Pedestal', swords_to_use.pop()))
|
place_item('Master Sword Pedestal', swords_to_use.pop())
|
||||||
else:
|
else:
|
||||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
else:
|
else:
|
||||||
if want_progressives():
|
pool.extend(diff.progressivesword if want_progressives() else diff.basicsword)
|
||||||
pool.extend(diff.progressivesword)
|
if swords == 'assured':
|
||||||
pool.extend(['Progressive Sword'])
|
if want_progressives():
|
||||||
else:
|
precollected_items.append('Progressive Sword')
|
||||||
pool.extend(diff.basicsword)
|
pool.remove('Progressive Sword')
|
||||||
pool.extend(['Fighter Sword'])
|
else:
|
||||||
|
precollected_items.append('Fighter Sword')
|
||||||
|
pool.remove('Fighter Sword')
|
||||||
|
pool.extend(['Rupees (50)'])
|
||||||
|
|
||||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
||||||
|
|
||||||
@@ -487,7 +509,7 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
extraitems -= len(extra)
|
extraitems -= len(extra)
|
||||||
|
|
||||||
if goal == 'pedestal' and swords != 'vanilla':
|
if goal == 'pedestal' and swords != 'vanilla':
|
||||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
if retro:
|
if retro:
|
||||||
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
|
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
|
||||||
pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool]
|
pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool]
|
||||||
@@ -496,30 +518,34 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
pool.extend(diff.retro)
|
pool.extend(diff.retro)
|
||||||
if mode == 'standard':
|
if mode == 'standard':
|
||||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||||
placed_items.append((key_location, 'Small Key (Universal)'))
|
place_item(key_location, 'Small Key (Universal)')
|
||||||
else:
|
else:
|
||||||
pool.extend(['Small Key (Universal)'])
|
pool.extend(['Small Key (Universal)'])
|
||||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||||
|
|
||||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = []
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
treasure_hunt_count = None
|
treasure_hunt_count = None
|
||||||
treasure_hunt_icon = None
|
treasure_hunt_icon = None
|
||||||
|
|
||||||
|
def place_item(loc, item):
|
||||||
|
assert loc not in placed_items
|
||||||
|
placed_items[loc] = item
|
||||||
|
|
||||||
# Correct for insanely oversized item counts and take initial steps to handle undersized pools.
|
# Correct for insanely oversized item counts and take initial steps to handle undersized pools.
|
||||||
for x in range(0, 64):
|
for x in range(0, 66):
|
||||||
if customitemarray[x] > total_items_to_place:
|
if customitemarray[x] > total_items_to_place:
|
||||||
customitemarray[x] = total_items_to_place
|
customitemarray[x] = total_items_to_place
|
||||||
if customitemarray[66] > total_items_to_place:
|
if customitemarray[68] > total_items_to_place:
|
||||||
customitemarray[66] = total_items_to_place
|
customitemarray[68] = total_items_to_place
|
||||||
itemtotal = 0
|
itemtotal = 0
|
||||||
for x in range(0, 65):
|
for x in range(0, 66):
|
||||||
itemtotal = itemtotal + customitemarray[x]
|
itemtotal = itemtotal + customitemarray[x]
|
||||||
itemtotal = itemtotal + customitemarray[66]
|
|
||||||
itemtotal = itemtotal + customitemarray[68]
|
itemtotal = itemtotal + customitemarray[68]
|
||||||
|
itemtotal = itemtotal + customitemarray[70]
|
||||||
|
|
||||||
pool.extend(['Bow'] * customitemarray[0])
|
pool.extend(['Bow'] * customitemarray[0])
|
||||||
pool.extend(['Silver Arrows']* customitemarray[1])
|
pool.extend(['Silver Arrows']* customitemarray[1])
|
||||||
@@ -580,8 +606,10 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||||||
pool.extend(['Blue Clock'] * customitemarray[61])
|
pool.extend(['Blue Clock'] * customitemarray[61])
|
||||||
pool.extend(['Green Clock'] * customitemarray[62])
|
pool.extend(['Green Clock'] * customitemarray[62])
|
||||||
pool.extend(['Red Clock'] * customitemarray[63])
|
pool.extend(['Red Clock'] * customitemarray[63])
|
||||||
pool.extend(['Triforce Piece'] * customitemarray[64])
|
pool.extend(['Progressive Bow'] * customitemarray[64])
|
||||||
pool.extend(['Triforce'] * customitemarray[66])
|
pool.extend(['Bombs (10)'] * customitemarray[65])
|
||||||
|
pool.extend(['Triforce Piece'] * customitemarray[66])
|
||||||
|
pool.extend(['Triforce'] * customitemarray[68])
|
||||||
|
|
||||||
diff = difficulties[difficulty]
|
diff = difficulties[difficulty]
|
||||||
|
|
||||||
@@ -596,12 +624,12 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||||||
thisbottle = random.choice(diff.bottles)
|
thisbottle = random.choice(diff.bottles)
|
||||||
pool.append(thisbottle)
|
pool.append(thisbottle)
|
||||||
|
|
||||||
if customitemarray[64] > 0 or customitemarray[65] > 0:
|
if customitemarray[66] > 0 or customitemarray[67] > 0:
|
||||||
treasure_hunt_count = max(min(customitemarray[65], 99), 1) #To display, count must be between 1 and 99.
|
treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99.
|
||||||
treasure_hunt_icon = 'Triforce Piece'
|
treasure_hunt_icon = 'Triforce Piece'
|
||||||
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
||||||
if (customitemarray[64] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray[66] == 0):
|
if (customitemarray[66] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray[68] == 0):
|
||||||
extrapieces = treasure_hunt_count - customitemarray[64]
|
extrapieces = treasure_hunt_count - customitemarray[66]
|
||||||
pool.extend(['Triforce Piece'] * extrapieces)
|
pool.extend(['Triforce Piece'] * extrapieces)
|
||||||
itemtotal = itemtotal + extrapieces
|
itemtotal = itemtotal + extrapieces
|
||||||
|
|
||||||
@@ -613,25 +641,25 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||||||
clock_mode = 'ohko'
|
clock_mode = 'ohko'
|
||||||
|
|
||||||
if goal == 'pedestal':
|
if goal == 'pedestal':
|
||||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
itemtotal = itemtotal + 1
|
itemtotal = itemtotal + 1
|
||||||
|
|
||||||
if mode == 'standard':
|
if mode == 'standard':
|
||||||
if retro:
|
if retro:
|
||||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||||
placed_items.append((key_location, 'Small Key (Universal)'))
|
place_item(key_location, 'Small Key (Universal)')
|
||||||
pool.extend(['Small Key (Universal)'] * max((customitemarray[68] - 1), 0))
|
pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
|
||||||
else:
|
else:
|
||||||
pool.extend(['Small Key (Universal)'] * customitemarray[68])
|
pool.extend(['Small Key (Universal)'] * customitemarray[70])
|
||||||
else:
|
else:
|
||||||
pool.extend(['Small Key (Universal)'] * customitemarray[68])
|
pool.extend(['Small Key (Universal)'] * customitemarray[70])
|
||||||
|
|
||||||
pool.extend(['Fighter Sword'] * customitemarray[32])
|
pool.extend(['Fighter Sword'] * customitemarray[32])
|
||||||
pool.extend(['Progressive Sword'] * customitemarray[36])
|
pool.extend(['Progressive Sword'] * customitemarray[36])
|
||||||
|
|
||||||
if shuffle == 'insanity_legacy':
|
if shuffle == 'insanity_legacy':
|
||||||
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
place_item('Link\'s House', 'Magic Mirror')
|
||||||
placed_items.append(('Sanctuary', 'Moon Pearl'))
|
place_item('Sanctuary', 'Moon Pearl')
|
||||||
pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0))
|
pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0))
|
||||||
pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0))
|
pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ def ItemFactory(items, player):
|
|||||||
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
||||||
item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
||||||
'Progressive Bow': (True, False, None, 0x64, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow': (True, False, None, 0x64, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
|
'Progressive Bow (Alt)': (True, False, None, 0x65, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Book of Mudora': (True, False, None, 0x1D, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
'Book of Mudora': (True, False, None, 0x1D, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
||||||
'Hammer': (True, False, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'),
|
'Hammer': (True, False, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'),
|
||||||
'Hookshot': (True, False, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
'Hookshot': (True, False, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
||||||
@@ -43,8 +44,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
|||||||
'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'),
|
'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'),
|
||||||
'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the ice rod'),
|
'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the ice rod'),
|
||||||
'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the mitts'),
|
'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the mitts'),
|
||||||
'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
|
||||||
'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
||||||
|
'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
||||||
'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
||||||
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'),
|
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'),
|
||||||
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'),
|
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'),
|
||||||
@@ -163,11 +164,12 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
|||||||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'),
|
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'),
|
||||||
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'),
|
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'),
|
||||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
|
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
|
||||||
'Red Potion': (False, False, None, 0x2E, None, None, None, None, None, None, None),
|
'Bee Trap': (False, False, None, 0xB0, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'),
|
||||||
'Green Potion': (False, False, None, 0x2F, None, None, None, None, None, None, None),
|
'Red Potion': (False, False, None, 0x2E, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
|
||||||
'Blue Potion': (False, False, None, 0x30, None, None, None, None, None, None, None),
|
'Green Potion': (False, False, None, 0x2F, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
|
||||||
'Bee': (False, False, None, 0x0E, None, None, None, None, None, None, None),
|
'Blue Potion': (False, False, None, 0x30, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
|
||||||
'Small Heart': (False, False, None, 0x42, None, None, None, None, None, None, None),
|
'Bee': (False, False, None, 0x0E, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
|
||||||
|
'Small Heart': (False, False, None, 0x42, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
|
||||||
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||||
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
|||||||
+44
-44
@@ -94,7 +94,7 @@ def build_key_layout(builder, start_regions, proposal, world, player):
|
|||||||
|
|
||||||
|
|
||||||
def calc_max_chests(builder, key_layout, world, player):
|
def calc_max_chests(builder, key_layout, world, player):
|
||||||
if world.doorShuffle != 'crossed':
|
if world.doorShuffle[player] != 'crossed':
|
||||||
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||||
return builder.key_doors_num - key_layout.max_drops
|
return builder.key_doors_num - key_layout.max_drops
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
queue = deque(sorted(queue, key=queue_sorter))
|
queue = deque(sorted(queue, key=queue_sorter))
|
||||||
parent_door, key_counter = queue.popleft()
|
parent_door, key_counter = queue.popleft()
|
||||||
chest_keys = available_chest_small_keys(key_counter, world)
|
chest_keys = available_chest_small_keys(key_counter, world, player)
|
||||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||||
available = raw_avail - key_counter.used_keys
|
available = raw_avail - key_counter.used_keys
|
||||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||||
@@ -132,7 +132,7 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
smallest_rule = None
|
smallest_rule = None
|
||||||
for child in key_counter.child_doors.keys():
|
for child in key_counter.child_doors.keys():
|
||||||
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
||||||
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world)
|
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
|
||||||
empty_flag = empty_counter(odd_counter)
|
empty_flag = empty_counter(odd_counter)
|
||||||
child_queue.append((child, odd_counter, empty_flag))
|
child_queue.append((child, odd_counter, empty_flag))
|
||||||
if child in doors_completed and child in key_logic.door_rules.keys():
|
if child in doors_completed and child in key_logic.door_rules.keys():
|
||||||
@@ -142,12 +142,12 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
while len(child_queue) > 0:
|
while len(child_queue) > 0:
|
||||||
child, odd_counter, empty_flag = child_queue.popleft()
|
child, odd_counter, empty_flag = child_queue.popleft()
|
||||||
if not child.bigKey and child not in doors_completed:
|
if not child.bigKey and child not in doors_completed:
|
||||||
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, False, empty_flag)
|
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag)
|
||||||
rule = create_rule(best_counter, key_counter, key_layout, world)
|
rule = create_rule(best_counter, key_counter, key_layout, world, player)
|
||||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||||
smallest_rule = rule.small_key_num
|
smallest_rule = rule.small_key_num
|
||||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world)
|
check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
|
||||||
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world)
|
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
|
||||||
key_logic.door_rules[child.name] = rule
|
key_logic.door_rules[child.name] = rule
|
||||||
doors_completed.add(child)
|
doors_completed.add(child)
|
||||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||||
@@ -161,7 +161,7 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
||||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
||||||
check_rules(original_key_counter, key_layout, world)
|
check_rules(original_key_counter, key_layout, world, player)
|
||||||
|
|
||||||
|
|
||||||
def count_key_drops(sector):
|
def count_key_drops(sector):
|
||||||
@@ -237,7 +237,7 @@ def unique_child_door(child, key_counter):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk, empty_flag): # try to waste as many keys as possible?
|
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible?
|
||||||
ignored_doors = {door, door.dest} if door is not None else {}
|
ignored_doors = {door, door.dest} if door is not None else {}
|
||||||
finished = False
|
finished = False
|
||||||
opened_doors = dict(key_counter.open_doors)
|
opened_doors = dict(key_counter.open_doors)
|
||||||
@@ -257,7 +257,7 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk
|
|||||||
# this means the new_door invalidates the door / leads to the same stuff
|
# this means the new_door invalidates the door / leads to the same stuff
|
||||||
if not empty_flag and relative_empty_counter(odd_counter, new_counter):
|
if not empty_flag and relative_empty_counter(odd_counter, new_counter):
|
||||||
ignored_doors.add(new_door)
|
ignored_doors.add(new_door)
|
||||||
elif empty_flag or key_wasted(new_door, door, last_counter, new_counter, key_layout, world):
|
elif empty_flag or key_wasted(new_door, door, last_counter, new_counter, key_layout, world, player):
|
||||||
last_counter = new_counter
|
last_counter = new_counter
|
||||||
opened_doors = proposed_doors
|
opened_doors = proposed_doors
|
||||||
bk_opened = bk_open
|
bk_opened = bk_open
|
||||||
@@ -285,13 +285,13 @@ def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
|||||||
return small_doors + big_doors
|
return small_doors + big_doors
|
||||||
|
|
||||||
|
|
||||||
def key_wasted(new_door, old_door, old_counter, new_counter, key_layout, world):
|
def key_wasted(new_door, old_door, old_counter, new_counter, key_layout, world, player):
|
||||||
if new_door.bigKey: # big keys are not wastes - it uses up a location
|
if new_door.bigKey: # big keys are not wastes - it uses up a location
|
||||||
return True
|
return True
|
||||||
chest_keys = available_chest_small_keys(old_counter, world)
|
chest_keys = available_chest_small_keys(old_counter, world, player)
|
||||||
old_key_diff = len(old_counter.key_only_locations) - old_counter.used_keys
|
old_key_diff = len(old_counter.key_only_locations) - old_counter.used_keys
|
||||||
old_avail = chest_keys + old_key_diff
|
old_avail = chest_keys + old_key_diff
|
||||||
new_chest_keys = available_chest_small_keys(new_counter, world)
|
new_chest_keys = available_chest_small_keys(new_counter, world, player)
|
||||||
new_key_diff = len(new_counter.key_only_locations) - new_counter.used_keys
|
new_key_diff = len(new_counter.key_only_locations) - new_counter.used_keys
|
||||||
new_avail = new_chest_keys + new_key_diff
|
new_avail = new_chest_keys + new_key_diff
|
||||||
if new_key_diff < old_key_diff or new_avail < old_avail:
|
if new_key_diff < old_key_diff or new_avail < old_avail:
|
||||||
@@ -306,7 +306,7 @@ def key_wasted(new_door, old_door, old_counter, new_counter, key_layout, world):
|
|||||||
proposed_doors = {**opened_doors, **dict.fromkeys([new_child, new_child.dest])}
|
proposed_doors = {**opened_doors, **dict.fromkeys([new_child, new_child.dest])}
|
||||||
bk_open = bk_opened or new_door.bigKey
|
bk_open = bk_opened or new_door.bigKey
|
||||||
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
||||||
if key_wasted(new_child, old_door, current_counter, new_counter, key_layout, world):
|
if key_wasted(new_child, old_door, current_counter, new_counter, key_layout, world, player):
|
||||||
return True # waste is possible
|
return True # waste is possible
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -324,16 +324,16 @@ def check_special_locations(locations):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def calc_avail_keys(key_counter, world):
|
def calc_avail_keys(key_counter, world, player):
|
||||||
chest_keys = available_chest_small_keys(key_counter, world)
|
chest_keys = available_chest_small_keys(key_counter, world, player)
|
||||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||||
return raw_avail - key_counter.used_keys
|
return raw_avail - key_counter.used_keys
|
||||||
|
|
||||||
|
|
||||||
def create_rule(key_counter, prev_counter, key_layout, world):
|
def create_rule(key_counter, prev_counter, key_layout, world, player):
|
||||||
# prev_chest_keys = available_chest_small_keys(prev_counter, world)
|
# prev_chest_keys = available_chest_small_keys(prev_counter, world)
|
||||||
# prev_avail = prev_chest_keys + len(prev_counter.key_only_locations)
|
# prev_avail = prev_chest_keys + len(prev_counter.key_only_locations)
|
||||||
chest_keys = available_chest_small_keys(key_counter, world)
|
chest_keys = available_chest_small_keys(key_counter, world, player)
|
||||||
key_gain = len(key_counter.key_only_locations) - len(prev_counter.key_only_locations)
|
key_gain = len(key_counter.key_only_locations) - len(prev_counter.key_only_locations)
|
||||||
# previous method
|
# previous method
|
||||||
# raw_avail = chest_keys + len(key_counter.key_only_locations)
|
# raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||||
@@ -348,9 +348,9 @@ def create_rule(key_counter, prev_counter, key_layout, world):
|
|||||||
return DoorRules(rule_num)
|
return DoorRules(rule_num)
|
||||||
|
|
||||||
|
|
||||||
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world):
|
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
|
||||||
if world.accessibility != 'locations':
|
if world.accessibility != 'locations':
|
||||||
counter = find_inverted_counter(door, parent_counter, key_layout, world)
|
counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
|
||||||
if not self_lock_possible(counter):
|
if not self_lock_possible(counter):
|
||||||
return
|
return
|
||||||
if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
|
if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
|
||||||
@@ -358,7 +358,7 @@ def check_for_self_lock_key(rule, door, parent_counter, key_layout, world):
|
|||||||
rule.small_location = next(iter(counter.free_locations))
|
rule.small_location = next(iter(counter.free_locations))
|
||||||
|
|
||||||
|
|
||||||
def find_inverted_counter(door, parent_counter, key_layout, world):
|
def find_inverted_counter(door, parent_counter, key_layout, world, player):
|
||||||
# open all doors in counter
|
# open all doors in counter
|
||||||
counter = open_all_counter(parent_counter, key_layout, door=door)
|
counter = open_all_counter(parent_counter, key_layout, door=door)
|
||||||
max_counter = find_max_counter(key_layout)
|
max_counter = find_max_counter(key_layout)
|
||||||
@@ -371,7 +371,7 @@ def find_inverted_counter(door, parent_counter, key_layout, world):
|
|||||||
inverted_counter.open_doors = dict_difference(max_counter.open_doors, counter.open_doors)
|
inverted_counter.open_doors = dict_difference(max_counter.open_doors, counter.open_doors)
|
||||||
inverted_counter.other_locations = dict_difference(max_counter.other_locations, counter.other_locations)
|
inverted_counter.other_locations = dict_difference(max_counter.other_locations, counter.other_locations)
|
||||||
for loc in inverted_counter.other_locations:
|
for loc in inverted_counter.other_locations:
|
||||||
if important_location(loc, world):
|
if important_location(loc, world, player):
|
||||||
inverted_counter.important_location = True
|
inverted_counter.important_location = True
|
||||||
return inverted_counter
|
return inverted_counter
|
||||||
|
|
||||||
@@ -425,8 +425,8 @@ def self_lock_possible(counter):
|
|||||||
return len(counter.free_locations) <= 1 and len(counter.key_only_locations) == 0 and not counter.important_location
|
return len(counter.free_locations) <= 1 and len(counter.key_only_locations) == 0 and not counter.important_location
|
||||||
|
|
||||||
|
|
||||||
def available_chest_small_keys(key_counter, world):
|
def available_chest_small_keys(key_counter, world, player):
|
||||||
if not world.keysanity and world.mode != 'retro':
|
if not world.keyshuffle[player] and not world.retro[player]:
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in key_counter.free_locations:
|
for loc in key_counter.free_locations:
|
||||||
if key_counter.big_key_opened or '- Big Chest' not in loc.name:
|
if key_counter.big_key_opened or '- Big Chest' not in loc.name:
|
||||||
@@ -436,8 +436,8 @@ def available_chest_small_keys(key_counter, world):
|
|||||||
return key_counter.max_chests
|
return key_counter.max_chests
|
||||||
|
|
||||||
|
|
||||||
def available_chest_small_keys_logic(key_counter, world, sm_restricted):
|
def available_chest_small_keys_logic(key_counter, world, player, sm_restricted):
|
||||||
if not world.keysanity and world.mode != 'retro':
|
if not world.keyshuffle[player] and not world.retro[player]:
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in key_counter.free_locations:
|
for loc in key_counter.free_locations:
|
||||||
if loc not in sm_restricted and (key_counter.big_key_opened or '- Big Chest' not in loc.name):
|
if loc not in sm_restricted and (key_counter.big_key_opened or '- Big Chest' not in loc.name):
|
||||||
@@ -447,11 +447,11 @@ def available_chest_small_keys_logic(key_counter, world, sm_restricted):
|
|||||||
return key_counter.max_chests
|
return key_counter.max_chests
|
||||||
|
|
||||||
|
|
||||||
def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world):
|
def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world, player):
|
||||||
if key_counter.big_key_opened:
|
if key_counter.big_key_opened:
|
||||||
return
|
return
|
||||||
best_counter = find_best_counter(door, odd_counter, key_counter, key_layout, world, True, empty_flag)
|
best_counter = find_best_counter(door, odd_counter, key_counter, key_layout, world, player, True, empty_flag)
|
||||||
bk_number = create_rule(best_counter, key_counter, key_layout, world).small_key_num
|
bk_number = create_rule(best_counter, key_counter, key_layout, world, player).small_key_num
|
||||||
if bk_number == rule.small_key_num:
|
if bk_number == rule.small_key_num:
|
||||||
return
|
return
|
||||||
door_open = find_next_counter(door, best_counter, key_layout)
|
door_open = find_next_counter(door, best_counter, key_layout)
|
||||||
@@ -586,7 +586,7 @@ def flatten_pair_list(paired_list):
|
|||||||
return flat_list
|
return flat_list
|
||||||
|
|
||||||
|
|
||||||
def check_rules(original_counter, key_layout, world):
|
def check_rules(original_counter, key_layout, world, player):
|
||||||
all_key_only = set()
|
all_key_only = set()
|
||||||
key_only_map = {}
|
key_only_map = {}
|
||||||
queue = deque([(None, original_counter, original_counter.key_only_locations)])
|
queue = deque([(None, original_counter, original_counter.key_only_locations)])
|
||||||
@@ -639,7 +639,7 @@ def check_rules(original_counter, key_layout, world):
|
|||||||
if check_non_bk:
|
if check_non_bk:
|
||||||
adjust_key_location_mins(key_layout, min_rule_non_bk, lambda r: r.small_key_num if r.alternate_small_key is None else r.alternate_small_key,
|
adjust_key_location_mins(key_layout, min_rule_non_bk, lambda r: r.small_key_num if r.alternate_small_key is None else r.alternate_small_key,
|
||||||
lambda r, v: r if r.alternate_small_key is None else setattr(r, 'alternate_small_key', v))
|
lambda r, v: r if r.alternate_small_key is None else setattr(r, 'alternate_small_key', v))
|
||||||
check_rules_deep(original_counter, key_layout, world)
|
check_rules_deep(original_counter, key_layout, world, player)
|
||||||
|
|
||||||
|
|
||||||
def adjust_key_location_mins(key_layout, min_rules, getter, setter):
|
def adjust_key_location_mins(key_layout, min_rules, getter, setter):
|
||||||
@@ -666,7 +666,7 @@ def adjust_key_location_mins(key_layout, min_rules, getter, setter):
|
|||||||
setter(rule, collected_keys)
|
setter(rule, collected_keys)
|
||||||
|
|
||||||
|
|
||||||
def check_rules_deep(original_counter, key_layout, world):
|
def check_rules_deep(original_counter, key_layout, world, player):
|
||||||
key_logic = key_layout.key_logic
|
key_logic = key_layout.key_logic
|
||||||
big_locations = {x for x in key_layout.all_chest_locations if x not in key_logic.bk_restricted}
|
big_locations = {x for x in key_layout.all_chest_locations if x not in key_logic.bk_restricted}
|
||||||
queue = deque([original_counter])
|
queue = deque([original_counter])
|
||||||
@@ -683,7 +683,7 @@ def check_rules_deep(original_counter, key_layout, world):
|
|||||||
else:
|
else:
|
||||||
bail = 0
|
bail = 0
|
||||||
last_counter = counter
|
last_counter = counter
|
||||||
chest_keys = available_chest_small_keys_logic(counter, world, key_logic.sm_restricted)
|
chest_keys = available_chest_small_keys_logic(counter, world, player, key_logic.sm_restricted)
|
||||||
big_avail = counter.big_key_opened
|
big_avail = counter.big_key_opened
|
||||||
big_maybe_not_found = not counter.big_key_opened
|
big_maybe_not_found = not counter.big_key_opened
|
||||||
if not key_layout.big_key_special and not big_avail:
|
if not key_layout.big_key_special and not big_avail:
|
||||||
@@ -787,8 +787,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
if not smalls_avail and num_bigs == 0:
|
if not smalls_avail and num_bigs == 0:
|
||||||
return True # I think that's the end
|
return True # I think that's the end
|
||||||
ttl_locations = state.ttl_locations if state.big_key_opened else count_locations_exclude_big_chest(state)
|
ttl_locations = state.ttl_locations if state.big_key_opened else count_locations_exclude_big_chest(state)
|
||||||
available_small_locations = cnt_avail_small_locations(key_layout, ttl_locations, state, world)
|
available_small_locations = cnt_avail_small_locations(key_layout, ttl_locations, state, world, player)
|
||||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world)
|
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||||
if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -821,14 +821,14 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cnt_avail_small_locations(key_layout, ttl_locations, state, world):
|
def cnt_avail_small_locations(key_layout, ttl_locations, state, world, player):
|
||||||
if not world.keysanity and world.mode != 'retro':
|
if not world.keyshuffle[player] and not world.retro[player]:
|
||||||
return min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
return min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
||||||
return state.key_locations - state.used_smalls
|
return state.key_locations - state.used_smalls
|
||||||
|
|
||||||
|
|
||||||
def cnt_avail_big_locations(ttl_locations, state, world):
|
def cnt_avail_big_locations(ttl_locations, state, world, player):
|
||||||
if not world.keysanity:
|
if not world.bigkeyshuffle[player]:
|
||||||
return ttl_locations - state.used_locations if not state.big_key_special else 0
|
return ttl_locations - state.used_locations if not state.big_key_special else 0
|
||||||
return 1 if not state.big_key_special else 0
|
return 1 if not state.big_key_special else 0
|
||||||
|
|
||||||
@@ -868,7 +868,7 @@ def create_key_counter(state, key_layout, world, player):
|
|||||||
key_counter = KeyCounter(key_layout.max_chests)
|
key_counter = KeyCounter(key_layout.max_chests)
|
||||||
key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors)))
|
key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors)))
|
||||||
for loc in state.found_locations:
|
for loc in state.found_locations:
|
||||||
if important_location(loc, world):
|
if important_location(loc, world, player):
|
||||||
key_counter.important_location = True
|
key_counter.important_location = True
|
||||||
key_counter.other_locations[loc] = None
|
key_counter.other_locations[loc] = None
|
||||||
elif loc.event and 'Small Key' in loc.item.name:
|
elif loc.event and 'Small Key' in loc.item.name:
|
||||||
@@ -891,14 +891,14 @@ def create_key_counter(state, key_layout, world, player):
|
|||||||
return key_counter
|
return key_counter
|
||||||
|
|
||||||
|
|
||||||
def important_location(loc, world):
|
def important_location(loc, world, player):
|
||||||
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
||||||
if world.mode == 'standard' or world.doorShuffle == 'crossed':
|
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed':
|
||||||
important_locations.append('Hyrule Dungeon Cellblock')
|
important_locations.append('Hyrule Dungeon Cellblock')
|
||||||
return '- Prize' in loc.name or loc.name in important_locations
|
return '- Prize' in loc.name or loc.name in important_locations
|
||||||
|
|
||||||
|
|
||||||
def create_odd_key_counter(door, parent_counter, key_layout, world):
|
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
|
||||||
odd_counter = KeyCounter(key_layout.max_chests)
|
odd_counter = KeyCounter(key_layout.max_chests)
|
||||||
next_counter = find_next_counter(door, parent_counter, key_layout)
|
next_counter = find_next_counter(door, parent_counter, key_layout)
|
||||||
odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations)
|
odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations)
|
||||||
@@ -906,7 +906,7 @@ def create_odd_key_counter(door, parent_counter, key_layout, world):
|
|||||||
odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors)
|
odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors)
|
||||||
odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations)
|
odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations)
|
||||||
for loc in odd_counter.other_locations:
|
for loc in odd_counter.other_locations:
|
||||||
if important_location(loc, world):
|
if important_location(loc, world, player):
|
||||||
odd_counter.important_location = True
|
odd_counter.important_location = True
|
||||||
return odd_counter
|
return odd_counter
|
||||||
|
|
||||||
|
|||||||
@@ -6,28 +6,34 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import zlib
|
||||||
|
|
||||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
||||||
from Regions import create_regions, mark_light_world_regions
|
from Items import ItemFactory
|
||||||
|
from Regions import create_regions, create_shops, mark_light_world_regions
|
||||||
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
|
||||||
|
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors
|
from DoorShuffle import link_doors
|
||||||
from Rom import patch_rom, get_enemizer_patch, apply_rom_settings, Sprite, LocalRom, JsonRom
|
|
||||||
from RoomData import create_rooms
|
from RoomData import create_rooms
|
||||||
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 Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
||||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||||
from Utils import output_path
|
from Utils import output_path, parse_names_string
|
||||||
|
|
||||||
__version__ = '0.0.1-pre'
|
__version__ = '0.0.1-pre'
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
start = time.process_time()
|
if args.outputpath:
|
||||||
|
os.makedirs(args.outputpath, exist_ok=True)
|
||||||
|
output_path.cached_path = args.outputpath
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints)
|
world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
if seed is None:
|
if seed is None:
|
||||||
random.seed(None)
|
random.seed(None)
|
||||||
@@ -36,47 +42,59 @@ def main(args, seed=None):
|
|||||||
world.seed = int(seed)
|
world.seed = int(seed)
|
||||||
random.seed(world.seed)
|
random.seed(world.seed)
|
||||||
|
|
||||||
world.crystals_needed_for_ganon = random.randint(0, 7) if args.crystals_ganon == 'random' else int(args.crystals_ganon)
|
world.mapshuffle = args.mapshuffle.copy()
|
||||||
world.crystals_needed_for_gt = random.randint(0, 7) if args.crystals_gt == 'random' else int(args.crystals_gt)
|
world.compassshuffle = args.compassshuffle.copy()
|
||||||
|
world.keyshuffle = args.keyshuffle.copy()
|
||||||
|
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
||||||
|
world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)}
|
||||||
|
world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)}
|
||||||
|
world.open_pyramid = args.openpyramid.copy()
|
||||||
|
world.boss_shuffle = args.shufflebosses.copy()
|
||||||
|
world.enemy_shuffle = args.shuffleenemies.copy()
|
||||||
|
world.enemy_health = args.enemy_health.copy()
|
||||||
|
world.enemy_damage = args.enemy_damage.copy()
|
||||||
|
world.beemizer = args.beemizer.copy()
|
||||||
|
|
||||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
logger.info('ALttP Door Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
|
logger.info('ALttP Door Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
|
||||||
|
|
||||||
world.difficulty_requirements = difficulties[world.difficulty]
|
for player in range(1, world.players + 1):
|
||||||
|
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] == 'standard' and world.enemy_shuffle[player] != 'none':
|
||||||
for player in range(1, world.players + 1):
|
world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it
|
||||||
|
|
||||||
|
for tok in filter(None, args.startinventory[player].split(',')):
|
||||||
|
item = ItemFactory(tok.strip(), player)
|
||||||
|
if item:
|
||||||
|
world.push_precollected(item)
|
||||||
|
|
||||||
|
if world.mode[player] != 'inverted':
|
||||||
create_regions(world, player)
|
create_regions(world, player)
|
||||||
create_doors(world, player)
|
else:
|
||||||
create_rooms(world, player)
|
create_inverted_regions(world, player)
|
||||||
create_dungeons(world, player)
|
create_shops(world, player)
|
||||||
else:
|
create_doors(world, player)
|
||||||
for player in range(1, world.players + 1):
|
create_rooms(world, player)
|
||||||
create_inverted_regions(world, player) # todo: port all the dungeon region work
|
create_dungeons(world, player)
|
||||||
create_doors(world, player)
|
|
||||||
create_rooms(world, player)
|
|
||||||
create_dungeons(world, player)
|
|
||||||
|
|
||||||
logger.info('Shuffling the World about.')
|
logger.info('Shuffling the World about.')
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
for player in range(1, world.players + 1):
|
||||||
for player in range(1, world.players + 1):
|
if world.mode[player] != 'inverted':
|
||||||
link_entrances(world, player)
|
link_entrances(world, player)
|
||||||
else:
|
else:
|
||||||
for player in range(1, world.players + 1):
|
|
||||||
link_inverted_entrances(world, player)
|
link_inverted_entrances(world, player)
|
||||||
|
|
||||||
logger.info('Shuffling dungeons')
|
logger.info('Shuffling dungeons')
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
link_doors(world, player)
|
link_doors(world, player)
|
||||||
|
if world.mode[player] != 'inverted':
|
||||||
if world.mode != 'inverted':
|
mark_light_world_regions(world, player)
|
||||||
mark_light_world_regions(world)
|
else:
|
||||||
else:
|
mark_dark_world_regions(world, player)
|
||||||
mark_dark_world_regions(world)
|
|
||||||
|
|
||||||
logger.info('Generating Item Pool.')
|
logger.info('Generating Item Pool.')
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
@@ -94,7 +112,8 @@ def main(args, seed=None):
|
|||||||
logger.info('Placing Dungeon Items.')
|
logger.info('Placing Dungeon Items.')
|
||||||
|
|
||||||
shuffled_locations = None
|
shuffled_locations = None
|
||||||
if args.algorithm in ['balanced', 'vt26'] or args.keysanity:
|
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||||
|
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
|
||||||
shuffled_locations = world.get_unfilled_locations()
|
shuffled_locations = world.get_unfilled_locations()
|
||||||
random.shuffle(shuffled_locations)
|
random.shuffle(shuffled_locations)
|
||||||
fill_dungeons_restrictive(world, shuffled_locations)
|
fill_dungeons_restrictive(world, shuffled_locations)
|
||||||
@@ -112,12 +131,12 @@ def main(args, seed=None):
|
|||||||
elif args.algorithm == 'freshness':
|
elif args.algorithm == 'freshness':
|
||||||
distribute_items_staleness(world)
|
distribute_items_staleness(world)
|
||||||
elif args.algorithm == 'vt25':
|
elif args.algorithm == 'vt25':
|
||||||
distribute_items_restrictive(world, 0)
|
distribute_items_restrictive(world, False)
|
||||||
elif args.algorithm == 'vt26':
|
elif args.algorithm == 'vt26':
|
||||||
|
|
||||||
distribute_items_restrictive(world, gt_filler(world), shuffled_locations)
|
distribute_items_restrictive(world, True, shuffled_locations)
|
||||||
elif args.algorithm == 'balanced':
|
elif args.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(world, gt_filler(world))
|
distribute_items_restrictive(world, True)
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
logger.info('Balancing multiworld progression.')
|
logger.info('Balancing multiworld progression.')
|
||||||
@@ -125,52 +144,67 @@ def main(args, seed=None):
|
|||||||
|
|
||||||
logger.info('Patching ROM.')
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
if args.sprite is not None:
|
player_names = parse_names_string(args.names)
|
||||||
if isinstance(args.sprite, Sprite):
|
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
|
||||||
sprite = args.sprite
|
|
||||||
else:
|
|
||||||
sprite = Sprite(args.sprite)
|
|
||||||
else:
|
|
||||||
sprite = None
|
|
||||||
|
|
||||||
outfilebase = 'DR_%s_%s-%s-%s-%s%s_%s-%s_%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, world.doorShuffle, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed)
|
|
||||||
|
|
||||||
use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots)
|
|
||||||
|
|
||||||
|
rom_names = []
|
||||||
jsonout = {}
|
jsonout = {}
|
||||||
if not args.suppress_rom:
|
if not args.suppress_rom:
|
||||||
if world.players > 1:
|
for player in range(1, world.players + 1):
|
||||||
raise NotImplementedError("Multiworld rom writes have not been implemented")
|
sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit'
|
||||||
else:
|
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none'
|
||||||
player = 1
|
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||||
|
or args.shufflepots[player] or sprite_random_on_hit)
|
||||||
|
|
||||||
local_rom = None
|
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
||||||
if args.jsonout:
|
|
||||||
rom = JsonRom()
|
patch_rom(world, player, rom, use_enemizer)
|
||||||
else:
|
rom_names.append((player, list(rom.name)))
|
||||||
if use_enemizer:
|
|
||||||
local_rom = LocalRom(args.rom)
|
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||||
rom = JsonRom()
|
patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||||
else:
|
if not args.jsonout:
|
||||||
|
patches = rom.patches
|
||||||
rom = LocalRom(args.rom)
|
rom = LocalRom(args.rom)
|
||||||
|
rom.merge_enemizer_patches(patches)
|
||||||
|
|
||||||
patch_rom(world, player, rom)
|
if args.race:
|
||||||
|
patch_race_rom(rom)
|
||||||
|
|
||||||
enemizer_patch = []
|
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], player_names)
|
||||||
if use_enemizer:
|
|
||||||
enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shuffleenemies, args.enemy_health, args.enemy_damage, args.shufflepalette, args.shufflepots)
|
|
||||||
|
|
||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
jsonout['patch'] = rom.patches
|
jsonout[f'patch{player}'] = rom.patches
|
||||||
if use_enemizer:
|
|
||||||
jsonout['enemizer' % player] = enemizer_patch
|
|
||||||
else:
|
else:
|
||||||
if use_enemizer:
|
mcsb_name = ''
|
||||||
local_rom.patch_enemizer(rom.patches, os.path.join(os.path.dirname(args.enemizercli), "enemizerBasePatch.json"), enemizer_patch)
|
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
|
||||||
rom = local_rom
|
mcsb_name = '-keysanity'
|
||||||
|
elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]].count(True) == 1:
|
||||||
|
mcsb_name = '-mapshuffle' if world.mapshuffle[player] else '-compassshuffle' if world.compassshuffle[player] else '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle'
|
||||||
|
elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
|
||||||
|
mcsb_name = '-%s%s%s%sshuffle' % (
|
||||||
|
'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '',
|
||||||
|
'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '')
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite)
|
playername = f"{f'_P{player}' if world.players > 1 else ''}{f'_{player_names[player]}' if player in player_names else ''}"
|
||||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player],
|
||||||
|
world.mode[player], world.goal[player],
|
||||||
|
"" if world.timer in ['none', 'display'] else "-" + world.timer,
|
||||||
|
world.shuffle[player], world.doorShuffle[player], world.algorithm, mcsb_name,
|
||||||
|
"-retro" if world.retro[player] else "",
|
||||||
|
"-prog_" + world.progressive if world.progressive in ['off', 'random'] else "",
|
||||||
|
"-nohints" if not world.hints[player] else "")) if not args.outputname else ''
|
||||||
|
rom.write_to_file(output_path(f'{outfilebase}{playername}{outfilesuffix}.sfc'))
|
||||||
|
|
||||||
|
multidata = zlib.compress(json.dumps((world.players,
|
||||||
|
rom_names,
|
||||||
|
[((location.address, location.player), (location.item.code, location.item.player)) for location in world.get_filled_locations() if type(location.address) is int])
|
||||||
|
).encode("utf-8"))
|
||||||
|
if args.jsonout:
|
||||||
|
jsonout["multidata"] = list(multidata)
|
||||||
|
else:
|
||||||
|
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||||
|
f.write(multidata)
|
||||||
|
|
||||||
if args.create_spoiler and not args.jsonout:
|
if args.create_spoiler and not args.jsonout:
|
||||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||||
@@ -185,48 +219,54 @@ 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.process_time() - start)
|
logger.debug('Total Time: %s', time.perf_counter() - start)
|
||||||
|
|
||||||
return world
|
return world
|
||||||
|
|
||||||
def gt_filler(world):
|
|
||||||
if world.goal == 'triforcehunt':
|
|
||||||
return random.randint(15, 50)
|
|
||||||
return random.randint(0, 15)
|
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints)
|
ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
|
||||||
ret.required_medallions = world.required_medallions.copy()
|
ret.required_medallions = world.required_medallions.copy()
|
||||||
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
||||||
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
|
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
|
||||||
ret.powder_patch_required = world.powder_patch_required.copy()
|
ret.powder_patch_required = world.powder_patch_required.copy()
|
||||||
ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
|
ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
|
||||||
ret.treasure_hunt_count = world.treasure_hunt_count
|
ret.treasure_hunt_count = world.treasure_hunt_count.copy()
|
||||||
ret.treasure_hunt_icon = world.treasure_hunt_icon
|
ret.treasure_hunt_icon = world.treasure_hunt_icon.copy()
|
||||||
ret.sewer_light_cone = world.sewer_light_cone
|
ret.sewer_light_cone = world.sewer_light_cone.copy()
|
||||||
ret.light_world_light_cone = world.light_world_light_cone
|
ret.light_world_light_cone = world.light_world_light_cone
|
||||||
ret.dark_world_light_cone = world.dark_world_light_cone
|
ret.dark_world_light_cone = world.dark_world_light_cone
|
||||||
ret.seed = world.seed
|
ret.seed = world.seed
|
||||||
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge
|
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy()
|
||||||
ret.can_access_trock_front = world.can_access_trock_front
|
ret.can_access_trock_front = world.can_access_trock_front.copy()
|
||||||
ret.can_access_trock_big_chest = world.can_access_trock_big_chest
|
ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy()
|
||||||
ret.can_access_trock_middle = world.can_access_trock_middle
|
ret.can_access_trock_middle = world.can_access_trock_middle.copy()
|
||||||
ret.can_take_damage = world.can_take_damage
|
ret.can_take_damage = world.can_take_damage
|
||||||
ret.difficulty_requirements = world.difficulty_requirements
|
ret.difficulty_requirements = world.difficulty_requirements.copy()
|
||||||
ret.fix_fake_world = world.fix_fake_world
|
ret.fix_fake_world = world.fix_fake_world.copy()
|
||||||
ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms
|
ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms
|
||||||
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon
|
ret.mapshuffle = world.mapshuffle.copy()
|
||||||
ret.crystals_needed_for_gt = world.crystals_needed_for_gt
|
ret.compassshuffle = world.compassshuffle.copy()
|
||||||
|
ret.keyshuffle = world.keyshuffle.copy()
|
||||||
|
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||||
|
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
|
||||||
|
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
|
||||||
|
ret.open_pyramid = world.open_pyramid.copy()
|
||||||
|
ret.boss_shuffle = world.boss_shuffle.copy()
|
||||||
|
ret.enemy_shuffle = world.enemy_shuffle.copy()
|
||||||
|
ret.enemy_health = world.enemy_health.copy()
|
||||||
|
ret.enemy_damage = world.enemy_damage.copy()
|
||||||
|
ret.beemizer = world.beemizer.copy()
|
||||||
|
|
||||||
if world.mode != 'inverted':
|
for player in range(1, world.players + 1):
|
||||||
for player in range(1, world.players + 1):
|
if world.mode[player] != 'inverted':
|
||||||
create_regions(ret, player)
|
create_regions(ret, player)
|
||||||
create_dungeons(ret, player)
|
else:
|
||||||
else:
|
|
||||||
for player in range(1, world.players + 1):
|
|
||||||
create_inverted_regions(ret, player)
|
create_inverted_regions(ret, player)
|
||||||
create_dungeons(ret, player)
|
create_shops(ret, player)
|
||||||
|
create_doors(ret, player)
|
||||||
|
create_rooms(ret, player)
|
||||||
|
create_dungeons(ret, player)
|
||||||
|
|
||||||
copy_dynamic_regions_and_locations(world, ret)
|
copy_dynamic_regions_and_locations(world, ret)
|
||||||
|
|
||||||
@@ -237,7 +277,6 @@ def copy_world(world):
|
|||||||
|
|
||||||
for shop in world.shops:
|
for shop in world.shops:
|
||||||
copied_shop = ret.get_region(shop.region.name, shop.region.player).shop
|
copied_shop = ret.get_region(shop.region.name, shop.region.player).shop
|
||||||
copied_shop.active = shop.active
|
|
||||||
copied_shop.inventory = copy.copy(shop.inventory)
|
copied_shop.inventory = copy.copy(shop.inventory)
|
||||||
|
|
||||||
# connect copied world
|
# connect copied world
|
||||||
@@ -283,14 +322,14 @@ def copy_world(world):
|
|||||||
def copy_dynamic_regions_and_locations(world, ret):
|
def copy_dynamic_regions_and_locations(world, ret):
|
||||||
for region in world.dynamic_regions:
|
for region in world.dynamic_regions:
|
||||||
new_reg = Region(region.name, region.type, region.hint_text, region.player)
|
new_reg = Region(region.name, region.type, region.hint_text, region.player)
|
||||||
new_reg.world = ret
|
|
||||||
ret.regions.append(new_reg)
|
ret.regions.append(new_reg)
|
||||||
|
ret.initialize_regions([new_reg])
|
||||||
ret.dynamic_regions.append(new_reg)
|
ret.dynamic_regions.append(new_reg)
|
||||||
|
|
||||||
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
||||||
|
|
||||||
if region.shop:
|
if region.shop:
|
||||||
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
|
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.custom, region.shop.locked)
|
||||||
ret.shops.append(new_reg.shop)
|
ret.shops.append(new_reg.shop)
|
||||||
|
|
||||||
for location in world.dynamic_locations:
|
for location in world.dynamic_locations:
|
||||||
@@ -312,7 +351,7 @@ def create_playthrough(world):
|
|||||||
world = copy_world(world)
|
world = copy_world(world)
|
||||||
|
|
||||||
# if we only check for beatable, we can do this sanity check first before writing down spheres
|
# if we only check for beatable, we can do this sanity check first before writing down spheres
|
||||||
if world.accessibility == 'none' and not world.can_beat_game():
|
if not world.can_beat_game():
|
||||||
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
@@ -323,8 +362,7 @@ def create_playthrough(world):
|
|||||||
sphere_candidates = list(prog_locations)
|
sphere_candidates = list(prog_locations)
|
||||||
logging.getLogger('').debug('Building up collection spheres.')
|
logging.getLogger('').debug('Building up collection spheres.')
|
||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
if not world.keysanity:
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_events(key_only=True)
|
|
||||||
state.sweep_for_crystal_access()
|
state.sweep_for_crystal_access()
|
||||||
|
|
||||||
sphere = []
|
sphere = []
|
||||||
@@ -344,9 +382,10 @@ def create_playthrough(world):
|
|||||||
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 (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||||
if not world.accessibility == 'none':
|
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||||
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:
|
||||||
|
old_world.spoiler.unreachables = sphere_candidates.copy()
|
||||||
break
|
break
|
||||||
|
|
||||||
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
|
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
|
||||||
@@ -357,7 +396,6 @@ def create_playthrough(world):
|
|||||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
location.item = None
|
location.item = None
|
||||||
state.remove(old_item)
|
|
||||||
if world.can_beat_game(state_cache[num]):
|
if world.can_beat_game(state_cache[num]):
|
||||||
to_delete.append(location)
|
to_delete.append(location)
|
||||||
else:
|
else:
|
||||||
@@ -368,6 +406,14 @@ def create_playthrough(world):
|
|||||||
for location in to_delete:
|
for location in to_delete:
|
||||||
sphere.remove(location)
|
sphere.remove(location)
|
||||||
|
|
||||||
|
# second phase, sphere 0
|
||||||
|
for item in [i for i in world.precollected_items if i.advancement]:
|
||||||
|
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
|
||||||
|
world.precollected_items.remove(item)
|
||||||
|
world.state.remove(item)
|
||||||
|
if not world.can_beat_game():
|
||||||
|
world.push_precollected(item)
|
||||||
|
|
||||||
# we are now down to just the required progress items in collection_spheres. Unfortunately
|
# we are now down to just the required progress items in collection_spheres. Unfortunately
|
||||||
# the previous pruning stage could potentially have made certain items dependant on others
|
# the previous pruning stage could potentially have made certain items dependant on others
|
||||||
# in the same or later sphere (because the location had 2 ways to access but the item originally
|
# in the same or later sphere (because the location had 2 ways to access but the item originally
|
||||||
@@ -378,8 +424,7 @@ def create_playthrough(world):
|
|||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
while required_locations:
|
while required_locations:
|
||||||
if not world.keysanity:
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_events(key_only=True)
|
|
||||||
state.sweep_for_crystal_access()
|
state.sweep_for_crystal_access()
|
||||||
|
|
||||||
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
|
||||||
@@ -415,10 +460,12 @@ def create_playthrough(world):
|
|||||||
old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
||||||
for _, path in dict(old_world.spoiler.paths).items():
|
for _, path in dict(old_world.spoiler.paths).items():
|
||||||
if any(exit == 'Pyramid Fairy' for (_, exit) in path):
|
if any(exit == 'Pyramid Fairy' for (_, exit) in path):
|
||||||
if world.mode != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player))
|
old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player))
|
||||||
else:
|
else:
|
||||||
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
|
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
|
||||||
|
|
||||||
# we can finally output our playthrough
|
# we can finally output our playthrough
|
||||||
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])
|
old_world.spoiler.playthrough = OrderedDict([("0", [str(item) for item in world.precollected_items if item.advancement])])
|
||||||
|
for i, sphere in enumerate(collection_spheres):
|
||||||
|
old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sphere}
|
||||||
|
|||||||
+921
@@ -0,0 +1,921 @@
|
|||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import Items
|
||||||
|
import Regions
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
import aioconsole
|
||||||
|
break
|
||||||
|
except ImportError:
|
||||||
|
aioconsole = None
|
||||||
|
print('Required python module "aioconsole" not found, press enter to install it')
|
||||||
|
input()
|
||||||
|
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'aioconsole'])
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
import websockets
|
||||||
|
break
|
||||||
|
except ImportError:
|
||||||
|
websockets = None
|
||||||
|
print('Required python module "websockets" not found, press enter to install it')
|
||||||
|
input()
|
||||||
|
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'websockets'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
import colorama
|
||||||
|
except ImportError:
|
||||||
|
colorama = None
|
||||||
|
|
||||||
|
class ReceivedItem:
|
||||||
|
def __init__(self, item, location, player_id, player_name):
|
||||||
|
self.item = item
|
||||||
|
self.location = location
|
||||||
|
self.player_id = player_id
|
||||||
|
self.player_name = player_name
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
def __init__(self, snes_address, server_address, password, name, team, slot):
|
||||||
|
self.snes_address = snes_address
|
||||||
|
self.server_address = server_address
|
||||||
|
|
||||||
|
self.exit_event = asyncio.Event()
|
||||||
|
|
||||||
|
self.input_queue = asyncio.Queue()
|
||||||
|
self.input_requests = 0
|
||||||
|
|
||||||
|
self.snes_socket = None
|
||||||
|
self.snes_state = SNES_DISCONNECTED
|
||||||
|
self.snes_recv_queue = asyncio.Queue()
|
||||||
|
self.snes_request_lock = asyncio.Lock()
|
||||||
|
self.is_sd2snes = False
|
||||||
|
self.snes_write_buffer = []
|
||||||
|
|
||||||
|
self.server_task = None
|
||||||
|
self.socket = None
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.team = team
|
||||||
|
self.slot = slot
|
||||||
|
|
||||||
|
self.locations_checked = set()
|
||||||
|
self.items_received = []
|
||||||
|
self.last_rom = None
|
||||||
|
self.expected_rom = None
|
||||||
|
self.rom_confirmed = False
|
||||||
|
|
||||||
|
def color_code(*args):
|
||||||
|
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||||
|
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||||
|
'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
|
||||||
|
return '\033[' + ';'.join([str(codes[arg]) for arg in args]) + 'm'
|
||||||
|
|
||||||
|
def color(text, *args):
|
||||||
|
return color_code(*args) + text + color_code('reset')
|
||||||
|
|
||||||
|
|
||||||
|
ROM_START = 0x000000
|
||||||
|
WRAM_START = 0xF50000
|
||||||
|
WRAM_SIZE = 0x20000
|
||||||
|
SRAM_START = 0xE00000
|
||||||
|
|
||||||
|
ROMNAME_START = SRAM_START + 0x2000
|
||||||
|
ROMNAME_SIZE = 0x15
|
||||||
|
|
||||||
|
INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||||
|
|
||||||
|
SAVEDATA_START = WRAM_START + 0xF000
|
||||||
|
SAVEDATA_SIZE = 0x500
|
||||||
|
|
||||||
|
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||||
|
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||||
|
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||||
|
ROOMID_ADDR = SAVEDATA_START + 0x4D4 # 2 bytes
|
||||||
|
ROOMDATA_ADDR = SAVEDATA_START + 0x4D6 # 1 byte
|
||||||
|
|
||||||
|
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||||
|
"Blind's Hideout - Left": (0x11d, 0x20),
|
||||||
|
"Blind's Hideout - Right": (0x11d, 0x40),
|
||||||
|
"Blind's Hideout - Far Left": (0x11d, 0x80),
|
||||||
|
"Blind's Hideout - Far Right": (0x11d, 0x100),
|
||||||
|
'Secret Passage': (0x55, 0x10),
|
||||||
|
'Waterfall Fairy - Left': (0x114, 0x10),
|
||||||
|
'Waterfall Fairy - Right': (0x114, 0x20),
|
||||||
|
"King's Tomb": (0x113, 0x10),
|
||||||
|
'Floodgate Chest': (0x10b, 0x10),
|
||||||
|
"Link's House": (0x104, 0x10),
|
||||||
|
'Kakariko Tavern': (0x103, 0x10),
|
||||||
|
'Chicken House': (0x108, 0x10),
|
||||||
|
"Aginah's Cave": (0x10a, 0x10),
|
||||||
|
"Sahasrahla's Hut - Left": (0x105, 0x10),
|
||||||
|
"Sahasrahla's Hut - Middle": (0x105, 0x20),
|
||||||
|
"Sahasrahla's Hut - Right": (0x105, 0x40),
|
||||||
|
'Kakariko Well - Top': (0x2f, 0x10),
|
||||||
|
'Kakariko Well - Left': (0x2f, 0x20),
|
||||||
|
'Kakariko Well - Middle': (0x2f, 0x40),
|
||||||
|
'Kakariko Well - Right': (0x2f, 0x80),
|
||||||
|
'Kakariko Well - Bottom': (0x2f, 0x100),
|
||||||
|
'Lost Woods Hideout': (0xe1, 0x200),
|
||||||
|
'Lumberjack Tree': (0xe2, 0x200),
|
||||||
|
'Cave 45': (0x11b, 0x400),
|
||||||
|
'Graveyard Cave': (0x11b, 0x200),
|
||||||
|
'Checkerboard Cave': (0x126, 0x200),
|
||||||
|
'Mini Moldorm Cave - Far Left': (0x123, 0x10),
|
||||||
|
'Mini Moldorm Cave - Left': (0x123, 0x20),
|
||||||
|
'Mini Moldorm Cave - Right': (0x123, 0x40),
|
||||||
|
'Mini Moldorm Cave - Far Right': (0x123, 0x80),
|
||||||
|
'Mini Moldorm Cave - Generous Guy': (0x123, 0x400),
|
||||||
|
'Ice Rod Cave': (0x120, 0x10),
|
||||||
|
'Bonk Rock Cave': (0x124, 0x10),
|
||||||
|
'Desert Palace - Big Chest': (0x73, 0x10),
|
||||||
|
'Desert Palace - Torch': (0x73, 0x400),
|
||||||
|
'Desert Palace - Map Chest': (0x74, 0x10),
|
||||||
|
'Desert Palace - Compass Chest': (0x85, 0x10),
|
||||||
|
'Desert Palace - Big Key Chest': (0x75, 0x10),
|
||||||
|
'Desert Palace - Boss': (0x33, 0x800),
|
||||||
|
'Eastern Palace - Compass Chest': (0xa8, 0x10),
|
||||||
|
'Eastern Palace - Big Chest': (0xa9, 0x10),
|
||||||
|
'Eastern Palace - Cannonball Chest': (0xb9, 0x10),
|
||||||
|
'Eastern Palace - Big Key Chest': (0xb8, 0x10),
|
||||||
|
'Eastern Palace - Map Chest': (0xaa, 0x10),
|
||||||
|
'Eastern Palace - Boss': (0xc8, 0x800),
|
||||||
|
'Hyrule Castle - Boomerang Chest': (0x71, 0x10),
|
||||||
|
'Hyrule Castle - Map Chest': (0x72, 0x10),
|
||||||
|
"Hyrule Castle - Zelda's Chest": (0x80, 0x10),
|
||||||
|
'Sewers - Dark Cross': (0x32, 0x10),
|
||||||
|
'Sewers - Secret Room - Left': (0x11, 0x10),
|
||||||
|
'Sewers - Secret Room - Middle': (0x11, 0x20),
|
||||||
|
'Sewers - Secret Room - Right': (0x11, 0x40),
|
||||||
|
'Sanctuary': (0x12, 0x10),
|
||||||
|
'Castle Tower - Room 03': (0xe0, 0x10),
|
||||||
|
'Castle Tower - Dark Maze': (0xd0, 0x10),
|
||||||
|
'Spectacle Rock Cave': (0xea, 0x400),
|
||||||
|
'Paradox Cave Lower - Far Left': (0xef, 0x10),
|
||||||
|
'Paradox Cave Lower - Left': (0xef, 0x20),
|
||||||
|
'Paradox Cave Lower - Right': (0xef, 0x40),
|
||||||
|
'Paradox Cave Lower - Far Right': (0xef, 0x80),
|
||||||
|
'Paradox Cave Lower - Middle': (0xef, 0x100),
|
||||||
|
'Paradox Cave Upper - Left': (0xff, 0x10),
|
||||||
|
'Paradox Cave Upper - Right': (0xff, 0x20),
|
||||||
|
'Spiral Cave': (0xfe, 0x10),
|
||||||
|
'Tower of Hera - Basement Cage': (0x87, 0x400),
|
||||||
|
'Tower of Hera - Map Chest': (0x77, 0x10),
|
||||||
|
'Tower of Hera - Big Key Chest': (0x87, 0x10),
|
||||||
|
'Tower of Hera - Compass Chest': (0x27, 0x20),
|
||||||
|
'Tower of Hera - Big Chest': (0x27, 0x10),
|
||||||
|
'Tower of Hera - Boss': (0x7, 0x800),
|
||||||
|
'Hype Cave - Top': (0x11e, 0x10),
|
||||||
|
'Hype Cave - Middle Right': (0x11e, 0x20),
|
||||||
|
'Hype Cave - Middle Left': (0x11e, 0x40),
|
||||||
|
'Hype Cave - Bottom': (0x11e, 0x80),
|
||||||
|
'Hype Cave - Generous Guy': (0x11e, 0x400),
|
||||||
|
'Peg Cave': (0x127, 0x400),
|
||||||
|
'Pyramid Fairy - Left': (0x116, 0x10),
|
||||||
|
'Pyramid Fairy - Right': (0x116, 0x20),
|
||||||
|
'Brewery': (0x106, 0x10),
|
||||||
|
'C-Shaped House': (0x11c, 0x10),
|
||||||
|
'Chest Game': (0x106, 0x400),
|
||||||
|
'Mire Shed - Left': (0x10d, 0x10),
|
||||||
|
'Mire Shed - Right': (0x10d, 0x20),
|
||||||
|
'Superbunny Cave - Top': (0xf8, 0x10),
|
||||||
|
'Superbunny Cave - Bottom': (0xf8, 0x20),
|
||||||
|
'Spike Cave': (0x117, 0x10),
|
||||||
|
'Hookshot Cave - Top Right': (0x3c, 0x10),
|
||||||
|
'Hookshot Cave - Top Left': (0x3c, 0x20),
|
||||||
|
'Hookshot Cave - Bottom Right': (0x3c, 0x80),
|
||||||
|
'Hookshot Cave - Bottom Left': (0x3c, 0x40),
|
||||||
|
'Mimic Cave': (0x10c, 0x10),
|
||||||
|
'Swamp Palace - Entrance': (0x28, 0x10),
|
||||||
|
'Swamp Palace - Map Chest': (0x37, 0x10),
|
||||||
|
'Swamp Palace - Big Chest': (0x36, 0x10),
|
||||||
|
'Swamp Palace - Compass Chest': (0x46, 0x10),
|
||||||
|
'Swamp Palace - Big Key Chest': (0x35, 0x10),
|
||||||
|
'Swamp Palace - West Chest': (0x34, 0x10),
|
||||||
|
'Swamp Palace - Flooded Room - Left': (0x76, 0x10),
|
||||||
|
'Swamp Palace - Flooded Room - Right': (0x76, 0x20),
|
||||||
|
'Swamp Palace - Waterfall Room': (0x66, 0x10),
|
||||||
|
'Swamp Palace - Boss': (0x6, 0x800),
|
||||||
|
"Thieves' Town - Big Key Chest": (0xdb, 0x20),
|
||||||
|
"Thieves' Town - Map Chest": (0xdb, 0x10),
|
||||||
|
"Thieves' Town - Compass Chest": (0xdc, 0x10),
|
||||||
|
"Thieves' Town - Ambush Chest": (0xcb, 0x10),
|
||||||
|
"Thieves' Town - Attic": (0x65, 0x10),
|
||||||
|
"Thieves' Town - Big Chest": (0x44, 0x10),
|
||||||
|
"Thieves' Town - Blind's Cell": (0x45, 0x10),
|
||||||
|
"Thieves' Town - Boss": (0xac, 0x800),
|
||||||
|
'Skull Woods - Compass Chest': (0x67, 0x10),
|
||||||
|
'Skull Woods - Map Chest': (0x58, 0x20),
|
||||||
|
'Skull Woods - Big Chest': (0x58, 0x10),
|
||||||
|
'Skull Woods - Pot Prison': (0x57, 0x20),
|
||||||
|
'Skull Woods - Pinball Room': (0x68, 0x10),
|
||||||
|
'Skull Woods - Big Key Chest': (0x57, 0x10),
|
||||||
|
'Skull Woods - Bridge Room': (0x59, 0x10),
|
||||||
|
'Skull Woods - Boss': (0x29, 0x800),
|
||||||
|
'Ice Palace - Compass Chest': (0x2e, 0x10),
|
||||||
|
'Ice Palace - Freezor Chest': (0x7e, 0x10),
|
||||||
|
'Ice Palace - Big Chest': (0x9e, 0x10),
|
||||||
|
'Ice Palace - Iced T Room': (0xae, 0x10),
|
||||||
|
'Ice Palace - Spike Room': (0x5f, 0x10),
|
||||||
|
'Ice Palace - Big Key Chest': (0x1f, 0x10),
|
||||||
|
'Ice Palace - Map Chest': (0x3f, 0x10),
|
||||||
|
'Ice Palace - Boss': (0xde, 0x800),
|
||||||
|
'Misery Mire - Big Chest': (0xc3, 0x10),
|
||||||
|
'Misery Mire - Map Chest': (0xc3, 0x20),
|
||||||
|
'Misery Mire - Main Lobby': (0xc2, 0x10),
|
||||||
|
'Misery Mire - Bridge Chest': (0xa2, 0x10),
|
||||||
|
'Misery Mire - Spike Chest': (0xb3, 0x10),
|
||||||
|
'Misery Mire - Compass Chest': (0xc1, 0x10),
|
||||||
|
'Misery Mire - Big Key Chest': (0xd1, 0x10),
|
||||||
|
'Misery Mire - Boss': (0x90, 0x800),
|
||||||
|
'Turtle Rock - Compass Chest': (0xd6, 0x10),
|
||||||
|
'Turtle Rock - Roller Room - Left': (0xb7, 0x10),
|
||||||
|
'Turtle Rock - Roller Room - Right': (0xb7, 0x20),
|
||||||
|
'Turtle Rock - Chain Chomps': (0xb6, 0x10),
|
||||||
|
'Turtle Rock - Big Key Chest': (0x14, 0x10),
|
||||||
|
'Turtle Rock - Big Chest': (0x24, 0x10),
|
||||||
|
'Turtle Rock - Crystaroller Room': (0x4, 0x10),
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Left': (0xd5, 0x80),
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Right': (0xd5, 0x40),
|
||||||
|
'Turtle Rock - Eye Bridge - Top Left': (0xd5, 0x20),
|
||||||
|
'Turtle Rock - Eye Bridge - Top Right': (0xd5, 0x10),
|
||||||
|
'Turtle Rock - Boss': (0xa4, 0x800),
|
||||||
|
'Palace of Darkness - Shooter Room': (0x9, 0x10),
|
||||||
|
'Palace of Darkness - The Arena - Bridge': (0x2a, 0x20),
|
||||||
|
'Palace of Darkness - Stalfos Basement': (0xa, 0x10),
|
||||||
|
'Palace of Darkness - Big Key Chest': (0x3a, 0x10),
|
||||||
|
'Palace of Darkness - The Arena - Ledge': (0x2a, 0x10),
|
||||||
|
'Palace of Darkness - Map Chest': (0x2b, 0x10),
|
||||||
|
'Palace of Darkness - Compass Chest': (0x1a, 0x20),
|
||||||
|
'Palace of Darkness - Dark Basement - Left': (0x6a, 0x10),
|
||||||
|
'Palace of Darkness - Dark Basement - Right': (0x6a, 0x20),
|
||||||
|
'Palace of Darkness - Dark Maze - Top': (0x19, 0x10),
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom': (0x19, 0x20),
|
||||||
|
'Palace of Darkness - Big Chest': (0x1a, 0x10),
|
||||||
|
'Palace of Darkness - Harmless Hellway': (0x1a, 0x40),
|
||||||
|
'Palace of Darkness - Boss': (0x5a, 0x800),
|
||||||
|
"Ganons Tower - Bob's Torch": (0x8c, 0x400),
|
||||||
|
'Ganons Tower - Hope Room - Left': (0x8c, 0x20),
|
||||||
|
'Ganons Tower - Hope Room - Right': (0x8c, 0x40),
|
||||||
|
'Ganons Tower - Tile Room': (0x8d, 0x10),
|
||||||
|
'Ganons Tower - Compass Room - Top Left': (0x9d, 0x10),
|
||||||
|
'Ganons Tower - Compass Room - Top Right': (0x9d, 0x20),
|
||||||
|
'Ganons Tower - Compass Room - Bottom Left': (0x9d, 0x40),
|
||||||
|
'Ganons Tower - Compass Room - Bottom Right': (0x9d, 0x80),
|
||||||
|
'Ganons Tower - DMs Room - Top Left': (0x7b, 0x10),
|
||||||
|
'Ganons Tower - DMs Room - Top Right': (0x7b, 0x20),
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left': (0x7b, 0x40),
|
||||||
|
'Ganons Tower - DMs Room - Bottom Right': (0x7b, 0x80),
|
||||||
|
'Ganons Tower - Map Chest': (0x8b, 0x10),
|
||||||
|
'Ganons Tower - Firesnake Room': (0x7d, 0x10),
|
||||||
|
'Ganons Tower - Randomizer Room - Top Left': (0x7c, 0x10),
|
||||||
|
'Ganons Tower - Randomizer Room - Top Right': (0x7c, 0x20),
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Left': (0x7c, 0x40),
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Right': (0x7c, 0x80),
|
||||||
|
"Ganons Tower - Bob's Chest": (0x8c, 0x80),
|
||||||
|
'Ganons Tower - Big Chest': (0x8c, 0x10),
|
||||||
|
'Ganons Tower - Big Key Room - Left': (0x1c, 0x20),
|
||||||
|
'Ganons Tower - Big Key Room - Right': (0x1c, 0x40),
|
||||||
|
'Ganons Tower - Big Key Chest': (0x1c, 0x10),
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Left': (0x3d, 0x10),
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Right': (0x3d, 0x20),
|
||||||
|
'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40),
|
||||||
|
'Ganons Tower - Validation Chest': (0x4d, 0x10)}
|
||||||
|
location_table_npc = {'Mushroom': 0x1000,
|
||||||
|
'King Zora': 0x2,
|
||||||
|
'Sahasrahla': 0x10,
|
||||||
|
'Blacksmith': 0x400,
|
||||||
|
'Magic Bat': 0x8000,
|
||||||
|
'Sick Kid': 0x4,
|
||||||
|
'Library': 0x80,
|
||||||
|
'Potion Shop': 0x2000,
|
||||||
|
'Old Man': 0x1,
|
||||||
|
'Ether Tablet': 0x100,
|
||||||
|
'Catfish': 0x20,
|
||||||
|
'Stumpy': 0x8,
|
||||||
|
'Bombos Tablet': 0x200}
|
||||||
|
location_table_ow = {'Flute Spot': 0x2a,
|
||||||
|
'Sunken Treasure': 0x3b,
|
||||||
|
"Zora's Ledge": 0x81,
|
||||||
|
'Lake Hylia Island': 0x35,
|
||||||
|
'Maze Race': 0x28,
|
||||||
|
'Desert Ledge': 0x30,
|
||||||
|
'Master Sword Pedestal': 0x80,
|
||||||
|
'Spectacle Rock': 0x3,
|
||||||
|
'Pyramid': 0x5b,
|
||||||
|
'Digging Game': 0x68,
|
||||||
|
'Bumper Cave Ledge': 0x4a,
|
||||||
|
'Floating Island': 0x5}
|
||||||
|
location_table_misc = {'Bottle Merchant': (0x3c9, 0x2),
|
||||||
|
'Purple Chest': (0x3c9, 0x10),
|
||||||
|
"Link's Uncle": (0x3c6, 0x1),
|
||||||
|
'Hobo': (0x3c9, 0x1)}
|
||||||
|
|
||||||
|
SNES_DISCONNECTED = 0
|
||||||
|
SNES_CONNECTING = 1
|
||||||
|
SNES_CONNECTED = 2
|
||||||
|
SNES_ATTACHED = 3
|
||||||
|
|
||||||
|
async def snes_connect(ctx : Context, address = None):
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
print('Already connected to snes')
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.snes_state = SNES_CONNECTING
|
||||||
|
recv_task = None
|
||||||
|
|
||||||
|
if address is None:
|
||||||
|
address = 'ws://' + ctx.snes_address
|
||||||
|
|
||||||
|
print("Connecting to QUsb2snes at %s ..." % address)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ctx.snes_socket = await websockets.connect(address, ping_timeout=None, ping_interval=None)
|
||||||
|
ctx.snes_state = SNES_CONNECTED
|
||||||
|
|
||||||
|
DeviceList_Request = {
|
||||||
|
"Opcode" : "DeviceList",
|
||||||
|
"Space" : "SNES"
|
||||||
|
}
|
||||||
|
await ctx.snes_socket.send(json.dumps(DeviceList_Request))
|
||||||
|
|
||||||
|
reply = json.loads(await ctx.snes_socket.recv())
|
||||||
|
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None
|
||||||
|
|
||||||
|
if not devices:
|
||||||
|
raise Exception('No device found')
|
||||||
|
|
||||||
|
print("Available devices:")
|
||||||
|
for id, device in enumerate(devices):
|
||||||
|
print("[%d] %s" % (id + 1, device))
|
||||||
|
|
||||||
|
device = None
|
||||||
|
while True:
|
||||||
|
print("Enter a number:")
|
||||||
|
choice = await console_input(ctx)
|
||||||
|
if choice is None:
|
||||||
|
raise Exception('Abort input')
|
||||||
|
if not choice.isdigit() or int(choice) < 1 or int(choice) > len(devices):
|
||||||
|
print("Invalid choice (%s)" % choice)
|
||||||
|
continue
|
||||||
|
|
||||||
|
device = devices[int(choice) - 1]
|
||||||
|
break
|
||||||
|
|
||||||
|
print("Attaching to " + device)
|
||||||
|
|
||||||
|
Attach_Request = {
|
||||||
|
"Opcode" : "Attach",
|
||||||
|
"Space" : "SNES",
|
||||||
|
"Operands" : [device]
|
||||||
|
}
|
||||||
|
await ctx.snes_socket.send(json.dumps(Attach_Request))
|
||||||
|
ctx.snes_state = SNES_ATTACHED
|
||||||
|
|
||||||
|
if 'SD2SNES'.lower() in device.lower() or (len(device) == 4 and device[:3] == 'COM'):
|
||||||
|
print("SD2SNES Detected")
|
||||||
|
ctx.is_sd2snes = True
|
||||||
|
await ctx.snes_socket.send(json.dumps({"Opcode" : "Info", "Space" : "SNES"}))
|
||||||
|
reply = json.loads(await ctx.snes_socket.recv())
|
||||||
|
if reply and 'Results' in reply:
|
||||||
|
print(reply['Results'])
|
||||||
|
else:
|
||||||
|
ctx.is_sd2snes = False
|
||||||
|
|
||||||
|
recv_task = asyncio.create_task(snes_recv_loop(ctx))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Error connecting to snes (%s)" % e)
|
||||||
|
if recv_task is not None:
|
||||||
|
if not ctx.snes_socket.closed:
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
else:
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
if not ctx.snes_socket.closed:
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
ctx.snes_socket = None
|
||||||
|
ctx.snes_state = SNES_DISCONNECTED
|
||||||
|
|
||||||
|
async def snes_recv_loop(ctx : Context):
|
||||||
|
try:
|
||||||
|
async for msg in ctx.snes_socket:
|
||||||
|
ctx.snes_recv_queue.put_nowait(msg)
|
||||||
|
print("Snes disconnected, type /snes to reconnect")
|
||||||
|
except Exception as e:
|
||||||
|
print("Lost connection to the snes, type /snes to reconnect")
|
||||||
|
if not isinstance(e, websockets.WebSocketException):
|
||||||
|
logging.exception(e)
|
||||||
|
finally:
|
||||||
|
socket, ctx.snes_socket = ctx.snes_socket, None
|
||||||
|
if socket is not None and not socket.closed:
|
||||||
|
await socket.close()
|
||||||
|
|
||||||
|
ctx.snes_state = SNES_DISCONNECTED
|
||||||
|
ctx.snes_recv_queue = asyncio.Queue()
|
||||||
|
ctx.hud_message_queue = []
|
||||||
|
|
||||||
|
ctx.rom_confirmed = False
|
||||||
|
ctx.last_rom = None
|
||||||
|
|
||||||
|
async def snes_read(ctx : Context, address, size):
|
||||||
|
try:
|
||||||
|
await ctx.snes_request_lock.acquire()
|
||||||
|
|
||||||
|
if ctx.snes_state != SNES_ATTACHED or ctx.snes_socket is None or not ctx.snes_socket.open or ctx.snes_socket.closed:
|
||||||
|
return None
|
||||||
|
|
||||||
|
GetAddress_Request = {
|
||||||
|
"Opcode" : "GetAddress",
|
||||||
|
"Space" : "SNES",
|
||||||
|
"Operands" : [hex(address)[2:], hex(size)[2:]]
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
await ctx.snes_socket.send(json.dumps(GetAddress_Request))
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = bytes()
|
||||||
|
while len(data) < size:
|
||||||
|
try:
|
||||||
|
data += await asyncio.wait_for(ctx.snes_recv_queue.get(), 5)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(data) != size:
|
||||||
|
print('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data)))
|
||||||
|
if len(data):
|
||||||
|
print(str(data))
|
||||||
|
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data
|
||||||
|
finally:
|
||||||
|
ctx.snes_request_lock.release()
|
||||||
|
|
||||||
|
async def snes_write(ctx : Context, write_list):
|
||||||
|
try:
|
||||||
|
await ctx.snes_request_lock.acquire()
|
||||||
|
|
||||||
|
if ctx.snes_state != SNES_ATTACHED or ctx.snes_socket is None or not ctx.snes_socket.open or ctx.snes_socket.closed:
|
||||||
|
return False
|
||||||
|
|
||||||
|
PutAddress_Request = {
|
||||||
|
"Opcode" : "PutAddress",
|
||||||
|
"Operands" : []
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.is_sd2snes:
|
||||||
|
cmd = b'\x00\xE2\x20\x48\xEB\x48'
|
||||||
|
|
||||||
|
for address, data in write_list:
|
||||||
|
if (address < WRAM_START) or ((address + len(data)) > (WRAM_START + WRAM_SIZE)):
|
||||||
|
print("SD2SNES: Write out of range %s (%d)" % (hex(address), len(data)))
|
||||||
|
return False
|
||||||
|
for ptr, byte in enumerate(data, address + 0x7E0000 - WRAM_START):
|
||||||
|
cmd += b'\xA9' # LDA
|
||||||
|
cmd += bytes([byte])
|
||||||
|
cmd += b'\x8F' # STA.l
|
||||||
|
cmd += bytes([ptr & 0xFF, (ptr >> 8) & 0xFF, (ptr >> 16) & 0xFF])
|
||||||
|
|
||||||
|
cmd += b'\xA9\x00\x8F\x00\x2C\x00\x68\xEB\x68\x28\x6C\xEA\xFF\x08'
|
||||||
|
|
||||||
|
PutAddress_Request['Space'] = 'CMD'
|
||||||
|
PutAddress_Request['Operands'] = ["2C00", hex(len(cmd)-1)[2:], "2C00", "1"]
|
||||||
|
try:
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
await ctx.snes_socket.send(cmd)
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
PutAddress_Request['Space'] = 'SNES'
|
||||||
|
try:
|
||||||
|
#will pack those requests as soon as qusb2snes actually supports that for real
|
||||||
|
for address, data in write_list:
|
||||||
|
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||||
|
if ctx.snes_socket is not None:
|
||||||
|
await ctx.snes_socket.send(data)
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
ctx.snes_request_lock.release()
|
||||||
|
|
||||||
|
def snes_buffered_write(ctx : Context, address, data):
|
||||||
|
if len(ctx.snes_write_buffer) > 0 and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address:
|
||||||
|
ctx.snes_write_buffer[-1] = (ctx.snes_write_buffer[-1][0], ctx.snes_write_buffer[-1][1] + data)
|
||||||
|
else:
|
||||||
|
ctx.snes_write_buffer.append((address, data))
|
||||||
|
|
||||||
|
async def snes_flush_writes(ctx : Context):
|
||||||
|
if not ctx.snes_write_buffer:
|
||||||
|
return
|
||||||
|
|
||||||
|
await snes_write(ctx, ctx.snes_write_buffer)
|
||||||
|
ctx.snes_write_buffer = []
|
||||||
|
|
||||||
|
async def send_msgs(websocket, msgs):
|
||||||
|
if not websocket or not websocket.open or websocket.closed:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await websocket.send(json.dumps(msgs))
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def server_loop(ctx : Context):
|
||||||
|
if ctx.socket is not None:
|
||||||
|
print('Already connected')
|
||||||
|
return
|
||||||
|
|
||||||
|
while not ctx.server_address:
|
||||||
|
print('Enter multiworld server address')
|
||||||
|
ctx.server_address = await console_input(ctx)
|
||||||
|
|
||||||
|
address = f"ws://{ctx.server_address}" if "://" not in ctx.server_address else ctx.server_address
|
||||||
|
|
||||||
|
print('Connecting to multiworld server at %s' % address)
|
||||||
|
try:
|
||||||
|
ctx.socket = await websockets.connect(address, ping_timeout=None, ping_interval=None)
|
||||||
|
print('Connected')
|
||||||
|
|
||||||
|
async for data in ctx.socket:
|
||||||
|
for msg in json.loads(data):
|
||||||
|
cmd, args = (msg[0], msg[1]) if len(msg) > 1 else (msg, None)
|
||||||
|
await process_server_cmd(ctx, cmd, args)
|
||||||
|
print('Disconnected from multiworld server, type /connect to reconnect')
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print('Connection refused by the multiworld server')
|
||||||
|
except (OSError, websockets.InvalidURI):
|
||||||
|
print('Failed to connect to the multiworld server')
|
||||||
|
except Exception as e:
|
||||||
|
print('Lost connection to the multiworld server, type /connect to reconnect')
|
||||||
|
if not isinstance(e, websockets.WebSocketException):
|
||||||
|
logging.exception(e)
|
||||||
|
finally:
|
||||||
|
ctx.name = None
|
||||||
|
ctx.team = None
|
||||||
|
ctx.slot = None
|
||||||
|
ctx.expected_rom = None
|
||||||
|
ctx.rom_confirmed = False
|
||||||
|
socket, ctx.socket = ctx.socket, None
|
||||||
|
if socket is not None and not socket.closed:
|
||||||
|
await socket.close()
|
||||||
|
ctx.server_task = None
|
||||||
|
|
||||||
|
async def process_server_cmd(ctx : Context, cmd, args):
|
||||||
|
if cmd == 'RoomInfo':
|
||||||
|
print('--------------------------------')
|
||||||
|
print('Room Information:')
|
||||||
|
print('--------------------------------')
|
||||||
|
if args['password']:
|
||||||
|
print('Password required')
|
||||||
|
print('%d players seed' % args['slots'])
|
||||||
|
if len(args['players']) < 1:
|
||||||
|
print('No player connected')
|
||||||
|
else:
|
||||||
|
args['players'].sort(key=lambda player: ('' if not player[1] else player[1].lower(), player[2]))
|
||||||
|
current_team = 0
|
||||||
|
print('Connected players:')
|
||||||
|
for name, team, slot in args['players']:
|
||||||
|
if team != current_team:
|
||||||
|
print(' Default team' if not team else ' Team: %s' % team)
|
||||||
|
current_team = team
|
||||||
|
print(' %s (Player %d)' % (name, slot))
|
||||||
|
await server_auth(ctx, args['password'])
|
||||||
|
|
||||||
|
if cmd == 'ConnectionRefused':
|
||||||
|
password_requested = False
|
||||||
|
if 'InvalidPassword' in args:
|
||||||
|
print('Invalid password')
|
||||||
|
ctx.password = None
|
||||||
|
password_requested = True
|
||||||
|
if 'InvalidName' in args:
|
||||||
|
print('Invalid name')
|
||||||
|
ctx.name = None
|
||||||
|
if 'NameAlreadyTaken' in args:
|
||||||
|
print('Name already taken')
|
||||||
|
ctx.name = None
|
||||||
|
if 'InvalidTeam' in args:
|
||||||
|
print('Invalid team name')
|
||||||
|
ctx.team = None
|
||||||
|
if 'InvalidSlot' in args:
|
||||||
|
print('Invalid player slot')
|
||||||
|
ctx.slot = None
|
||||||
|
if 'SlotAlreadyTaken' in args:
|
||||||
|
print('Player slot already in use for that team')
|
||||||
|
ctx.team = None
|
||||||
|
ctx.slot = None
|
||||||
|
await server_auth(ctx, password_requested)
|
||||||
|
|
||||||
|
if cmd == 'Connected':
|
||||||
|
ctx.expected_rom = args
|
||||||
|
if ctx.last_rom is not None:
|
||||||
|
if ctx.last_rom[:len(args)] == ctx.expected_rom:
|
||||||
|
rom_confirmed(ctx)
|
||||||
|
if ctx.locations_checked:
|
||||||
|
await send_msgs(ctx.socket, [['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]]])
|
||||||
|
else:
|
||||||
|
raise Exception('Different ROM expected from server')
|
||||||
|
|
||||||
|
if cmd == 'ReceivedItems':
|
||||||
|
start_index, items = args
|
||||||
|
if start_index == 0:
|
||||||
|
ctx.items_received = []
|
||||||
|
elif start_index != len(ctx.items_received):
|
||||||
|
sync_msg = [['Sync']]
|
||||||
|
if ctx.locations_checked:
|
||||||
|
sync_msg.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]])
|
||||||
|
await send_msgs(ctx.socket, sync_msg)
|
||||||
|
if start_index == len(ctx.items_received):
|
||||||
|
for item in items:
|
||||||
|
ctx.items_received.append(ReceivedItem(item[0], item[1], item[2], item[3]))
|
||||||
|
|
||||||
|
if cmd == 'ItemSent':
|
||||||
|
player_sent, player_recvd, item, location = args
|
||||||
|
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.name else 'green')
|
||||||
|
player_sent = color(player_sent, 'yellow' if player_sent != ctx.name else 'magenta')
|
||||||
|
player_recvd = color(player_recvd, 'yellow' if player_recvd != ctx.name else 'magenta')
|
||||||
|
print('(%s) %s sent %s to %s (%s)' % (ctx.team if ctx.team else 'Team', player_sent, item, player_recvd, get_location_name_from_address(location)))
|
||||||
|
|
||||||
|
if cmd == 'Print':
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
async def server_auth(ctx : Context, password_requested):
|
||||||
|
if password_requested and not ctx.password:
|
||||||
|
print('Enter the password required to join this game:')
|
||||||
|
ctx.password = await console_input(ctx)
|
||||||
|
while not ctx.name or not re.match(r'\w{1,10}', ctx.name):
|
||||||
|
print('Enter your name (10 characters):')
|
||||||
|
ctx.name = await console_input(ctx)
|
||||||
|
if not ctx.team:
|
||||||
|
print('Enter your team name (optional):')
|
||||||
|
ctx.team = await console_input(ctx)
|
||||||
|
if ctx.team == '': ctx.team = None
|
||||||
|
if not ctx.slot:
|
||||||
|
print('Choose your player slot (optional):')
|
||||||
|
slot = await console_input(ctx)
|
||||||
|
ctx.slot = int(slot) if slot.isdigit() else None
|
||||||
|
await send_msgs(ctx.socket, [['Connect', {'password': ctx.password, 'name': ctx.name, 'team': ctx.team, 'slot': ctx.slot}]])
|
||||||
|
|
||||||
|
async def console_input(ctx : Context):
|
||||||
|
ctx.input_requests += 1
|
||||||
|
return await ctx.input_queue.get()
|
||||||
|
|
||||||
|
async def console_loop(ctx : Context):
|
||||||
|
while not ctx.exit_event.is_set():
|
||||||
|
input = await aioconsole.ainput()
|
||||||
|
|
||||||
|
if ctx.input_requests > 0:
|
||||||
|
ctx.input_requests -= 1
|
||||||
|
ctx.input_queue.put_nowait(input)
|
||||||
|
continue
|
||||||
|
|
||||||
|
command = input.split()
|
||||||
|
if not command:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if command[0] == '/exit':
|
||||||
|
ctx.exit_event.set()
|
||||||
|
|
||||||
|
if command[0] == '/installcolors' and 'colorama' not in sys.modules:
|
||||||
|
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'colorama'])
|
||||||
|
global colorama
|
||||||
|
import colorama
|
||||||
|
colorama.init()
|
||||||
|
|
||||||
|
if command[0] == '/snes':
|
||||||
|
asyncio.create_task(snes_connect(ctx, command[1] if len(command) > 1 else None))
|
||||||
|
if command[0] in ['/snes_close', '/snes_quit']:
|
||||||
|
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
|
||||||
|
async def disconnect():
|
||||||
|
if ctx.socket is not None and not ctx.socket.closed:
|
||||||
|
await ctx.socket.close()
|
||||||
|
if ctx.server_task is not None:
|
||||||
|
await ctx.server_task
|
||||||
|
async def connect():
|
||||||
|
await disconnect()
|
||||||
|
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
||||||
|
|
||||||
|
if command[0] in ['/connect', '/reconnect']:
|
||||||
|
if len(command) > 1:
|
||||||
|
ctx.server_address = command[1]
|
||||||
|
asyncio.create_task(connect())
|
||||||
|
if command[0] == '/disconnect':
|
||||||
|
asyncio.create_task(disconnect())
|
||||||
|
if command[0][:1] != '/':
|
||||||
|
asyncio.create_task(send_msgs(ctx.socket, [['Say', input]]))
|
||||||
|
|
||||||
|
if command[0] == '/received':
|
||||||
|
print('Received items:')
|
||||||
|
for index, item in enumerate(ctx.items_received, 1):
|
||||||
|
print('%s from %s (%s) (%d/%d in list)' % (
|
||||||
|
color(get_item_name_from_id(item.item), 'red', 'bold'), color(item.player_name, 'yellow'),
|
||||||
|
get_location_name_from_address(item.location), index, len(ctx.items_received)))
|
||||||
|
|
||||||
|
if command[0] == '/missing':
|
||||||
|
for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]:
|
||||||
|
if location not in ctx.locations_checked:
|
||||||
|
print('Missing: ' + location)
|
||||||
|
if command[0] == '/getitem' and len(command) > 1:
|
||||||
|
item = input[9:]
|
||||||
|
item_id = Items.item_table[item][3] if item in Items.item_table else None
|
||||||
|
if type(item_id) is int and item_id in range(0x100):
|
||||||
|
print('Sending item: ' + item)
|
||||||
|
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item_id]))
|
||||||
|
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([0]))
|
||||||
|
else:
|
||||||
|
print('Invalid item: ' + item)
|
||||||
|
|
||||||
|
await snes_flush_writes(ctx)
|
||||||
|
|
||||||
|
def rom_confirmed(ctx : Context):
|
||||||
|
ctx.rom_confirmed = True
|
||||||
|
print('ROM hash Confirmed')
|
||||||
|
|
||||||
|
def get_item_name_from_id(code):
|
||||||
|
items = [k for k, i in Items.item_table.items() if type(i[3]) is int and i[3] == code]
|
||||||
|
return items[0] if items else 'Unknown item'
|
||||||
|
|
||||||
|
def get_location_name_from_address(address):
|
||||||
|
if type(address) is str:
|
||||||
|
return address
|
||||||
|
|
||||||
|
locs = [k for k, l in Regions.location_table.items() if type(l[0]) is int and l[0] == address]
|
||||||
|
return locs[0] if locs else 'Unknown location'
|
||||||
|
|
||||||
|
async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
|
new_locations = []
|
||||||
|
def new_check(location):
|
||||||
|
ctx.locations_checked.add(location)
|
||||||
|
print("New check: %s (%d/216)" % (location, len(ctx.locations_checked)))
|
||||||
|
new_locations.append(Regions.location_table[location][0])
|
||||||
|
|
||||||
|
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
||||||
|
if location not in ctx.locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
uw_begin = 0x129
|
||||||
|
uw_end = 0
|
||||||
|
uw_unchecked = {}
|
||||||
|
for location, (roomid, mask) in location_table_uw.items():
|
||||||
|
if location not in ctx.locations_checked:
|
||||||
|
uw_unchecked[location] = (roomid, mask)
|
||||||
|
uw_begin = min(uw_begin, roomid)
|
||||||
|
uw_end = max(uw_end, roomid + 1)
|
||||||
|
if uw_begin < uw_end:
|
||||||
|
uw_data = await snes_read(ctx, SAVEDATA_START + (uw_begin * 2), (uw_end - uw_begin) * 2)
|
||||||
|
if uw_data is not None:
|
||||||
|
for location, (roomid, mask) in uw_unchecked.items():
|
||||||
|
offset = (roomid - uw_begin) * 2
|
||||||
|
roomdata = uw_data[offset] | (uw_data[offset + 1] << 8)
|
||||||
|
if roomdata & mask != 0:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
ow_begin = 0x82
|
||||||
|
ow_end = 0
|
||||||
|
ow_unchecked = {}
|
||||||
|
for location, screenid in location_table_ow.items():
|
||||||
|
if location not in ctx.locations_checked:
|
||||||
|
ow_unchecked[location] = screenid
|
||||||
|
ow_begin = min(ow_begin, screenid)
|
||||||
|
ow_end = max(ow_end, screenid + 1)
|
||||||
|
if ow_begin < ow_end:
|
||||||
|
ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin)
|
||||||
|
if ow_data is not None:
|
||||||
|
for location, screenid in ow_unchecked.items():
|
||||||
|
if ow_data[screenid - ow_begin] & 0x40 != 0:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
if not all([location in ctx.locations_checked for location in location_table_npc.keys()]):
|
||||||
|
npc_data = await snes_read(ctx, SAVEDATA_START + 0x410, 2)
|
||||||
|
if npc_data is not None:
|
||||||
|
npc_value = npc_data[0] | (npc_data[1] << 8)
|
||||||
|
for location, mask in location_table_npc.items():
|
||||||
|
if npc_value & mask != 0 and location not in ctx.locations_checked:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
if not all([location in ctx.locations_checked for location in location_table_misc.keys()]):
|
||||||
|
misc_data = await snes_read(ctx, SAVEDATA_START + 0x3c6, 4)
|
||||||
|
if misc_data is not None:
|
||||||
|
for location, (offset, mask) in location_table_misc.items():
|
||||||
|
assert(0x3c6 <= offset <= 0x3c9)
|
||||||
|
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
||||||
|
|
||||||
|
async def game_watcher(ctx : Context):
|
||||||
|
while not ctx.exit_event.is_set():
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
if not ctx.rom_confirmed:
|
||||||
|
rom = await snes_read(ctx, ROMNAME_START, ROMNAME_SIZE)
|
||||||
|
if rom is None or rom == bytes([0] * ROMNAME_SIZE):
|
||||||
|
continue
|
||||||
|
if list(rom) != ctx.last_rom:
|
||||||
|
ctx.last_rom = list(rom)
|
||||||
|
ctx.locations_checked = set()
|
||||||
|
if ctx.expected_rom is not None:
|
||||||
|
if ctx.last_rom[:len(ctx.expected_rom)] != ctx.expected_rom:
|
||||||
|
print("Wrong ROM detected")
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
rom_confirmed(ctx)
|
||||||
|
|
||||||
|
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||||
|
if gamemode is None or gamemode[0] not in INGAME_MODES:
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = await snes_read(ctx, RECV_PROGRESS_ADDR, 7)
|
||||||
|
if data is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
recv_index = data[0] | (data[1] << 8)
|
||||||
|
assert(RECV_ITEM_ADDR == RECV_PROGRESS_ADDR + 2)
|
||||||
|
recv_item = data[2]
|
||||||
|
assert(ROOMID_ADDR == RECV_PROGRESS_ADDR + 4)
|
||||||
|
roomid = data[4] | (data[5] << 8)
|
||||||
|
assert(ROOMDATA_ADDR == RECV_PROGRESS_ADDR + 6)
|
||||||
|
roomdata = data[6]
|
||||||
|
|
||||||
|
await track_locations(ctx, roomid, roomdata)
|
||||||
|
|
||||||
|
if recv_index < len(ctx.items_received) and recv_item == 0:
|
||||||
|
item = ctx.items_received[recv_index]
|
||||||
|
print('Received %s from %s (%s) (%d/%d in list)' % (
|
||||||
|
color(get_item_name_from_id(item.item), 'red', 'bold'), color(item.player_name, 'yellow'),
|
||||||
|
get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))
|
||||||
|
recv_index += 1
|
||||||
|
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||||
|
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
|
||||||
|
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([item.player_id]))
|
||||||
|
|
||||||
|
await snes_flush_writes(ctx)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--snes', default='localhost:8080', help='Address of the QUsb2snes server.')
|
||||||
|
parser.add_argument('--connect', default=None, help='Address of the multiworld host.')
|
||||||
|
parser.add_argument('--password', default=None, help='Password of the multiworld host.')
|
||||||
|
parser.add_argument('--name', default=None)
|
||||||
|
parser.add_argument('--team', default=None)
|
||||||
|
parser.add_argument('--slot', default=None, type=int)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ctx = Context(args.snes, args.connect, args.password, args.name, args.team, args.slot)
|
||||||
|
|
||||||
|
input_task = asyncio.create_task(console_loop(ctx))
|
||||||
|
|
||||||
|
await snes_connect(ctx)
|
||||||
|
|
||||||
|
if ctx.server_task is None:
|
||||||
|
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
||||||
|
|
||||||
|
watcher_task = asyncio.create_task(game_watcher(ctx))
|
||||||
|
|
||||||
|
|
||||||
|
await ctx.exit_event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
await watcher_task
|
||||||
|
|
||||||
|
if ctx.socket is not None and not ctx.socket.closed:
|
||||||
|
await ctx.socket.close()
|
||||||
|
if ctx.server_task is not None:
|
||||||
|
await ctx.server_task
|
||||||
|
|
||||||
|
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||||
|
await ctx.snes_socket.close()
|
||||||
|
|
||||||
|
while ctx.input_requests > 0:
|
||||||
|
ctx.input_queue.put_nowait(None)
|
||||||
|
ctx.input_requests -= 1
|
||||||
|
|
||||||
|
await input_task
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if 'colorama' in sys.modules:
|
||||||
|
colorama.init()
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
if 'colorama' in sys.modules:
|
||||||
|
colorama.deinit()
|
||||||
+398
@@ -0,0 +1,398 @@
|
|||||||
|
import aioconsole
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
import websockets
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
import Items
|
||||||
|
import Regions
|
||||||
|
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.socket = socket
|
||||||
|
self.auth = False
|
||||||
|
self.name = None
|
||||||
|
self.team = None
|
||||||
|
self.slot = None
|
||||||
|
self.send_index = 0
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
def __init__(self, host, port, password):
|
||||||
|
self.data_filename = None
|
||||||
|
self.save_filename = None
|
||||||
|
self.disable_save = False
|
||||||
|
self.players = 0
|
||||||
|
self.rom_names = {}
|
||||||
|
self.locations = {}
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.password = password
|
||||||
|
self.server = None
|
||||||
|
self.clients = []
|
||||||
|
self.received_items = {}
|
||||||
|
|
||||||
|
def get_room_info(ctx : Context):
|
||||||
|
return {
|
||||||
|
'password': ctx.password is not None,
|
||||||
|
'slots': ctx.players,
|
||||||
|
'players': [(client.name, client.team, client.slot) for client in ctx.clients if client.auth]
|
||||||
|
}
|
||||||
|
|
||||||
|
def same_name(lhs, rhs):
|
||||||
|
return lhs.lower() == rhs.lower()
|
||||||
|
|
||||||
|
def same_team(lhs, rhs):
|
||||||
|
return (type(lhs) is type(rhs)) and ((not lhs and not rhs) or (lhs.lower() == rhs.lower()))
|
||||||
|
|
||||||
|
async def send_msgs(websocket, msgs):
|
||||||
|
if not websocket or not websocket.open or websocket.closed:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await websocket.send(json.dumps(msgs))
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def broadcast_all(ctx : Context, msgs):
|
||||||
|
for client in ctx.clients:
|
||||||
|
if client.auth:
|
||||||
|
asyncio.create_task(send_msgs(client.socket, msgs))
|
||||||
|
|
||||||
|
def broadcast_team(ctx : Context, team, msgs):
|
||||||
|
for client in ctx.clients:
|
||||||
|
if client.auth and same_team(client.team, team):
|
||||||
|
asyncio.create_task(send_msgs(client.socket, msgs))
|
||||||
|
|
||||||
|
def notify_all(ctx : Context, text):
|
||||||
|
print("Notice (all): %s" % text)
|
||||||
|
broadcast_all(ctx, [['Print', text]])
|
||||||
|
|
||||||
|
def notify_team(ctx : Context, team : str, text : str):
|
||||||
|
print("Team notice (%s): %s" % ("Default" if not team else team, text))
|
||||||
|
broadcast_team(ctx, team, [['Print', text]])
|
||||||
|
|
||||||
|
def notify_client(client : Client, text : str):
|
||||||
|
if not client.auth:
|
||||||
|
return
|
||||||
|
print("Player notice (%s): %s" % (client.name, text))
|
||||||
|
asyncio.create_task(send_msgs(client.socket, [['Print', text]]))
|
||||||
|
|
||||||
|
async def server(websocket, path, ctx : Context):
|
||||||
|
client = Client(websocket)
|
||||||
|
ctx.clients.append(client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await on_client_connected(ctx, client)
|
||||||
|
async for data in websocket:
|
||||||
|
for msg in json.loads(data):
|
||||||
|
if len(msg) == 1:
|
||||||
|
cmd = msg
|
||||||
|
args = None
|
||||||
|
else:
|
||||||
|
cmd = msg[0]
|
||||||
|
args = msg[1]
|
||||||
|
await process_client_cmd(ctx, client, cmd, args)
|
||||||
|
except Exception as e:
|
||||||
|
if not isinstance(e, websockets.WebSocketException):
|
||||||
|
logging.exception(e)
|
||||||
|
finally:
|
||||||
|
await on_client_disconnected(ctx, client)
|
||||||
|
ctx.clients.remove(client)
|
||||||
|
|
||||||
|
async def on_client_connected(ctx : Context, client : Client):
|
||||||
|
await send_msgs(client.socket, [['RoomInfo', get_room_info(ctx)]])
|
||||||
|
|
||||||
|
async def on_client_disconnected(ctx : Context, client : Client):
|
||||||
|
if client.auth:
|
||||||
|
await on_client_left(ctx, client)
|
||||||
|
|
||||||
|
async def on_client_joined(ctx : Context, client : Client):
|
||||||
|
notify_all(ctx, "%s has joined the game as player %d for %s" % (client.name, client.slot, "the default team" if not client.team else "team %s" % client.team))
|
||||||
|
|
||||||
|
async def on_client_left(ctx : Context, client : Client):
|
||||||
|
notify_all(ctx, "%s (Player %d, %s) has left the game" % (client.name, client.slot, "Default team" if not client.team else "Team %s" % client.team))
|
||||||
|
|
||||||
|
def get_connected_players_string(ctx : Context):
|
||||||
|
auth_clients = [c for c in ctx.clients if c.auth]
|
||||||
|
if not auth_clients:
|
||||||
|
return 'No player connected'
|
||||||
|
|
||||||
|
auth_clients.sort(key=lambda c: ('' if not c.team else c.team.lower(), c.slot))
|
||||||
|
current_team = 0
|
||||||
|
text = ''
|
||||||
|
for c in auth_clients:
|
||||||
|
if c.team != current_team:
|
||||||
|
text += '::' + ('default team' if not c.team else c.team) + ':: '
|
||||||
|
current_team = c.team
|
||||||
|
text += '%d:%s ' % (c.slot, c.name)
|
||||||
|
return 'Connected players: ' + text[:-1]
|
||||||
|
|
||||||
|
def get_player_name_in_team(ctx : Context, team, slot):
|
||||||
|
for client in ctx.clients:
|
||||||
|
if client.auth and same_team(team, client.team) and client.slot == slot:
|
||||||
|
return client.name
|
||||||
|
return "Player %d" % slot
|
||||||
|
|
||||||
|
def get_client_from_name(ctx : Context, name):
|
||||||
|
for client in ctx.clients:
|
||||||
|
if client.auth and same_name(name, client.name):
|
||||||
|
return client
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_received_items(ctx : Context, team, player):
|
||||||
|
for (c_team, c_id), items in ctx.received_items.items():
|
||||||
|
if c_id == player and same_team(c_team, team):
|
||||||
|
return items
|
||||||
|
ctx.received_items[(team, player)] = []
|
||||||
|
return ctx.received_items[(team, player)]
|
||||||
|
|
||||||
|
def tuplize_received_items(items):
|
||||||
|
return [(item.item, item.location, item.player_id, item.player_name) for item in items]
|
||||||
|
|
||||||
|
def send_new_items(ctx : Context):
|
||||||
|
for client in ctx.clients:
|
||||||
|
if not client.auth:
|
||||||
|
continue
|
||||||
|
items = get_received_items(ctx, client.team, client.slot)
|
||||||
|
if len(items) > client.send_index:
|
||||||
|
asyncio.create_task(send_msgs(client.socket, [['ReceivedItems', (client.send_index, tuplize_received_items(items)[client.send_index:])]]))
|
||||||
|
client.send_index = len(items)
|
||||||
|
|
||||||
|
def forfeit_player(ctx : Context, team, slot, name):
|
||||||
|
all_locations = [values[0] for values in Regions.location_table.values() if type(values[0]) is int]
|
||||||
|
notify_all(ctx, "%s (Player %d) in team %s has forfeited" % (name, slot, team if team else 'default'))
|
||||||
|
register_location_checks(ctx, name, team, slot, all_locations)
|
||||||
|
|
||||||
|
def register_location_checks(ctx : Context, name, team, slot, locations):
|
||||||
|
found_items = False
|
||||||
|
for location in locations:
|
||||||
|
if (location, slot) in ctx.locations:
|
||||||
|
target_item, target_player = ctx.locations[(location, slot)]
|
||||||
|
if target_player != slot:
|
||||||
|
found = False
|
||||||
|
recvd_items = get_received_items(ctx, team, target_player)
|
||||||
|
for recvd_item in recvd_items:
|
||||||
|
if recvd_item.location == location and recvd_item.player_id == slot:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
new_item = ReceivedItem(target_item, location, slot, name)
|
||||||
|
recvd_items.append(new_item)
|
||||||
|
target_player_name = get_player_name_in_team(ctx, team, target_player)
|
||||||
|
broadcast_team(ctx, team, [['ItemSent', (name, target_player_name, target_item, location)]])
|
||||||
|
print('(%s) %s sent %s to %s (%s)' % (team if team else 'Team', name, get_item_name_from_id(target_item), target_player_name, get_location_name_from_address(location)))
|
||||||
|
found_items = True
|
||||||
|
send_new_items(ctx)
|
||||||
|
|
||||||
|
if found_items and not ctx.disable_save:
|
||||||
|
try:
|
||||||
|
with open(ctx.save_filename, "wb") as f:
|
||||||
|
jsonstr = json.dumps((ctx.players,
|
||||||
|
[(k, v) for k, v in ctx.rom_names.items()],
|
||||||
|
[(k, [i.__dict__ for i in v]) for k, v in ctx.received_items.items()]))
|
||||||
|
f.write(zlib.compress(jsonstr.encode("utf-8")))
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
|
||||||
|
async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
||||||
|
if type(cmd) is not str:
|
||||||
|
await send_msgs(client.socket, [['InvalidCmd']])
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'Connect':
|
||||||
|
if not args or type(args) is not dict or \
|
||||||
|
'password' not in args or type(args['password']) not in [str, type(None)] or \
|
||||||
|
'name' not in args or type(args['name']) is not str or \
|
||||||
|
'team' not in args or type(args['team']) not in [str, type(None)] or \
|
||||||
|
'slot' not in args or type(args['slot']) not in [int, type(None)]:
|
||||||
|
await send_msgs(client.socket, [['InvalidArguments', 'Connect']])
|
||||||
|
return
|
||||||
|
|
||||||
|
errors = set()
|
||||||
|
if ctx.password is not None and ('password' not in args or args['password'] != ctx.password):
|
||||||
|
errors.add('InvalidPassword')
|
||||||
|
|
||||||
|
if 'name' not in args or not args['name'] or not re.match(r'\w{1,10}', args['name']):
|
||||||
|
errors.add('InvalidName')
|
||||||
|
elif any([same_name(c.name, args['name']) for c in ctx.clients if c.auth]):
|
||||||
|
errors.add('NameAlreadyTaken')
|
||||||
|
else:
|
||||||
|
client.name = args['name']
|
||||||
|
|
||||||
|
if 'team' in args and args['team'] is not None and not re.match(r'\w{1,15}', args['team']):
|
||||||
|
errors.add('InvalidTeam')
|
||||||
|
else:
|
||||||
|
client.team = args['team'] if 'team' in args else None
|
||||||
|
|
||||||
|
if 'slot' in args and any([c.slot == args['slot'] for c in ctx.clients if c.auth and same_team(c.team, client.team)]):
|
||||||
|
errors.add('SlotAlreadyTaken')
|
||||||
|
elif 'slot' not in args or not args['slot']:
|
||||||
|
for slot in range(1, ctx.players + 1):
|
||||||
|
if slot not in [c.slot for c in ctx.clients if c.auth and same_team(c.team, client.team)]:
|
||||||
|
client.slot = slot
|
||||||
|
break
|
||||||
|
elif slot == ctx.players:
|
||||||
|
errors.add('SlotAlreadyTaken')
|
||||||
|
elif args['slot'] not in range(1, ctx.players + 1):
|
||||||
|
errors.add('InvalidSlot')
|
||||||
|
else:
|
||||||
|
client.slot = args['slot']
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
client.name = None
|
||||||
|
client.team = None
|
||||||
|
client.slot = None
|
||||||
|
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
|
||||||
|
else:
|
||||||
|
client.auth = True
|
||||||
|
reply = [['Connected', ctx.rom_names[client.slot]]]
|
||||||
|
items = get_received_items(ctx, client.team, client.slot)
|
||||||
|
if items:
|
||||||
|
reply.append(['ReceivedItems', (0, tuplize_received_items(items))])
|
||||||
|
client.send_index = len(items)
|
||||||
|
await send_msgs(client.socket, reply)
|
||||||
|
await on_client_joined(ctx, client)
|
||||||
|
|
||||||
|
if not client.auth:
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'Sync':
|
||||||
|
items = get_received_items(ctx, client.team, client.slot)
|
||||||
|
if items:
|
||||||
|
client.send_index = len(items)
|
||||||
|
await send_msgs(client.socket, ['ReceivedItems', (0, tuplize_received_items(items))])
|
||||||
|
|
||||||
|
if cmd == 'LocationChecks':
|
||||||
|
if type(args) is not list:
|
||||||
|
await send_msgs(client.socket, [['InvalidArguments', 'LocationChecks']])
|
||||||
|
return
|
||||||
|
register_location_checks(ctx, client.name, client.team, client.slot, args)
|
||||||
|
|
||||||
|
if cmd == 'Say':
|
||||||
|
if type(args) is not str or not args.isprintable():
|
||||||
|
await send_msgs(client.socket, [['InvalidArguments', 'Say']])
|
||||||
|
return
|
||||||
|
|
||||||
|
notify_all(ctx, client.name + ': ' + args)
|
||||||
|
|
||||||
|
if args[:8] == '!players':
|
||||||
|
notify_all(ctx, get_connected_players_string(ctx))
|
||||||
|
if args[:8] == '!forfeit':
|
||||||
|
forfeit_player(ctx, client.team, client.slot, client.name)
|
||||||
|
|
||||||
|
def set_password(ctx : Context, password):
|
||||||
|
ctx.password = password
|
||||||
|
print('Password set to ' + password if password is not None else 'Password disabled')
|
||||||
|
|
||||||
|
async def console(ctx : Context):
|
||||||
|
while True:
|
||||||
|
input = await aioconsole.ainput()
|
||||||
|
|
||||||
|
command = input.split()
|
||||||
|
if not command:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if command[0] == '/exit':
|
||||||
|
ctx.server.ws_server.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
if command[0] == '/players':
|
||||||
|
print(get_connected_players_string(ctx))
|
||||||
|
if command[0] == '/password':
|
||||||
|
set_password(ctx, command[1] if len(command) > 1 else None)
|
||||||
|
if command[0] == '/kick' and len(command) > 1:
|
||||||
|
client = get_client_from_name(ctx, command[1])
|
||||||
|
if client and client.socket and not client.socket.closed:
|
||||||
|
await client.socket.close()
|
||||||
|
|
||||||
|
if command[0] == '/forfeitslot' and len(command) == 3 and command[2].isdigit():
|
||||||
|
team = command[1] if command[1] != 'default' else None
|
||||||
|
slot = int(command[2])
|
||||||
|
name = get_player_name_in_team(ctx, team, slot)
|
||||||
|
forfeit_player(ctx, team, slot, name)
|
||||||
|
if command[0] == '/forfeitplayer' and len(command) > 1:
|
||||||
|
client = get_client_from_name(ctx, command[1])
|
||||||
|
if client:
|
||||||
|
forfeit_player(ctx, client.team, client.slot, client.name)
|
||||||
|
if command[0] == '/senditem' and len(command) > 2:
|
||||||
|
[(player, item)] = re.findall(r'\S* (\S*) (.*)', input)
|
||||||
|
if item in Items.item_table:
|
||||||
|
client = get_client_from_name(ctx, player)
|
||||||
|
if client:
|
||||||
|
new_item = ReceivedItem(Items.item_table[item][3], "cheat console", 0, "server")
|
||||||
|
get_received_items(ctx, client.team, client.slot).append(new_item)
|
||||||
|
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
|
||||||
|
send_new_items(ctx)
|
||||||
|
else:
|
||||||
|
print("Unknown item: " + item)
|
||||||
|
|
||||||
|
if command[0][0] != '/':
|
||||||
|
notify_all(ctx, '[Server]: ' + input)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--host', default=None)
|
||||||
|
parser.add_argument('--port', default=38281, type=int)
|
||||||
|
parser.add_argument('--password', default=None)
|
||||||
|
parser.add_argument('--multidata', default=None)
|
||||||
|
parser.add_argument('--savefile', default=None)
|
||||||
|
parser.add_argument('--disable_save', default=False, action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ctx = Context(args.host, args.port, args.password)
|
||||||
|
|
||||||
|
ctx.data_filename = args.multidata
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not ctx.data_filename:
|
||||||
|
import tkinter
|
||||||
|
import tkinter.filedialog
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
ctx.data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data","*multidata"),))
|
||||||
|
|
||||||
|
with open(ctx.data_filename, 'rb') as f:
|
||||||
|
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
|
||||||
|
ctx.players = jsonobj[0]
|
||||||
|
ctx.rom_names = {k: v for k, v in jsonobj[1]}
|
||||||
|
ctx.locations = {tuple(k): tuple(v) for k, v in jsonobj[2]}
|
||||||
|
except Exception as e:
|
||||||
|
print('Failed to read multiworld data (%s)' % e)
|
||||||
|
return
|
||||||
|
|
||||||
|
ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host
|
||||||
|
print('Hosting game of %d players (%s) at %s:%d' % (ctx.players, 'No password' if not ctx.password else 'Password: %s' % ctx.password, ip, ctx.port))
|
||||||
|
|
||||||
|
ctx.disable_save = args.disable_save
|
||||||
|
if not ctx.disable_save:
|
||||||
|
if not ctx.save_filename:
|
||||||
|
ctx.save_filename = (ctx.data_filename[:-9] if ctx.data_filename[-9:] == 'multidata' else (ctx.data_filename + '_')) + 'multisave'
|
||||||
|
try:
|
||||||
|
with open(ctx.save_filename, 'rb') as f:
|
||||||
|
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
|
||||||
|
players = jsonobj[0]
|
||||||
|
rom_names = {k: v for k, v in jsonobj[1]}
|
||||||
|
received_items = {tuple(k): [ReceivedItem(**i) for i in v] for k, v in jsonobj[2]}
|
||||||
|
if players != ctx.players or rom_names != ctx.rom_names:
|
||||||
|
raise Exception('Save file mismatch, will start a new game')
|
||||||
|
ctx.received_items = received_items
|
||||||
|
print('Loaded save file with %d received items for %d players' % (sum([len(p) for p in received_items.values()]), len(received_items)))
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('No save data found, starting a new game')
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
ctx.server = websockets.serve(functools.partial(server,ctx=ctx), ctx.host, ctx.port, ping_timeout=None, ping_interval=None)
|
||||||
|
await ctx.server
|
||||||
|
await console(ctx)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
|
||||||
|
loop.close()
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from DungeonRandomizer import parse_arguments
|
||||||
|
from Main import main as DRMain
|
||||||
|
|
||||||
|
def parse_yaml(txt):
|
||||||
|
def strip(s):
|
||||||
|
s = s.strip()
|
||||||
|
return '' if not s else s.strip('"') if s[0] == '"' else s.strip("'") if s[0] == "'" else s
|
||||||
|
ret = {}
|
||||||
|
indents = {len(txt) - len(txt.lstrip(' ')): ret}
|
||||||
|
for line in txt.splitlines():
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
name, val = line.split(':', 1)
|
||||||
|
val = strip(val)
|
||||||
|
spaces = len(name) - len(name.lstrip(' '))
|
||||||
|
name = strip(name)
|
||||||
|
if val:
|
||||||
|
indents[spaces][name] = val
|
||||||
|
else:
|
||||||
|
newdict = {}
|
||||||
|
indents[spaces][name] = newdict
|
||||||
|
indents[spaces+2] = newdict
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
|
||||||
|
multiargs, _ = parser.parse_known_args()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--weights', help='Path to the weights file to use for rolling game settings, urls are also valid')
|
||||||
|
parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player', action='store_true')
|
||||||
|
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||||
|
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
|
||||||
|
parser.add_argument('--names', default='')
|
||||||
|
parser.add_argument('--create_spoiler', action='store_true')
|
||||||
|
parser.add_argument('--rom')
|
||||||
|
parser.add_argument('--enemizercli')
|
||||||
|
parser.add_argument('--outputpath')
|
||||||
|
for player in range(1, multiargs.multi + 1):
|
||||||
|
parser.add_argument(f'--p{player}', help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.seed is None:
|
||||||
|
random.seed(None)
|
||||||
|
seed = random.randint(0, 999999999)
|
||||||
|
else:
|
||||||
|
seed = args.seed
|
||||||
|
random.seed(seed)
|
||||||
|
|
||||||
|
seedname = f'M{random.randint(0, 999999999)}'
|
||||||
|
print(f"Generating mystery for {args.multi} player{'s' if args.multi > 1 else ''}, {seedname} Seed {seed}")
|
||||||
|
|
||||||
|
weights_cache = {}
|
||||||
|
if args.weights:
|
||||||
|
weights_cache[args.weights] = get_weights(args.weights)
|
||||||
|
print(f"Weights: {args.weights} >> {weights_cache[args.weights]['description']}")
|
||||||
|
for player in range(1, args.multi + 1):
|
||||||
|
path = getattr(args, f'p{player}')
|
||||||
|
if path:
|
||||||
|
if path not in weights_cache:
|
||||||
|
weights_cache[path] = get_weights(path)
|
||||||
|
print(f"P{player} Weights: {path} >> {weights_cache[path]['description']}")
|
||||||
|
|
||||||
|
erargs = parse_arguments(['--multi', str(args.multi)])
|
||||||
|
erargs.seed = seed
|
||||||
|
erargs.names = args.names
|
||||||
|
erargs.create_spoiler = args.create_spoiler
|
||||||
|
erargs.race = True
|
||||||
|
erargs.outputname = seedname
|
||||||
|
erargs.outputpath = args.outputpath
|
||||||
|
|
||||||
|
if args.rom:
|
||||||
|
erargs.rom = args.rom
|
||||||
|
if args.enemizercli:
|
||||||
|
erargs.enemizercli = args.enemizercli
|
||||||
|
|
||||||
|
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
|
||||||
|
|
||||||
|
for player in range(1, args.multi + 1):
|
||||||
|
path = getattr(args, f'p{player}') if getattr(args, f'p{player}') else args.weights
|
||||||
|
if path:
|
||||||
|
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
||||||
|
for k, v in vars(settings).items():
|
||||||
|
if v is not None:
|
||||||
|
getattr(erargs, k)[player] = v
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f'No weights specified for player {player}')
|
||||||
|
|
||||||
|
# set up logger
|
||||||
|
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[erargs.loglevel]
|
||||||
|
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||||
|
|
||||||
|
DRMain(erargs, seed)
|
||||||
|
|
||||||
|
def get_weights(path):
|
||||||
|
try:
|
||||||
|
if urllib.parse.urlparse(path).scheme:
|
||||||
|
yaml = str(urllib.request.urlopen(path).read(), "utf-8")
|
||||||
|
else:
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
yaml = str(f.read(), "utf-8")
|
||||||
|
except Exception as e:
|
||||||
|
print('Failed to read weights (%s)' % e)
|
||||||
|
return
|
||||||
|
|
||||||
|
return parse_yaml(yaml)
|
||||||
|
|
||||||
|
def roll_settings(weights):
|
||||||
|
def get_choice(option, root=weights):
|
||||||
|
if option not in root:
|
||||||
|
return None
|
||||||
|
if type(root[option]) is not dict:
|
||||||
|
return root[option]
|
||||||
|
if not root[option]:
|
||||||
|
return None
|
||||||
|
return random.choices(list(root[option].keys()), weights=list(map(int,root[option].values())))[0]
|
||||||
|
|
||||||
|
ret = argparse.Namespace()
|
||||||
|
|
||||||
|
glitches_required = get_choice('glitches_required')
|
||||||
|
if glitches_required not in ['none', 'no_logic']:
|
||||||
|
print("Only NMG and No Logic supported")
|
||||||
|
glitches_required = 'none'
|
||||||
|
ret.logic = {'none': 'noglitches', 'no_logic': 'nologic'}[glitches_required]
|
||||||
|
|
||||||
|
item_placement = get_choice('item_placement')
|
||||||
|
# not supported in ER
|
||||||
|
|
||||||
|
dungeon_items = get_choice('dungeon_items')
|
||||||
|
ret.mapshuffle = get_choice('map_shuffle') == 'on' if 'map_shuffle' in weights else dungeon_items in ['mc', 'mcs', 'full']
|
||||||
|
ret.compassshuffle = get_choice('compass_shuffle') == 'on' if 'compass_shuffle' in weights else dungeon_items in ['mc', 'mcs', 'full']
|
||||||
|
ret.keyshuffle = get_choice('smallkey_shuffle') == 'on' if 'smallkey_shuffle' in weights else dungeon_items in ['mcs', 'full']
|
||||||
|
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
|
||||||
|
|
||||||
|
ret.accessibility = get_choice('accessibility')
|
||||||
|
|
||||||
|
entrance_shuffle = get_choice('entrance_shuffle')
|
||||||
|
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
||||||
|
door_shuffle = get_choice('door_shuffle')
|
||||||
|
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||||
|
|
||||||
|
ret.goal = {'ganon': 'ganon',
|
||||||
|
'fast_ganon': 'crystals',
|
||||||
|
'dungeons': 'dungeons',
|
||||||
|
'pedestal': 'pedestal',
|
||||||
|
'triforce-hunt': 'triforcehunt'
|
||||||
|
}[get_choice('goals')]
|
||||||
|
ret.openpyramid = ret.goal == 'fast_ganon'
|
||||||
|
|
||||||
|
ret.crystals_gt = get_choice('tower_open')
|
||||||
|
|
||||||
|
ret.crystals_ganon = get_choice('ganon_open')
|
||||||
|
|
||||||
|
ret.mode = get_choice('world_state')
|
||||||
|
if ret.mode == 'retro':
|
||||||
|
ret.mode = 'open'
|
||||||
|
ret.retro = True
|
||||||
|
|
||||||
|
ret.hints = get_choice('hints') == 'on'
|
||||||
|
|
||||||
|
ret.swords = {'randomized': 'random',
|
||||||
|
'assured': 'assured',
|
||||||
|
'vanilla': 'vanilla',
|
||||||
|
'swordless': 'swordless'
|
||||||
|
}[get_choice('weapons')]
|
||||||
|
|
||||||
|
ret.difficulty = get_choice('item_pool')
|
||||||
|
|
||||||
|
ret.item_functionality = get_choice('item_functionality')
|
||||||
|
|
||||||
|
ret.shufflebosses = {'none': 'none',
|
||||||
|
'simple': 'basic',
|
||||||
|
'full': 'normal',
|
||||||
|
'random': 'chaos'
|
||||||
|
}[get_choice('boss_shuffle')]
|
||||||
|
|
||||||
|
ret.shuffleenemies = {'none': 'none',
|
||||||
|
'shuffled': 'shuffled',
|
||||||
|
'random': 'chaos'
|
||||||
|
}[get_choice('enemy_shuffle')]
|
||||||
|
|
||||||
|
ret.enemy_damage = {'default': 'default',
|
||||||
|
'shuffled': 'shuffled',
|
||||||
|
'random': 'chaos'
|
||||||
|
}[get_choice('enemy_damage')]
|
||||||
|
|
||||||
|
ret.enemy_health = get_choice('enemy_health')
|
||||||
|
|
||||||
|
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
||||||
|
|
||||||
|
ret.beemizer = int(get_choice('beemizer')) if 'beemizer' in weights else 0
|
||||||
|
|
||||||
|
inventoryweights = weights.get('startinventory', {})
|
||||||
|
startitems = []
|
||||||
|
for item in inventoryweights.keys():
|
||||||
|
if get_choice(item, inventoryweights) == 'on':
|
||||||
|
startitems.append(item)
|
||||||
|
ret.startinventory = ','.join(startitems)
|
||||||
|
|
||||||
|
if 'rom' in weights:
|
||||||
|
romweights = weights['rom']
|
||||||
|
ret.sprite = get_choice('sprite', romweights)
|
||||||
|
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
|
||||||
|
ret.quickswap = get_choice('quickswap', romweights) == 'on'
|
||||||
|
ret.fastmenu = get_choice('menuspeed', romweights)
|
||||||
|
ret.heartcolor = get_choice('heartcolor', romweights)
|
||||||
|
ret.heartbeep = get_choice('heartbeep', romweights)
|
||||||
|
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
||||||
|
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -10,7 +10,7 @@ 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, apply_rom_settings, get_sprite_from_name
|
||||||
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
|
||||||
@@ -23,7 +23,7 @@ def main(args):
|
|||||||
start_time = time.process_time()
|
start_time = time.process_time()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None, 'none', False)
|
world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
|
||||||
hasher = hashlib.md5()
|
hasher = hashlib.md5()
|
||||||
@@ -36,7 +36,7 @@ def main(args):
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
world.difficulty_requirements = difficulties[world.difficulty]
|
world.difficulty_requirements[1] = difficulties[world.difficulty[1]]
|
||||||
|
|
||||||
create_regions(world, 1)
|
create_regions(world, 1)
|
||||||
create_dungeons(world, 1)
|
create_dungeons(world, 1)
|
||||||
@@ -68,13 +68,10 @@ def main(args):
|
|||||||
|
|
||||||
logger.info('Patching ROM.')
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
if args.sprite is not None:
|
|
||||||
sprite = Sprite(args.sprite)
|
|
||||||
else:
|
|
||||||
sprite = None
|
|
||||||
|
|
||||||
rom = LocalRom(args.rom)
|
rom = LocalRom(args.rom)
|
||||||
patch_rom(world, 1, rom, args.heartbeep, args.heartcolor, sprite)
|
patch_rom(world, 1, rom, False)
|
||||||
|
|
||||||
|
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes)
|
||||||
|
|
||||||
for textname, texttype, text in text_patches:
|
for textname, texttype, text in text_patches:
|
||||||
if texttype == 'text':
|
if texttype == 'text':
|
||||||
@@ -114,16 +111,16 @@ def fill_world(world, plando, text_patches):
|
|||||||
tr_medallion = medallionstr.strip()
|
tr_medallion = medallionstr.strip()
|
||||||
elif line.startswith('!mode'):
|
elif line.startswith('!mode'):
|
||||||
_, modestr = line.split(':', 1)
|
_, modestr = line.split(':', 1)
|
||||||
world.mode = modestr.strip()
|
world.mode = {1: modestr.strip()}
|
||||||
elif line.startswith('!logic'):
|
elif line.startswith('!logic'):
|
||||||
_, logicstr = line.split(':', 1)
|
_, logicstr = line.split(':', 1)
|
||||||
world.logic = logicstr.strip()
|
world.logic = {1: logicstr.strip()}
|
||||||
elif line.startswith('!goal'):
|
elif line.startswith('!goal'):
|
||||||
_, goalstr = line.split(':', 1)
|
_, goalstr = line.split(':', 1)
|
||||||
world.goal = goalstr.strip()
|
world.goal = {1: goalstr.strip()}
|
||||||
elif line.startswith('!light_cone_sewers'):
|
elif line.startswith('!light_cone_sewers'):
|
||||||
_, sewerstr = line.split(':', 1)
|
_, sewerstr = line.split(':', 1)
|
||||||
world.sewer_light_cone = sewerstr.strip().lower() == 'true'
|
world.sewer_light_cone = {1: sewerstr.strip().lower() == 'true'}
|
||||||
elif line.startswith('!light_cone_lw'):
|
elif line.startswith('!light_cone_lw'):
|
||||||
_, lwconestr = line.split(':', 1)
|
_, lwconestr = line.split(':', 1)
|
||||||
world.light_world_light_cone = lwconestr.strip().lower() == 'true'
|
world.light_world_light_cone = lwconestr.strip().lower() == 'true'
|
||||||
@@ -132,19 +129,19 @@ def fill_world(world, plando, text_patches):
|
|||||||
world.dark_world_light_cone = dwconestr.strip().lower() == 'true'
|
world.dark_world_light_cone = dwconestr.strip().lower() == 'true'
|
||||||
elif line.startswith('!fix_trock_doors'):
|
elif line.startswith('!fix_trock_doors'):
|
||||||
_, trdstr = line.split(':', 1)
|
_, trdstr = line.split(':', 1)
|
||||||
world.fix_trock_doors = trdstr.strip().lower() == 'true'
|
world.fix_trock_doors = {1: trdstr.strip().lower() == 'true'}
|
||||||
elif line.startswith('!fix_trock_exit'):
|
elif line.startswith('!fix_trock_exit'):
|
||||||
_, trfstr = line.split(':', 1)
|
_, trfstr = line.split(':', 1)
|
||||||
world.fix_trock_exit = trfstr.strip().lower() == 'true'
|
world.fix_trock_exit = {1: trfstr.strip().lower() == 'true'}
|
||||||
elif line.startswith('!fix_gtower_exit'):
|
elif line.startswith('!fix_gtower_exit'):
|
||||||
_, gtfstr = line.split(':', 1)
|
_, gtfstr = line.split(':', 1)
|
||||||
world.fix_gtower_exit = gtfstr.strip().lower() == 'true'
|
world.fix_gtower_exit = gtfstr.strip().lower() == 'true'
|
||||||
elif line.startswith('!fix_pod_exit'):
|
elif line.startswith('!fix_pod_exit'):
|
||||||
_, podestr = line.split(':', 1)
|
_, podestr = line.split(':', 1)
|
||||||
world.fix_palaceofdarkness_exit = podestr.strip().lower() == 'true'
|
world.fix_palaceofdarkness_exit = {1: podestr.strip().lower() == 'true'}
|
||||||
elif line.startswith('!fix_skullwoods_exit'):
|
elif line.startswith('!fix_skullwoods_exit'):
|
||||||
_, swestr = line.split(':', 1)
|
_, swestr = line.split(':', 1)
|
||||||
world.fix_skullwoods_exit = swestr.strip().lower() == 'true'
|
world.fix_skullwoods_exit = {1: swestr.strip().lower() == 'true'}
|
||||||
elif line.startswith('!check_beatable_only'):
|
elif line.startswith('!check_beatable_only'):
|
||||||
_, chkbtstr = line.split(':', 1)
|
_, chkbtstr = line.split(':', 1)
|
||||||
world.check_beatable_only = chkbtstr.strip().lower() == 'true'
|
world.check_beatable_only = chkbtstr.strip().lower() == 'true'
|
||||||
@@ -172,7 +169,7 @@ def fill_world(world, plando, text_patches):
|
|||||||
item = ItemFactory(itemstr.strip(), 1)
|
item = ItemFactory(itemstr.strip(), 1)
|
||||||
if item is not None:
|
if item is not None:
|
||||||
world.push_item(location, item)
|
world.push_item(location, item)
|
||||||
if item.key:
|
if item.smallkey or item.bigkey:
|
||||||
location.event = True
|
location.event = True
|
||||||
elif '<=>' in line:
|
elif '<=>' in line:
|
||||||
entrance, exit = line.split('<=>', 1)
|
entrance, exit = line.split('<=>', 1)
|
||||||
@@ -211,6 +208,8 @@ def start():
|
|||||||
help='Select the rate at which the heart beep sound is played at low health.')
|
help='Select the rate at which the heart beep sound is played at low health.')
|
||||||
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow'],
|
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow'],
|
||||||
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
||||||
|
parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||||
|
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||||
parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.')
|
parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.')
|
||||||
parser.add_argument('--plando', help='Filled out template to use for setting up the rom.')
|
parser.add_argument('--plando', help='Filled out template to use for setting up the rom.')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -222,8 +221,8 @@ def start():
|
|||||||
if not os.path.isfile(args.plando):
|
if not os.path.isfile(args.plando):
|
||||||
input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando)
|
input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if args.sprite is not None and not os.path.isfile(args.rom):
|
if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite):
|
||||||
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
|
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# set up logger
|
# set up logger
|
||||||
|
|||||||
@@ -40,6 +40,32 @@ Doors are not shuffled.
|
|||||||
|
|
||||||
Used for development testing. This will be removed in a future version. Use at your own risk. Might play like a plando.
|
Used for development testing. This will be removed in a future version. Use at your own risk. Might play like a plando.
|
||||||
|
|
||||||
|
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
|
||||||
|
|
||||||
|
These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon.
|
||||||
|
Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that is traditionally
|
||||||
|
a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but the rest
|
||||||
|
of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
||||||
|
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
||||||
|
|
||||||
|
## Retro
|
||||||
|
|
||||||
|
This setting turns all Small Keys into universal Small Keys that can be used in any dungeon and are distributed across the world.
|
||||||
|
The Bow now consumed rupees to shoot; the cost is 10 rupees per Wood Arrow and 50 per Silver Arrow. Shooting Wood Arrows requires
|
||||||
|
the purchase of an arrow item from shops, and to account for this and the dynamic use of keys, both Wood Arrows and Small Keys will
|
||||||
|
be added to several shops around the world. Four "take any" caves are added that allow the player to choose between an extra Heart
|
||||||
|
Container and a Bottle being filled with Blue Potion, and one of the four swords from the item pool is placed into a special cave as
|
||||||
|
well. The five caves that are removed for these will be randomly selected single entrance caves that did not contain any items or any shops.
|
||||||
|
In further concert with the Bow changes, all arrows under pots, in chests, and elsewhere in the seed will be replaced with rupees.
|
||||||
|
|
||||||
|
## Seed
|
||||||
|
|
||||||
|
Can be used to set a seed number to generate. Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output.
|
||||||
|
|
||||||
|
## Count
|
||||||
|
|
||||||
|
Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time).
|
||||||
|
|
||||||
# Command Line Options
|
# Command Line Options
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+272
-288
@@ -705,21 +705,8 @@ def create_regions(world, player):
|
|||||||
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])
|
create_dungeon_region(player, 'GT Agahnim 2', 'Ganon\'s Tower', ['Agahnim 2'], ['GT Agahnim 2 SW'])
|
||||||
]
|
]
|
||||||
|
|
||||||
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
world.initialize_regions()
|
||||||
region = world.get_region(region_name, player)
|
|
||||||
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
|
||||||
region.shop = shop
|
|
||||||
world.shops.append(shop)
|
|
||||||
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
|
||||||
shop.add_inventory(index, item, price)
|
|
||||||
|
|
||||||
region = world.get_region('Capacity Upgrade', player)
|
|
||||||
shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
|
|
||||||
region.shop = shop
|
|
||||||
world.shops.append(shop)
|
|
||||||
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
|
|
||||||
shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7)
|
|
||||||
world.intialize_regions()
|
|
||||||
|
|
||||||
def create_lw_region(player, name, locations=None, exits=None):
|
def create_lw_region(player, name, locations=None, exits=None):
|
||||||
return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
|
return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
|
||||||
@@ -746,14 +733,14 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
|
|||||||
if location in key_only_locations:
|
if location in key_only_locations:
|
||||||
ret.locations.append(Location(player, location, None, False, None, ret, key_only_locations[location]))
|
ret.locations.append(Location(player, location, None, False, None, ret, key_only_locations[location]))
|
||||||
else:
|
else:
|
||||||
address, crystal, hint_text = location_table[location]
|
address, player_address, crystal, hint_text = location_table[location]
|
||||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret))
|
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def mark_light_world_regions(world):
|
def mark_light_world_regions(world, player):
|
||||||
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
|
||||||
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
# That is ok. the bunny logic will check for this case and incorporate special rules.
|
||||||
queue = collections.deque(region for region in world.regions if region.type == RegionType.LightWorld)
|
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld)
|
||||||
seen = set(queue)
|
seen = set(queue)
|
||||||
while queue:
|
while queue:
|
||||||
current = queue.popleft()
|
current = queue.popleft()
|
||||||
@@ -766,7 +753,7 @@ def mark_light_world_regions(world):
|
|||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
queue = collections.deque(region for region in world.regions if region.type == RegionType.DarkWorld)
|
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld)
|
||||||
seen = set(queue)
|
seen = set(queue)
|
||||||
while queue:
|
while queue:
|
||||||
current = queue.popleft()
|
current = queue.popleft()
|
||||||
@@ -780,37 +767,35 @@ def mark_light_world_regions(world):
|
|||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
# (room_id, shopkeeper, replaceable)
|
|
||||||
shop_table = {
|
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
|
|
||||||
'Red Shield Shop': (0x0110, 0xC1, True),
|
|
||||||
'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
|
|
||||||
'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
|
|
||||||
'Village of Outcasts Shop': (0x010F, 0xC1, True),
|
|
||||||
'Dark World Potion Shop': (0x010F, 0xC1, True),
|
|
||||||
'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
|
|
||||||
'Kakariko Shop': (0x011F, 0xA0, True),
|
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
|
|
||||||
'Potion Shop': (0x0109, 0xFF, False),
|
|
||||||
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
|
||||||
}
|
|
||||||
# region, [item]
|
|
||||||
# slot, item, price, max=0, replacement=None, replacement_price=0
|
|
||||||
# item = (item, price)
|
|
||||||
|
|
||||||
|
def create_shops(world, player):
|
||||||
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items():
|
||||||
|
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
||||||
|
locked = True
|
||||||
|
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
region = world.get_region(region_name, player)
|
||||||
|
shop = Shop(region, room_id, type, shopkeeper, custom, locked)
|
||||||
|
region.shop = shop
|
||||||
|
world.shops.append(shop)
|
||||||
|
for index, item in enumerate(inventory):
|
||||||
|
shop.add_inventory(index, *item)
|
||||||
|
|
||||||
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||||
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
default_shop_contents = {
|
shop_table = {
|
||||||
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
|
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
|
||||||
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
|
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
||||||
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
|
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
|
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Village of Outcasts Shop': _dark_world_shop_defaults,
|
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Dark World Potion Shop': _dark_world_shop_defaults,
|
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Light World Death Mountain Shop': _basic_shop_defaults,
|
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Kakariko Shop': _basic_shop_defaults,
|
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
|
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('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)])
|
||||||
}
|
}
|
||||||
|
|
||||||
key_only_locations = {
|
key_only_locations = {
|
||||||
@@ -863,244 +848,243 @@ flooded_keys_reverse = {
|
|||||||
'Swamp Palace - Trench 1 Pot Key': 'Trench 1 Switch',
|
'Swamp Palace - Trench 1 Pot Key': 'Trench 1 Switch',
|
||||||
'Swamp Palace - Trench 2 Pot Key': 'Trench 2 Switch'
|
'Swamp Palace - Trench 2 Pot Key': 'Trench 2 Switch'
|
||||||
}
|
}
|
||||||
|
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
||||||
'Bottle Merchant': (0x2eb18, False, 'with a merchant'),
|
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
||||||
'Flute Spot': (0x18014a, False, 'underground'),
|
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
|
||||||
'Sunken Treasure': (0x180145, False, 'underwater'),
|
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
|
||||||
'Purple Chest': (0x33d68, False, 'from a box'),
|
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
|
||||||
"Blind's Hideout - Top": (0xeb0f, False, 'in a basement'),
|
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
|
||||||
"Blind's Hideout - Left": (0xeb12, False, 'in a basement'),
|
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
|
||||||
"Blind's Hideout - Right": (0xeb15, False, 'in a basement'),
|
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
|
||||||
"Blind's Hideout - Far Left": (0xeb18, False, 'in a basement'),
|
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
|
||||||
"Blind's Hideout - Far Right": (0xeb1b, False, 'in a basement'),
|
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
|
||||||
"Link's Uncle": (0x2df45, False, 'with your uncle'),
|
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
|
||||||
'Secret Passage': (0xe971, False, 'near your uncle'),
|
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
|
||||||
'King Zora': (0xee1c3, False, 'at a high price'),
|
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
|
||||||
"Zora's Ledge": (0x180149, False, 'near Zora'),
|
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
|
||||||
'Waterfall Fairy - Left': (0xe9b0, False, 'near a fairy'),
|
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
|
||||||
'Waterfall Fairy - Right': (0xe9d1, False, 'near a fairy'),
|
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
|
||||||
"King's Tomb": (0xe97a, False, 'alone in a cave'),
|
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
|
||||||
'Floodgate Chest': (0xe98c, False, 'in the dam'),
|
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
|
||||||
"Link's House": (0xe9bc, False, 'in your home'),
|
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
|
||||||
'Kakariko Tavern': (0xe9ce, False, 'in the bar'),
|
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
|
||||||
'Chicken House': (0xe9e9, False, 'near poultry'),
|
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
|
||||||
"Aginah's Cave": (0xe9f2, False, 'with Aginah'),
|
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
|
||||||
"Sahasrahla's Hut - Left": (0xea82, False, 'near the elder'),
|
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
|
||||||
"Sahasrahla's Hut - Middle": (0xea85, False, 'near the elder'),
|
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
|
||||||
"Sahasrahla's Hut - Right": (0xea88, False, 'near the elder'),
|
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
|
||||||
'Sahasrahla': (0x2f1fc, False, 'with the elder'),
|
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
|
||||||
'Kakariko Well - Top': (0xea8e, False, 'in a well'),
|
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
|
||||||
'Kakariko Well - Left': (0xea91, False, 'in a well'),
|
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
|
||||||
'Kakariko Well - Middle': (0xea94, False, 'in a well'),
|
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
|
||||||
'Kakariko Well - Right': (0xea97, False, 'in a well'),
|
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
|
||||||
'Kakariko Well - Bottom': (0xea9a, False, 'in a well'),
|
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
|
||||||
'Blacksmith': (0x18002a, False, 'with the smith'),
|
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
|
||||||
'Magic Bat': (0x180015, False, 'with the bat'),
|
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
|
||||||
'Sick Kid': (0x339cf, False, 'with the sick'),
|
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
|
||||||
'Hobo': (0x33e7d, False, 'with the hobo'),
|
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
|
||||||
'Lost Woods Hideout': (0x180000, False, 'near a thief'),
|
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
|
||||||
'Lumberjack Tree': (0x180001, False, 'in a hole'),
|
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
|
||||||
'Cave 45': (0x180003, False, 'alone in a cave'),
|
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
|
||||||
'Graveyard Cave': (0x180004, False, 'alone in a cave'),
|
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
|
||||||
'Checkerboard Cave': (0x180005, False, 'alone in a cave'),
|
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
|
||||||
'Mini Moldorm Cave - Far Left': (0xeb42, False, 'near Moldorms'),
|
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
|
||||||
'Mini Moldorm Cave - Left': (0xeb45, False, 'near Moldorms'),
|
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
|
||||||
'Mini Moldorm Cave - Right': (0xeb48, False, 'near Moldorms'),
|
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
|
||||||
'Mini Moldorm Cave - Far Right': (0xeb4b, False, 'near Moldorms'),
|
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
|
||||||
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'),
|
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
|
||||||
'Ice Rod Cave': (0xeb4e, False, 'in a frozen cave'),
|
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
|
||||||
'Bonk Rock Cave': (0xeb3f, False, 'alone in a cave'),
|
'Library': (0x180012, 0x18635c, False, 'near books'),
|
||||||
'Library': (0x180012, False, 'near books'),
|
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
|
||||||
'Potion Shop': (0x180014, False, 'near potions'),
|
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
|
||||||
'Lake Hylia Island': (0x180144, False, 'on an island'),
|
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
|
||||||
'Maze Race': (0x180142, False, 'at the race'),
|
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
|
||||||
'Desert Ledge': (0x180143, False, 'in the desert'),
|
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
|
||||||
'Desert Palace - Big Chest': (0xe98f, False, 'in Desert Palace'),
|
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
|
||||||
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'),
|
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
|
||||||
'Desert Palace - Map Chest': (0xe9b6, False, 'in Desert Palace'),
|
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
|
||||||
'Desert Palace - Compass Chest': (0xe9cb, False, 'in Desert Palace'),
|
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
|
||||||
'Desert Palace - Big Key Chest': (0xe9c2, False, 'in Desert Palace'),
|
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
|
||||||
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'),
|
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
|
||||||
'Eastern Palace - Compass Chest': (0xe977, False, 'in Eastern Palace'),
|
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
|
||||||
'Eastern Palace - Big Chest': (0xe97d, False, 'in Eastern Palace'),
|
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
|
||||||
'Eastern Palace - Cannonball Chest': (0xe9b3, False, 'in Eastern Palace'),
|
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
|
||||||
'Eastern Palace - Big Key Chest': (0xe9b9, False, 'in Eastern Palace'),
|
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
|
||||||
'Eastern Palace - Map Chest': (0xe9f5, False, 'in Eastern Palace'),
|
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
|
||||||
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'),
|
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
|
||||||
'Master Sword Pedestal': (0x289b0, False, 'at the pedestal'),
|
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
|
||||||
'Hyrule Castle - Boomerang Chest': (0xe974, False, 'in Hyrule Castle'),
|
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
|
||||||
'Hyrule Castle - Map Chest': (0xeb0c, False, 'in Hyrule Castle'),
|
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
|
||||||
"Hyrule Castle - Zelda's Chest": (0xeb09, False, 'in Hyrule Castle'),
|
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
|
||||||
'Sewers - Dark Cross': (0xe96e, False, 'in the sewers'),
|
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
|
||||||
'Sewers - Secret Room - Left': (0xeb5d, False, 'in the sewers'),
|
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
|
||||||
'Sewers - Secret Room - Middle': (0xeb60, False, 'in the sewers'),
|
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
|
||||||
'Sewers - Secret Room - Right': (0xeb63, False, 'in the sewers'),
|
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
|
||||||
'Sanctuary': (0xea79, False, 'in Sanctuary'),
|
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
|
||||||
'Castle Tower - Room 03': (0xeab5, False, 'in Castle Tower'),
|
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
|
||||||
'Castle Tower - Dark Maze': (0xeab2, False, 'in Castle Tower'),
|
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
|
||||||
'Old Man': (0xf69fa, False, 'with the old man'),
|
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
|
||||||
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'),
|
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Lower - Far Left': (0xeb2a, False, 'in a cave with seven chests'),
|
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Lower - Left': (0xeb2d, False, 'in a cave with seven chests'),
|
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Lower - Right': (0xeb30, False, 'in a cave with seven chests'),
|
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Lower - Far Right': (0xeb33, False, 'in a cave with seven chests'),
|
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Lower - Middle': (0xeb36, False, 'in a cave with seven chests'),
|
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Upper - Left': (0xeb39, False, 'in a cave with seven chests'),
|
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
|
||||||
'Paradox Cave Upper - Right': (0xeb3c, False, 'in a cave with seven chests'),
|
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
|
||||||
'Spiral Cave': (0xe9bf, False, 'in spiral cave'),
|
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
|
||||||
'Ether Tablet': (0x180016, False, 'at a monolith'),
|
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
|
||||||
'Spectacle Rock': (0x180140, False, 'atop a rock'),
|
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
|
||||||
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'),
|
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
|
||||||
'Tower of Hera - Map Chest': (0xe9ad, False, 'in Tower of Hera'),
|
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
|
||||||
'Tower of Hera - Big Key Chest': (0xe9e6, False, 'in Tower of Hera'),
|
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
|
||||||
'Tower of Hera - Compass Chest': (0xe9fb, False, 'in Tower of Hera'),
|
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
|
||||||
'Tower of Hera - Big Chest': (0xe9f8, False, 'in Tower of Hera'),
|
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
|
||||||
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'),
|
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
|
||||||
'Pyramid': (0x180147, False, 'on the pyramid'),
|
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
|
||||||
'Catfish': (0xee185, False, 'with a catfish'),
|
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
|
||||||
'Stumpy': (0x330c7, False, 'with tree boy'),
|
'Digging Game': (0x180148, 0x186357, False, 'underground'),
|
||||||
'Digging Game': (0x180148, False, 'underground'),
|
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
|
||||||
'Bombos Tablet': (0x180017, False, 'at a monolith'),
|
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
|
||||||
'Hype Cave - Top': (0xeb1e, False, 'near a bat-like man'),
|
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
|
||||||
'Hype Cave - Middle Right': (0xeb21, False, 'near a bat-like man'),
|
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
|
||||||
'Hype Cave - Middle Left': (0xeb24, False, 'near a bat-like man'),
|
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
|
||||||
'Hype Cave - Bottom': (0xeb27, False, 'near a bat-like man'),
|
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
|
||||||
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'),
|
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
|
||||||
'Peg Cave': (0x180006, False, 'alone in a cave'),
|
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
|
||||||
'Pyramid Fairy - Left': (0xe980, False, 'near a fairy'),
|
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
|
||||||
'Pyramid Fairy - Right': (0xe983, False, 'near a fairy'),
|
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
|
||||||
'Brewery': (0xe9ec, False, 'alone in a home'),
|
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
|
||||||
'C-Shaped House': (0xe9ef, False, 'alone in a home'),
|
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
|
||||||
'Chest Game': (0xeda8, False, 'as a prize'),
|
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
|
||||||
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'),
|
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
|
||||||
'Mire Shed - Left': (0xea73, False, 'near sparks'),
|
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
|
||||||
'Mire Shed - Right': (0xea76, False, 'near sparks'),
|
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
|
||||||
'Superbunny Cave - Top': (0xea7c, False, 'in a connection'),
|
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
|
||||||
'Superbunny Cave - Bottom': (0xea7f, False, 'in a connection'),
|
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
|
||||||
'Spike Cave': (0xea8b, False, 'beyond spikes'),
|
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
|
||||||
'Hookshot Cave - Top Right': (0xeb51, False, 'across pits'),
|
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
|
||||||
'Hookshot Cave - Top Left': (0xeb54, False, 'across pits'),
|
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
|
||||||
'Hookshot Cave - Bottom Right': (0xeb5a, False, 'across pits'),
|
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
|
||||||
'Hookshot Cave - Bottom Left': (0xeb57, False, 'across pits'),
|
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
|
||||||
'Floating Island': (0x180141, False, 'on an island'),
|
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
|
||||||
'Mimic Cave': (0xe9c5, False, 'in a cave of mimicry'),
|
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Entrance': (0xea9d, False, 'in Swamp Palace'),
|
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Map Chest': (0xe986, False, 'in Swamp Palace'),
|
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Big Chest': (0xe989, False, 'in Swamp Palace'),
|
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Compass Chest': (0xeaa0, False, 'in Swamp Palace'),
|
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Big Key Chest': (0xeaa6, False, 'in Swamp Palace'),
|
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - West Chest': (0xeaa3, False, 'in Swamp Palace'),
|
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Flooded Room - Left': (0xeaa9, False, 'in Swamp Palace'),
|
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Flooded Room - Right': (0xeaac, False, 'in Swamp Palace'),
|
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
|
||||||
'Swamp Palace - Waterfall Room': (0xeaaf, False, 'in Swamp Palace'),
|
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
|
||||||
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'),
|
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Big Key Chest": (0xea04, False, "in Thieves' Town"),
|
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Map Chest": (0xea01, False, "in Thieves' Town"),
|
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Compass Chest": (0xea07, False, "in Thieves' Town"),
|
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Ambush Chest": (0xea0a, False, "in Thieves' Town"),
|
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Attic": (0xea0d, False, "in Thieves' Town"),
|
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Big Chest": (0xea10, False, "in Thieves' Town"),
|
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
|
||||||
"Thieves' Town - Blind's Cell": (0xea13, False, "in Thieves' Town"),
|
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
|
||||||
"Thieves' Town - Boss": (0x180156, False, 'with Blind'),
|
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Compass Chest': (0xe992, False, 'in Skull Woods'),
|
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Map Chest': (0xe99b, False, 'in Skull Woods'),
|
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Big Chest': (0xe998, False, 'in Skull Woods'),
|
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Pot Prison': (0xe9a1, False, 'in Skull Woods'),
|
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Pinball Room': (0xe9c8, False, 'in Skull Woods'),
|
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
|
||||||
'Skull Woods - Big Key Chest': (0xe99e, False, 'in Skull Woods'),
|
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
|
||||||
'Skull Woods - Bridge Room': (0xe9fe, False, 'near Mothula'),
|
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
|
||||||
'Skull Woods - Boss': (0x180155, False, 'with Mothula'),
|
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Compass Chest': (0xe9d4, False, 'in Ice Palace'),
|
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Freezor Chest': (0xe995, False, 'in Ice Palace'),
|
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Big Chest': (0xe9aa, False, 'in Ice Palace'),
|
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Iced T Room': (0xe9e3, False, 'in Ice Palace'),
|
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Spike Room': (0xe9e0, False, 'in Ice Palace'),
|
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Big Key Chest': (0xe9a4, False, 'in Ice Palace'),
|
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
|
||||||
'Ice Palace - Map Chest': (0xe9dd, False, 'in Ice Palace'),
|
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
|
||||||
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'),
|
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Big Chest': (0xea67, False, 'in Misery Mire'),
|
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Map Chest': (0xea6a, False, 'in Misery Mire'),
|
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Main Lobby': (0xea5e, False, 'in Misery Mire'),
|
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Bridge Chest': (0xea61, False, 'in Misery Mire'),
|
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Spike Chest': (0xe9da, False, 'in Misery Mire'),
|
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Compass Chest': (0xea64, False, 'in Misery Mire'),
|
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
|
||||||
'Misery Mire - Big Key Chest': (0xea6d, False, 'in Misery Mire'),
|
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
|
||||||
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'),
|
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Compass Chest': (0xea22, False, 'in Turtle Rock'),
|
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Roller Room - Left': (0xea1c, False, 'in Turtle Rock'),
|
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Roller Room - Right': (0xea1f, False, 'in Turtle Rock'),
|
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Chain Chomps': (0xea16, False, 'in Turtle Rock'),
|
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Big Key Chest': (0xea25, False, 'in Turtle Rock'),
|
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Big Chest': (0xea19, False, 'in Turtle Rock'),
|
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Crystaroller Room': (0xea34, False, 'in Turtle Rock'),
|
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, False, 'in Turtle Rock'),
|
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, False, 'in Turtle Rock'),
|
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, False, 'in Turtle Rock'),
|
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
|
||||||
'Turtle Rock - Eye Bridge - Top Right': (0xea28, False, 'in Turtle Rock'),
|
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
|
||||||
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'),
|
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Shooter Room': (0xea5b, False, 'in Palace of Darkness'),
|
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - The Arena - Bridge': (0xea3d, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Stalfos Basement': (0xea49, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Big Key Chest': (0xea37, False, 'in Palace of Darkness'),
|
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - The Arena - Ledge': (0xea3a, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Map Chest': (0xea52, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Compass Chest': (0xea43, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Dark Basement - Left': (0xea4c, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Dark Basement - Right': (0xea4f, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Dark Maze - Top': (0xea55, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Dark Maze - Bottom': (0xea58, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Big Chest': (0xea40, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
|
||||||
'Palace of Darkness - Harmless Hellway': (0xea46, False, 'in Palace of Darkness'),
|
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
|
||||||
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'),
|
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
|
||||||
"Ganons Tower - Bob's Torch": (0x180161, False, "in Ganon's Tower"),
|
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Hope Room - Left': (0xead9, False, "in Ganon's Tower"),
|
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Hope Room - Right': (0xeadc, False, "in Ganon's Tower"),
|
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Tile Room': (0xeae2, False, "in Ganon's Tower"),
|
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Top Left': (0xeae5, False, "in Ganon's Tower"),
|
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Top Right': (0xeae8, False, "in Ganon's Tower"),
|
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, False, "in Ganon's Tower"),
|
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, False, "in Ganon's Tower"),
|
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Top Left': (0xeab8, False, "in Ganon's Tower"),
|
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Top Right': (0xeabb, False, "in Ganon's Tower"),
|
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, False, "in Ganon's Tower"),
|
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, False, "in Ganon's Tower"),
|
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Map Chest': (0xead3, False, "in Ganon's Tower"),
|
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Firesnake Room': (0xead0, False, "in Ganon's Tower"),
|
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, False, "in Ganon's Tower"),
|
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, False, "in Ganon's Tower"),
|
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, False, "in Ganon's Tower"),
|
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, False, "in Ganon's Tower"),
|
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
|
||||||
"Ganons Tower - Bob's Chest": (0xeadf, False, "in Ganon's Tower"),
|
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Big Chest': (0xead6, False, "in Ganon's Tower"),
|
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Big Key Room - Left': (0xeaf4, False, "in Ganon's Tower"),
|
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Big Key Room - Right': (0xeaf7, False, "in Ganon's Tower"),
|
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
|
||||||
'Ganons Tower - Big Key Chest': (0xeaf1, False, "in Ganon's Tower"),
|
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, False, "atop Ganon's Tower"),
|
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, False, "atop Ganon's Tower"),
|
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, False, "atop Ganon's Tower"),
|
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
|
||||||
'Ganons Tower - Validation Chest': (0xeb06, False, "atop Ganon's Tower"),
|
'Ganon': (None, None, False, 'from me'),
|
||||||
'Ganon': (None, False, 'from me'),
|
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||||
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'),
|
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||||
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'),
|
'Floodgate': (None, None, False, None),
|
||||||
'Floodgate': (None, False, None),
|
'Frog': (None, None, False, None),
|
||||||
'Frog': (None, False, None),
|
'Missing Smith': (None, None, False, None),
|
||||||
'Missing Smith': (None, False, None),
|
'Dark Blacksmith Ruins': (None, None, False, None),
|
||||||
'Dark Blacksmith Ruins': (None, False, None),
|
'Trench 1 Switch': (None, None, False, None),
|
||||||
'Trench 1 Switch': (None, False, None),
|
'Trench 2 Switch': (None, None, False, None),
|
||||||
'Trench 2 Switch': (None, False, None),
|
'Swamp Drain': (None, None, False, None),
|
||||||
'Swamp Drain': (None, False, None),
|
'Attic Cracked Floor': (None, None, False, None),
|
||||||
'Attic Cracked Floor': (None, False, None),
|
'Suspicious Maiden': (None, None, False, None),
|
||||||
'Suspicious Maiden': (None, False, None),
|
'Revealing Light': (None, None, False, None),
|
||||||
'Revealing Light': (None, False, None),
|
'Ice Block Drop': (None, None, False, None),
|
||||||
'Ice Block Drop': (None, False, None),
|
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
|
||||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
|
||||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
|
||||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
|
||||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'),
|
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
|
||||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'),
|
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
|
||||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'),
|
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
|
||||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'),
|
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
|
||||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'),
|
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
|
||||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'),
|
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')}
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
def parse_names_string(names):
|
||||||
|
return {player: name for player, name in enumerate([n for n in re.split(r'[, ]', names) if n], 1)}
|
||||||
|
|
||||||
def int16_as_bytes(value):
|
def int16_as_bytes(value):
|
||||||
value = value & 0xFFFF
|
value = value & 0xFFFF
|
||||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||||
@@ -224,4 +228,6 @@ def print_wiki_doors(d_regions, world, player):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
read_entrance_data(old_rom='C:\\Users\\Randall\\Documents\\kwyn\\orig\\z3.sfc')
|
pass
|
||||||
|
# make_new_base2current()
|
||||||
|
# read_entrance_data(old_rom='')
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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.
Reference in New Issue
Block a user