Merge remote-tracking branch 'remotes/multi/multiworld_31' into Dev
This commit is contained in:
@@ -13,3 +13,4 @@ README.html
|
|||||||
*multisave
|
*multisave
|
||||||
EnemizerCLI/
|
EnemizerCLI/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
RaceRom.py
|
||||||
@@ -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
@@ -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))
|
||||||
|
|
||||||
|
|||||||
+144
-89
@@ -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,8 +1086,7 @@ 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
|
||||||
|
|
||||||
return json.dumps(out)
|
return json.dumps(out)
|
||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
+20
-19
@@ -8,7 +8,7 @@ from Items import ItemFactory
|
|||||||
|
|
||||||
def create_dungeons(world, player):
|
def create_dungeons(world, player):
|
||||||
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
|
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
|
||||||
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items, player)
|
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys, dungeon_items, player)
|
||||||
dungeon.boss = BossFactory(default_boss, player)
|
dungeon.boss = BossFactory(default_boss, player)
|
||||||
for region in dungeon.regions:
|
for region in dungeon.regions:
|
||||||
world.get_region(region, player).dungeon = dungeon
|
world.get_region(region, player).dungeon = dungeon
|
||||||
@@ -27,7 +27,7 @@ def create_dungeons(world, player):
|
|||||||
MM = make_dungeon('Misery Mire', 'Vitreous', ['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.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
||||||
if item.key:
|
all_state_base.collect(item, True)
|
||||||
item.advancement = True
|
item.advancement = True
|
||||||
elif item.map or item.compass:
|
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
||||||
item.priority = True
|
item.priority = True
|
||||||
return
|
|
||||||
|
|
||||||
dungeon_items = get_dungeon_item_pool(world)
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
|
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||||
|
or (item.map and not world.mapshuffle[item.player])
|
||||||
|
or (item.compass and not world.compassshuffle[item.player]))]
|
||||||
|
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items)
|
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
+58
-88
@@ -19,17 +19,17 @@ def link_entrances(world, player):
|
|||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
# if we do not shuffle, set default connections
|
# if we do not shuffle, set default connections
|
||||||
if world.shuffle == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
for exitname, regionname in default_dungeon_connections:
|
for exitname, regionname in default_dungeon_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
elif world.shuffle == 'dungeonssimple':
|
elif world.shuffle[player] == 'dungeonssimple':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
elif world.shuffle == 'dungeonsfull':
|
elif world.shuffle[player] == 'dungeonsfull':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ def link_entrances(world, player):
|
|||||||
lw_entrances = list(LW_Dungeon_Entrances)
|
lw_entrances = list(LW_Dungeon_Entrances)
|
||||||
dw_entrances = list(DW_Dungeon_Entrances)
|
dw_entrances = list(DW_Dungeon_Entrances)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -52,14 +52,14 @@ def link_entrances(world, player):
|
|||||||
dw_entrances.append('Ganons Tower')
|
dw_entrances.append('Ganons Tower')
|
||||||
dungeon_exits.append('Ganons Tower Exit')
|
dungeon_exits.append('Ganons Tower Exit')
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
|
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
|
||||||
connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player)
|
||||||
else:
|
else:
|
||||||
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
|
||||||
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
|
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
|
||||||
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
|
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
|
||||||
elif world.shuffle == 'simple':
|
elif world.shuffle[player] == 'simple':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
old_man_entrances = list(Old_Man_Entrances)
|
old_man_entrances = list(Old_Man_Entrances)
|
||||||
@@ -130,7 +130,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'restricted':
|
elif world.shuffle[player] == 'restricted':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
||||||
@@ -201,7 +201,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'restricted_legacy':
|
elif world.shuffle[player] == 'restricted_legacy':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances)
|
lw_entrances = list(LW_Entrances)
|
||||||
@@ -256,7 +256,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'full':
|
elif world.shuffle[player] == 'full':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
|
||||||
@@ -273,7 +273,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -309,7 +309,7 @@ def link_entrances(world, player):
|
|||||||
pass
|
pass
|
||||||
else: #if the cave wasn't placed we get here
|
else: #if the cave wasn't placed we get here
|
||||||
connect_caves(world, lw_entrances, [], old_man_house, player)
|
connect_caves(world, lw_entrances, [], old_man_house, player)
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world
|
# rest of hyrule castle must be in light world
|
||||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'crossed':
|
elif world.shuffle[player] == 'crossed':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
|
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
|
||||||
@@ -376,7 +376,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -392,7 +392,7 @@ def link_entrances(world, player):
|
|||||||
#place must-exit caves
|
#place must-exit caves
|
||||||
connect_mandatory_exits(world, entrances, caves, must_exits, player)
|
connect_mandatory_exits(world, entrances, caves, must_exits, player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be dealt with
|
# rest of hyrule castle must be dealt with
|
||||||
connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, entrances, door_targets, player)
|
connect_doors(world, entrances, door_targets, player)
|
||||||
elif world.shuffle == 'full_legacy':
|
elif world.shuffle[player] == 'full_legacy':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
||||||
@@ -451,7 +451,7 @@ def link_entrances(world, player):
|
|||||||
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
||||||
door_targets = list(Single_Cave_Targets)
|
door_targets = list(Single_Cave_Targets)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
else:
|
else:
|
||||||
@@ -471,7 +471,7 @@ def link_entrances(world, player):
|
|||||||
else:
|
else:
|
||||||
connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
|
connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
|
||||||
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
|
connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# rest of hyrule castle must be in light world
|
# rest of hyrule castle must be in light world
|
||||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||||
|
|
||||||
@@ -513,7 +513,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'madness_legacy':
|
elif world.shuffle[player] == 'madness_legacy':
|
||||||
# here lie dragons, connections are no longer two way
|
# here lie dragons, connections are no longer two way
|
||||||
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
|
||||||
dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
|
dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
|
||||||
@@ -552,7 +552,7 @@ def link_entrances(world, player):
|
|||||||
('Lumberjack Tree Exit', 'Lumberjack Tree (top)'),
|
('Lumberjack Tree Exit', 'Lumberjack Tree (top)'),
|
||||||
(('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')]
|
(('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')]
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -606,7 +606,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, target, player)
|
connect_entrance(world, hole, target, player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -755,7 +755,7 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
elif world.shuffle == 'insanity':
|
elif world.shuffle[player] == 'insanity':
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
||||||
@@ -792,7 +792,7 @@ def link_entrances(world, player):
|
|||||||
# tavern back door cannot be shuffled yet
|
# tavern back door cannot be shuffled yet
|
||||||
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
connect_doors(world, ['Tavern North'], ['Tavern'], player)
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -825,7 +825,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, hole_targets.pop(), player)
|
connect_entrance(world, hole, hole_targets.pop(), player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -902,8 +902,8 @@ def link_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'insanity_legacy':
|
elif world.shuffle[player] == 'insanity_legacy':
|
||||||
world.fix_fake_world = False
|
world.fix_fake_world[player] = False
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
|
||||||
@@ -927,7 +927,7 @@ def link_entrances(world, player):
|
|||||||
hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)',
|
hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)',
|
||||||
'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
|
'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
|
||||||
|
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# cannot move uncle cave
|
# cannot move uncle cave
|
||||||
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
|
||||||
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
|
||||||
@@ -960,7 +960,7 @@ def link_entrances(world, player):
|
|||||||
connect_entrance(world, hole, hole_targets.pop(), player)
|
connect_entrance(world, hole, hole_targets.pop(), player)
|
||||||
|
|
||||||
# hyrule castle handling
|
# hyrule castle handling
|
||||||
if world.mode == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
# must connect front of hyrule castle to do escape
|
# must connect front of hyrule castle to do escape
|
||||||
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||||
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
|
||||||
@@ -1079,17 +1079,17 @@ def link_inverted_entrances(world, player):
|
|||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
# if we do not shuffle, set default connections
|
# if we do not shuffle, set default connections
|
||||||
if world.shuffle == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
for exitname, regionname in inverted_default_dungeon_connections:
|
for exitname, regionname in inverted_default_dungeon_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
elif world.shuffle == 'dungeonssimple':
|
elif world.shuffle[player] == 'dungeonssimple':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
elif world.shuffle == 'dungeonsfull':
|
elif world.shuffle[player] == 'dungeonsfull':
|
||||||
for exitname, regionname in inverted_default_connections:
|
for exitname, regionname in inverted_default_connections:
|
||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
@@ -1151,7 +1151,7 @@ def link_inverted_entrances(world, player):
|
|||||||
remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances]
|
remaining_lw_entrances = [i for i in all_dungeon_entrances if i in lw_entrances]
|
||||||
connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player)
|
connect_caves(world, remaining_lw_entrances, remaining_dw_entrances, dungeon_exits, player)
|
||||||
|
|
||||||
elif world.shuffle == 'simple':
|
elif world.shuffle[player] == 'simple':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
old_man_entrances = list(Inverted_Old_Man_Entrances)
|
old_man_entrances = list(Inverted_Old_Man_Entrances)
|
||||||
@@ -1160,7 +1160,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
single_doors = list(Single_Cave_Doors)
|
single_doors = list(Single_Cave_Doors)
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# we shuffle all 2 entrance caves as pairs as a start
|
# we shuffle all 2 entrance caves as pairs as a start
|
||||||
@@ -1191,6 +1191,8 @@ def link_inverted_entrances(world, player):
|
|||||||
bomb_shop_doors.remove(links_house)
|
bomb_shop_doors.remove(links_house)
|
||||||
if links_house in blacksmith_doors:
|
if links_house in blacksmith_doors:
|
||||||
blacksmith_doors.remove(links_house)
|
blacksmith_doors.remove(links_house)
|
||||||
|
if links_house in old_man_entrances:
|
||||||
|
old_man_entrances.remove(links_house)
|
||||||
|
|
||||||
# place dark sanc
|
# place dark sanc
|
||||||
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
|
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
|
||||||
@@ -1243,7 +1245,7 @@ def link_inverted_entrances(world, player):
|
|||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, single_doors, door_targets, player)
|
connect_doors(world, single_doors, door_targets, player)
|
||||||
|
|
||||||
elif world.shuffle == 'restricted':
|
elif world.shuffle[player] == 'restricted':
|
||||||
simple_shuffle_dungeons(world, player)
|
simple_shuffle_dungeons(world, player)
|
||||||
|
|
||||||
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
|
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
|
||||||
@@ -1253,7 +1255,7 @@ def link_inverted_entrances(world, player):
|
|||||||
caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House)
|
caves = list(Cave_Exits + Cave_Three_Exits + Old_Man_House)
|
||||||
single_doors = list(Single_Cave_Doors)
|
single_doors = list(Single_Cave_Doors)
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# place links house
|
# place links house
|
||||||
@@ -1326,7 +1328,7 @@ def link_inverted_entrances(world, player):
|
|||||||
doors = lw_entrances + dw_entrances
|
doors = lw_entrances + dw_entrances
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'full':
|
elif world.shuffle[player] == 'full':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
|
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
|
||||||
@@ -1335,7 +1337,7 @@ def link_inverted_entrances(world, player):
|
|||||||
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
||||||
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
old_man_house = list(Old_Man_House)
|
old_man_house = list(Old_Man_House)
|
||||||
|
|
||||||
@@ -1477,7 +1479,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, doors, door_targets, player)
|
connect_doors(world, doors, door_targets, player)
|
||||||
elif world.shuffle == 'crossed':
|
elif world.shuffle[player] == 'crossed':
|
||||||
skull_woods_shuffle(world, player)
|
skull_woods_shuffle(world, player)
|
||||||
|
|
||||||
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
|
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
|
||||||
@@ -1486,7 +1488,7 @@ def link_inverted_entrances(world, player):
|
|||||||
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
old_man_entrances = list(Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Inverted Agahnims Tower', 'Tower of Hera'])
|
||||||
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits + Old_Man_House) # don't need to consider three exit caves, have one exit caves to avoid parity issues
|
||||||
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
bomb_shop_doors = list(Inverted_Bomb_Shop_Single_Cave_Doors + Inverted_Bomb_Shop_Multi_Cave_Doors)
|
||||||
blacksmith_doors = list(Inverted_Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
blacksmith_doors = list(Blacksmith_Single_Cave_Doors + Blacksmith_Multi_Cave_Doors)
|
||||||
door_targets = list(Inverted_Single_Cave_Targets)
|
door_targets = list(Inverted_Single_Cave_Targets)
|
||||||
|
|
||||||
# randomize which desert ledge door is a must-exit
|
# randomize which desert ledge door is a must-exit
|
||||||
@@ -1587,7 +1589,7 @@ def link_inverted_entrances(world, player):
|
|||||||
|
|
||||||
# place remaining doors
|
# place remaining doors
|
||||||
connect_doors(world, entrances, door_targets, player)
|
connect_doors(world, entrances, door_targets, player)
|
||||||
elif world.shuffle == 'insanity':
|
elif world.shuffle[player] == 'insanity':
|
||||||
# beware ye who enter here
|
# beware ye who enter here
|
||||||
|
|
||||||
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
|
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
|
||||||
@@ -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,39 +2717,8 @@ Inverted_Bomb_Shop_Single_Cave_Doors = ['Waterfall of Wishing',
|
|||||||
'Bumper Cave (Top)',
|
'Bumper Cave (Top)',
|
||||||
'Mimic Cave',
|
'Mimic Cave',
|
||||||
'Dark Lake Hylia Shop',
|
'Dark Lake Hylia Shop',
|
||||||
'Inverted Links House']
|
'Inverted Links House',
|
||||||
|
'Inverted Big Bomb Shop']
|
||||||
Inverted_Blacksmith_Single_Cave_Doors = ['Blinds Hideout',
|
|
||||||
'Lake Hylia Fairy',
|
|
||||||
'Light Hype Fairy',
|
|
||||||
'Desert Fairy',
|
|
||||||
'Chicken House',
|
|
||||||
'Aginahs Cave',
|
|
||||||
'Sahasrahlas Hut',
|
|
||||||
'Cave Shop (Lake Hylia)',
|
|
||||||
'Blacksmiths Hut',
|
|
||||||
'Sick Kids House',
|
|
||||||
'Lost Woods Gamble',
|
|
||||||
'Fortune Teller (Light)',
|
|
||||||
'Snitch Lady (East)',
|
|
||||||
'Snitch Lady (West)',
|
|
||||||
'Bush Covered House',
|
|
||||||
'Tavern (Front)',
|
|
||||||
'Light World Bomb Hut',
|
|
||||||
'Kakariko Shop',
|
|
||||||
'Mini Moldorm Cave',
|
|
||||||
'Long Fairy Cave',
|
|
||||||
'Good Bee Cave',
|
|
||||||
'20 Rupee Cave',
|
|
||||||
'50 Rupee Cave',
|
|
||||||
'Ice Rod Cave',
|
|
||||||
'Library',
|
|
||||||
'Potion Shop',
|
|
||||||
'Dam',
|
|
||||||
'Lumberjack House',
|
|
||||||
'Lake Hylia Fortune Teller',
|
|
||||||
'Kakariko Gamble Game',
|
|
||||||
'Inverted Big Bomb Shop']
|
|
||||||
|
|
||||||
|
|
||||||
Inverted_Single_Cave_Targets = ['Blinds Hideout',
|
Inverted_Single_Cave_Targets = ['Blinds Hideout',
|
||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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,45 +169,51 @@ def fill_restrictive(world, base_state, locations, itempool):
|
|||||||
new_state.sweep_for_events()
|
new_state.sweep_for_events()
|
||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
while itempool and locations:
|
unplaced_items = []
|
||||||
items_to_place = []
|
|
||||||
nextpool = []
|
|
||||||
placing_players = set()
|
|
||||||
for item in reversed(itempool):
|
|
||||||
if item.player not in placing_players:
|
|
||||||
placing_players.add(item.player)
|
|
||||||
items_to_place.append(item)
|
|
||||||
else:
|
|
||||||
nextpool.insert(0, item)
|
|
||||||
itempool = nextpool
|
|
||||||
|
|
||||||
maximum_exploration_state = sweep_from_pool()
|
no_access_checks = {}
|
||||||
|
reachable_items = {}
|
||||||
|
for item in itempool:
|
||||||
|
if world.accessibility[item.player] == 'none':
|
||||||
|
no_access_checks.setdefault(item.player, []).append(item)
|
||||||
|
else:
|
||||||
|
reachable_items.setdefault(item.player, []).append(item)
|
||||||
|
|
||||||
perform_access_check = True
|
for player_items in [no_access_checks, reachable_items]:
|
||||||
if world.accessibility == 'none':
|
while any(player_items.values()) and locations:
|
||||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state)
|
items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items]
|
||||||
|
|
||||||
for item_to_place in items_to_place:
|
maximum_exploration_state = sweep_from_pool()
|
||||||
spot_to_fill = None
|
has_beaten_game = world.has_beaten_game(maximum_exploration_state)
|
||||||
for location in locations:
|
|
||||||
if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
for item_to_place in items_to_place:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
perform_access_check = True
|
||||||
if world.can_beat_game():
|
if world.accessibility[item_to_place.player] == 'none':
|
||||||
if world.accessibility != 'none':
|
perform_access_check = not world.has_beaten_game(maximum_exploration_state, item_to_place.player) if single_player_placement else not has_beaten_game
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
|
||||||
continue
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
spot_to_fill = None
|
||||||
locations.remove(spot_to_fill)
|
for location in locations:
|
||||||
spot_to_fill.event = True
|
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
|
||||||
|
break
|
||||||
|
|
||||||
|
if spot_to_fill is None:
|
||||||
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
|
unplaced_items.insert(0, item_to_place)
|
||||||
|
if world.can_beat_game():
|
||||||
|
if world.accessibility[item_to_place.player] != 'none':
|
||||||
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
||||||
|
continue
|
||||||
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=None):
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
locations.remove(spot_to_fill)
|
||||||
|
spot_to_fill.event = True
|
||||||
|
|
||||||
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
fill_locations = world.get_unfilled_locations()
|
fill_locations = world.get_unfilled_locations()
|
||||||
@@ -221,23 +227,26 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
|
|||||||
|
|
||||||
# fill in gtower locations with trash first
|
# fill in gtower locations with trash first
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.ganonstower_vanilla[player]:
|
if not gftower_trash or not world.ganonstower_vanilla[player]:
|
||||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
continue
|
||||||
random.shuffle(gtower_locations)
|
|
||||||
trashcnt = 0
|
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
|
||||||
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
|
||||||
spot_to_fill = gtower_locations.pop()
|
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
||||||
item_to_place = restitempool.pop()
|
random.shuffle(gtower_locations)
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
trashcnt = 0
|
||||||
fill_locations.remove(spot_to_fill)
|
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
||||||
trashcnt += 1
|
spot_to_fill = gtower_locations.pop()
|
||||||
|
item_to_place = restitempool.pop()
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
trashcnt += 1
|
||||||
|
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
fill_locations.reverse()
|
fill_locations.reverse()
|
||||||
|
|
||||||
# Make sure the escape small key is placed first in standard keysanity to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
if world.keysanity and world.mode == 'standard':
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.mode[item.player] == 'standard' and world.keyshuffle[item.player] else 0)
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0)
|
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
|
||||||
@@ -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,8 +336,7 @@ def balance_multiworld_progression(world):
|
|||||||
reachable_locations_count[player] = 0
|
reachable_locations_count[player] = 0
|
||||||
|
|
||||||
def get_sphere_locations(sphere_state, locations):
|
def get_sphere_locations(sphere_state, locations):
|
||||||
if not world.keysanity:
|
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
|
||||||
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -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.')
|
||||||
|
|||||||
@@ -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
@@ -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')}
|
||||||
|
|||||||
+94
-45
@@ -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:
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,44 +228,47 @@ 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)
|
||||||
|
|
||||||
copy_dynamic_regions_and_locations(world, ret)
|
copy_dynamic_regions_and_locations(world, ret)
|
||||||
|
|
||||||
@@ -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,8 +358,7 @@ def create_playthrough(world):
|
|||||||
sphere_candidates = list(prog_locations)
|
sphere_candidates = list(prog_locations)
|
||||||
logging.getLogger('').debug('Building up collection spheres.')
|
logging.getLogger('').debug('Building up collection spheres.')
|
||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
if not world.keysanity:
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_events(key_only=True)
|
|
||||||
|
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
@@ -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,8 +412,7 @@ def create_playthrough(world):
|
|||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
while required_locations:
|
while required_locations:
|
||||||
if not world.keysanity:
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_events(key_only=True)
|
|
||||||
|
|
||||||
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
@@ -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
@@ -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
@@ -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()
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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')}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user