Merge remote-tracking branch 'remotes/multi/multiworld_31' into Dev

This commit is contained in:
compiling
2020-01-04 20:07:29 +11:00
52 changed files with 3306 additions and 1394 deletions
+1
View File
@@ -13,3 +13,4 @@ README.html
*multisave *multisave
EnemizerCLI/ EnemizerCLI/
.mypy_cache/ .mypy_cache/
RaceRom.py
+1
View File
@@ -38,6 +38,7 @@ 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
+3 -2
View File
@@ -1,8 +1,9 @@
import os import os
import re
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, Sprite, apply_rom_settings
@@ -26,7 +27,7 @@ def adjust(args):
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, sprite, parse_names_string(args.names))
rom.write_to_file(output_path('%s.sfc' % outfilebase)) rom.write_to_file(output_path('%s.sfc' % outfilebase))
+143 -88
View File
@@ -4,21 +4,22 @@ import logging
import json import json
from collections import OrderedDict from collections import OrderedDict
from _vendor.collections_extended import bag from _vendor.collections_extended import bag
from EntranceShuffle import door_addresses
from Utils import int16_as_bytes from Utils import int16_as_bytes
class World(object): class World(object):
def __init__(self, players, shuffle, 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, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, retro, custom, customitemarray, hints):
self.players = players self.players = players
self.shuffle = shuffle self.shuffle = shuffle.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 = []
@@ -27,73 +28,88 @@ 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']
self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
self.fix_trock_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
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.can_access_trock_front = None
self.can_access_trock_big_chest = None
self.can_access_trock_middle = None
self.quickswap = quickswap self.quickswap = quickswap
self.fastmenu = fastmenu self.fastmenu = fastmenu
self.disable_music = disable_music self.disable_music = disable_music
self.keysanity = keysanity self.retro = retro.copy()
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)
self.lamps_needed_for_dark_rooms = 1 self.lamps_needed_for_dark_rooms = 1
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))
@@ -140,13 +156,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):
@@ -158,23 +174,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:
@@ -312,14 +328,14 @@ 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]
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)
new_regions = len(rrp) > reachable_regions_count new_regions = len(rrp) > reachable_regions_count
@@ -351,12 +367,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)]
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:
self.events.append((event.name, event.player)) self.events.append((event.name, event.player))
@@ -370,7 +388,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
@@ -404,7 +422,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)
@@ -422,9 +440,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)
@@ -439,7 +457,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))
@@ -490,7 +508,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]]:
@@ -516,16 +534,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:
@@ -540,13 +558,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:
@@ -559,7 +577,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:
@@ -626,6 +644,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)
@@ -674,9 +694,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
@@ -781,18 +804,19 @@ 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): def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, player_address=None):
self.name = name self.name = name
self.parent_region = parent self.parent_region = parent
self.item = None self.item = None
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.event = False self.event = False
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
@@ -834,14 +858,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'
@@ -933,6 +961,7 @@ class Spoiler(object):
self.entrances = OrderedDict() self.entrances = OrderedDict()
self.medallions = {} self.medallions = {}
self.playthrough = {} self.playthrough = {}
self.unreachables = []
self.locations = {} self.locations = {}
self.paths = {} self.paths = {}
self.metadata = {} self.metadata = {}
@@ -1006,7 +1035,7 @@ class Spoiler(object):
self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name
self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name
self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name
if self.world.mode != 'inverted': if self.world.mode[player] != 'inverted':
self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name
@@ -1025,14 +1054,26 @@ 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,
'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):
@@ -1045,7 +1086,6 @@ class Spoiler(object):
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
@@ -1055,19 +1095,31 @@ 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('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('Compass shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['compassshuffle'].items()})
outfile.write('Small Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['keyshuffle'].items()})
outfile.write('Big Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['bigkeyshuffle'].items()})
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()})
outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No')) outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No'))
outfile.write('Menu speed: %s\n' % self.world.fastmenu) outfile.write('Menu speed: %s' % self.world.fastmenu)
outfile.write('Keysanity enabled: %s\n' % ('Yes' if self.metadata['keysanity'] else 'No'))
outfile.write('Players: %d' % self.world.players)
if self.entrances: if self.entrances:
outfile.write('\n\nEntrances:\n\n') outfile.write('\n\nEntrances:\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.entrances.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.entrances.values()]))
@@ -1085,6 +1137,9 @@ class Spoiler(object):
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()])) 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 = []
+12 -12
View File
@@ -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)
+14 -13
View File
@@ -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', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], 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', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], 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', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], 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', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], 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', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], 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', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], 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.key: if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
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)
+1 -1
View File
@@ -85,7 +85,7 @@ In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two loc
Graveyard Cave Graveyard Cave
Mimic 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 keysanity is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses. 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:
+80 -47
View File
@@ -1,13 +1,15 @@
#!/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 Utils import is_bundled, close_console
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
@@ -15,11 +17,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:
@@ -28,7 +37,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.
@@ -41,7 +50,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.
@@ -55,7 +64,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
@@ -67,21 +76,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.
@@ -101,7 +110,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
@@ -115,7 +124,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 derivitive that aims to strike a balance between balanced: vt26 derivitive that aims to strike a balance between
@@ -138,7 +147,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('full'), 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
@@ -162,7 +171,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('--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.
@@ -171,16 +180,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.
@@ -189,49 +200,45 @@ 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('--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('--custom', default=defval(False), help='Not supported.')
parser.add_argument('--customitemarray', default=False, help='Not supported.') parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
parser.add_argument('--nodungeonitems', help='''\ parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
Remove Maps and Compasses from Itempool, replacing them by
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('--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
@@ -246,21 +253,47 @@ 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(''))
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('--shufflepalette', default=defval(False), action='store_true')
parser.add_argument('--shufflepots', default=False, action='store_true') parser.add_argument('--shufflepots', default=defval(False), action='store_true')
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255)) parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
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 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', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle',
'retro', 'accessibility', 'hints', 'shufflepalette', 'shufflepots', 'beemizer',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage']:
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
+57 -87
View File
@@ -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)']
@@ -1610,7 +1612,7 @@ def link_inverted_entrances(world, player):
# 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 + ['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,38 +2717,7 @@ 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_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 Big Bomb Shop']
@@ -3019,6 +2988,7 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
] ]
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'),
@@ -3237,7 +3207,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
('Lumberjack House', 'Lumberjack House'), ('Lumberjack House', 'Lumberjack House'),
("Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance"), ("Hyrule Castle Secret Entrance Drop", "Hyrule Castle Secret Entrance"),
("Hyrule Castle Secret Entrance Stairs", "Hyrule Castle Secret Entrance"), ("Hyrule Castle Secret Entrance Stairs", "Hyrule Castle Secret Entrance"),
("Hyrule Castle Secret Entrance Exit", "Light World"), ("Hyrule Castle Secret Entrance Exit", "Hyrule Castle Courtyard"),
('Bonk Fairy (Light)', 'Bonk Fairy (Light)'), ('Bonk Fairy (Light)', 'Bonk Fairy (Light)'),
('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'), ('Lake Hylia Fairy', 'Lake Hylia Healer Fairy'),
('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'), ('Lake Hylia Fortune Teller', 'Lake Hylia Fortune Teller'),
@@ -3548,7 +3518,7 @@ default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Palace
('Hyrule Castle Entrance (South)', 'Hyrule Castle'), ('Hyrule Castle Entrance (South)', 'Hyrule Castle'),
('Hyrule Castle Entrance (West)', 'Hyrule Castle'), ('Hyrule Castle Entrance (West)', 'Hyrule Castle'),
('Hyrule Castle Entrance (East)', 'Hyrule Castle'), ('Hyrule Castle Entrance (East)', 'Hyrule Castle'),
('Hyrule Castle Exit (South)', 'Light World'), ('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'),
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'), ('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'), ('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
('Agahnims Tower', 'Agahnims Tower'), ('Agahnims Tower', 'Agahnims Tower'),
+50 -37
View File
@@ -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,35 +169,40 @@ 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 = [] no_access_checks = {}
placing_players = set() reachable_items = {}
for item in reversed(itempool): for item in itempool:
if item.player not in placing_players: if world.accessibility[item.player] == 'none':
placing_players.add(item.player) no_access_checks.setdefault(item.player, []).append(item)
items_to_place.append(item)
else: else:
nextpool.insert(0, item) reachable_items.setdefault(item.player, []).append(item)
itempool = nextpool
for player_items in [no_access_checks, reachable_items]:
while any(player_items.values()) and locations:
items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items]
maximum_exploration_state = sweep_from_pool() maximum_exploration_state = sweep_from_pool()
has_beaten_game = world.has_beaten_game(maximum_exploration_state)
perform_access_check = True
if world.accessibility == 'none':
perform_access_check = not world.has_beaten_game(maximum_exploration_state)
for item_to_place in items_to_place: for item_to_place in items_to_place:
perform_access_check = True
if world.accessibility[item_to_place.player] == '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
spot_to_fill = None spot_to_fill = None
for location in locations: for location in locations:
if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check): if (not single_player_placement or location.player == item_to_place.player)\
and location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
spot_to_fill = location spot_to_fill = location
break break
if spot_to_fill is None: if spot_to_fill is None:
# we filled all reachable spots. Maybe the game can be beaten anyway? # 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.can_beat_game():
if world.accessibility != 'none': 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) logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
continue continue
raise FillError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
@@ -206,8 +211,9 @@ def fill_restrictive(world, base_state, locations, itempool):
locations.remove(spot_to_fill) locations.remove(spot_to_fill)
spot_to_fill.event = True spot_to_fill.event = True
itempool.extend(unplaced_items)
def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=None): 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()
@@ -221,7 +227,11 @@ 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]:
continue
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player] gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
random.shuffle(gtower_locations) random.shuffle(gtower_locations)
trashcnt = 0 trashcnt = 0
@@ -235,9 +245,8 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
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)
@@ -307,7 +316,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
@@ -327,7 +336,6 @@ 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)]
@@ -338,8 +346,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:
@@ -350,9 +357,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:
@@ -360,11 +367,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()
@@ -374,9 +384,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)
@@ -386,13 +393,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:
@@ -402,9 +413,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.')
+71 -21
View File
@@ -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 EntranceRandomizer 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):
@@ -60,12 +62,20 @@ def guiMain(args=None):
suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar) suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar)
quickSwapVar = IntVar() quickSwapVar = IntVar()
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar) quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
keysanityVar = IntVar() openpyramidVar = IntVar()
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar) openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar)
mcsbshuffleFrame = Frame(checkBoxFrame)
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() disableMusicVar = IntVar()
disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar) disableMusicCheckbutton = Checkbutton(checkBoxFrame, text="Disable game music", variable=disableMusicVar)
shuffleGanonVar = IntVar() shuffleGanonVar = IntVar()
@@ -80,9 +90,14 @@ 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) quickSwapCheckbutton.pack(expand=True, anchor=W)
keysanityCheckbutton.pack(expand=True, anchor=W) openpyramidCheckbutton.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) 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)
@@ -120,7 +135,7 @@ def guiMain(args=None):
romDialogFrame = Frame(fileDialogFrame) romDialogFrame = Frame(fileDialogFrame)
baseRomLabel = Label(romDialogFrame, text='Base Rom') baseRomLabel = Label(romDialogFrame, text='Base Rom')
romVar = StringVar() romVar = StringVar(value="Zelda no Densetsu - Kamigami no Triforce (Japan).sfc")
romEntry = Entry(romDialogFrame, textvariable=romVar) romEntry = Entry(romDialogFrame, textvariable=romVar)
def RomSelect(): def RomSelect():
@@ -346,6 +361,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)
@@ -354,8 +372,9 @@ 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()
@@ -376,16 +395,19 @@ 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.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 = 'chaos' if bool(enemyShuffleVar.get()) else 'none'
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.shufflepalette = bool(paletteShuffleVar.get())
@@ -401,10 +423,13 @@ def guiMain(args=None):
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(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(progbowVar.get()), int(bomb10Var.get()), int(triforcepieceVar.get()),
int(triforcecountVar.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
@@ -414,14 +439,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))
@@ -502,15 +534,23 @@ def guiMain(args=None):
fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed') fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed')
fastMenuLabel2.pack(side=LEFT) fastMenuLabel2.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)
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()
@@ -518,12 +558,18 @@ def guiMain(args=None):
guiargs.disablemusic = bool(disableMusicVar.get()) guiargs.disablemusic = bool(disableMusicVar.get())
guiargs.rom = romVar2.get() guiargs.rom = romVar2.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)
@@ -1127,13 +1173,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:
+242 -242
View File
@@ -9,7 +9,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)',
@@ -316,7 +316,7 @@ def create_inverted_regions(world, player):
world.shops.append(shop) world.shops.append(shop)
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7) shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7)
world.intialize_regions() world.initialize_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)
@@ -340,14 +340,14 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
for exit in exits: for exit in exits:
ret.exits.append(Entrance(player, exit, ret)) ret.exits.append(Entrance(player, exit, ret))
for location in locations: for location in locations:
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, player_address))
return ret return ret
def mark_dark_world_regions(world): def mark_dark_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.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 +360,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()
@@ -377,7 +377,7 @@ def mark_dark_world_regions(world):
shop_table = { shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True), 'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
'Red Shield Shop': (0x0110, 0xC1, True), 'Red Shield Shop': (0x0110, 0xC1, True),
'Dark Lake Hylia Shop': (0x010F, 0xC1, True), 'Dark Lake Hylia Shop': (0x010F, 0xC1, False),
'Dark World Lumberjack Shop': (0x010F, 0xC1, True), 'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
'Village of Outcasts Shop': (0x010F, 0xC1, True), 'Village of Outcasts Shop': (0x010F, 0xC1, True),
'Dark World Potion Shop': (0x010F, 0xC1, True), 'Dark World Potion Shop': (0x010F, 0xC1, True),
@@ -396,7 +396,7 @@ _dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (
default_shop_contents = { default_shop_contents = {
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults, 'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], 'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
'Dark Lake Hylia Shop': _dark_world_shop_defaults, 'Dark Lake Hylia Shop': [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)],
'Dark World Lumberjack Shop': _dark_world_shop_defaults, 'Dark World Lumberjack Shop': _dark_world_shop_defaults,
'Village of Outcasts Shop': _dark_world_shop_defaults, 'Village of Outcasts Shop': _dark_world_shop_defaults,
'Dark World Potion Shop': _dark_world_shop_defaults, 'Dark World Potion Shop': _dark_world_shop_defaults,
@@ -406,236 +406,236 @@ default_shop_contents = {
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
} }
location_table = {'Mushroom': (0x180013, False, 'in the woods'), location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
'Bottle Merchant': (0x2EB18, False, 'with a merchant'), 'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
'Flute Spot': (0x18014A, False, 'underground'), 'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
'Sunken Treasure': (0x180145, False, 'underwater'), 'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
'Purple Chest': (0x33D68, False, 'from a box'), 'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'), "Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'), "Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'), "Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'), "Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'), "Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
'Link\'s Uncle': (0x2DF45, False, 'with your uncle'), "Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
'Secret Passage': (0xE971, False, 'near your uncle'), 'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
'King Zora': (0xEE1C3, False, 'at a high price'), 'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
'Zora\'s Ledge': (0x180149, False, 'near Zora'), "Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'), 'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'), 'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
'King\'s Tomb': (0xE97A, False, 'alone in a cave'), "King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
'Floodgate Chest': (0xE98C, False, 'in the dam'), 'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
'Link\'s House': (0xE9BC, False, 'in your home'), "Link's House": (0xe9bc, 0x186190, False, 'in your home'),
'Kakariko Tavern': (0xE9CE, False, 'in the bar'), 'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
'Chicken House': (0xE9E9, False, 'near poultry'), 'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'), "Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'), "Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'), "Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'), "Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
'Sahasrahla': (0x2F1FC, False, 'with the elder'), 'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
'Kakariko Well - Top': (0xEA8E, False, 'in a well'), 'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
'Kakariko Well - Left': (0xEA91, False, 'in a well'), 'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
'Kakariko Well - Middle': (0xEA94, False, 'in a well'), 'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
'Kakariko Well - Right': (0xEA97, False, 'in a well'), 'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'), 'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
'Blacksmith': (0x18002A, False, 'with the smith'), 'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
'Magic Bat': (0x180015, False, 'with the bat'), 'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
'Sick Kid': (0x339CF, False, 'with the sick'), 'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
'Hobo': (0x33E7D, False, 'with the hobo'), 'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
'Lost Woods Hideout': (0x180000, False, 'near a thief'), 'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
'Lumberjack Tree': (0x180001, False, 'in a hole'), 'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
'Cave 45': (0x180003, False, 'alone in a cave'), 'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
'Graveyard Cave': (0x180004, False, 'alone in a cave'), 'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
'Checkerboard Cave': (0x180005, False, 'alone in a cave'), 'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'), 'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'), 'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'), 'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'), 'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'), 'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'), 'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'), 'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
'Library': (0x180012, False, 'near books'), 'Library': (0x180012, 0x18635c, False, 'near books'),
'Potion Shop': (0x180014, False, 'near potions'), 'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
'Lake Hylia Island': (0x180144, False, 'on an island'), 'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
'Maze Race': (0x180142, False, 'at the race'), 'Maze Race': (0x180142, 0x186351, False, 'at the race'),
'Desert Ledge': (0x180143, False, 'in the desert'), 'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'), 'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'), 'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'), 'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'), 'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'), 'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'), 'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'), 'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'), 'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'), 'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'), 'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'), 'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'), 'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'), 'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'), 'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'), 'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'), "Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'), 'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'), 'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'), 'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'), 'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
'Sanctuary': (0xEA79, False, 'in Sanctuary'), 'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'), 'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'), 'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
'Old Man': (0xF69FA, False, 'with the old man'), 'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'), 'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'), 'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'), 'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
'Spiral Cave': (0xE9BF, False, 'in spiral cave'), 'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
'Ether Tablet': (0x180016, False, 'at a monolith'), 'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
'Spectacle Rock': (0x180140, False, 'atop a rock'), 'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'), 'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'), 'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'), 'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'), 'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'), 'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'), 'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
'Pyramid': (0x180147, False, 'on the pyramid'), 'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
'Catfish': (0xEE185, False, 'with a catfish'), 'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
'Stumpy': (0x330C7, False, 'with tree boy'), 'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
'Digging Game': (0x180148, False, 'underground'), 'Digging Game': (0x180148, 0x186357, False, 'underground'),
'Bombos Tablet': (0x180017, False, 'at a monolith'), 'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'), 'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'), 'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'), 'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'), 'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'), 'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
'Peg Cave': (0x180006, False, 'alone in a cave'), 'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'), 'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'), 'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
'Brewery': (0xE9EC, False, 'alone in a home'), 'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
'C-Shaped House': (0xE9EF, False, 'alone in a home'), 'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
'Chest Game': (0xEDA8, False, 'as a prize'), 'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'), 'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
'Mire Shed - Left': (0xEA73, False, 'near sparks'), 'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
'Mire Shed - Right': (0xEA76, False, 'near sparks'), 'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'), 'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'), 'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
'Spike Cave': (0xEA8B, False, 'beyond spikes'), 'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'), 'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'), 'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'), 'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'), 'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
'Floating Island': (0x180141, False, 'on an island'), 'Floating Island': (0x180141, 0x186350, False, 'on an island'),
'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'), 'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'), 'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'), 'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'), 'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'), 'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'), 'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'), 'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'), 'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'), 'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'), 'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'), 'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'), "Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'), "Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'), "Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'), "Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'), "Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'), "Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'), "Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
'Thieves\' Town - Boss': (0x180156, False, 'with Blind'), "Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'), 'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'), 'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'), 'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'), 'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'), 'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'), 'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'), 'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
'Skull Woods - Boss': (0x180155, False, 'with Mothula'), 'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'), 'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'), 'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'), 'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'), 'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'), 'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'), 'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'), 'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'), 'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'), 'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'), 'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'), 'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'), 'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'), 'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'), 'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'), 'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'), 'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'), 'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'), 'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'), 'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'), 'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'), 'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'), 'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'), 'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'), 'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'), 'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'), 'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'), 'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'), 'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'), 'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'), 'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'), 'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'), 'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'), 'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'), 'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'), "Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'), 'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'), 'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'), 'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'), 'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'), 'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'), 'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'), 'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'), 'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'), 'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'), 'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'), 'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'), 'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'), 'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'), 'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'), 'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'), 'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'), 'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'), "Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'), 'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'), 'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'), 'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'), 'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'), 'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'), 'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'), 'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'), 'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
'Ganon': (None, False, 'from me'), 'Ganon': (None, None, False, 'from me'),
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'), 'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'), 'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
'Floodgate': (None, False, None), 'Floodgate': (None, None, False, None),
'Frog': (None, False, None), 'Frog': (None, None, False, None),
'Missing Smith': (None, False, None), 'Missing Smith': (None, None, False, None),
'Dark Blacksmith Ruins': (None, False, None), 'Dark Blacksmith Ruins': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'), 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'), 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'), 'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'), 'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'), 'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'), 'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'), 'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'), 'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'), 'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')} 'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
+93 -44
View File
@@ -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)
@@ -177,38 +174,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[69], 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)]
@@ -217,7 +258,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)
@@ -233,7 +274,7 @@ 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)
@@ -275,7 +316,7 @@ def set_up_take_anys(world, player):
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:
@@ -312,9 +353,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) 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
@@ -330,13 +371,13 @@ def set_up_shops(world, player):
for shop in world.shops: for shop in world.shops:
shop.active = True shop.active = True
if world.retro: if world.retro[player]:
rss = world.get_region('Red Shield Shop', player).shop rss = world.get_region('Red Shield Shop', player).shop
rss.active = True rss.active = True
rss.add_inventory(2, 'Single Arrow', 80) rss.add_inventory(2, 'Single Arrow', 80)
# Randomized changes to Shops # Randomized changes to Shops
if world.retro: if world.retro[player]:
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): 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.active = True
shop.add_inventory(0, 'Single Arrow', 80) shop.add_inventory(0, 'Single Arrow', 80)
@@ -347,7 +388,7 @@ def set_up_shops(world, player):
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
@@ -355,6 +396,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'
@@ -367,8 +412,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'])
@@ -429,13 +474,13 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
swords_to_use.extend(['Fighter Sword']) 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(): if want_progressives():
pool.extend(diff.progressivesword) pool.extend(diff.progressivesword)
@@ -466,7 +511,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]
@@ -475,19 +520,23 @@ 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, 66): for x in range(0, 66):
if customitemarray[x] > total_items_to_place: if customitemarray[x] > total_items_to_place:
@@ -594,13 +643,13 @@ 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[70] - 1), 0)) pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
else: else:
pool.extend(['Small Key (Universal)'] * customitemarray[70]) pool.extend(['Small Key (Universal)'] * customitemarray[70])
@@ -611,8 +660,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
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:
+6 -5
View File
@@ -161,11 +161,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),
+115 -61
View File
@@ -4,6 +4,7 @@ from itertools import zip_longest
import json import json
import logging import logging
import os import os
import pickle
import random import random
import time import time
@@ -11,20 +12,27 @@ from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from Regions import create_regions, mark_light_world_regions from Regions import create_regions, 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, get_enemizer_patch, apply_rom_settings, Sprite, LocalRom, JsonRom from Rom import patch_rom, get_race_rom_patches, get_enemizer_patch, apply_rom_settings, Sprite, LocalRom, JsonRom
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from 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.6.3-pre' __version__ = '0.6.3-pre'
def main(args, seed=None): def main(args, seed=None):
if args.outputpath:
try:
os.mkdir(args.outputpath)
except OSError:
pass
output_path.cached_path = args.outputpath
start = time.perf_counter() start = time.perf_counter()
# initialize the world # initialize the world
world = World(args.multi, args.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.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, 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)
@@ -33,36 +41,44 @@ 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 Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed) logger.info('ALttP Entrance Randomizer Version %s - Seed: %s\n\n', __version__, world.seed)
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
if world.mode[player] != 'inverted':
create_regions(world, player) create_regions(world, player)
create_dungeons(world, player)
else: else:
for player in range(1, world.players + 1):
create_inverted_regions(world, player) create_inverted_regions(world, player)
create_dungeons(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)
mark_light_world_regions(world, player)
mark_light_world_regions(world)
else: else:
for player in range(1, world.players + 1):
link_inverted_entrances(world, player) link_inverted_entrances(world, player)
mark_dark_world_regions(world, player)
mark_dark_world_regions(world)
logger.info('Generating Item Pool.') logger.info('Generating Item Pool.')
@@ -81,7 +97,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)
@@ -99,12 +116,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.')
@@ -120,16 +137,19 @@ def main(args, seed=None):
else: else:
sprite = None sprite = None
outfilebase = 'ER_%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, "-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) player_names = parse_names_string(args.names)
outfilebase = 'ER_%s' % (args.outputname if args.outputname 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)
jsonout = {} jsonout = {}
if not args.suppress_rom: if not args.suppress_rom:
if world.players > 1: from MultiServer import MultiWorld
raise NotImplementedError("Multiworld rom writes have not been implemented") multidata = MultiWorld()
else: multidata.players = world.players
player = 1
for player in range(1, world.players + 1):
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none'
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
or args.shufflepalette[player] or args.shufflepots[player])
local_rom = None local_rom = None
if args.jsonout: if args.jsonout:
@@ -140,24 +160,56 @@ def main(args, seed=None):
rom = JsonRom() rom = JsonRom()
else: else:
rom = LocalRom(args.rom) rom = LocalRom(args.rom)
patch_rom(world, player, rom, use_enemizer)
patch_rom(world, player, rom)
enemizer_patch = [] enemizer_patch = []
if use_enemizer: if use_enemizer and (args.enemizercli or not args.jsonout):
enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shuffleenemies, args.enemy_health, args.enemy_damage, args.shufflepalette, args.shufflepots) enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepalette[player], args.shufflepots[player])
multidata.rom_names[player] = list(rom.name)
for location in world.get_filled_locations(player):
if type(location.address) is int:
multidata.locations[(location.address, player)] = (location.item.code, location.item.player)
if args.jsonout: if args.jsonout:
jsonout['patch'] = rom.patches jsonout[f'patch{player}'] = rom.patches
if use_enemizer: if use_enemizer:
jsonout['enemizer' % player] = enemizer_patch jsonout[f'enemizer{player}'] = enemizer_patch
if args.race:
jsonout[f'race{player}'] = get_race_rom_patches(rom)
else: else:
if use_enemizer: if use_enemizer:
local_rom.patch_enemizer(rom.patches, os.path.join(os.path.dirname(args.enemizercli), "enemizerBasePatch.json"), enemizer_patch) local_rom.patch_enemizer(rom.patches, os.path.join(os.path.dirname(args.enemizercli), "enemizerBasePatch.json"), enemizer_patch)
rom = local_rom rom = local_rom
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite) if args.race:
rom.write_to_file(output_path('%s.sfc' % outfilebase)) for addr, values in get_race_rom_patches(rom).items():
rom.write_bytes(int(addr), values)
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, player_names)
mcsb_name = ''
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
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 '')
playername = f"{f'_P{player}' if world.players > 1 else ''}{f'_{player_names[player]}' if player in player_names else ''}"
outfilesuffix = ('_%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.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'))
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
pickle.dump(multidata, f, pickle.HIGHEST_PROTOCOL)
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))
@@ -176,42 +228,45 @@ def main(args, seed=None):
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.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.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, 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_dungeons(ret, player)
@@ -263,8 +318,8 @@ 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
@@ -292,7 +347,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
@@ -303,7 +358,6 @@ 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)
sphere = [] sphere = []
@@ -323,9 +377,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 +412,6 @@ 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)
sphere = list(filter(state.can_reach, required_locations)) sphere = list(filter(state.can_reach, required_locations))
@@ -393,7 +447,7 @@ 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))
+921
View File
@@ -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 = 'ws://' + 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()
+393
View File
@@ -0,0 +1,393 @@
import aioconsole
import argparse
import asyncio
import functools
import json
import logging
import pickle
import re
import urllib.request
import websockets
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 MultiWorld:
def __init__(self):
self.players = None
self.rom_names = {}
self.locations = {}
class Context:
def __init__(self, host, port, password):
self.data_filename = None
self.save_filename = None
self.disable_save = False
self.world = MultiWorld()
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.world.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.world.locations:
target_item, target_player = ctx.world.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:
pickle.dump((ctx.world.players, ctx.world.rom_names, ctx.received_items), f, pickle.HIGHEST_PROTOCOL)
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.world.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.world.players:
errors.add('SlotAlreadyTaken')
elif args['slot'] not in range(1, ctx.world.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.world.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:
ctx.world = pickle.load(f)
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.world.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:
players, rom_names, received_items = pickle.load(f)
if players != ctx.world.players or rom_names != ctx.world.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()
+207
View File
@@ -0,0 +1,207 @@
import argparse
import logging
import random
import urllib.request
import urllib.parse
from EntranceRandomizer import parse_arguments
from Main import main as ERmain
def parse_yaml(txt):
ret = {}
indents = {len(txt) - len(txt.lstrip(' ')): ret}
for line in txt.splitlines():
if not line:
continue
name, val = line.split(':', 1)
val = val.strip()
spaces = len(name) - len(name.lstrip(' '))
name = name.strip()
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
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():
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)
ERmain(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):
return random.choices(list(weights[option].keys()), weights=list(map(int,weights[option].values())))[0].replace('"','').replace("'",'')
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
if {'map_shuffle', 'compass_shuffle', 'smallkey_shuffle', 'bigkey_shuffle'}.issubset(weights.keys()):
ret.mapshuffle = get_choice('map_shuffle') == 'on'
ret.compassshuffle = get_choice('compass_shuffle') == 'on'
ret.keyshuffle = get_choice('smallkey_shuffle') == 'on'
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on'
else:
dungeon_items = get_choice('dungeon_items')
ret.mapshuffle = dungeon_items in ['mc', 'mcs', 'full']
ret.compassshuffle = dungeon_items in ['mc', 'mcs', 'full']
ret.keyshuffle = dungeon_items in ['mcs', 'full']
ret.bigkeyshuffle = dungeon_items in ['full']
accessibility = get_choice('accessibility')
ret.accessibility = accessibility
entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
goals = get_choice('goals')
ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals',
'dungeons': 'dungeons',
'pedestal': 'pedestal',
'triforce-hunt': 'triforcehunt'
}[goals]
ret.openpyramid = goals == 'fast_ganon'
tower_open = get_choice('tower_open')
ret.crystals_gt = tower_open
ganon_open = get_choice('ganon_open')
ret.crystals_ganon = ganon_open
world_state = get_choice('world_state')
ret.mode = world_state
if world_state == 'retro':
ret.mode = 'open'
ret.retro = True
hints = get_choice('hints')
ret.hints = hints == 'on'
weapons = get_choice('weapons')
ret.swords = {'randomized': 'random',
'assured': 'assured',
'vanilla': 'vanilla',
'swordless': 'swordless'
}[weapons]
item_pool = get_choice('item_pool')
ret.difficulty = item_pool
item_functionality = get_choice('item_functionality')
ret.item_functionality = item_functionality
boss_shuffle = get_choice('boss_shuffle')
ret.shufflebosses = {'none': 'none',
'simple': 'basic',
'full': 'normal',
'random': 'chaos'
}[boss_shuffle]
enemy_shuffle = get_choice('enemy_shuffle')
ret.shuffleenemies = {'none': 'none',
'shuffled': 'shuffled',
'random': 'chaos'
}[enemy_shuffle]
enemy_damage = get_choice('enemy_damage')
ret.enemy_damage = {'default': 'default',
'shuffled': 'shuffled',
'random': 'chaos'
}[enemy_damage]
enemy_health = get_choice('enemy_health')
ret.enemy_health = enemy_health
ret.beemizer = int(get_choice('beemizer')) if 'beemizer' in weights.keys() else 1 # suck it :)
return ret
if __name__ == '__main__':
main()
+15 -13
View File
@@ -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, Sprite, write_string_to_rom, apply_rom_settings
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.perf_counter() start_time = time.perf_counter()
# 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, args.quickswap, args.fastmenu, args.disablemusic, 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)
@@ -74,7 +74,9 @@ def main(args):
sprite = None 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)
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite)
for textname, texttype, text in text_patches: for textname, texttype, text in text_patches:
if texttype == 'text': if texttype == 'text':
@@ -114,16 +116,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 +134,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 +174,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)
+8 -15
View File
@@ -121,7 +121,7 @@ Does not invoke a timer.
### Display ### Display
Displays a timer on-screen but does not alter the item pool. Displays a timer on-screen but does not alter the item pool.
This will prevent the dungeon item count feature in Easy and Keysanity from working. This will prevent the dungeon item count feature in Easy and Compass shuffle from working.
### Timed ### Timed
@@ -264,12 +264,12 @@ generate spoilers for statistical analysis.
Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair. Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair.
## Keysanity ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon.
in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that is traditionally
is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but the rest
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell 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. for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
## Retro ## Retro
@@ -422,10 +422,10 @@ Alters the rate at which the menu opens and closes. (default: normal)
Disables game music, resulting in the game sound being just the SFX. (default: False) Disables game music, resulting in the game sound being just the SFX. (default: False)
``` ```
--keysanity --mapshuffle --compassshuffle --keyshuffle --bigkeyshuffle
``` ```
Enable Keysanity (default: False) Respectively enable Map/Compass/SmallKey/BigKey shuffle (default: False)
``` ```
--retro --retro
@@ -433,13 +433,6 @@ Enable Keysanity (default: False)
Enable Retro mode (default: False) Enable Retro mode (default: False)
```
--nodungeonitems
```
If set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
This may lead to different amount of itempool items being placed in a dungeon than you are used to. (default: False)
``` ```
--heartbeep [{normal,half,quarter,off}] --heartbeep [{normal,half,quarter,off}]
``` ```
+239 -239
View File
@@ -307,7 +307,7 @@ def create_regions(world, player):
world.shops.append(shop) world.shops.append(shop)
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7) shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7) shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7)
world.intialize_regions() world.initialize_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)
@@ -331,14 +331,14 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
for exit in exits: for exit in exits:
ret.exits.append(Entrance(player, exit, ret)) ret.exits.append(Entrance(player, exit, ret))
for location in locations: for location in locations:
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, 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()
@@ -351,7 +351,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()
@@ -397,236 +397,236 @@ default_shop_contents = {
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
} }
location_table = {'Mushroom': (0x180013, False, 'in the woods'), location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
'Bottle Merchant': (0x2eb18, False, 'with a merchant'), 'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
'Flute Spot': (0x18014a, False, 'underground'), 'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
'Sunken Treasure': (0x180145, False, 'underwater'), 'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
'Purple Chest': (0x33d68, False, 'from a box'), 'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
"Blind's Hideout - Top": (0xeb0f, False, 'in a basement'), "Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
"Blind's Hideout - Left": (0xeb12, False, 'in a basement'), "Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
"Blind's Hideout - Right": (0xeb15, False, 'in a basement'), "Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
"Blind's Hideout - Far Left": (0xeb18, False, 'in a basement'), "Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
"Blind's Hideout - Far Right": (0xeb1b, False, 'in a basement'), "Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
"Link's Uncle": (0x2df45, False, 'with your uncle'), "Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
'Secret Passage': (0xe971, False, 'near your uncle'), 'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
'King Zora': (0xee1c3, False, 'at a high price'), 'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
"Zora's Ledge": (0x180149, False, 'near Zora'), "Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
'Waterfall Fairy - Left': (0xe9b0, False, 'near a fairy'), 'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
'Waterfall Fairy - Right': (0xe9d1, False, 'near a fairy'), 'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
"King's Tomb": (0xe97a, False, 'alone in a cave'), "King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
'Floodgate Chest': (0xe98c, False, 'in the dam'), 'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
"Link's House": (0xe9bc, False, 'in your home'), "Link's House": (0xe9bc, 0x186190, False, 'in your home'),
'Kakariko Tavern': (0xe9ce, False, 'in the bar'), 'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
'Chicken House': (0xe9e9, False, 'near poultry'), 'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
"Aginah's Cave": (0xe9f2, False, 'with Aginah'), "Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
"Sahasrahla's Hut - Left": (0xea82, False, 'near the elder'), "Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
"Sahasrahla's Hut - Middle": (0xea85, False, 'near the elder'), "Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
"Sahasrahla's Hut - Right": (0xea88, False, 'near the elder'), "Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
'Sahasrahla': (0x2f1fc, False, 'with the elder'), 'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
'Kakariko Well - Top': (0xea8e, False, 'in a well'), 'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
'Kakariko Well - Left': (0xea91, False, 'in a well'), 'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
'Kakariko Well - Middle': (0xea94, False, 'in a well'), 'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
'Kakariko Well - Right': (0xea97, False, 'in a well'), 'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
'Kakariko Well - Bottom': (0xea9a, False, 'in a well'), 'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
'Blacksmith': (0x18002a, False, 'with the smith'), 'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
'Magic Bat': (0x180015, False, 'with the bat'), 'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
'Sick Kid': (0x339cf, False, 'with the sick'), 'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
'Hobo': (0x33e7d, False, 'with the hobo'), 'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
'Lost Woods Hideout': (0x180000, False, 'near a thief'), 'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
'Lumberjack Tree': (0x180001, False, 'in a hole'), 'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
'Cave 45': (0x180003, False, 'alone in a cave'), 'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
'Graveyard Cave': (0x180004, False, 'alone in a cave'), 'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
'Checkerboard Cave': (0x180005, False, 'alone in a cave'), 'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
'Mini Moldorm Cave - Far Left': (0xeb42, False, 'near Moldorms'), 'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
'Mini Moldorm Cave - Left': (0xeb45, False, 'near Moldorms'), 'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
'Mini Moldorm Cave - Right': (0xeb48, False, 'near Moldorms'), 'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
'Mini Moldorm Cave - Far Right': (0xeb4b, False, 'near Moldorms'), 'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'), 'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
'Ice Rod Cave': (0xeb4e, False, 'in a frozen cave'), 'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
'Bonk Rock Cave': (0xeb3f, False, 'alone in a cave'), 'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
'Library': (0x180012, False, 'near books'), 'Library': (0x180012, 0x18635c, False, 'near books'),
'Potion Shop': (0x180014, False, 'near potions'), 'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
'Lake Hylia Island': (0x180144, False, 'on an island'), 'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
'Maze Race': (0x180142, False, 'at the race'), 'Maze Race': (0x180142, 0x186351, False, 'at the race'),
'Desert Ledge': (0x180143, False, 'in the desert'), 'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
'Desert Palace - Big Chest': (0xe98f, False, 'in Desert Palace'), 'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'), 'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
'Desert Palace - Map Chest': (0xe9b6, False, 'in Desert Palace'), 'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
'Desert Palace - Compass Chest': (0xe9cb, False, 'in Desert Palace'), 'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
'Desert Palace - Big Key Chest': (0xe9c2, False, 'in Desert Palace'), 'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'), 'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
'Eastern Palace - Compass Chest': (0xe977, False, 'in Eastern Palace'), 'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
'Eastern Palace - Big Chest': (0xe97d, False, 'in Eastern Palace'), 'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
'Eastern Palace - Cannonball Chest': (0xe9b3, False, 'in Eastern Palace'), 'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
'Eastern Palace - Big Key Chest': (0xe9b9, False, 'in Eastern Palace'), 'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
'Eastern Palace - Map Chest': (0xe9f5, False, 'in Eastern Palace'), 'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'), 'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
'Master Sword Pedestal': (0x289b0, False, 'at the pedestal'), 'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
'Hyrule Castle - Boomerang Chest': (0xe974, False, 'in Hyrule Castle'), 'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
'Hyrule Castle - Map Chest': (0xeb0c, False, 'in Hyrule Castle'), 'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
"Hyrule Castle - Zelda's Chest": (0xeb09, False, 'in Hyrule Castle'), "Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
'Sewers - Dark Cross': (0xe96e, False, 'in the sewers'), 'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
'Sewers - Secret Room - Left': (0xeb5d, False, 'in the sewers'), 'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
'Sewers - Secret Room - Middle': (0xeb60, False, 'in the sewers'), 'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
'Sewers - Secret Room - Right': (0xeb63, False, 'in the sewers'), 'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
'Sanctuary': (0xea79, False, 'in Sanctuary'), 'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
'Castle Tower - Room 03': (0xeab5, False, 'in Castle Tower'), 'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
'Castle Tower - Dark Maze': (0xeab2, False, 'in Castle Tower'), 'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
'Old Man': (0xf69fa, False, 'with the old man'), 'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'), 'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
'Paradox Cave Lower - Far Left': (0xeb2a, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Left': (0xeb2d, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Right': (0xeb30, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Far Right': (0xeb33, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Middle': (0xeb36, False, 'in a cave with seven chests'), 'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Left': (0xeb39, False, 'in a cave with seven chests'), 'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Right': (0xeb3c, False, 'in a cave with seven chests'), 'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
'Spiral Cave': (0xe9bf, False, 'in spiral cave'), 'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
'Ether Tablet': (0x180016, False, 'at a monolith'), 'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
'Spectacle Rock': (0x180140, False, 'atop a rock'), 'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'), 'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
'Tower of Hera - Map Chest': (0xe9ad, False, 'in Tower of Hera'), 'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
'Tower of Hera - Big Key Chest': (0xe9e6, False, 'in Tower of Hera'), 'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
'Tower of Hera - Compass Chest': (0xe9fb, False, 'in Tower of Hera'), 'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
'Tower of Hera - Big Chest': (0xe9f8, False, 'in Tower of Hera'), 'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'), 'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
'Pyramid': (0x180147, False, 'on the pyramid'), 'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
'Catfish': (0xee185, False, 'with a catfish'), 'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
'Stumpy': (0x330c7, False, 'with tree boy'), 'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
'Digging Game': (0x180148, False, 'underground'), 'Digging Game': (0x180148, 0x186357, False, 'underground'),
'Bombos Tablet': (0x180017, False, 'at a monolith'), 'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
'Hype Cave - Top': (0xeb1e, False, 'near a bat-like man'), 'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
'Hype Cave - Middle Right': (0xeb21, False, 'near a bat-like man'), 'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
'Hype Cave - Middle Left': (0xeb24, False, 'near a bat-like man'), 'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
'Hype Cave - Bottom': (0xeb27, False, 'near a bat-like man'), 'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'), 'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
'Peg Cave': (0x180006, False, 'alone in a cave'), 'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
'Pyramid Fairy - Left': (0xe980, False, 'near a fairy'), 'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
'Pyramid Fairy - Right': (0xe983, False, 'near a fairy'), 'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
'Brewery': (0xe9ec, False, 'alone in a home'), 'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
'C-Shaped House': (0xe9ef, False, 'alone in a home'), 'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
'Chest Game': (0xeda8, False, 'as a prize'), 'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'), 'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
'Mire Shed - Left': (0xea73, False, 'near sparks'), 'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
'Mire Shed - Right': (0xea76, False, 'near sparks'), 'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
'Superbunny Cave - Top': (0xea7c, False, 'in a connection'), 'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
'Superbunny Cave - Bottom': (0xea7f, False, 'in a connection'), 'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
'Spike Cave': (0xea8b, False, 'beyond spikes'), 'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
'Hookshot Cave - Top Right': (0xeb51, False, 'across pits'), 'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
'Hookshot Cave - Top Left': (0xeb54, False, 'across pits'), 'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
'Hookshot Cave - Bottom Right': (0xeb5a, False, 'across pits'), 'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
'Hookshot Cave - Bottom Left': (0xeb57, False, 'across pits'), 'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
'Floating Island': (0x180141, False, 'on an island'), 'Floating Island': (0x180141, 0x186350, False, 'on an island'),
'Mimic Cave': (0xe9c5, False, 'in a cave of mimicry'), 'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
'Swamp Palace - Entrance': (0xea9d, False, 'in Swamp Palace'), 'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
'Swamp Palace - Map Chest': (0xe986, False, 'in Swamp Palace'), 'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
'Swamp Palace - Big Chest': (0xe989, False, 'in Swamp Palace'), 'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
'Swamp Palace - Compass Chest': (0xeaa0, False, 'in Swamp Palace'), 'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
'Swamp Palace - Big Key Chest': (0xeaa6, False, 'in Swamp Palace'), 'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
'Swamp Palace - West Chest': (0xeaa3, False, 'in Swamp Palace'), 'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Left': (0xeaa9, False, 'in Swamp Palace'), 'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Right': (0xeaac, False, 'in Swamp Palace'), 'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
'Swamp Palace - Waterfall Room': (0xeaaf, False, 'in Swamp Palace'), 'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'), 'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
"Thieves' Town - Big Key Chest": (0xea04, False, "in Thieves' Town"), "Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
"Thieves' Town - Map Chest": (0xea01, False, "in Thieves' Town"), "Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
"Thieves' Town - Compass Chest": (0xea07, False, "in Thieves' Town"), "Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
"Thieves' Town - Ambush Chest": (0xea0a, False, "in Thieves' Town"), "Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
"Thieves' Town - Attic": (0xea0d, False, "in Thieves' Town"), "Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
"Thieves' Town - Big Chest": (0xea10, False, "in Thieves' Town"), "Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
"Thieves' Town - Blind's Cell": (0xea13, False, "in Thieves' Town"), "Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
"Thieves' Town - Boss": (0x180156, False, 'with Blind'), "Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
'Skull Woods - Compass Chest': (0xe992, False, 'in Skull Woods'), 'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
'Skull Woods - Map Chest': (0xe99b, False, 'in Skull Woods'), 'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
'Skull Woods - Big Chest': (0xe998, False, 'in Skull Woods'), 'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
'Skull Woods - Pot Prison': (0xe9a1, False, 'in Skull Woods'), 'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
'Skull Woods - Pinball Room': (0xe9c8, False, 'in Skull Woods'), 'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
'Skull Woods - Big Key Chest': (0xe99e, False, 'in Skull Woods'), 'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
'Skull Woods - Bridge Room': (0xe9fe, False, 'near Mothula'), 'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
'Skull Woods - Boss': (0x180155, False, 'with Mothula'), 'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
'Ice Palace - Compass Chest': (0xe9d4, False, 'in Ice Palace'), 'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
'Ice Palace - Freezor Chest': (0xe995, False, 'in Ice Palace'), 'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
'Ice Palace - Big Chest': (0xe9aa, False, 'in Ice Palace'), 'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
'Ice Palace - Iced T Room': (0xe9e3, False, 'in Ice Palace'), 'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
'Ice Palace - Spike Room': (0xe9e0, False, 'in Ice Palace'), 'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
'Ice Palace - Big Key Chest': (0xe9a4, False, 'in Ice Palace'), 'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
'Ice Palace - Map Chest': (0xe9dd, False, 'in Ice Palace'), 'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'), 'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
'Misery Mire - Big Chest': (0xea67, False, 'in Misery Mire'), 'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
'Misery Mire - Map Chest': (0xea6a, False, 'in Misery Mire'), 'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
'Misery Mire - Main Lobby': (0xea5e, False, 'in Misery Mire'), 'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
'Misery Mire - Bridge Chest': (0xea61, False, 'in Misery Mire'), 'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
'Misery Mire - Spike Chest': (0xe9da, False, 'in Misery Mire'), 'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
'Misery Mire - Compass Chest': (0xea64, False, 'in Misery Mire'), 'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
'Misery Mire - Big Key Chest': (0xea6d, False, 'in Misery Mire'), 'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'), 'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
'Turtle Rock - Compass Chest': (0xea22, False, 'in Turtle Rock'), 'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Left': (0xea1c, False, 'in Turtle Rock'), 'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Right': (0xea1f, False, 'in Turtle Rock'), 'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
'Turtle Rock - Chain Chomps': (0xea16, False, 'in Turtle Rock'), 'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
'Turtle Rock - Big Key Chest': (0xea25, False, 'in Turtle Rock'), 'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
'Turtle Rock - Big Chest': (0xea19, False, 'in Turtle Rock'), 'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
'Turtle Rock - Crystaroller Room': (0xea34, False, 'in Turtle Rock'), 'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Right': (0xea28, False, 'in Turtle Rock'), 'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'), 'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
'Palace of Darkness - Shooter Room': (0xea5b, False, 'in Palace of Darkness'), 'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Bridge': (0xea3d, False, 'in Palace of Darkness'), 'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
'Palace of Darkness - Stalfos Basement': (0xea49, False, 'in Palace of Darkness'), 'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Key Chest': (0xea37, False, 'in Palace of Darkness'), 'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Ledge': (0xea3a, False, 'in Palace of Darkness'), 'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
'Palace of Darkness - Map Chest': (0xea52, False, 'in Palace of Darkness'), 'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
'Palace of Darkness - Compass Chest': (0xea43, False, 'in Palace of Darkness'), 'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Left': (0xea4c, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Right': (0xea4f, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Top': (0xea55, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Bottom': (0xea58, False, 'in Palace of Darkness'), 'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Chest': (0xea40, False, 'in Palace of Darkness'), 'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
'Palace of Darkness - Harmless Hellway': (0xea46, False, 'in Palace of Darkness'), 'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'), 'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
"Ganons Tower - Bob's Torch": (0x180161, False, "in Ganon's Tower"), "Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Left': (0xead9, False, "in Ganon's Tower"), 'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
'Ganons Tower - Hope Room - Right': (0xeadc, False, "in Ganon's Tower"), 'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
'Ganons Tower - Tile Room': (0xeae2, False, "in Ganon's Tower"), 'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Left': (0xeae5, False, "in Ganon's Tower"), 'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Top Right': (0xeae8, False, "in Ganon's Tower"), 'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, False, "in Ganon's Tower"), 'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, False, "in Ganon's Tower"), 'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Left': (0xeab8, False, "in Ganon's Tower"), 'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Top Right': (0xeabb, False, "in Ganon's Tower"), 'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, False, "in Ganon's Tower"), 'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, False, "in Ganon's Tower"), 'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
'Ganons Tower - Map Chest': (0xead3, False, "in Ganon's Tower"), 'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
'Ganons Tower - Firesnake Room': (0xead0, False, "in Ganon's Tower"), 'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, False, "in Ganon's Tower"), 'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, False, "in Ganon's Tower"), 'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, False, "in Ganon's Tower"), 'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, False, "in Ganon's Tower"), 'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
"Ganons Tower - Bob's Chest": (0xeadf, False, "in Ganon's Tower"), "Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
'Ganons Tower - Big Chest': (0xead6, False, "in Ganon's Tower"), 'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Left': (0xeaf4, False, "in Ganon's Tower"), 'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Room - Right': (0xeaf7, False, "in Ganon's Tower"), 'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
'Ganons Tower - Big Key Chest': (0xeaf1, False, "in Ganon's Tower"), 'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, False, "atop Ganon's Tower"), 'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, False, "atop Ganon's Tower"), 'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, False, "atop Ganon's Tower"), 'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
'Ganons Tower - Validation Chest': (0xeb06, False, "atop Ganon's Tower"), 'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
'Ganon': (None, False, 'from me'), 'Ganon': (None, None, False, 'from me'),
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'), 'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'), 'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
'Floodgate': (None, False, None), 'Floodgate': (None, None, False, None),
'Frog': (None, False, None), 'Frog': (None, None, False, None),
'Missing Smith': (None, False, None), 'Missing Smith': (None, None, False, None),
'Dark Blacksmith Ruins': (None, False, None), 'Dark Blacksmith Ruins': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'), 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'), 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'), 'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'), 'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'), 'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'), 'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'), 'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'), 'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'), 'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')} 'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
+499 -315
View File
File diff suppressed because it is too large Load Diff
+61 -83
View File
@@ -5,9 +5,9 @@ from BaseClasses import CollectionState
def set_rules(world, player): def set_rules(world, player):
if world.logic == 'nologic': if world.logic[player] == 'nologic':
logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!') logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
if world.mode != 'inverted': if world.mode[player] != 'inverted':
world.get_region('Links House', player).can_reach_private = lambda state: True world.get_region('Links House', player).can_reach_private = lambda state: True
world.get_region('Sanctuary', player).can_reach_private = lambda state: True world.get_region('Sanctuary', player).can_reach_private = lambda state: True
old_rule = world.get_region('Old Man House', player).can_reach old_rule = world.get_region('Old Man House', player).can_reach
@@ -16,40 +16,41 @@ def set_rules(world, player):
else: else:
world.get_region('Inverted Links House', player).can_reach_private = lambda state: True world.get_region('Inverted Links House', player).can_reach_private = lambda state: True
world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True
if world.shuffle != 'vanilla': if world.shuffle[player] != 'vanilla':
old_rule = world.get_region('Old Man House', player).can_reach old_rule = world.get_region('Old Man House', player).can_reach
world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
world.get_region('Hyrule Castle Ledge', player).can_reach_private = lambda state: True
return return
global_rules(world, player) global_rules(world, player)
if world.mode != 'inverted': if world.mode[player] != 'inverted':
default_rules(world, player) default_rules(world, player)
if world.mode == 'open': if world.mode[player] == 'open':
open_rules(world, player) open_rules(world, player)
elif world.mode == 'standard': elif world.mode[player] == 'standard':
standard_rules(world, player) standard_rules(world, player)
elif world.mode == 'inverted': elif world.mode[player] == 'inverted':
open_rules(world, player) open_rules(world, player)
inverted_rules(world, player) inverted_rules(world, player)
else: else:
raise NotImplementedError('Not implemented yet') raise NotImplementedError('Not implemented yet')
if world.logic == 'noglitches': if world.logic[player] == 'noglitches':
no_glitches_rules(world, player) no_glitches_rules(world, player)
elif world.logic == 'minorglitches': elif world.logic[player] == 'minorglitches':
logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.') logging.getLogger('').info('Minor Glitches may be buggy still. No guarantee for proper logic checks.')
else: else:
raise NotImplementedError('Not implemented yet') raise NotImplementedError('Not implemented yet')
if world.goal == 'dungeons': if world.goal[player] == 'dungeons':
# require all dungeons to beat ganon # require all dungeons to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player)) add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and state.has_crystals(7, player))
elif world.goal == 'ganon': elif world.goal[player] == 'ganon':
# require aga2 to beat ganon # require aga2 to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
if world.mode != 'inverted': if world.mode[player] != 'inverted':
set_big_bomb_rules(world, player) set_big_bomb_rules(world, player)
else: else:
set_inverted_big_bomb_rules(world, player) set_inverted_big_bomb_rules(world, player)
@@ -58,7 +59,7 @@ def set_rules(world, player):
if not world.swamp_patch_required[player]: if not world.swamp_patch_required[player]:
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has_Mirror(player)) add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has_Mirror(player))
if world.mode != 'inverted': if world.mode[player] != 'inverted':
set_bunny_rules(world, player) set_bunny_rules(world, player)
else: else:
set_inverted_bunny_rules(world, player) set_inverted_bunny_rules(world, player)
@@ -106,11 +107,6 @@ def item_name(state, location, player):
return (location.item.name, location.item.player) return (location.item.name, location.item.player)
def global_rules(world, player): def global_rules(world, player):
if world.goal == 'triforcehunt':
for location in world.get_locations():
if location.player != player:
forbid_item(location, 'Triforce Piece', player)
# ganon can only carry triforce # ganon can only carry triforce
add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
@@ -146,7 +142,7 @@ def global_rules(world, player):
set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player)) set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player)) set_rule(world.get_entrance('Sewers Door', player), lambda state: state.has_key('Small Key (Escape)', player) or (world.retro[player] and world.mode[player] == 'standard')) # standard retro cannot access the shop
set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player)) set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state.has_key('Small Key (Escape)', player))
set_rule(world.get_entrance('Agahnim 1', player), lambda state: state.has_sword(player) and state.has_key('Small Key (Agahnims Tower)', player, 2)) set_rule(world.get_entrance('Agahnim 1', player), lambda state: state.has_sword(player) and state.has_key('Small Key (Agahnims Tower)', player, 2))
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
@@ -173,7 +169,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) set_always_allow(world.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player)
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
@@ -186,7 +182,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state.has_key('Small Key (Swamp Palace)', player)) set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state.has_key('Small Key (Swamp Palace)', player))
set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player))
set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player) or item_name(state, 'Swamp Palace - Big Chest', player) == ('Big Key (Swamp Palace)', player)) set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player) or item_name(state, 'Swamp Palace - Big Chest', player) == ('Big Key (Swamp Palace)', player))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Swamp Palace - Big Chest', player), lambda state, item: item.name == 'Big Key (Swamp Palace)' and item.player == player) set_always_allow(world.get_location('Swamp Palace - Big Chest', player), lambda state, item: item.name == 'Big Key (Swamp Palace)' and item.player == player)
set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player))
set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player))
@@ -199,7 +195,7 @@ def global_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Thieves\' Town - Prize', player))
set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has_key('Small Key (Thieves Town)', player) or item_name(state, 'Thieves\' Town - Big Chest', player) == ('Small Key (Thieves Town)', player)) and state.has('Hammer', player)) set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has_key('Small Key (Thieves Town)', player) or item_name(state, 'Thieves\' Town - Big Chest', player) == ('Small Key (Thieves Town)', player)) and state.has('Hammer', player))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player and state.has('Hammer', player)) set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player and state.has('Hammer', player))
set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state.has_key('Small Key (Thieves Town)', player)) set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state.has_key('Small Key (Thieves Town)', player))
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
@@ -212,7 +208,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2)) set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 2))
set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) or item_name(state, 'Skull Woods - Big Chest', player) == ('Big Key (Skull Woods)', player)) set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) or item_name(state, 'Skull Woods - Big Chest', player) == ('Big Key (Skull Woods)', player))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Skull Woods - Big Chest', player), lambda state, item: item.name == 'Big Key (Skull Woods)' and item.player == player) set_always_allow(world.get_location('Skull Woods - Big Chest', player), lambda state, item: item.name == 'Big Key (Skull Woods)' and item.player == player)
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and state.has_sword(player)) # sword required for curtain set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and state.has_sword(player)) # sword required for curtain
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
@@ -277,13 +273,13 @@ def global_rules(world, player):
set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 3))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 3)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5))
else: else:
forbid_item(world.get_location('Palace of Darkness - Big Key Chest', player), 'Small Key (Palace of Darkness)', player) forbid_item(world.get_location('Palace of Darkness - Big Key Chest', player), 'Small Key (Palace of Darkness)', player)
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 4))) set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state.has_key('Small Key (Palace of Darkness)', player, 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state.has_key('Small Key (Palace of Darkness)', player, 4)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5)) set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state.has_key('Small Key (Palace of Darkness)', player, 5))
else: else:
forbid_item(world.get_location('Palace of Darkness - Harmless Hellway', player), 'Small Key (Palace of Darkness)', player) forbid_item(world.get_location('Palace of Darkness - Harmless Hellway', player), 'Small Key (Palace of Darkness)', player)
@@ -301,7 +297,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player), ('Small Key (Ganons Tower)', player)] and state.has_key('Small Key (Ganons Tower)', player, 3))) set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state.has_key('Small Key (Ganons Tower)', player, 4) or (item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player), ('Small Key (Ganons Tower)', player)] and state.has_key('Small Key (Ganons Tower)', player, 3)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state.has_key('Small Key (Ganons Tower)', player, 3)) set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state.has_key('Small Key (Ganons Tower)', player, 3))
else: else:
forbid_item(world.get_location('Ganons Tower - Map Chest', player), 'Small Key (Ganons Tower)', player) forbid_item(world.get_location('Ganons Tower - Map Chest', player), 'Small Key (Ganons Tower)', player)
@@ -339,13 +335,13 @@ def global_rules(world, player):
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']: 'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Validation Chest']:
forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player) forbid_item(world.get_location(location, player), 'Big Key (Ganons Tower)', player)
set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon, player) set_rule(world.get_location('Ganon', player), lambda state: state.has_beam_sword(player) and state.has_fire_source(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player)
and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (state.has('Silver Arrows', player) and state.can_shoot_arrows(player)) or state.has('Lamp', player) or state.can_extend_magic(player, 12))) # need to light torch a sufficient amount of times
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop
def default_rules(world, player): def default_rules(world, player):
if world.mode == 'standard': if world.mode[player] == 'standard':
world.get_region('Hyrule Castle Secret Entrance', player).can_reach_private = lambda state: True world.get_region('Hyrule Castle Secret Entrance', player).can_reach_private = lambda state: True
old_rule = world.get_region('Links House', player).can_reach_private old_rule = world.get_region('Links House', player).can_reach_private
world.get_region('Links House', player).can_reach_private = lambda state: state.can_reach('Sanctuary', 'Region', player) or old_rule(state) world.get_region('Links House', player).can_reach_private = lambda state: state.can_reach('Sanctuary', 'Region', player) or old_rule(state)
@@ -455,15 +451,15 @@ def default_rules(world, player):
set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!)
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player])
set_rule(world.get_entrance('Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. set_rule(world.get_entrance('Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic.
if world.swords == 'swordless': if world.swords[player] == 'swordless':
swordless_rules(world, player) swordless_rules(world, player)
set_trock_key_rules(world, player) set_trock_key_rules(world, player)
set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt, player)) set_rule(world.get_entrance('Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
def inverted_rules(world, player): def inverted_rules(world, player):
# s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached
@@ -610,18 +606,18 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.can_flute(player)) set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player)) set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player])
set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic. set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic.
if world.swords == 'swordless': if world.swords[player] == 'swordless':
swordless_rules(world, player) swordless_rules(world, player)
set_trock_key_rules(world, player) set_trock_key_rules(world, player)
set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt, player)) set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
def no_glitches_rules(world, player): def no_glitches_rules(world, player):
if world.mode != 'inverted': if world.mode[player] != 'inverted':
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player)) set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Flippers', player) or state.can_lift_rocks(player))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has('Flippers', player))
@@ -631,10 +627,12 @@ def no_glitches_rules(world, player):
else: else:
set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player))) set_rule(world.get_entrance('Zoras River', player), lambda state: state.has_Pearl(player) and (state.has('Flippers', player) or state.can_lift_rocks(player)))
set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) # can be fake flippered to
set_rule(world.get_entrance('Lake Hylia Island', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player)) set_rule(world.get_entrance('Hobo Bridge', player), lambda state: state.has_Pearl(player) and state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player))) set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Flippers', player) and (state.has('Hammer', player) or state.can_lift_rocks(player)))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Dark Lake Hylia Ledge Drop', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('East Dark World Pier', player), lambda state: state.has('Flippers', player))
add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player)) add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player)) add_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state.has('Hookshot', player))
@@ -671,7 +669,7 @@ def no_glitches_rules(world, player):
add_conditional_lamp('Palace of Darkness Maze Door', 'Palace of Darkness (Entrance)', 'Entrance') add_conditional_lamp('Palace of Darkness Maze Door', 'Palace of Darkness (Entrance)', 'Entrance')
add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'Palace of Darkness (Entrance)', 'Location') add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'Palace of Darkness (Entrance)', 'Location')
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', 'Location') add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', 'Location')
if world.mode != 'inverted': if world.mode[player] != 'inverted':
add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance')
add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower', 'Location') add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower', 'Location')
else: else:
@@ -687,7 +685,7 @@ def no_glitches_rules(world, player):
add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location') add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location')
add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location') add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location')
if not world.sewer_light_cone: if not world.sewer_light_cone[player]:
add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player) add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player)
add_lamp_requirement(world.get_entrance('Sewers Back Door', player), player) add_lamp_requirement(world.get_entrance('Sewers Back Door', player), player)
add_lamp_requirement(world.get_entrance('Throne Room', player), player) add_lamp_requirement(world.get_entrance('Throne Room', player), player)
@@ -695,9 +693,6 @@ def no_glitches_rules(world, player):
def open_rules(world, player): def open_rules(world, player):
# softlock protection as you can reach the sewers small key door with a guard drop key # softlock protection as you can reach the sewers small key door with a guard drop key
forbid_item(world.get_location('Hyrule Castle - Boomerang Chest', player), 'Small Key (Escape)', player)
forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest', player), 'Small Key (Escape)', player)
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player)) set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_key('Small Key (Escape)', player)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
@@ -708,10 +703,10 @@ def swordless_rules(world, player):
set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Ether Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state.has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace
set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon, player)) set_rule(world.get_location('Ganon', player), lambda state: state.has('Hammer', player) and state.has_fire_source(player) and state.has('Silver Arrows', player) and state.can_shoot_arrows(player) and state.has_crystals(world.crystals_needed_for_ganon[player], player))
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop
if world.mode != 'inverted': if world.mode[player] != 'inverted':
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!)
@@ -722,32 +717,11 @@ def swordless_rules(world, player):
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player)) set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
def standard_rules(world, player):
add_rule(world.get_entrance('Sewers Door', player), lambda state: state.can_kill_most_things(player))
def standard_rules(world, player):
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
# ensures the required weapon for escape lands on uncle (unless player has it pre-equipped)
add_rule(world.get_location('Secret Passage', player), lambda state: state.can_kill_most_things(player))
add_rule(world.get_location('Hyrule Castle - Map Chest', player), lambda state: state.can_kill_most_things(player))
def uncle_item_rule(item):
copy_state = CollectionState(world)
copy_state.collect(item)
copy_state.sweep_for_events()
return copy_state.can_reach('Sanctuary', 'Region', player)
add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule)
# easiest way to enforce key placement not relevant for open
set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.can_kill_most_things(player))
def set_trock_key_rules(world, player): def set_trock_key_rules(world, player):
@@ -759,14 +733,14 @@ def set_trock_key_rules(world, player):
all_state = world.get_all_state(True) all_state = world.get_all_state(True)
# Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon. # Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon.
can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge is None else world.can_access_trock_eyebridge can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge[player] is None else world.can_access_trock_eyebridge[player]
world.can_access_trock_eyebridge = can_reach_back world.can_access_trock_eyebridge[player] = can_reach_back
can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) if world.can_access_trock_front is None else world.can_access_trock_front can_reach_front = all_state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) if world.can_access_trock_front[player] is None else world.can_access_trock_front[player]
world.can_access_trock_front = can_reach_front world.can_access_trock_front[player] = can_reach_front
can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) if world.can_access_trock_big_chest is None else world.can_access_trock_big_chest can_reach_big_chest = all_state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)) if world.can_access_trock_big_chest[player] is None else world.can_access_trock_big_chest[player]
world.can_access_trock_big_chest = can_reach_big_chest world.can_access_trock_big_chest[player] = can_reach_big_chest
can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) if world.can_access_trock_middle is None else world.can_access_trock_middle can_reach_middle = all_state.can_reach(world.get_region('Turtle Rock (Second Section)', player)) if world.can_access_trock_middle[player] is None else world.can_access_trock_middle[player]
world.can_access_trock_middle = can_reach_middle world.can_access_trock_middle[player] = can_reach_middle
# No matter what, the key requirement for going from the middle to the bottom should be three keys. # No matter what, the key requirement for going from the middle to the bottom should be three keys.
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 3)) set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 3))
@@ -793,14 +767,16 @@ def set_trock_key_rules(world, player):
# might open all the locked doors in any order so we need maximally restrictive rules. # might open all the locked doors in any order so we need maximally restrictive rules.
if can_reach_back: if can_reach_back:
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)))
else: else:
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player)
elif can_reach_front and can_reach_middle: elif can_reach_front and can_reach_middle:
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state))) set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(world.get_region('Turtle Rock (Entrance)', player)) and state.can_reach(world.get_region('Turtle Rock (Second Section)', player)))
else: else:
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player)
non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left',
@@ -810,8 +786,9 @@ def set_trock_key_rules(world, player):
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2))
set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 1)) set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 1))
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state))) set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.has_key('Small Key (Turtle Rock)', player, 2)) set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player and state.has_key('Small Key (Turtle Rock)', player, 2)
and state.can_reach(world.get_region('Turtle Rock (Entrance)', player)))
else: else:
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player)
non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left',
@@ -820,24 +797,25 @@ def set_trock_key_rules(world, player):
elif can_reach_big_chest: elif can_reach_big_chest:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4))
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
and state.can_reach(world.get_region('Turtle Rock (Big Chest)', player)))
else: else:
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Small Key (Turtle Rock)', player)
non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left',
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
'Turtle Rock - Eye Bridge - Top Right'] 'Turtle Rock - Eye Bridge - Top Right']
if not world.keysanity: if not world.keyshuffle[player]:
non_big_key_locations += ['Turtle Rock - Big Key Chest'] non_big_key_locations += ['Turtle Rock - Big Key Chest']
else: else:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4))
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player))) set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state.has_key('Small Key (Turtle Rock)', player, 4) or item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
if world.accessibility != 'locations': if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player) set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player)
non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left',
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
'Turtle Rock - Eye Bridge - Top Right'] 'Turtle Rock - Eye Bridge - Top Right']
if not world.keysanity: if not world.keyshuffle[player]:
non_big_key_locations += ['Turtle Rock - Big Key Chest', 'Turtle Rock - Chain Chomps'] non_big_key_locations += ['Turtle Rock - Big Key Chest', 'Turtle Rock - Chain Chomps']
# set big key restrictions # set big key restrictions
+4
View File
@@ -1,7 +1,11 @@
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]
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.