Merged in DR v1.0.0.2
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -38,4 +38,6 @@ get-pip.py
|
|||||||
|
|
||||||
venv
|
venv
|
||||||
test
|
test
|
||||||
|
test_games/
|
||||||
|
data/sprites/official/selan.1.zspr
|
||||||
*.zspr
|
*.zspr
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from Utils import int16_as_bytes
|
|||||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||||
from RoomData import Room
|
from RoomData import Room
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
||||||
@@ -133,6 +134,7 @@ class World(object):
|
|||||||
set_player_attr('compassshuffle', False)
|
set_player_attr('compassshuffle', False)
|
||||||
set_player_attr('keyshuffle', False)
|
set_player_attr('keyshuffle', False)
|
||||||
set_player_attr('bigkeyshuffle', False)
|
set_player_attr('bigkeyshuffle', False)
|
||||||
|
set_player_attr('restrict_boss_items', 'none')
|
||||||
set_player_attr('bombbag', False)
|
set_player_attr('bombbag', False)
|
||||||
set_player_attr('difficulty_requirements', None)
|
set_player_attr('difficulty_requirements', None)
|
||||||
set_player_attr('boss_shuffle', 'none')
|
set_player_attr('boss_shuffle', 'none')
|
||||||
@@ -256,6 +258,11 @@ class World(object):
|
|||||||
return r_location
|
return r_location
|
||||||
raise RuntimeError('No such location %s for player %d' % (location, player))
|
raise RuntimeError('No such location %s for player %d' % (location, player))
|
||||||
|
|
||||||
|
def get_location_unsafe(self, location, player):
|
||||||
|
if (location, player) in self._location_cache:
|
||||||
|
return self._location_cache[(location, player)]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_dungeon(self, dungeonname, player):
|
def get_dungeon(self, dungeonname, player):
|
||||||
if isinstance(dungeonname, Dungeon):
|
if isinstance(dungeonname, Dungeon):
|
||||||
return dungeonname
|
return dungeonname
|
||||||
@@ -389,7 +396,7 @@ class World(object):
|
|||||||
elif item.name.startswith('Bottle'):
|
elif item.name.startswith('Bottle'):
|
||||||
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||||
ret.prog_items[item.name, item.player] += 1
|
ret.prog_items[item.name, item.player] += 1
|
||||||
elif item.advancement or item.smallkey or item.bigkey:
|
elif item.advancement or item.smallkey or item.bigkey or item.compass or item.map:
|
||||||
ret.prog_items[item.name, item.player] += 1
|
ret.prog_items[item.name, item.player] += 1
|
||||||
|
|
||||||
for item in self.itempool:
|
for item in self.itempool:
|
||||||
@@ -404,6 +411,8 @@ class World(object):
|
|||||||
key_list += [dungeon.big_key.name]
|
key_list += [dungeon.big_key.name]
|
||||||
if len(dungeon.small_keys) > 0:
|
if len(dungeon.small_keys) > 0:
|
||||||
key_list += [x.name for x in dungeon.small_keys]
|
key_list += [x.name for x in dungeon.small_keys]
|
||||||
|
# map/compass may be required now
|
||||||
|
key_list += [x.name for x in dungeon.dungeon_items]
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
for item in ItemFactory(key_list, p):
|
for item in ItemFactory(key_list, p):
|
||||||
soft_collect(item)
|
soft_collect(item)
|
||||||
@@ -915,7 +924,7 @@ class CollectionState(object):
|
|||||||
reduced = Counter()
|
reduced = Counter()
|
||||||
for item, cnt in self.prog_items.items():
|
for item, cnt in self.prog_items.items():
|
||||||
item_name, item_player = item
|
item_name, item_player = item
|
||||||
if item_player == player and self.check_if_progressive(item_name):
|
if item_player == player and self.check_if_progressive(item_name, player):
|
||||||
if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles
|
if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles
|
||||||
bottle_count += cnt
|
bottle_count += cnt
|
||||||
elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']:
|
elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']:
|
||||||
@@ -931,8 +940,7 @@ class CollectionState(object):
|
|||||||
reduced[('Heart Container', player)] = 1
|
reduced[('Heart Container', player)] = 1
|
||||||
return frozenset(reduced.items())
|
return frozenset(reduced.items())
|
||||||
|
|
||||||
@staticmethod
|
def check_if_progressive(self, item_name, player):
|
||||||
def check_if_progressive(item_name):
|
|
||||||
return (item_name in
|
return (item_name in
|
||||||
['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot',
|
['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot',
|
||||||
'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel',
|
'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel',
|
||||||
@@ -944,7 +952,8 @@ class CollectionState(object):
|
|||||||
'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna',
|
'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna',
|
||||||
'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)',
|
'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)',
|
||||||
'Magic Upgrade (1/4)']
|
'Magic Upgrade (1/4)']
|
||||||
or item_name.startswith(('Bottle', 'Small Key', 'Big Key')))
|
or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))
|
||||||
|
or (self.world.restrict_boss_items[player] != 'none' and item_name.startswith(('Map', 'Compass'))))
|
||||||
|
|
||||||
def can_reach(self, spot, resolution_hint=None, player=None):
|
def can_reach(self, spot, resolution_hint=None, player=None):
|
||||||
try:
|
try:
|
||||||
@@ -1838,6 +1847,10 @@ class Dungeon(object):
|
|||||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||||
|
|
||||||
|
|
||||||
|
class FillError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class DoorType(Enum):
|
class DoorType(Enum):
|
||||||
Normal = 1
|
Normal = 1
|
||||||
@@ -2319,6 +2332,8 @@ class Sector(object):
|
|||||||
self.destination_entrance = False
|
self.destination_entrance = False
|
||||||
self.equations = None
|
self.equations = None
|
||||||
self.item_logic = set()
|
self.item_logic = set()
|
||||||
|
self.chest_location_set = set()
|
||||||
|
|
||||||
|
|
||||||
def region_set(self):
|
def region_set(self):
|
||||||
if self.r_name_set is None:
|
if self.r_name_set is None:
|
||||||
@@ -2427,6 +2442,12 @@ class Portal(object):
|
|||||||
self.dependent = None
|
self.dependent = None
|
||||||
self.deadEnd = False
|
self.deadEnd = False
|
||||||
self.light_world = False
|
self.light_world = False
|
||||||
|
self.chosen = False
|
||||||
|
|
||||||
|
def find_portal_entrance(self):
|
||||||
|
p_region = self.door.entrance.connected_region
|
||||||
|
return next((x for x in p_region.entrances
|
||||||
|
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None)
|
||||||
|
|
||||||
def change_boss_exit(self, exit_idx):
|
def change_boss_exit(self, exit_idx):
|
||||||
self.default = False
|
self.default = False
|
||||||
@@ -2561,6 +2582,7 @@ class Location(object):
|
|||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.staleness_count = 0
|
self.staleness_count = 0
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
self.real = not crystal
|
||||||
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
|
||||||
@@ -2656,6 +2678,12 @@ class Item(object):
|
|||||||
item_dungeon = 'Hyrule Castle'
|
item_dungeon = 'Hyrule Castle'
|
||||||
return item_dungeon
|
return item_dungeon
|
||||||
|
|
||||||
|
def is_inside_dungeon_item(self, world):
|
||||||
|
return ((self.smallkey and not world.keyshuffle[self.player])
|
||||||
|
or (self.bigkey and not world.bigkeyshuffle[self.player])
|
||||||
|
or (self.compass and not world.compassshuffle[self.player])
|
||||||
|
or (self.map and not world.mapshuffle[self.player]))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.__unicode__())
|
return str(self.__unicode__())
|
||||||
|
|
||||||
@@ -2823,6 +2851,7 @@ class Spoiler(object):
|
|||||||
'ganon_crystals': self.world.crystals_needed_for_ganon,
|
'ganon_crystals': self.world.crystals_needed_for_ganon,
|
||||||
'open_pyramid': self.world.open_pyramid,
|
'open_pyramid': self.world.open_pyramid,
|
||||||
'accessibility': self.world.accessibility,
|
'accessibility': self.world.accessibility,
|
||||||
|
'restricted_boss_items': self.world.restrict_boss_items,
|
||||||
'hints': self.world.hints,
|
'hints': self.world.hints,
|
||||||
'mapshuffle': self.world.mapshuffle,
|
'mapshuffle': self.world.mapshuffle,
|
||||||
'compassshuffle': self.world.compassshuffle,
|
'compassshuffle': self.world.compassshuffle,
|
||||||
@@ -2838,6 +2867,7 @@ class Spoiler(object):
|
|||||||
'experimental': self.world.experimental,
|
'experimental': self.world.experimental,
|
||||||
'keydropshuffle': self.world.keydropshuffle,
|
'keydropshuffle': self.world.keydropshuffle,
|
||||||
'shopsanity': self.world.shopsanity,
|
'shopsanity': self.world.shopsanity,
|
||||||
|
'pseudoboots': self.world.pseudoboots,
|
||||||
'triforcegoal': self.world.treasure_hunt_count,
|
'triforcegoal': self.world.treasure_hunt_count,
|
||||||
'triforcepool': self.world.treasure_hunt_total,
|
'triforcepool': self.world.treasure_hunt_total,
|
||||||
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
|
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
|
||||||
@@ -2966,6 +2996,9 @@ class Spoiler(object):
|
|||||||
return json.dumps(out)
|
return json.dumps(out)
|
||||||
|
|
||||||
def meta_to_file(self, filename):
|
def meta_to_file(self, filename):
|
||||||
|
def yn(flag):
|
||||||
|
return 'Yes' if flag else 'No'
|
||||||
|
|
||||||
self.parse_meta()
|
self.parse_meta()
|
||||||
with open(filename, 'w') as outfile:
|
with open(filename, 'w') as outfile:
|
||||||
line_width = 35
|
line_width = 35
|
||||||
@@ -2981,7 +3014,7 @@ class Spoiler(object):
|
|||||||
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
|
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
|
||||||
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
|
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
|
||||||
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
||||||
outfile.write('Retro:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['retro'][player] else 'No'))
|
outfile.write('Retro:'.ljust(line_width) + '%s\n' % yn(self.metadata['retro'][player]))
|
||||||
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
||||||
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
||||||
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
|
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
|
||||||
@@ -2990,38 +3023,40 @@ class Spoiler(object):
|
|||||||
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
||||||
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
|
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
|
||||||
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
|
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
|
||||||
|
outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player])
|
||||||
outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player])
|
outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player])
|
||||||
outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player])
|
outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player])
|
||||||
outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shopsanity'][player] else 'No'))
|
outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player]))
|
||||||
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No'))
|
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player]))
|
||||||
|
outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
|
||||||
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
|
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
|
||||||
if self.metadata['ow_shuffle'][player] != 'vanilla':
|
if self.metadata['ow_shuffle'][player] != 'vanilla':
|
||||||
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No'))
|
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
|
||||||
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
|
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
|
||||||
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No'))
|
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
|
||||||
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_whirlpool'][player] else 'No'))
|
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
|
||||||
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
|
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
|
||||||
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
|
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
|
||||||
if self.metadata['shuffle'][player] != 'vanilla':
|
if self.metadata['shuffle'][player] != 'vanilla':
|
||||||
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No'))
|
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player]))
|
||||||
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shufflelinks'][player] else 'No'))
|
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
|
||||||
if self.metadata['goal'][player] != 'trinity':
|
if self.metadata['goal'][player] != 'trinity':
|
||||||
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
|
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % yn(self.metadata['open_pyramid'][player]))
|
||||||
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
|
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
|
||||||
if self.metadata['door_shuffle'][player] != 'vanilla':
|
if self.metadata['door_shuffle'][player] != 'vanilla':
|
||||||
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
|
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
|
||||||
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
|
||||||
outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No'))
|
outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
|
||||||
outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
|
outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keydropshuffle'][player]))
|
||||||
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
|
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))
|
||||||
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No'))
|
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player]))
|
||||||
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No'))
|
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keyshuffle'][player]))
|
||||||
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No'))
|
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player]))
|
||||||
outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player])
|
outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player])
|
||||||
outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player])
|
outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player])
|
||||||
outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player])
|
outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player])
|
||||||
outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player])
|
outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player])
|
||||||
outfile.write('Hints:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
|
||||||
|
|
||||||
if self.startinventory:
|
if self.startinventory:
|
||||||
outfile.write('Starting Inventory:'.ljust(line_width))
|
outfile.write('Starting Inventory:'.ljust(line_width))
|
||||||
@@ -3266,6 +3301,16 @@ enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
|
|||||||
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
||||||
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
||||||
|
|
||||||
|
# byte 8: RRAA A??? (restrict boss mode, algorithm, ? = unused)
|
||||||
|
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
||||||
|
# algorithm: todo with "biased shuffles"
|
||||||
|
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4}
|
||||||
|
|
||||||
|
# additions
|
||||||
|
# psuedoboots does not effect code
|
||||||
|
# sfx_shuffle and other adjust items does not effect settings code
|
||||||
|
|
||||||
|
|
||||||
class Settings(object):
|
class Settings(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -3294,7 +3339,9 @@ class Settings(object):
|
|||||||
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
|
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
|
||||||
|
|
||||||
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
|
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
|
||||||
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0)])
|
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
|
||||||
|
|
||||||
|
(rb_mode[w.restrict_boss_items[p]] << 6)])
|
||||||
return base64.b64encode(code, "+-".encode()).decode()
|
return base64.b64encode(code, "+-".encode()).decode()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -3341,6 +3388,8 @@ class Settings(object):
|
|||||||
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
||||||
args.bombbag[p] = True if settings[7] & 0x2 else False
|
args.bombbag[p] = True if settings[7] & 0x2 else False
|
||||||
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
||||||
|
if len(settings) > 8:
|
||||||
|
args.restrict_boss_items[p] = True if r(rb_mode)[(settings[8] & 0x80) >> 6] else False
|
||||||
|
|
||||||
|
|
||||||
class KeyRuleType(FastEnum):
|
class KeyRuleType(FastEnum):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
|
|
||||||
from BaseClasses import Boss
|
from BaseClasses import Boss, FillError
|
||||||
from Fill import FillError
|
|
||||||
|
|
||||||
def BossFactory(boss, player):
|
def BossFactory(boss, player):
|
||||||
if boss is None:
|
if boss is None:
|
||||||
|
|||||||
4
CLI.py
4
CLI.py
@@ -97,7 +97,7 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
|
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
|
||||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'bombbag', 'shuffleganon',
|
'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||||
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
||||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
||||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||||
@@ -141,6 +141,7 @@ def parse_settings():
|
|||||||
"progressive": "on",
|
"progressive": "on",
|
||||||
"accessibility": "items",
|
"accessibility": "items",
|
||||||
"algorithm": "balanced",
|
"algorithm": "balanced",
|
||||||
|
"restrict_boss_items": "none",
|
||||||
|
|
||||||
# Shuffle Ganon defaults to TRUE
|
# Shuffle Ganon defaults to TRUE
|
||||||
"openpyramid": False,
|
"openpyramid": False,
|
||||||
@@ -153,6 +154,7 @@ def parse_settings():
|
|||||||
"ow_fluteshuffle": "vanilla",
|
"ow_fluteshuffle": "vanilla",
|
||||||
"shuffle": "vanilla",
|
"shuffle": "vanilla",
|
||||||
"shufflelinks": False,
|
"shufflelinks": False,
|
||||||
|
"overworld_map": "default",
|
||||||
"pseudoboots": False,
|
"pseudoboots": False,
|
||||||
|
|
||||||
"shufflepots": False,
|
"shufflepots": False,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from RoomData import DoorKind, PairedDoor, reset_rooms
|
|||||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
|
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
|
||||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||||
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
|
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
|
||||||
|
from DungeonGenerator import valid_region_to_explore as valid_region_to_explore_lim
|
||||||
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
||||||
from Utils import ncr, kth_combination
|
from Utils import ncr, kth_combination
|
||||||
|
|
||||||
@@ -44,10 +45,10 @@ def link_doors(world, player):
|
|||||||
reset_rooms(world, player)
|
reset_rooms(world, player)
|
||||||
world.get_door("Skull Pinball WS", player).no_exit()
|
world.get_door("Skull Pinball WS", player).no_exit()
|
||||||
world.swamp_patch_required[player] = orig_swamp_patch
|
world.swamp_patch_required[player] = orig_swamp_patch
|
||||||
|
link_doors_prep(world, player)
|
||||||
|
|
||||||
|
|
||||||
def link_doors_main(world, player):
|
def link_doors_prep(world, player):
|
||||||
|
|
||||||
# Drop-down connections & push blocks
|
# Drop-down connections & push blocks
|
||||||
for exitName, regionName in logical_connections:
|
for exitName, regionName in logical_connections:
|
||||||
connect_simple_door(world, exitName, regionName, player)
|
connect_simple_door(world, exitName, regionName, player)
|
||||||
@@ -100,6 +101,7 @@ def link_doors_main(world, player):
|
|||||||
analyze_portals(world, player)
|
analyze_portals(world, player)
|
||||||
for portal in world.dungeon_portals[player]:
|
for portal in world.dungeon_portals[player]:
|
||||||
connect_portal(portal, world, player)
|
connect_portal(portal, world, player)
|
||||||
|
|
||||||
if not world.doorShuffle[player] == 'vanilla':
|
if not world.doorShuffle[player] == 'vanilla':
|
||||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||||
else:
|
else:
|
||||||
@@ -120,11 +122,14 @@ def link_doors_main(world, player):
|
|||||||
for ent, ext in default_one_way_connections:
|
for ent, ext in default_one_way_connections:
|
||||||
connect_one_way(world, ent, ext, player)
|
connect_one_way(world, ent, ext, player)
|
||||||
vanilla_key_logic(world, player)
|
vanilla_key_logic(world, player)
|
||||||
elif world.doorShuffle[player] == 'basic':
|
|
||||||
|
|
||||||
|
def link_doors_main(world, player):
|
||||||
|
if world.doorShuffle[player] == 'basic':
|
||||||
within_dungeon(world, player)
|
within_dungeon(world, player)
|
||||||
elif world.doorShuffle[player] == 'crossed':
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
cross_dungeon(world, player)
|
cross_dungeon(world, player)
|
||||||
else:
|
elif world.doorShuffle[player] != 'vanilla':
|
||||||
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
|
|
||||||
@@ -215,11 +220,16 @@ def vanilla_key_logic(world, player):
|
|||||||
world.key_logic[player] = {}
|
world.key_logic[player] = {}
|
||||||
analyze_dungeon(key_layout, world, player)
|
analyze_dungeon(key_layout, world, player)
|
||||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||||
|
world.key_layout[player][builder.name] = key_layout
|
||||||
log_key_logic(builder.name, key_layout.key_logic)
|
log_key_logic(builder.name, key_layout.key_logic)
|
||||||
# if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none' and not world.owMixed[player] and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
# if world.shuffle[player] == 'vanilla' and world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'none' and not world.owMixed[player] and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
|
||||||
# validate_vanilla_key_logic(world, player)
|
# validate_vanilla_key_logic(world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_vanilla_reservation(dungeon, world, player):
|
||||||
|
return validate_key_layout(world.key_layout[player][dungeon.name], world, player)
|
||||||
|
|
||||||
|
|
||||||
# some useful functions
|
# some useful functions
|
||||||
oppositemap = {
|
oppositemap = {
|
||||||
Direction.South: Direction.North,
|
Direction.South: Direction.North,
|
||||||
@@ -1279,6 +1289,7 @@ def refine_boss_exits(world, player):
|
|||||||
if 0 < len(filtered) < len(reachable_portals):
|
if 0 < len(filtered) < len(reachable_portals):
|
||||||
reachable_portals = filtered
|
reachable_portals = filtered
|
||||||
chosen_one = random.choice(reachable_portals) if len(reachable_portals) > 1 else reachable_portals[0]
|
chosen_one = random.choice(reachable_portals) if len(reachable_portals) > 1 else reachable_portals[0]
|
||||||
|
chosen_one.chosen = True
|
||||||
if chosen_one != current_boss:
|
if chosen_one != current_boss:
|
||||||
chosen_one.change_boss_exit(current_boss.boss_exit_idx)
|
chosen_one.change_boss_exit(current_boss.boss_exit_idx)
|
||||||
current_boss.change_boss_exit(-1)
|
current_boss.change_boss_exit(-1)
|
||||||
@@ -1369,6 +1380,8 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
|||||||
dungeon_builders[recombine.name] = recombine
|
dungeon_builders[recombine.name] = recombine
|
||||||
|
|
||||||
|
|
||||||
|
# todo: this allows cross-dungeon exploring via HC Ledge or Inaccessible Regions
|
||||||
|
# todo: @deprecated
|
||||||
def valid_region_to_explore(region, world, player):
|
def valid_region_to_explore(region, world, player):
|
||||||
return region and (region.type == RegionType.Dungeon
|
return region and (region.type == RegionType.Dungeon
|
||||||
or region.name in world.inaccessible_regions[player]
|
or region.name in world.inaccessible_regions[player]
|
||||||
@@ -1560,7 +1573,7 @@ okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.
|
|||||||
|
|
||||||
|
|
||||||
def find_key_door_candidates(region, checked, world, player):
|
def find_key_door_candidates(region, checked, world, player):
|
||||||
dungeon = region.dungeon
|
dungeon_name = region.dungeon.name
|
||||||
candidates = []
|
candidates = []
|
||||||
checked_doors = list(checked)
|
checked_doors = list(checked)
|
||||||
queue = deque([(region, None, None)])
|
queue = deque([(region, None, None)])
|
||||||
@@ -1570,14 +1583,16 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
d = ext.door
|
d = ext.door
|
||||||
if d and d.controller:
|
if d and d.controller:
|
||||||
d = d.controller
|
d = d.controller
|
||||||
if d and not d.blocked and not d.entranceFlag and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
|
if d and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
|
||||||
valid = False
|
valid = False
|
||||||
if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
|
if (0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]
|
||||||
|
and not d.entranceFlag):
|
||||||
room = world.get_room(d.roomIndex, player)
|
room = world.get_room(d.roomIndex, player)
|
||||||
position, kind = room.doorList[d.doorListPos]
|
position, kind = room.doorList[d.doorListPos]
|
||||||
|
|
||||||
if d.type == DoorType.Interior:
|
if d.type == DoorType.Interior:
|
||||||
valid = kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable]
|
valid = kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable]
|
||||||
|
if valid and d.dest not in candidates: # interior doors are not separable yet
|
||||||
|
candidates.append(d.dest)
|
||||||
elif d.type == DoorType.SpiralStairs:
|
elif d.type == DoorType.SpiralStairs:
|
||||||
valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow]
|
valid = kind in [DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow]
|
||||||
elif d.type == DoorType.Normal:
|
elif d.type == DoorType.Normal:
|
||||||
@@ -1596,7 +1611,7 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
if valid and d not in candidates:
|
if valid and d not in candidates:
|
||||||
candidates.append(d)
|
candidates.append(d)
|
||||||
connected = ext.connected_region
|
connected = ext.connected_region
|
||||||
if connected and (connected.type != RegionType.Dungeon or connected.dungeon == dungeon):
|
if valid_region_to_explore_lim(connected, dungeon_name, world, player):
|
||||||
queue.append((ext.connected_region, d, current))
|
queue.append((ext.connected_region, d, current))
|
||||||
if d is not None:
|
if d is not None:
|
||||||
checked_doors.append(d)
|
checked_doors.append(d)
|
||||||
|
|||||||
@@ -218,7 +218,8 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
|
|||||||
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
|
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
|
||||||
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
|
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
|
||||||
valid_doors, bk_flag, world, player, exception)
|
valid_doors, bk_flag, world, player, exception)
|
||||||
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception)
|
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception,
|
||||||
|
world, player)
|
||||||
either_crystal = True # if all hooks from the origin are either, explore all bits with either
|
either_crystal = True # if all hooks from the origin are either, explore all bits with either
|
||||||
for hook, crystal in dungeon['Origin'].hooks.items():
|
for hook, crystal in dungeon['Origin'].hooks.items():
|
||||||
if crystal != CrystalBarrier.Either:
|
if crystal != CrystalBarrier.Either:
|
||||||
@@ -239,7 +240,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
|
|||||||
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, all_regions,
|
o_state = extend_reachable_state_improved([parent], init_state, proposed_map, all_regions,
|
||||||
valid_doors, bk_flag, world, player, exception)
|
valid_doors, bk_flag, world, player, exception)
|
||||||
o_state_cache[door.name] = o_state
|
o_state_cache[door.name] = o_state
|
||||||
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception)
|
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map, exception, world, player)
|
||||||
dungeon[door.name] = piece
|
dungeon[door.name] = piece
|
||||||
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, all_regions, valid_doors,
|
check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, all_regions, valid_doors,
|
||||||
group_flags, door_map, world, player, exception)
|
group_flags, door_map, world, player, exception)
|
||||||
@@ -339,7 +340,7 @@ def explore_blue_state(door, dungeon, o_state, proposed_map, all_regions, valid_
|
|||||||
blue_start.big_key_special = o_state.big_key_special
|
blue_start.big_key_special = o_state.big_key_special
|
||||||
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, all_regions, valid_doors, bk_flag,
|
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, all_regions, valid_doors, bk_flag,
|
||||||
world, player, exception)
|
world, player, exception)
|
||||||
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception)
|
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player)
|
||||||
|
|
||||||
|
|
||||||
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices, name):
|
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices, name):
|
||||||
@@ -603,7 +604,7 @@ def winnow_hangers(hangers, hooks):
|
|||||||
hangers[hanger].remove(door)
|
hangers[hanger].remove(door)
|
||||||
|
|
||||||
|
|
||||||
def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception):
|
def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exception, world, player):
|
||||||
# todo: info about dungeon events - not sure about that
|
# todo: info about dungeon events - not sure about that
|
||||||
graph_piece = GraphPiece()
|
graph_piece = GraphPiece()
|
||||||
all_unattached = {}
|
all_unattached = {}
|
||||||
@@ -635,16 +636,15 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
|
|||||||
graph_piece.visited_regions.update(o_state.visited_orange)
|
graph_piece.visited_regions.update(o_state.visited_orange)
|
||||||
graph_piece.visited_regions.update(b_state.visited_blue)
|
graph_piece.visited_regions.update(b_state.visited_blue)
|
||||||
graph_piece.visited_regions.update(b_state.visited_orange)
|
graph_piece.visited_regions.update(b_state.visited_orange)
|
||||||
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found))
|
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(o_state.bk_found, world, player))
|
||||||
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found))
|
graph_piece.possible_bk_locations.update(filter_for_potential_bk_locations(b_state.bk_found, world, player))
|
||||||
graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used
|
graph_piece.pinball_used = o_state.pinball_used or b_state.pinball_used
|
||||||
return graph_piece
|
return graph_piece
|
||||||
|
|
||||||
|
|
||||||
def filter_for_potential_bk_locations(locations):
|
def filter_for_potential_bk_locations(locations, world, player):
|
||||||
return [x for x in locations if
|
return [x for x in locations if '- Big Chest' not in x.name and not reserved_location(x, world, player) and
|
||||||
'- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events
|
not x.forced_item and not prize_or_event(x) and not blind_boss_unavail(x, locations, world, player)]
|
||||||
and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']]
|
|
||||||
|
|
||||||
|
|
||||||
type_map = {
|
type_map = {
|
||||||
@@ -987,12 +987,8 @@ class ExplorationState(object):
|
|||||||
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
|
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def count_locations_exclude_specials(self):
|
def count_locations_exclude_specials(self, world, player):
|
||||||
cnt = 0
|
return count_locations_exclude_big_chest(self.found_locations, world, player)
|
||||||
for loc in self.found_locations:
|
|
||||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
|
|
||||||
cnt += 1
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
def validate(self, door, region, world, player):
|
def validate(self, door, region, world, player):
|
||||||
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
|
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
|
||||||
@@ -1033,6 +1029,32 @@ class ExplorationState(object):
|
|||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def count_locations_exclude_big_chest(locations, world, player):
|
||||||
|
cnt = 0
|
||||||
|
for loc in locations:
|
||||||
|
if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player)
|
||||||
|
and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)):
|
||||||
|
cnt += 1
|
||||||
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
|
def prize_or_event(loc):
|
||||||
|
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
|
||||||
|
|
||||||
|
|
||||||
|
def reserved_location(loc, world, player):
|
||||||
|
return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player]
|
||||||
|
|
||||||
|
|
||||||
|
def blind_boss_unavail(loc, locations, world, player):
|
||||||
|
if loc.name == "Thieves' Town - Boss":
|
||||||
|
return (loc.parent_region.dungeon.boss.name == 'Blind' and
|
||||||
|
(not any(x for x in locations if x.name == 'Suspicious Maiden') or
|
||||||
|
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
|
||||||
|
not any(x for x in locations if x.name == 'Attic Cracked Floor'))))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ExplorableDoor(object):
|
class ExplorableDoor(object):
|
||||||
|
|
||||||
def __init__(self, door, crystal, flag):
|
def __init__(self, door, crystal, flag):
|
||||||
@@ -1056,7 +1078,8 @@ def extend_reachable_state_improved(search_regions, state, proposed_map, all_reg
|
|||||||
explorable_door = local_state.next_avail_door()
|
explorable_door = local_state.next_avail_door()
|
||||||
if explorable_door.door.bigKey:
|
if explorable_door.door.bigKey:
|
||||||
if bk_flag:
|
if bk_flag:
|
||||||
big_not_found = not special_big_key_found(local_state) if local_state.big_key_special else local_state.count_locations_exclude_specials() == 0
|
big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special
|
||||||
|
else local_state.count_locations_exclude_specials(world, player) == 0)
|
||||||
if big_not_found:
|
if big_not_found:
|
||||||
continue # we can't open this door
|
continue # we can't open this door
|
||||||
if explorable_door.door in proposed_map:
|
if explorable_door.door in proposed_map:
|
||||||
@@ -1139,6 +1162,8 @@ class DungeonBuilder(object):
|
|||||||
self.sectors = []
|
self.sectors = []
|
||||||
self.location_cnt = 0
|
self.location_cnt = 0
|
||||||
self.key_drop_cnt = 0
|
self.key_drop_cnt = 0
|
||||||
|
self.dungeon_items = None # during fill how many dungeon items are left
|
||||||
|
self.free_items = None # during fill how many dungeon items are left
|
||||||
self.bk_required = False
|
self.bk_required = False
|
||||||
self.bk_provided = False
|
self.bk_provided = False
|
||||||
self.c_switch_required = False
|
self.c_switch_required = False
|
||||||
@@ -1301,7 +1326,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
polarized_sectors[sector] = None
|
polarized_sectors[sector] = None
|
||||||
if bow_sectors:
|
if bow_sectors:
|
||||||
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
||||||
assign_location_sectors(dungeon_map, free_location_sectors, global_pole)
|
assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player)
|
||||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
||||||
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
||||||
for sector in leftover:
|
for sector in leftover:
|
||||||
@@ -1452,6 +1477,7 @@ def define_sector_features(sectors):
|
|||||||
sector.bk_provided = True
|
sector.bk_provided = True
|
||||||
elif loc.name not in dungeon_events and not loc.forced_item:
|
elif loc.name not in dungeon_events and not loc.forced_item:
|
||||||
sector.chest_locations += 1
|
sector.chest_locations += 1
|
||||||
|
sector.chest_location_set.add(loc.name)
|
||||||
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
|
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
|
||||||
"Thieves' Town - Blind's Cell"]:
|
"Thieves' Town - Blind's Cell"]:
|
||||||
sector.bk_required = True
|
sector.bk_required = True
|
||||||
@@ -1532,19 +1558,26 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole):
|
|||||||
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
||||||
|
|
||||||
|
|
||||||
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole):
|
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player):
|
||||||
valid = False
|
valid = False
|
||||||
choices = None
|
choices = None
|
||||||
sector_list = list(free_location_sectors)
|
sector_list = list(free_location_sectors)
|
||||||
random.shuffle(sector_list)
|
random.shuffle(sector_list)
|
||||||
|
orig_location_set = build_orig_location_set(dungeon_map)
|
||||||
|
num_dungeon_items = requested_dungeon_items(world, player)
|
||||||
while not valid:
|
while not valid:
|
||||||
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
|
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
|
||||||
|
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
||||||
for i, sector in enumerate(sector_list):
|
for i, sector in enumerate(sector_list):
|
||||||
choice = d_idx[choices[i].name]
|
d_name = choices[i].name
|
||||||
|
choice = d_idx[d_name]
|
||||||
totals[choice] += sector.chest_locations
|
totals[choice] += sector.chest_locations
|
||||||
|
location_set[d_name].update(sector.chest_location_set)
|
||||||
valid = True
|
valid = True
|
||||||
for d_name, idx in d_idx.items():
|
for d_name, idx in d_idx.items():
|
||||||
if totals[idx] < 5: # min locations for dungeons is 5 (bk exception)
|
free_items = count_reserved_locations(world, player, location_set[d_name])
|
||||||
|
target = max(free_items, 2) + num_dungeon_items
|
||||||
|
if totals[idx] < target:
|
||||||
valid = False
|
valid = False
|
||||||
break
|
break
|
||||||
for i, choice in enumerate(choices):
|
for i, choice in enumerate(choices):
|
||||||
@@ -1575,6 +1608,30 @@ def weighted_random_locations(dungeon_map, free_location_sectors):
|
|||||||
return choices, d_idx, totals
|
return choices, d_idx, totals
|
||||||
|
|
||||||
|
|
||||||
|
def build_orig_location_set(dungeon_map):
|
||||||
|
orig_locations = {}
|
||||||
|
for name, builder in dungeon_map.items():
|
||||||
|
orig_locations[name] = set().union(*(s.chest_location_set for s in builder.sectors))
|
||||||
|
return orig_locations
|
||||||
|
|
||||||
|
|
||||||
|
def requested_dungeon_items(world, player):
|
||||||
|
num = 0
|
||||||
|
if not world.bigkeyshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
if not world.compassshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
if not world.mapshuffle[player]:
|
||||||
|
num += 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
def count_reserved_locations(world, player, proposed_set):
|
||||||
|
if world.item_pool_config:
|
||||||
|
return len([x for x in proposed_set if x in world.item_pool_config.reserved_locations[player]])
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
|
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
|
||||||
population = []
|
population = []
|
||||||
some_c_switches_present = False
|
some_c_switches_present = False
|
||||||
|
|||||||
149
Dungeons.py
149
Dungeons.py
@@ -1,8 +1,5 @@
|
|||||||
import RaceRandom as random
|
|
||||||
|
|
||||||
from BaseClasses import Dungeon
|
from BaseClasses import Dungeon
|
||||||
from Bosses import BossFactory
|
from Bosses import BossFactory
|
||||||
from Fill import fill_restrictive
|
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
@@ -36,119 +33,6 @@ def create_dungeons(world, player):
|
|||||||
|
|
||||||
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
|
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
|
||||||
|
|
||||||
def fill_dungeons(world):
|
|
||||||
freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest']
|
|
||||||
|
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
|
||||||
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
if world.retro[player]:
|
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
else:
|
|
||||||
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
pinball_room.event = True
|
|
||||||
pinball_room.locked = True
|
|
||||||
|
|
||||||
dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons]
|
|
||||||
|
|
||||||
loopcnt = 0
|
|
||||||
while dungeons:
|
|
||||||
loopcnt += 1
|
|
||||||
dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
|
|
||||||
# this is what we need to fill
|
|
||||||
dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions]
|
|
||||||
random.shuffle(dungeon_locations)
|
|
||||||
|
|
||||||
all_state = all_state_base.copy()
|
|
||||||
|
|
||||||
# first place big key
|
|
||||||
if big_key is not None:
|
|
||||||
bk_location = None
|
|
||||||
for location in dungeon_locations:
|
|
||||||
if location.item_rule(big_key):
|
|
||||||
bk_location = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if bk_location is None:
|
|
||||||
raise RuntimeError('No suitable location for %s' % big_key)
|
|
||||||
|
|
||||||
world.push_item(bk_location, big_key, False)
|
|
||||||
bk_location.event = True
|
|
||||||
bk_location.locked = True
|
|
||||||
dungeon_locations.remove(bk_location)
|
|
||||||
big_key = None
|
|
||||||
|
|
||||||
# next place small keys
|
|
||||||
while small_keys:
|
|
||||||
small_key = small_keys.pop()
|
|
||||||
all_state.sweep_for_events()
|
|
||||||
sk_location = None
|
|
||||||
for location in dungeon_locations:
|
|
||||||
if location.name in freebes or (location.can_reach(all_state) and location.item_rule(small_key)):
|
|
||||||
sk_location = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if sk_location is None:
|
|
||||||
# need to retry this later
|
|
||||||
small_keys.append(small_key)
|
|
||||||
dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items))
|
|
||||||
# infinite regression protection
|
|
||||||
if loopcnt < (30 * world.players):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('No suitable location for %s' % small_key)
|
|
||||||
|
|
||||||
world.push_item(sk_location, small_key, False)
|
|
||||||
sk_location.event = True
|
|
||||||
sk_location.locked = True
|
|
||||||
dungeon_locations.remove(sk_location)
|
|
||||||
|
|
||||||
if small_keys:
|
|
||||||
# key placement not finished, loop again
|
|
||||||
continue
|
|
||||||
|
|
||||||
# next place dungeon items
|
|
||||||
for dungeon_item in dungeon_items:
|
|
||||||
di_location = dungeon_locations.pop()
|
|
||||||
world.push_item(di_location, dungeon_item, False)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
|
||||||
return [item for dungeon in world.dungeons for item in dungeon.all_items]
|
|
||||||
|
|
||||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
# for player in range(1, world.players + 1):
|
|
||||||
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
# if world.retro[player]:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
# else:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
# pinball_room.event = True
|
|
||||||
# pinball_room.locked = True
|
|
||||||
# shuffled_locations.remove(pinball_room)
|
|
||||||
|
|
||||||
# with shuffled dungeon items they are distributed as part of the normal item pool
|
|
||||||
for item in world.get_items():
|
|
||||||
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
|
||||||
item.advancement = True
|
|
||||||
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
|
||||||
item.priority = True
|
|
||||||
|
|
||||||
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_order = {"BigKey": 3, "SmallKey": 2}
|
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
|
||||||
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, single_player_placement=True)
|
|
||||||
|
|
||||||
|
|
||||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||||
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
|
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
|
||||||
@@ -378,8 +262,8 @@ flexible_starts = {
|
|||||||
|
|
||||||
class DungeonInfo:
|
class DungeonInfo:
|
||||||
|
|
||||||
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize=None):
|
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx):
|
||||||
# todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
|
# todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
|
||||||
self.free_items = free
|
self.free_items = free
|
||||||
self.key_num = keys
|
self.key_num = keys
|
||||||
self.bk_present = bk
|
self.bk_present = bk
|
||||||
@@ -389,21 +273,23 @@ class DungeonInfo:
|
|||||||
self.key_drops = drops
|
self.key_drops = drops
|
||||||
self.prize = prize
|
self.prize = prize
|
||||||
|
|
||||||
|
self.map_index = midx
|
||||||
|
|
||||||
|
|
||||||
dungeon_table = {
|
dungeon_table = {
|
||||||
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None),
|
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0xc),
|
||||||
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize'),
|
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize', 0x0),
|
||||||
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize'),
|
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize', 0x2),
|
||||||
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize'),
|
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize', 0x1),
|
||||||
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None),
|
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0xb),
|
||||||
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize'),
|
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize', 0x3),
|
||||||
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize'),
|
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize', 0x9),
|
||||||
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize'),
|
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize', 0x4),
|
||||||
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize"),
|
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize", 0x6),
|
||||||
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize'),
|
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize', 0x8),
|
||||||
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize'),
|
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize', 0x7),
|
||||||
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize'),
|
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize', 0x5),
|
||||||
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None),
|
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0xa),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -439,7 +325,6 @@ dungeon_bigs = {
|
|||||||
'Ganons Tower': 'Big Key (Ganons Tower)'
|
'Ganons Tower': 'Big Key (Ganons Tower)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dungeon_hints = {
|
dungeon_hints = {
|
||||||
'Hyrule Castle': 'in Hyrule Castle',
|
'Hyrule Castle': 'in Hyrule Castle',
|
||||||
'Eastern Palace': 'in Eastern Palace',
|
'Eastern Palace': 'in Eastern Palace',
|
||||||
|
|||||||
@@ -2611,3 +2611,135 @@ exit_ids = {'Links House Exit': (0x01, 0x00),
|
|||||||
'Skull Pinball': 0x78,
|
'Skull Pinball': 0x78,
|
||||||
'Skull Pot Circle': 0x76,
|
'Skull Pot Circle': 0x76,
|
||||||
'Pyramid': 0x7B}
|
'Pyramid': 0x7B}
|
||||||
|
|
||||||
|
ow_prize_table = {'Links House': (0x8b1, 0xb2d),
|
||||||
|
'Desert Palace Entrance (South)': (0x108, 0xd70), 'Desert Palace Entrance (West)': (0x031, 0xca0),
|
||||||
|
'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0),
|
||||||
|
'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080),
|
||||||
|
'Hyrule Castle Entrance (South)': (0x7b0, 0x730), 'Hyrule Castle Entrance (West)': (0x700, 0x640),
|
||||||
|
'Hyrule Castle Entrance (East)': (0x8a0, 0x640), 'Inverted Pyramid Entrance': (0x720, 0x700),
|
||||||
|
'Agahnims Tower': (0x7e0, 0x640),
|
||||||
|
'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x240, 0x280),
|
||||||
|
'Skull Woods Second Section Door (East)': (0x1a0, 0x240),
|
||||||
|
'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), 'Skull Woods Final Section': (0x082, 0x0b0),
|
||||||
|
'Ice Palace': (0xca0, 0xda0),
|
||||||
|
'Misery Mire': (0x100, 0xca0),
|
||||||
|
'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0),
|
||||||
|
'Turtle Rock': (0xf11, 0x103),
|
||||||
|
'Dark Death Mountain Ledge (West)': (0xb80, 0x180),
|
||||||
|
'Dark Death Mountain Ledge (East)': (0xc80, 0x180),
|
||||||
|
'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240),
|
||||||
|
'Hyrule Castle Secret Entrance Stairs': (0x850, 0x700),
|
||||||
|
'Kakariko Well Cave': (0x060, 0x680),
|
||||||
|
'Bat Cave Cave': (0x540, 0x8f0),
|
||||||
|
'Elder House (East)': (0x2b0, 0x6a0),
|
||||||
|
'Elder House (West)': (0x230, 0x6a0),
|
||||||
|
'North Fairy Cave': (0xa80, 0x440),
|
||||||
|
'Lost Woods Hideout Stump': (0x240, 0x280),
|
||||||
|
'Lumberjack Tree Cave': (0x4e0, 0x004),
|
||||||
|
'Two Brothers House (East)': (0x200, 0x0b60),
|
||||||
|
'Two Brothers House (West)': (0x180, 0x0b60),
|
||||||
|
'Sanctuary': (0x720, 0x4a0),
|
||||||
|
'Old Man Cave (West)': (0x580, 0x2c0),
|
||||||
|
'Old Man Cave (East)': (0x620, 0x2c0),
|
||||||
|
'Old Man House (Bottom)': (0x720, 0x320),
|
||||||
|
'Old Man House (Top)': (0x820, 0x220),
|
||||||
|
'Death Mountain Return Cave (East)': (0x600, 0x220),
|
||||||
|
'Death Mountain Return Cave (West)': (0x500, 0x1c0),
|
||||||
|
'Spectacle Rock Cave Peak': (0x720, 0x0a0),
|
||||||
|
'Spectacle Rock Cave': (0x790, 0x1a0),
|
||||||
|
'Spectacle Rock Cave (Bottom)': (0x710, 0x0a0),
|
||||||
|
'Paradox Cave (Bottom)': (0xd80, 0x180),
|
||||||
|
'Paradox Cave (Middle)': (0xd80, 0x380),
|
||||||
|
'Paradox Cave (Top)': (0xd80, 0x020),
|
||||||
|
'Fairy Ascension Cave (Bottom)': (0xcc8, 0x2a0),
|
||||||
|
'Fairy Ascension Cave (Top)': (0xc00, 0x240),
|
||||||
|
'Spiral Cave': (0xb80, 0x180),
|
||||||
|
'Spiral Cave (Bottom)': (0xb80, 0x2c0),
|
||||||
|
'Bumper Cave (Bottom)': (0x580, 0x2c0),
|
||||||
|
'Bumper Cave (Top)': (0x500, 0x1c0),
|
||||||
|
'Superbunny Cave (Top)': (0xd80, 0x020),
|
||||||
|
'Superbunny Cave (Bottom)': (0xd00, 0x180),
|
||||||
|
'Hookshot Cave': (0xc80, 0x0c0),
|
||||||
|
'Hookshot Cave Back Entrance': (0xcf0, 0x004),
|
||||||
|
'Ganons Tower': (0x8D0, 0x080),
|
||||||
|
'Pyramid Entrance': (0x640, 0x7c0),
|
||||||
|
'Skull Woods First Section Hole (West)': None,
|
||||||
|
'Skull Woods First Section Hole (East)': None,
|
||||||
|
'Skull Woods First Section Hole (North)': None,
|
||||||
|
'Skull Woods Second Section Hole': None,
|
||||||
|
'Pyramid Hole': None,
|
||||||
|
'Inverted Pyramid Hole': None,
|
||||||
|
'Waterfall of Wishing': (0xe80, 0x280),
|
||||||
|
'Dam': (0x759, 0xED0),
|
||||||
|
'Blinds Hideout': (0x190, 0x6c0),
|
||||||
|
'Hyrule Castle Secret Entrance Drop': None,
|
||||||
|
'Bonk Fairy (Light)': (0x740, 0xa80),
|
||||||
|
'Lake Hylia Fairy': (0xd40, 0x9f0),
|
||||||
|
'Light Hype Fairy': (0x940, 0xc80),
|
||||||
|
'Desert Fairy': (0x420, 0xe00),
|
||||||
|
'Kings Grave': (0x920, 0x520),
|
||||||
|
'Tavern North': None, # can't mark this one technically
|
||||||
|
'Chicken House': (0x120, 0x880),
|
||||||
|
'Aginahs Cave': (0x2e0, 0xd00),
|
||||||
|
'Sahasrahlas Hut': (0xcf0, 0x6c0),
|
||||||
|
'Cave Shop (Lake Hylia)': (0xbc0, 0xc00),
|
||||||
|
'Capacity Upgrade': (0xca0, 0xda0),
|
||||||
|
'Kakariko Well Drop': None,
|
||||||
|
'Blacksmiths Hut': (0x4a0, 0x880),
|
||||||
|
'Bat Cave Drop': None,
|
||||||
|
'Sick Kids House': (0x220, 0x880),
|
||||||
|
'North Fairy Cave Drop': None,
|
||||||
|
'Lost Woods Gamble': (0x240, 0x080),
|
||||||
|
'Fortune Teller (Light)': (0x2c0, 0x4c0),
|
||||||
|
'Snitch Lady (East)': (0x310, 0x7a0),
|
||||||
|
'Snitch Lady (West)': (0x800, 0x7a0),
|
||||||
|
'Bush Covered House': (0x2e0, 0x880),
|
||||||
|
'Tavern (Front)': (0x270, 0x980),
|
||||||
|
'Light World Bomb Hut': (0x070, 0x980),
|
||||||
|
'Kakariko Shop': (0x170, 0x980),
|
||||||
|
'Lost Woods Hideout Drop': None,
|
||||||
|
'Lumberjack Tree Tree': None,
|
||||||
|
'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430),
|
||||||
|
'Checkerboard Cave': (0x260, 0xc00),
|
||||||
|
'Mini Moldorm Cave': (0xa40, 0xe80),
|
||||||
|
'Long Fairy Cave': (0xf60, 0xb00),
|
||||||
|
'Good Bee Cave': (0xec0, 0xc00),
|
||||||
|
'20 Rupee Cave': (0xe80, 0xca0),
|
||||||
|
'50 Rupee Cave': (0x4d0, 0xed0),
|
||||||
|
'Ice Rod Cave': (0xe00, 0xc00),
|
||||||
|
'Bonk Rock Cave': (0x5f0, 0x460),
|
||||||
|
'Library': (0x270, 0xaa0),
|
||||||
|
'Potion Shop': (0xc80, 0x4c0),
|
||||||
|
'Sanctuary Grave': None,
|
||||||
|
'Hookshot Fairy': (0xd00, 0x180),
|
||||||
|
'Pyramid Fairy': (0x740, 0x740),
|
||||||
|
'East Dark World Hint': (0xf60, 0xb00),
|
||||||
|
'Palace of Darkness Hint': (0xd60, 0x7c0),
|
||||||
|
'Dark Lake Hylia Fairy': (0xd40, 0x9f0),
|
||||||
|
'Dark Lake Hylia Ledge Fairy': (0xe00, 0xc00),
|
||||||
|
'Dark Lake Hylia Ledge Spike Cave': (0xe80, 0xca0),
|
||||||
|
'Dark Lake Hylia Ledge Hint': (0xec0, 0xc00),
|
||||||
|
'Hype Cave': (0x940, 0xc80),
|
||||||
|
'Bonk Fairy (Dark)': (0x740, 0xa80),
|
||||||
|
'Brewery': (0x170, 0x980), 'C-Shaped House': (0x310, 0x7a0), 'Chest Game': (0x800, 0x7a0),
|
||||||
|
'Dark World Hammer Peg Cave': (0x4c0, 0x940),
|
||||||
|
'Red Shield Shop': (0x500, 0x680),
|
||||||
|
'Dark Sanctuary Hint': (0x720, 0x4a0),
|
||||||
|
'Fortune Teller (Dark)': (0x2c0, 0x4c0),
|
||||||
|
'Dark World Shop': (0x2e0, 0x880),
|
||||||
|
'Dark World Lumberjack Shop': (0x4e0, 0x0d0),
|
||||||
|
'Dark World Potion Shop': (0xc80, 0x4c0),
|
||||||
|
'Archery Game': (0x2f0, 0xaf0),
|
||||||
|
'Mire Shed': (0x060, 0xc90),
|
||||||
|
'Dark Desert Hint': (0x2e0, 0xd00),
|
||||||
|
'Dark Desert Fairy': (0x1c0, 0xc90),
|
||||||
|
'Spike Cave': (0x860, 0x180),
|
||||||
|
'Cave Shop (Dark Death Mountain)': (0xd80, 0x180),
|
||||||
|
'Dark Death Mountain Fairy': (0x620, 0x2c0),
|
||||||
|
'Mimic Cave': (0xc80, 0x180),
|
||||||
|
'Big Bomb Shop': (0x8b1, 0xb2d),
|
||||||
|
'Dark Lake Hylia Shop': (0xa40, 0xc40),
|
||||||
|
'Lumberjack House': (0x4e0, 0x0d0),
|
||||||
|
'Lake Hylia Fortune Teller': (0xa40, 0xc40),
|
||||||
|
'Kakariko Gamble Game': (0x2f0, 0xaf0)}
|
||||||
|
|||||||
499
Fill.py
499
Fill.py
@@ -3,173 +3,68 @@ import collections
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState, FillError
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Regions import shop_to_location_table, retro_shops
|
from Regions import shop_to_location_table, retro_shops
|
||||||
|
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
|
||||||
|
|
||||||
|
|
||||||
class FillError(RuntimeError):
|
def get_dungeon_item_pool(world):
|
||||||
pass
|
return [item for dungeon in world.dungeons for item in dungeon.all_items]
|
||||||
|
|
||||||
def distribute_items_cutoff(world, cutoffrate=0.33):
|
|
||||||
# get list of locations to fill in
|
|
||||||
fill_locations = world.get_unfilled_locations()
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
|
|
||||||
# get items to distribute
|
|
||||||
random.shuffle(world.itempool)
|
|
||||||
itempool = world.itempool
|
|
||||||
|
|
||||||
total_advancement_items = len([item for item in itempool if item.advancement])
|
|
||||||
placed_advancement_items = 0
|
|
||||||
|
|
||||||
progress_done = False
|
|
||||||
advancement_placed = False
|
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
|
||||||
world.state.sweep_for_events()
|
|
||||||
|
|
||||||
while itempool and fill_locations:
|
|
||||||
candidate_item_to_place = None
|
|
||||||
item_to_place = None
|
|
||||||
for item in itempool:
|
|
||||||
if advancement_placed or (progress_done and (item.advancement or item.priority)):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
if item.advancement:
|
|
||||||
candidate_item_to_place = item
|
|
||||||
if world.unlocks_new_location(item):
|
|
||||||
item_to_place = item
|
|
||||||
placed_advancement_items += 1
|
|
||||||
break
|
|
||||||
|
|
||||||
if item_to_place is None:
|
|
||||||
# check if we can reach all locations and that is why we find no new locations to place
|
|
||||||
if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
# check if we have now placed all advancement items
|
|
||||||
if progress_done:
|
|
||||||
advancement_placed = True
|
|
||||||
continue
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
|
||||||
if candidate_item_to_place is not None:
|
|
||||||
item_to_place = candidate_item_to_place
|
|
||||||
placed_advancement_items += 1
|
|
||||||
else:
|
|
||||||
# we placed all available progress items. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
raise FillError('No more progress items left to place.')
|
|
||||||
|
|
||||||
spot_to_fill = None
|
|
||||||
for location in fill_locations if placed_advancement_items / total_advancement_items < cutoffrate else reversed(fill_locations):
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
|
||||||
break
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, True)
|
|
||||||
itempool.remove(item_to_place)
|
|
||||||
fill_locations.remove(spot_to_fill)
|
|
||||||
unplaced = [item.name for item in itempool]
|
|
||||||
unfilled = [location.name for location in fill_locations]
|
|
||||||
if unplaced or unfilled:
|
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_staleness(world):
|
def promote_dungeon_items(world):
|
||||||
# get list of locations to fill in
|
world.itempool += get_dungeon_item_pool(world)
|
||||||
fill_locations = world.get_unfilled_locations()
|
|
||||||
random.shuffle(fill_locations)
|
|
||||||
|
|
||||||
# get items to distribute
|
for item in world.get_items():
|
||||||
random.shuffle(world.itempool)
|
if item.smallkey or item.bigkey:
|
||||||
itempool = world.itempool
|
item.advancement = True
|
||||||
|
elif item.map or item.compass:
|
||||||
|
item.priority = True
|
||||||
|
dungeon_tracking(world)
|
||||||
|
|
||||||
progress_done = False
|
|
||||||
advancement_placed = False
|
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
def dungeon_tracking(world):
|
||||||
world.state.sweep_for_events()
|
for dungeon in world.dungeons:
|
||||||
|
layout = world.dungeon_layouts[dungeon.player][dungeon.name]
|
||||||
|
layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)])
|
||||||
|
layout.free_items = layout.location_cnt - layout.dungeon_items
|
||||||
|
|
||||||
while itempool and fill_locations:
|
|
||||||
candidate_item_to_place = None
|
|
||||||
item_to_place = None
|
|
||||||
for item in itempool:
|
|
||||||
if advancement_placed or (progress_done and (item.advancement or item.priority)):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
if item.advancement:
|
|
||||||
candidate_item_to_place = item
|
|
||||||
if world.unlocks_new_location(item):
|
|
||||||
item_to_place = item
|
|
||||||
break
|
|
||||||
|
|
||||||
if item_to_place is None:
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
# check if we can reach all locations and that is why we find no new locations to place
|
dungeon_tracking(world)
|
||||||
if not progress_done and len(world.get_reachable_locations()) == len(world.get_locations()):
|
all_state_base = world.get_all_state()
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
# check if we have now placed all advancement items
|
|
||||||
if progress_done:
|
|
||||||
advancement_placed = True
|
|
||||||
continue
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
|
||||||
if candidate_item_to_place is not None:
|
|
||||||
item_to_place = candidate_item_to_place
|
|
||||||
else:
|
|
||||||
# we placed all available progress items. Maybe the game can be beaten anyway?
|
|
||||||
if world.can_beat_game():
|
|
||||||
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
raise FillError('No more progress items left to place.')
|
|
||||||
|
|
||||||
spot_to_fill = None
|
# for player in range(1, world.players + 1):
|
||||||
for location in fill_locations:
|
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
||||||
# increase likelyhood of skipping a location if it has been found stale
|
# if world.retro[player]:
|
||||||
if not progress_done and random.randint(0, location.staleness_count) > 2:
|
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
||||||
continue
|
# else:
|
||||||
|
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
||||||
|
# pinball_room.event = True
|
||||||
|
# pinball_room.locked = True
|
||||||
|
# shuffled_locations.remove(pinball_room)
|
||||||
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
spot_to_fill = location
|
for item in world.get_items():
|
||||||
break
|
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
||||||
else:
|
item.advancement = True
|
||||||
location.staleness_count += 1
|
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
||||||
|
item.priority = True
|
||||||
|
|
||||||
# might have skipped too many locations due to potential staleness. Do not check for staleness now to find a candidate
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
||||||
if spot_to_fill is None:
|
|
||||||
for location in fill_locations:
|
|
||||||
if location.can_fill(world.state, item_to_place):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
if world.can_beat_game():
|
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
||||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
|
||||||
break
|
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, True)
|
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
||||||
itempool.remove(item_to_place)
|
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)},
|
||||||
fill_locations.remove(spot_to_fill)
|
single_player_placement=True)
|
||||||
|
|
||||||
unplaced = [item.name for item in itempool]
|
|
||||||
unfilled = [location.name for location in fill_locations]
|
|
||||||
if unplaced or unfilled:
|
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False):
|
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
|
||||||
|
vanilla=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:
|
||||||
@@ -201,43 +96,63 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
|
|||||||
|
|
||||||
spot_to_fill = None
|
spot_to_fill = None
|
||||||
|
|
||||||
for location in locations:
|
item_locations = filter_locations(item_to_place, locations, world, vanilla)
|
||||||
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
for location in item_locations:
|
||||||
location.item = item_to_place
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
||||||
test_state = maximum_exploration_state.copy()
|
single_player_placement, perform_access_check, itempool,
|
||||||
test_state.stale[item_to_place.player] = True
|
keys_in_itempool, world)
|
||||||
else:
|
if spot_to_fill:
|
||||||
test_state = maximum_exploration_state
|
|
||||||
if (not single_player_placement or location.player == item_to_place.player)\
|
|
||||||
and location.can_fill(test_state, item_to_place, perform_access_check)\
|
|
||||||
and valid_key_placement(item_to_place, location, itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
break
|
||||||
if item_to_place.smallkey or item_to_place.bigkey:
|
|
||||||
location.item = None
|
|
||||||
|
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
if vanilla:
|
||||||
unplaced_items.insert(0, item_to_place)
|
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
|
continue
|
||||||
spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state,
|
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
||||||
base_state, itempool, keys_in_itempool, single_player_placement)
|
base_state, itempool, perform_access_check, item_locations,
|
||||||
|
keys_in_itempool, single_player_placement)
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
|
unplaced_items.insert(0, item_to_place)
|
||||||
|
if world.can_beat_game():
|
||||||
|
if world.accessibility[item_to_place.player] != 'none':
|
||||||
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
|
||||||
|
f' (Could not place {item_to_place})')
|
||||||
|
continue
|
||||||
raise FillError('No more spots to place %s' % item_to_place)
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||||
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
spot_to_fill.event = True
|
spot_to_fill.event = True
|
||||||
|
|
||||||
itempool.extend(unplaced_items)
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
||||||
|
itempool, keys_in_itempool, world):
|
||||||
|
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
||||||
|
location.item = item_to_place
|
||||||
|
test_state = max_exp_state.copy()
|
||||||
|
test_state.stale[item_to_place.player] = True
|
||||||
|
else:
|
||||||
|
test_state = max_exp_state
|
||||||
|
if not single_player_placement or location.player == item_to_place.player:
|
||||||
|
if location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
|
test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool
|
||||||
|
if valid_key_placement(item_to_place, location, test_pool, world):
|
||||||
|
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
||||||
|
return location
|
||||||
|
if item_to_place.smallkey or item_to_place.bigkey:
|
||||||
|
location.item = None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def valid_key_placement(item, location, itempool, world):
|
def valid_key_placement(item, location, itempool, world):
|
||||||
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic':
|
if not valid_reserved_placement(item, location, world):
|
||||||
|
return False
|
||||||
|
if ((not item.smallkey and not item.bigkey) or item.player != location.player
|
||||||
|
or world.retro[item.player] or world.logic[item.player] == 'nologic'):
|
||||||
return True
|
return True
|
||||||
dungeon = location.parent_region.dungeon
|
dungeon = location.parent_region.dungeon
|
||||||
if dungeon:
|
if dungeon:
|
||||||
@@ -251,9 +166,24 @@ def valid_key_placement(item, location, itempool, world):
|
|||||||
cr_count = world.crystals_needed_for_gt[location.player]
|
cr_count = world.crystals_needed_for_gt[location.player]
|
||||||
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count)
|
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count)
|
||||||
else:
|
else:
|
||||||
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
return not item.is_inside_dungeon_item(world)
|
||||||
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
|
||||||
return not inside_dungeon_item
|
|
||||||
|
def valid_reserved_placement(item, location, world):
|
||||||
|
if item.player == location.player and item.is_inside_dungeon_item(world):
|
||||||
|
return location.name not in world.item_pool_config.reserved_locations[location.player]
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def valid_dungeon_placement(item, location, world):
|
||||||
|
if location.parent_region.dungeon:
|
||||||
|
layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
|
||||||
|
if not is_dungeon_item(item, world) or item.player != location.player:
|
||||||
|
return layout.free_items > 0
|
||||||
|
else:
|
||||||
|
# the second half probably doesn't matter much - should always return true
|
||||||
|
return item.dungeon == location.parent_region.dungeon.name and layout.dungeon_items > 0
|
||||||
|
return not is_dungeon_item(item, world)
|
||||||
|
|
||||||
|
|
||||||
def track_outside_keys(item, location, world):
|
def track_outside_keys(item, location, world):
|
||||||
@@ -267,6 +197,72 @@ def track_outside_keys(item, location, world):
|
|||||||
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
||||||
|
|
||||||
|
|
||||||
|
def track_dungeon_items(item, location, world):
|
||||||
|
if location.parent_region.dungeon and not item.crystal:
|
||||||
|
layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
|
||||||
|
if is_dungeon_item(item, world) and item.player == location.player:
|
||||||
|
layout.dungeon_items -= 1
|
||||||
|
else:
|
||||||
|
layout.free_items -= 1
|
||||||
|
|
||||||
|
|
||||||
|
def is_dungeon_item(item, world):
|
||||||
|
return ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
|
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||||
|
or (item.compass and not world.compassshuffle[item.player])
|
||||||
|
or (item.map and not world.mapshuffle[item.player]))
|
||||||
|
|
||||||
|
|
||||||
|
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
||||||
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
|
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
|
||||||
|
if world.algorithm in ['balanced', 'equitable']:
|
||||||
|
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
|
||||||
|
single_player_placement)
|
||||||
|
elif world.algorithm == 'vanilla_fill':
|
||||||
|
if item_to_place.type == 'Crystal':
|
||||||
|
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
||||||
|
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool, single_player_placement)
|
||||||
|
else:
|
||||||
|
i, config = 0, world.item_pool_config
|
||||||
|
tried = set(attempted)
|
||||||
|
if not item_to_place.is_inside_dungeon_item(world):
|
||||||
|
while i < len(config.location_groups[item_to_place.player]):
|
||||||
|
fallback_locations = config.location_groups[item_to_place.player][i].locations
|
||||||
|
other_locs = [x for x in locations if x.name in fallback_locations]
|
||||||
|
for location in other_locs:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
i += 1
|
||||||
|
tried.update(other_locs)
|
||||||
|
else:
|
||||||
|
other_locations = vanilla_fallback(item_to_place, locations, world)
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
tried.update(other_locations)
|
||||||
|
other_locations = [x for x in locations if x not in tried]
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
other_locations = [x for x in locations if x not in attempted]
|
||||||
|
for location in other_locations:
|
||||||
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
|
perform_access_check, itempool, keys_in_itempool, world)
|
||||||
|
if spot_to_fill:
|
||||||
|
return spot_to_fill
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
def location_preference(loc):
|
def location_preference(loc):
|
||||||
@@ -285,7 +281,12 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
|
|||||||
possible_swaps = [x for x in state.locations_checked
|
possible_swaps = [x for x in state.locations_checked
|
||||||
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
||||||
swap_locations = sorted(possible_swaps, key=location_preference)
|
swap_locations = sorted(possible_swaps, key=location_preference)
|
||||||
|
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool, single_player_placement)
|
||||||
|
|
||||||
|
|
||||||
|
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
|
keys_in_itempool=None, single_player_placement=False):
|
||||||
for location in swap_locations:
|
for location in swap_locations:
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
new_pool = list(itempool) + [old_item]
|
new_pool = list(itempool) + [old_item]
|
||||||
@@ -348,17 +349,21 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
|
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
|
classify_major_items(world)
|
||||||
random.shuffle(world.itempool)
|
random.shuffle(world.itempool)
|
||||||
progitempool = [item for item in world.itempool if item.advancement]
|
progitempool = [item for item in world.itempool if item.advancement]
|
||||||
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
||||||
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
|
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
|
||||||
|
|
||||||
|
gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_only']
|
||||||
|
# dungeon only may fill up the dungeon... and push items out into the overworld
|
||||||
|
|
||||||
# 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 not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
|
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
|
||||||
continue
|
continue
|
||||||
|
max_trash = 8 if world.algorithm == 'dungeon_only' else 15
|
||||||
gftower_trash_count = (random.randint(15, 50) if world.goal[player] in ['triforcehunt', 'trinity'] else random.randint(0, 15))
|
gftower_trash_count = (random.randint(15, 50) if world.goal[player] in ['triforcehunt', 'trinity'] else random.randint(0, max_trash))
|
||||||
|
|
||||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
||||||
random.shuffle(gtower_locations)
|
random.shuffle(gtower_locations)
|
||||||
@@ -376,21 +381,51 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
# Make sure the escape small key is placed first in standard with key shuffle 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
|
||||||
# todo: crossed
|
# todo: crossed
|
||||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
|
||||||
|
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool,
|
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
||||||
keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players + 1)})
|
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True)
|
||||||
|
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
|
if world.algorithm == 'balanced':
|
||||||
|
fast_fill(world, prioitempool, fill_locations)
|
||||||
|
elif world.algorithm == 'vanilla_fill':
|
||||||
|
fast_vanilla_fill(world, prioitempool, fill_locations)
|
||||||
|
elif world.algorithm in ['major_only', 'dungeon_only', 'district']:
|
||||||
|
filtered_fill(world, prioitempool, fill_locations)
|
||||||
|
else: # just need to ensure dungeon items still get placed in dungeons
|
||||||
|
fast_equitable_fill(world, prioitempool, fill_locations)
|
||||||
|
# placeholder work
|
||||||
|
if world.algorithm == 'district':
|
||||||
|
random.shuffle(fill_locations)
|
||||||
|
placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
|
num_ph_items = len(placeholder_items)
|
||||||
|
if num_ph_items > 0:
|
||||||
|
placeholder_locations = filter_locations('Placeholder', fill_locations, world)
|
||||||
|
num_ph_locations = len(placeholder_locations)
|
||||||
|
if num_ph_items < num_ph_locations < len(fill_locations):
|
||||||
|
for _ in range(num_ph_locations - num_ph_items):
|
||||||
|
placeholder_items.append(replace_trash_item(restitempool, 'Rupee (1)'))
|
||||||
|
assert len(placeholder_items) == len(placeholder_locations)
|
||||||
|
for i in placeholder_items:
|
||||||
|
restitempool.remove(i)
|
||||||
|
for l in placeholder_locations:
|
||||||
|
fill_locations.remove(l)
|
||||||
|
filtered_fill(world, placeholder_items, placeholder_locations)
|
||||||
|
|
||||||
fast_fill(world, prioitempool, fill_locations)
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||||
fast_fill(world, restitempool, fill_locations)
|
else:
|
||||||
|
fast_fill(world, restitempool, fill_locations)
|
||||||
|
|
||||||
unplaced = [item.name for item in prioitempool + restitempool]
|
unplaced = [item.name for item in prioitempool + restitempool]
|
||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in fill_locations]
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world, item_pool, fill_locations):
|
def fast_fill(world, item_pool, fill_locations):
|
||||||
while item_pool and fill_locations:
|
while item_pool and fill_locations:
|
||||||
spot_to_fill = fill_locations.pop()
|
spot_to_fill = fill_locations.pop()
|
||||||
@@ -398,77 +433,59 @@ def fast_fill(world, item_pool, fill_locations):
|
|||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
|
|
||||||
def flood_items(world):
|
def filtered_fill(world, item_pool, fill_locations):
|
||||||
# get items to distribute
|
while item_pool and fill_locations:
|
||||||
random.shuffle(world.itempool)
|
item_to_place = item_pool.pop()
|
||||||
itempool = world.itempool
|
item_locations = filter_locations(item_to_place, fill_locations, world)
|
||||||
progress_done = False
|
spot_to_fill = next(iter(item_locations))
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
# sweep once to pick up preplaced items
|
# sweep once to pick up preplaced items
|
||||||
world.state.sweep_for_events()
|
world.state.sweep_for_events()
|
||||||
|
|
||||||
# fill world from top of itempool while we can
|
|
||||||
while not progress_done:
|
|
||||||
location_list = world.get_unfilled_locations()
|
|
||||||
random.shuffle(location_list)
|
|
||||||
spot_to_fill = None
|
|
||||||
for location in location_list:
|
|
||||||
if location.can_fill(world.state, itempool[0]):
|
|
||||||
spot_to_fill = location
|
|
||||||
break
|
|
||||||
|
|
||||||
if spot_to_fill:
|
def fast_vanilla_fill(world, item_pool, fill_locations):
|
||||||
item = itempool.pop(0)
|
next_item_pool = []
|
||||||
world.push_item(spot_to_fill, item, True)
|
while item_pool and fill_locations:
|
||||||
continue
|
item_to_place = item_pool.pop()
|
||||||
|
locations = filter_locations(item_to_place, fill_locations, world, vanilla_skip=True)
|
||||||
|
if len(locations):
|
||||||
|
spot_to_fill = locations.pop()
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
else:
|
||||||
|
next_item_pool.append(item_to_place)
|
||||||
|
while next_item_pool and fill_locations:
|
||||||
|
item_to_place = next_item_pool.pop()
|
||||||
|
spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world)))
|
||||||
|
fill_locations.remove(spot_to_fill)
|
||||||
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
# ran out of spots, check if we need to step in and correct things
|
|
||||||
if len(world.get_reachable_locations()) == len(world.get_locations()):
|
|
||||||
progress_done = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
# need to place a progress item instead of an already placed item, find candidate
|
def filtered_equitable_fill(world, item_pool, fill_locations):
|
||||||
item_to_place = None
|
while item_pool and fill_locations:
|
||||||
candidate_item_to_place = None
|
item_to_place = item_pool.pop()
|
||||||
for item in itempool:
|
item_locations = filter_locations(item_to_place, fill_locations, world)
|
||||||
if item.advancement:
|
spot_to_fill = next(l for l in item_locations if valid_dungeon_placement(item_to_place, l, world))
|
||||||
candidate_item_to_place = item
|
fill_locations.remove(spot_to_fill)
|
||||||
if world.unlocks_new_location(item):
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
item_to_place = item
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
break
|
|
||||||
|
|
||||||
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
|
||||||
if item_to_place is None:
|
|
||||||
if candidate_item_to_place is not None:
|
|
||||||
item_to_place = candidate_item_to_place
|
|
||||||
else:
|
|
||||||
raise FillError('No more progress items left to place.')
|
|
||||||
|
|
||||||
# find item to replace with progress item
|
def fast_equitable_fill(world, item_pool, fill_locations):
|
||||||
location_list = world.get_reachable_locations()
|
while item_pool and fill_locations:
|
||||||
random.shuffle(location_list)
|
item_to_place = item_pool.pop()
|
||||||
for location in location_list:
|
spot_to_fill = next(l for l in fill_locations if valid_dungeon_placement(item_to_place, l, world))
|
||||||
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:
|
fill_locations.remove(spot_to_fill)
|
||||||
# safe to replace
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
replace_item = location.item
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
replace_item.location = None
|
|
||||||
itempool.append(replace_item)
|
|
||||||
world.push_item(location, item_to_place, True)
|
|
||||||
itempool.remove(item_to_place)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def lock_shop_locations(world, player):
|
def lock_shop_locations(world, player):
|
||||||
for shop, loc_names in shop_to_location_table.items():
|
for shop, loc_names in shop_to_location_table.items():
|
||||||
for loc in loc_names:
|
for loc in loc_names:
|
||||||
world.get_location(loc, player).event = True
|
|
||||||
world.get_location(loc, player).locked = True
|
world.get_location(loc, player).locked = True
|
||||||
# I don't believe these locations exist in non-shopsanity
|
|
||||||
# if world.retro[player]:
|
|
||||||
# for shop, loc_names in retro_shops.items():
|
|
||||||
# for loc in loc_names:
|
|
||||||
# world.get_location(loc, player).event = True
|
|
||||||
# world.get_location(loc, player).locked = True
|
|
||||||
|
|
||||||
|
|
||||||
def sell_potions(world, player):
|
def sell_potions(world, player):
|
||||||
@@ -479,7 +496,7 @@ def sell_potions(world, player):
|
|||||||
loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]]
|
loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]]
|
||||||
locations = [loc for loc in loc_choices if not loc.item]
|
locations = [loc for loc in loc_choices if not loc.item]
|
||||||
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']:
|
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']:
|
||||||
location = random.choice(locations)
|
location = random.choice(filter_locations(ItemFactory(potion, player), locations, world))
|
||||||
locations.remove(location)
|
locations.remove(location)
|
||||||
p_item = next(item for item in world.itempool if item.name == potion and item.player == player)
|
p_item = next(item for item in world.itempool if item.name == potion and item.player == player)
|
||||||
world.push_item(location, p_item, collect=False)
|
world.push_item(location, p_item, collect=False)
|
||||||
@@ -490,7 +507,9 @@ def sell_keys(world, player):
|
|||||||
# exclude the old man or take any caves because free keys are too good
|
# exclude the old man or take any caves because free keys are too good
|
||||||
shop_names = {shop.region.name: shop for shop in world.shops[player] if shop.region.name in shop_to_location_table}
|
shop_names = {shop.region.name: shop for shop in world.shops[player] if shop.region.name in shop_to_location_table}
|
||||||
choices = [(world.get_location(loc, player), shop) for shop in shop_names for loc in shop_to_location_table[shop]]
|
choices = [(world.get_location(loc, player), shop) for shop in shop_names for loc in shop_to_location_table[shop]]
|
||||||
locations = [(loc, shop) for loc, shop in choices if not loc.item]
|
locations = [l for l, shop in choices]
|
||||||
|
locations = filter_locations(ItemFactory('Small Key (Universal)', player), locations, world)
|
||||||
|
locations = [(loc, shop) for loc, shop in choices if not loc.item and loc in locations]
|
||||||
location, shop = random.choice(locations)
|
location, shop = random.choice(locations)
|
||||||
universal_key = next(i for i in world.itempool if i.name == 'Small Key (Universal)' and i.player == player)
|
universal_key = next(i for i in world.itempool if i.name == 'Small Key (Universal)' and i.player == player)
|
||||||
world.push_item(location, universal_key, collect=False)
|
world.push_item(location, universal_key, collect=False)
|
||||||
|
|||||||
13
ItemList.py
13
ItemList.py
@@ -4,12 +4,13 @@ import math
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
|
|
||||||
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
||||||
from Dungeons import get_dungeon_item_pool
|
|
||||||
from EntranceShuffle import connect_entrance
|
from EntranceShuffle import connect_entrance
|
||||||
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
|
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
|
||||||
from Fill import FillError, fill_restrictive, fast_fill
|
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
from source.item.FillUtil import trash_items
|
||||||
|
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
|
|
||||||
|
|
||||||
@@ -271,8 +272,12 @@ def generate_itempool(world, player):
|
|||||||
if player in world.pool_adjustment.keys():
|
if player in world.pool_adjustment.keys():
|
||||||
amt = world.pool_adjustment[player]
|
amt = world.pool_adjustment[player]
|
||||||
if amt < 0:
|
if amt < 0:
|
||||||
for _ in range(amt, 0):
|
trash_options = [x for x in pool if x in trash_items]
|
||||||
pool.remove(next(iter([x for x in pool if x in ['Rupees (20)', 'Rupees (5)', 'Rupee (1)']])))
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x], reverse=True)
|
||||||
|
while amt > 0 and len(trash_options) > 0:
|
||||||
|
pool.remove(trash_options.pop())
|
||||||
|
amt -= 1
|
||||||
elif amt > 0:
|
elif amt > 0:
|
||||||
for _ in range(0, amt):
|
for _ in range(0, amt):
|
||||||
pool.append('Rupees (20)')
|
pool.append('Rupees (20)')
|
||||||
|
|||||||
2
Items.py
2
Items.py
@@ -22,7 +22,7 @@ def ItemFactory(items, player):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
# Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
||||||
item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
||||||
'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from collections import defaultdict, deque
|
|||||||
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
|
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
|
||||||
from Regions import dungeon_events
|
from Regions import dungeon_events
|
||||||
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
|
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
|
||||||
from DungeonGenerator import ExplorationState, special_big_key_doors
|
from DungeonGenerator import ExplorationState, special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
|
||||||
|
from DungeonGenerator import reserved_location, blind_boss_unavail
|
||||||
|
|
||||||
|
|
||||||
class KeyLayout(object):
|
class KeyLayout(object):
|
||||||
@@ -186,6 +187,8 @@ class PlacementRule(object):
|
|||||||
return True
|
return True
|
||||||
available_keys = outside_keys
|
available_keys = outside_keys
|
||||||
empty_chests = 0
|
empty_chests = 0
|
||||||
|
# todo: sometimes we need an extra empty chest to accomodate the big key too
|
||||||
|
# dungeon bias seed 563518200 for example
|
||||||
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||||
for loc in check_locations:
|
for loc in check_locations:
|
||||||
if not loc.item:
|
if not loc.item:
|
||||||
@@ -1100,40 +1103,30 @@ def location_is_bk_locked(loc, key_logic):
|
|||||||
return loc in key_logic.bk_chests or loc in key_logic.bk_locked
|
return loc in key_logic.bk_chests or loc in key_logic.bk_locked
|
||||||
|
|
||||||
|
|
||||||
def prize_or_event(loc):
|
# todo: verfiy this code is defunct
|
||||||
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
|
# def prize_or_event(loc):
|
||||||
|
# return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
|
||||||
|
#
|
||||||
def boss_unavail(loc, world, player):
|
#
|
||||||
# todo: ambrosia
|
# def reserved_location(loc, world, player):
|
||||||
# return world.bossdrops[player] == 'ambrosia' and "- Boss" in loc.name
|
# return loc in world.item_pool.config.reserved_locations[player]
|
||||||
return False
|
#
|
||||||
|
#
|
||||||
|
# def blind_boss_unavail(loc, state, world, player):
|
||||||
def blind_boss_unavail(loc, state, world, player):
|
# if loc.name == "Thieves' Town - Boss":
|
||||||
if loc.name == "Thieves' Town - Boss":
|
# return (loc.parent_region.dungeon.boss.name == 'Blind' and
|
||||||
# todo: check attic
|
# (not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
|
||||||
return (loc.parent_region.dungeon.boss.name == 'Blind' and
|
# (world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
|
||||||
(not any(x for x in state.found_locations if x.name == 'Suspicious Maiden') or
|
# not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
|
||||||
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
|
# return False
|
||||||
not any(x for x in state.found_locations if x.name == 'Attic Cracked Floor'))))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
# counts free locations for keys - hence why reserved locations don't count
|
||||||
def count_free_locations(state, world, player):
|
def count_free_locations(state, world, player):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in state.found_locations:
|
for loc in state.found_locations:
|
||||||
if (not prize_or_event(loc) and not loc.forced_item and not boss_unavail(loc, world, player)
|
if (not prize_or_event(loc) and not loc.forced_item and not reserved_location(loc, world, player)
|
||||||
and not blind_boss_unavail(loc, state, world, player)):
|
and not blind_boss_unavail(loc, state.found_locations, world, player)):
|
||||||
cnt += 1
|
|
||||||
return cnt
|
|
||||||
|
|
||||||
|
|
||||||
def count_locations_exclude_big_chest(state, world, player):
|
|
||||||
cnt = 0
|
|
||||||
for loc in state.found_locations:
|
|
||||||
if ('- Big Chest' not in loc.name and not loc.forced_item and not boss_unavail(loc, world, player)
|
|
||||||
and not prize_or_event(loc) and not blind_boss_unavail(loc, state, world, player)):
|
|
||||||
cnt += 1
|
cnt += 1
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
@@ -1437,7 +1430,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
if state.big_key_opened:
|
if state.big_key_opened:
|
||||||
ttl_locations = count_free_locations(state, world, player)
|
ttl_locations = count_free_locations(state, world, player)
|
||||||
else:
|
else:
|
||||||
ttl_locations = count_locations_exclude_big_chest(state, world, player)
|
ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
|
||||||
ttl_small_key_only = count_small_key_only_locations(state)
|
ttl_small_key_only = count_small_key_only_locations(state)
|
||||||
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
|
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
|
||||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||||
@@ -1663,7 +1656,7 @@ def can_open_door(door, state, world, player):
|
|||||||
if state.big_key_opened:
|
if state.big_key_opened:
|
||||||
ttl_locations = count_free_locations(state, world, player)
|
ttl_locations = count_free_locations(state, world, player)
|
||||||
else:
|
else:
|
||||||
ttl_locations = count_locations_exclude_big_chest(state, world, player)
|
ttl_locations = count_locations_exclude_big_chest(state.found_locations, world, player)
|
||||||
if door.smallKey:
|
if door.smallKey:
|
||||||
ttl_small_key_only = count_small_key_only_locations(state)
|
ttl_small_key_only = count_small_key_only_locations(state)
|
||||||
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
|
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
|
||||||
|
|||||||
66
Main.py
66
Main.py
@@ -1,4 +1,3 @@
|
|||||||
from collections import OrderedDict
|
|
||||||
import copy
|
import copy
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
import json
|
import json
|
||||||
@@ -21,16 +20,19 @@ from OverworldShuffle import link_overworld, update_world_regions, create_flute_
|
|||||||
from EntranceShuffle import link_entrances
|
from EntranceShuffle import link_entrances
|
||||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors, connect_portal
|
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
||||||
from RoomData import create_rooms
|
from RoomData import create_rooms
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
from Dungeons import create_dungeons
|
||||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items
|
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive
|
||||||
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
|
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
|
||||||
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
__version__ = '0.5.1.7-u'
|
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '1.0.0.2-u'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -108,6 +110,8 @@ def main(args, seed=None, fish=None):
|
|||||||
world.treasure_hunt_total = args.triforce_pool.copy()
|
world.treasure_hunt_total = args.triforce_pool.copy()
|
||||||
world.shufflelinks = args.shufflelinks.copy()
|
world.shufflelinks = args.shufflelinks.copy()
|
||||||
world.pseudoboots = args.pseudoboots.copy()
|
world.pseudoboots = args.pseudoboots.copy()
|
||||||
|
world.overworld_map = args.overworld_map.copy()
|
||||||
|
world.restrict_boss_items = args.restrict_boss_items.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)}
|
||||||
|
|
||||||
@@ -184,7 +188,13 @@ def main(args, seed=None, fish=None):
|
|||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
link_entrances(world, player)
|
link_entrances(world, player)
|
||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.prep"))
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
link_doors_prep(world, player)
|
||||||
|
|
||||||
|
create_item_pool_config(world)
|
||||||
|
|
||||||
|
logger.info(world.fish.translate("cli", "cli", "shuffling.dungeons"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
link_doors(world, player)
|
link_doors(world, player)
|
||||||
@@ -192,8 +202,7 @@ def main(args, seed=None, fish=None):
|
|||||||
mark_light_world_regions(world, player)
|
mark_light_world_regions(world, player)
|
||||||
else:
|
else:
|
||||||
mark_dark_world_regions(world, player)
|
mark_dark_world_regions(world, player)
|
||||||
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
logger.info(world.fish.translate("cli", "cli", "generating.itempool"))
|
||||||
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
generate_itempool(world, player)
|
generate_itempool(world, player)
|
||||||
@@ -214,7 +223,9 @@ def main(args, seed=None, fish=None):
|
|||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
set_prize_drops(world, player)
|
set_prize_drops(world, player)
|
||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
|
district_item_pool_config(world)
|
||||||
|
massage_item_pool(world)
|
||||||
|
logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes"))
|
||||||
|
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
@@ -223,14 +234,12 @@ def main(args, seed=None, fish=None):
|
|||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
||||||
|
|
||||||
shuffled_locations = None
|
if args.algorithm != 'equitable':
|
||||||
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)
|
||||||
else:
|
else:
|
||||||
fill_dungeons(world)
|
promote_dungeon_items(world)
|
||||||
|
|
||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
if world.logic[player] != 'nologic':
|
if world.logic[player] != 'nologic':
|
||||||
@@ -248,34 +257,22 @@ def main(args, seed=None, fish=None):
|
|||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","fill.world"))
|
logger.info(world.fish.translate("cli","cli","fill.world"))
|
||||||
|
|
||||||
if args.algorithm == 'flood':
|
distribute_items_restrictive(world, True)
|
||||||
flood_items(world) # different algo, biased towards early game progress items
|
|
||||||
elif args.algorithm == 'vt21':
|
|
||||||
distribute_items_cutoff(world, 1)
|
|
||||||
elif args.algorithm == 'vt22':
|
|
||||||
distribute_items_cutoff(world, 0.66)
|
|
||||||
elif args.algorithm == 'freshness':
|
|
||||||
distribute_items_staleness(world)
|
|
||||||
elif args.algorithm == 'vt25':
|
|
||||||
distribute_items_restrictive(world, False)
|
|
||||||
elif args.algorithm == 'vt26':
|
|
||||||
|
|
||||||
distribute_items_restrictive(world, True, shuffled_locations)
|
|
||||||
elif args.algorithm == 'balanced':
|
|
||||||
distribute_items_restrictive(world, True)
|
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
logger.info(world.fish.translate("cli","cli","balance.multiworld"))
|
logger.info(world.fish.translate("cli", "cli", "balance.multiworld"))
|
||||||
balance_multiworld_progression(world)
|
if args.algorithm in ['balanced', 'equitable']:
|
||||||
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
# if we only check for beatable, we can do this sanity check first before creating the rom
|
# if we only check for beatable, we can do this sanity check first before creating the rom
|
||||||
if not world.can_beat_game(log_error=True):
|
if not world.can_beat_game(log_error=True):
|
||||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
|
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.beat.game"))
|
||||||
|
|
||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
customize_shops(world, player)
|
customize_shops(world, player)
|
||||||
balance_money_progression(world)
|
if args.algorithm in ['balanced', 'equitable']:
|
||||||
|
balance_money_progression(world)
|
||||||
|
|
||||||
rom_names = []
|
rom_names = []
|
||||||
jsonout = {}
|
jsonout = {}
|
||||||
@@ -435,6 +432,7 @@ def copy_world(world):
|
|||||||
ret.owswaps = world.owswaps.copy()
|
ret.owswaps = world.owswaps.copy()
|
||||||
ret.owflutespots = world.owflutespots.copy()
|
ret.owflutespots = world.owflutespots.copy()
|
||||||
ret.prizes = world.prizes.copy()
|
ret.prizes = world.prizes.copy()
|
||||||
|
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||||
|
|
||||||
ret.exp_cache = world.exp_cache.copy()
|
ret.exp_cache = world.exp_cache.copy()
|
||||||
|
|
||||||
@@ -611,11 +609,11 @@ def create_playthrough(world):
|
|||||||
# todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic
|
# todo: this is not very efficient, but I'm not sure how else to do it for this backwards logic
|
||||||
# world.clear_exp_cache()
|
# world.clear_exp_cache()
|
||||||
if world.can_beat_game(state_cache[num]):
|
if world.can_beat_game(state_cache[num]):
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
||||||
to_delete.add(location)
|
to_delete.add(location)
|
||||||
else:
|
else:
|
||||||
# still required, got to keep it around
|
# still required, got to keep it around
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
||||||
location.item = old_item
|
location.item = old_item
|
||||||
|
|
||||||
# cull entries in spheres for spoiler walkthrough at end
|
# cull entries in spheres for spoiler walkthrough at end
|
||||||
|
|||||||
16
Mystery.py
16
Mystery.py
@@ -29,6 +29,7 @@ def main():
|
|||||||
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
|
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
|
||||||
parser.add_argument('--create_spoiler', action='store_true')
|
parser.add_argument('--create_spoiler', action='store_true')
|
||||||
parser.add_argument('--no_race', action='store_true')
|
parser.add_argument('--no_race', action='store_true')
|
||||||
|
parser.add_argument('--suppress_rom', action='store_true')
|
||||||
parser.add_argument('--rom')
|
parser.add_argument('--rom')
|
||||||
parser.add_argument('--enemizercli')
|
parser.add_argument('--enemizercli')
|
||||||
parser.add_argument('--outputpath')
|
parser.add_argument('--outputpath')
|
||||||
@@ -62,6 +63,7 @@ def main():
|
|||||||
erargs.seed = seed
|
erargs.seed = seed
|
||||||
erargs.names = args.names
|
erargs.names = args.names
|
||||||
erargs.create_spoiler = args.create_spoiler
|
erargs.create_spoiler = args.create_spoiler
|
||||||
|
erargs.suppress_rom = args.suppress_rom
|
||||||
erargs.race = not args.no_race
|
erargs.race = not args.no_race
|
||||||
erargs.outputname = seedname
|
erargs.outputname = seedname
|
||||||
if args.outputpath:
|
if args.outputpath:
|
||||||
@@ -73,6 +75,8 @@ def main():
|
|||||||
if args.enemizercli:
|
if args.enemizercli:
|
||||||
erargs.enemizercli = args.enemizercli
|
erargs.enemizercli = args.enemizercli
|
||||||
|
|
||||||
|
mw_settings = {'algorithm': False}
|
||||||
|
|
||||||
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
|
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):
|
for player in range(1, args.multi + 1):
|
||||||
@@ -81,7 +85,12 @@ def main():
|
|||||||
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
|
||||||
for k, v in vars(settings).items():
|
for k, v in vars(settings).items():
|
||||||
if v is not None:
|
if v is not None:
|
||||||
getattr(erargs, k)[player] = v
|
if k == 'algorithm': # multiworld wide parameters
|
||||||
|
if not mw_settings[k]: # only use the first roll
|
||||||
|
setattr(erargs, k, v)
|
||||||
|
mw_settings[k] = True
|
||||||
|
else:
|
||||||
|
getattr(erargs, k)[player] = v
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f'No weights specified for player {player}')
|
raise RuntimeError(f'No weights specified for player {player}')
|
||||||
|
|
||||||
@@ -129,6 +138,8 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret = argparse.Namespace()
|
ret = argparse.Namespace()
|
||||||
|
|
||||||
|
ret.algorithm = get_choice('algorithm')
|
||||||
|
|
||||||
glitches_required = get_choice('glitches_required')
|
glitches_required = get_choice('glitches_required')
|
||||||
if glitches_required is not None:
|
if glitches_required is not None:
|
||||||
if glitches_required not in ['none', 'owg', 'no_logic']:
|
if glitches_required not in ['none', 'owg', 'no_logic']:
|
||||||
@@ -146,6 +157,7 @@ def roll_settings(weights):
|
|||||||
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
|
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
|
||||||
|
|
||||||
ret.accessibility = get_choice('accessibility')
|
ret.accessibility = get_choice('accessibility')
|
||||||
|
ret.restrict_boss_items = get_choice('restrict_boss_items')
|
||||||
|
|
||||||
overworld_shuffle = get_choice('overworld_shuffle')
|
overworld_shuffle = get_choice('overworld_shuffle')
|
||||||
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
|
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
|
||||||
@@ -157,6 +169,8 @@ def roll_settings(weights):
|
|||||||
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
|
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
|
||||||
entrance_shuffle = get_choice('entrance_shuffle')
|
entrance_shuffle = get_choice('entrance_shuffle')
|
||||||
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
||||||
|
overworld_map = get_choice('overworld_map')
|
||||||
|
ret.overworld_map = overworld_map if overworld_map != 'default' else 'default'
|
||||||
door_shuffle = get_choice('door_shuffle')
|
door_shuffle = get_choice('door_shuffle')
|
||||||
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||||
ret.intensity = get_choice('intensity')
|
ret.intensity = get_choice('intensity')
|
||||||
|
|||||||
198
RELEASENOTES.md
198
RELEASENOTES.md
@@ -1,91 +1,127 @@
|
|||||||
# New Features
|
## New Features
|
||||||
|
|
||||||
## Shuffle SFX
|
## Restricted Item Placement Algorithm
|
||||||
|
|
||||||
Shuffles a large portion of the sounds effects. Can be used with the adjuster.
|
|
||||||
|
|
||||||
CLI: ```--shuffle_sfx```
|
The "Item Sorting" option or ```--algorithm``` has been updated with new placement algorithms. Older algorithms have been removed.
|
||||||
|
|
||||||
## Bomb Logic
|
When referenced below, Major Items include all Y items, all A items, all equipment (swords, shields, & armor) and Heart Containers. Dungeon items are considered major if shuffled outside of dungeons. Bomb and arrows upgrades are Major if shopsanity is turned on. The arrow quiver and universal small keys are Major if retro is turned on. Triforce Pieces are Major if that is the goal, and the Bomb Bag is Major if that is enabled.
|
||||||
|
|
||||||
When enabling this option, you do not start with bomb capacity but rather you must find 1 of 2 bomb bags. (They are represented by the +10 capacity item.) Bomb capacity upgrades are otherwise unavailable.
|
|
||||||
|
|
||||||
CLI: ```--bombbag```
|
Here are the current fill options:
|
||||||
|
|
||||||
|
### Balanced
|
||||||
|
|
||||||
|
This one stays the same as before and is recommended for the most random distribution of items.
|
||||||
|
|
||||||
|
### Equitable
|
||||||
|
|
||||||
|
This one is currently under development and may not fill correctly. It is a new method that should allow item and key logic to interact. (Vanilla key placement in PoD is theoretically possible, but isn't yet.)
|
||||||
|
|
||||||
|
### Vanilla Fill
|
||||||
|
|
||||||
|
This fill attempts to place all items in their vanilla locations when possible. Obviously shuffling entrances or the dungeon interiors will often prevent items from being placed in their vanilla location. If the vanilla fill is not possible, then other locations are tried in sequence preferring "major" locations (see below), then heart piece locations, then the rest except for GT locations which are preferred last. Note the PoD small key that is normally found in the dark maze in vanilla is move to Harmless Hellway due to the placement algorithm limitation.
|
||||||
|
|
||||||
|
### Major Location Restriction
|
||||||
|
|
||||||
|
This fill attempts to place major items in major locations. Major locations are where the major items are found in the vanilla game. This includes the spot next to Uncle in the Sewers, and the Boomerang chest in Hyrule Castle.
|
||||||
|
|
||||||
|
This location pool is expanded to where dungeon items are locations if those dungeon items are shuffled. The Capacity Fairy locations are included if Shopsanity is on. If retro is enabled in addition to shopsanity, then the Old Man Sword Cave and one location in each retro cave is included. Key drop locations can be included if small or big key shuffle is on. This gives a very good balance between overworld and underworld locations though the dungeons ones will be on bosses and in big chests generally. Seeds do become more linear but usually easier to figure out.
|
||||||
|
|
||||||
|
### Dungeon Restriction
|
||||||
|
|
||||||
|
The fill attempts to place all major items in dungeons. It will overflow to the overworld if there are more items than locations (e.g. Triforce hunt.) This fill does attempt to run the GT trash fill when possible. Seeds are typically very linear but tend to be more difficult.
|
||||||
|
|
||||||
|
### District Restriction
|
||||||
|
|
||||||
|
The world is divided up into different regions or districts. Each dungeon is it's own district. The overworld consists of the following districts:
|
||||||
|
|
||||||
|
Light world:
|
||||||
|
|
||||||
|
* Kakariko (The main screen, blacksmith screen, and library/maze race screens)
|
||||||
|
* Northwest Hyrule (The lost woods and fortune teller all the way to the rive west of the potion shop)
|
||||||
|
* Central Hyrule (Hyrule castle, Link's House, the marsh, and the haunted grove)
|
||||||
|
* Desert (From the thief to the main desert screen)
|
||||||
|
* Lake Hylia (Around the lake)
|
||||||
|
* Eastern Hyrule (The eastern wild, the potion shop, and Zora's Domain)
|
||||||
|
* Death Mountain
|
||||||
|
|
||||||
|
Dark world:
|
||||||
|
|
||||||
|
* East Dark World (The pyramid, Palace of darkness, and Catfish)
|
||||||
|
* South Dark World (The dark lake, swamp area, to the dig game)
|
||||||
|
* Northwest Dark World (Village of Outcasts, to the Dark Sanctuary and screens in between)
|
||||||
|
* The Mire
|
||||||
|
* Dark Death Mountain
|
||||||
|
|
||||||
|
These districts are chosen at random and then filled with major items. If a location is part of a chosen district, but there are no more major items to place, a single green rupee is placed in the extra to indicate that as a placeholder. All other single green rupees are changed to be a blue rupee in order to not give false positives.
|
||||||
|
|
||||||
|
In entrance shuffle, what is shuffled to the entrances is considered instead of where the interior was originally. For example, if Blind's Hut is shuffled to the Dam, then the 5 chests in Blind's Hut are part of Central Hyrule instead of Kakariko.
|
||||||
|
|
||||||
|
Bombos Table, Lake Hylia Island, Bumper Cave Ledge, the Floating Island, Cave 45, the Graveyard Cave, Checkerboard Cave and Mimic Cave are considered part of the dark world region that you mirror from to get there (except in inverted where these are only accessible in the Light World). Note that Spectacle Rock is always part of light Death Mountain.
|
||||||
|
|
||||||
|
In multiworld, the districts chosen apply to all players.
|
||||||
|
|
||||||
|
### CLI values:
|
||||||
|
|
||||||
|
```balanced, equitable, vanilla_fill, major_only, dungeon_only, district```
|
||||||
|
|
||||||
|
## New Hints
|
||||||
|
|
||||||
|
Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it check for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish. At least two "foolish" districts are chosen and the rest are random.
|
||||||
|
|
||||||
|
|
||||||
# Bug Fixes and Notes.
|
### Overworld Map shows dungeon location
|
||||||
|
|
||||||
* 0.5.1.7
|
Option to move indicators on overworld map to reference dungeon location. The non-default options include indicators for Hyrule Castle, Agahnim's Tower, and Ganon's Tower.
|
||||||
* Baserom update
|
|
||||||
* Fix for Inverted Mode: Dark Lake Hylia shop defaults to selling a blue potion
|
|
||||||
* Fix for Ijwu's enemizer: Boss door in Thieves' Town no longer closes after the maiden hint if Blind is shuffled to Theives' Town in boss shuffle + crossed mode
|
|
||||||
* No logic now sets the AllowAccidentalMajorGlitches flag in the rom appropriately
|
|
||||||
* Houlihan room now exits wherever Link's House is shuffled to
|
|
||||||
* Rom fixes from Catobat and Codemann8. Thanks!
|
|
||||||
* 0.5.1.6
|
|
||||||
* Rules fixes for TT (Boss and Cell) can now have TT Big Key if not otherwise required (boss shuffle + crossed dungeon)
|
|
||||||
* BUg fix for money balancing
|
|
||||||
* Add some bomb assumptions for bosses in bombbag mode
|
|
||||||
* 0.5.1.5
|
|
||||||
* Fix for hard pool capacity upgrades missing
|
|
||||||
* Bonk Fairy (Light) is no longer in logic for ER Standard and is forbidden to be a connector, so rain state isn't exitable
|
|
||||||
* Bug fix for retro + enemizer and arrows appearing under pots
|
|
||||||
* Added bombbag and shufflelinks to settings code
|
|
||||||
* Catobat fixes:
|
|
||||||
* Fairy refills in spoiler
|
|
||||||
* Subweights support in mystery
|
|
||||||
* More defaults for mystery weights
|
|
||||||
* Less camera jank for straight stair transitions
|
|
||||||
* Bug with Straight stairs with vanilla doors where Link's walking animation stopped early is fixed
|
|
||||||
* 0.5.1.4
|
|
||||||
* Revert quadrant glitch fix for baserom
|
|
||||||
* Fix for inverted
|
|
||||||
* 0.5.1.3
|
|
||||||
* Certain lobbies forbidden in standard when rupee bow is enabled
|
|
||||||
* PoD EG disarmed when mirroring (except in nologic)
|
|
||||||
* Fixed issue with key logic
|
|
||||||
* Updated baserom
|
|
||||||
* 0.5.1.2
|
|
||||||
* Allowed Blind's Cell to be shuffled anywhere if Blind is not the boss of Thieves Town
|
|
||||||
* Remove unique annotation from a FastEnum that was causing problems
|
|
||||||
* Updated prevent mixed_travel setting to prevent more mixed travel
|
|
||||||
* Prevent key door loops on the same supertile where you could have spent 2 keys on one logical door
|
|
||||||
* Promoted dynamic soft-lock prevention on "stonewalls" from experimental to be the primary prevention (Stonewalls are now never pre-opened)
|
|
||||||
* Fix to money balancing algorithm with small item_pool, thanks Catobat
|
|
||||||
* Many fixes and refinements to key logic and generation
|
|
||||||
* 0.5.1.1
|
|
||||||
* Shop hints in ER are now more generic instead of using "near X" because they aren't near that anymore
|
|
||||||
* Added memory location for mutliworld scripts to read what item was just obtain (longer than one frame)
|
|
||||||
* Fix for bias in boss shuffle "full"
|
|
||||||
* Fix for certain lone big chests in keysanity (allowed you to get contents without big key)
|
|
||||||
* Fix for pinball checking
|
|
||||||
* Fix for multi-entrance dungeons
|
|
||||||
* 2 fixes for big key placement logic
|
|
||||||
* ensure big key is placed early if the validator assumes it)
|
|
||||||
* Open big key doors appropriately when generating rules and big key is forced somewhere
|
|
||||||
* Updated cutoff entrances for intensity 3
|
|
||||||
* 0.5.1.0
|
|
||||||
* Large logic refactor introducing a new method of key logic
|
|
||||||
* Some performance optimization
|
|
||||||
* Some outstanding bug fixes (boss shuffle "full" picks three unique bosses to be duplicated, e.g.)
|
|
||||||
* 0.5.0.3
|
|
||||||
* Fixed a bug in retro+vanilla and big key placement
|
|
||||||
* Fixed a problem with shops not registering in the Multiclient until you visit one
|
|
||||||
* Fixed a bug in the Mystery code with sfx
|
|
||||||
* 0.5.0.2
|
|
||||||
* --shuffle_sfx option added
|
|
||||||
* 0.5.0.1
|
|
||||||
* --bombbag option added
|
|
||||||
* 0.5.0.0
|
|
||||||
* Handles headered roms for enemizer (Thanks compiling)
|
|
||||||
* Warning added for earlier version of python (Thanks compiling)
|
|
||||||
* Minor logic issue for defeating Aga in standard (Thanks compiling)
|
|
||||||
* Fix for boss music in non-DR modes (Thanks codemann8)
|
|
||||||
|
|
||||||
# Known Issues
|
CLI ```--overworld_map```
|
||||||
|
|
||||||
* Shopsanity Issues
|
#### Options
|
||||||
* Hints for items in shops can be misleading (ER)
|
|
||||||
* Forfeit in Multiworld not granting all shop items
|
##### default
|
||||||
* Potential keylocks in multi-entrance dungeons
|
|
||||||
* Incorrect vanilla key logic for Mire
|
Status quo. Showing only the prize markers on the vanilla dungeon locations.
|
||||||
|
|
||||||
|
##### compass
|
||||||
|
|
||||||
|
The compass item controls whether the marker is moved to the dungeons locations. If you possess the compass but not the map, only a glowing X will be present regardless of dungeon prize type, if you only possess the map, the prizes will be shown in predicable locations at the bottom of the overworld map instead of the vanilla location. Light world dungeons on the light world map and dark world dungeons on the dark world map. If you posses both map and compass, then the prize of the dungeon and the location will be on the map.
|
||||||
|
|
||||||
|
If you do not shuffle the compass or map outside of the dungeon, the non-shuffled items are not needed to display the information. If a dungeon does not have a map or compass, it is not needed for the information. Talking to the bomb shop or Sahasrahla furnishes you with complete information as well as map information.
|
||||||
|
|
||||||
|
##### map
|
||||||
|
|
||||||
|
The map item plays double duty in this mode and only possession of the map will show both prize and location of the dungeon. If you do not shuffle maps or the dungeon does not have a map, the information will be displayed without needing to find any items.
|
||||||
|
|
||||||
|
## Restricted Dungeon Items on Bosses
|
||||||
|
|
||||||
|
You may now restrict the items that can appear on the boss, like the popular ambrosia preset does.
|
||||||
|
|
||||||
|
CLI: ```--restrict_boss_items <option>```
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
##### none
|
||||||
|
|
||||||
|
As before, the boss may have any item including any dungeon item that could occur there.
|
||||||
|
|
||||||
|
##### mapcompass
|
||||||
|
|
||||||
|
The map and compass are logically required to defeat a boss. This prevents both of those from appearing on the dungeon boss. Note that this does affect item placement logic and the placement algorithm as maps and compasses are considered as required items to beat a boss.
|
||||||
|
|
||||||
|
##### dungeon
|
||||||
|
|
||||||
|
Same as above but both small keys and bigs keys of the dungeon are not allowed on a boss. (Note: this does not affect universal keys as they are not dungeon-specific)
|
||||||
|
|
||||||
|
## Notes and Bug Fixes
|
||||||
|
|
||||||
|
* 1.0.0.2
|
||||||
|
* Include 1.0.1 fixes
|
||||||
|
* District hint rework
|
||||||
|
* 1.0.0.1
|
||||||
|
* Add Light Hype Fairy to bombbag mode as needing bombs
|
||||||
|
|
||||||
|
### From stable DoorDev
|
||||||
|
|
||||||
|
* 1.0.1
|
||||||
|
* Fixed a bug with key doors not detecting one side of an interior door
|
||||||
|
* Sprite selector fix for systems with SSL issues
|
||||||
@@ -22,6 +22,7 @@ def _wrap(name):
|
|||||||
|
|
||||||
# These are for intellisense purposes only, and will be overwritten below
|
# These are for intellisense purposes only, and will be overwritten below
|
||||||
choice = _prng_inst.choice
|
choice = _prng_inst.choice
|
||||||
|
choices = _prng_inst.choices
|
||||||
gauss = _prng_inst.gauss
|
gauss = _prng_inst.gauss
|
||||||
getrandbits = _prng_inst.getrandbits
|
getrandbits = _prng_inst.getrandbits
|
||||||
randint = _prng_inst.randint
|
randint = _prng_inst.randint
|
||||||
|
|||||||
109
Regions.py
109
Regions.py
@@ -29,9 +29,9 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Death Mountain TR Pegs Ledge', None, ['TR Pegs Ledge Leave', 'TR Pegs Ledge Drop', 'Turtle Rock Ledge Mirror Spot', 'TR Pegs Teleporter']),
|
create_lw_region(player, 'Death Mountain TR Pegs Ledge', None, ['TR Pegs Ledge Leave', 'TR Pegs Ledge Drop', 'Turtle Rock Ledge Mirror Spot', 'TR Pegs Teleporter']),
|
||||||
create_lw_region(player, 'Mountain Entry Area', None, ['Mountain Entry Entrance Rock (West)', 'Bumper Cave Area Mirror Spot', 'Mountain Entry NW', 'Mountain Entry SE']),
|
create_lw_region(player, 'Mountain Entry Area', None, ['Mountain Entry Entrance Rock (West)', 'Bumper Cave Area Mirror Spot', 'Mountain Entry NW', 'Mountain Entry SE']),
|
||||||
create_lw_region(player, 'Mountain Entry Entrance', None, ['Mountain Entry Entrance Rock (East)', 'Mountain Entry Entrance Ledge Drop', 'Old Man Cave (West)', 'Bumper Cave Entry Mirror Spot']),
|
create_lw_region(player, 'Mountain Entry Entrance', None, ['Mountain Entry Entrance Rock (East)', 'Mountain Entry Entrance Ledge Drop', 'Old Man Cave (West)', 'Bumper Cave Entry Mirror Spot']),
|
||||||
create_lw_region(player, 'Mountain Entry Ledge', None, ['Mountain Entry Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot']),
|
create_lw_region(player, 'Mountain Entry Ledge', None, ['Mountain Entry Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot'], 'a ledge in the foothills'),
|
||||||
create_lw_region(player, 'Zora Waterfall Area', None, ['Zora Waterfall Water Entry', 'Catfish Mirror Spot', 'Zora Waterfall SE', 'Zora Waterfall NE']),
|
create_lw_region(player, 'Zora Waterfall Area', None, ['Zora Waterfall Water Entry', 'Catfish Mirror Spot', 'Zora Waterfall SE', 'Zora Waterfall NE']),
|
||||||
create_lw_region(player, 'Zora Waterfall Water', None, ['Waterfall of Wishing Cave Entry', 'Zora Waterfall Landing', 'Zora Whirlpool'], Terrain.Water),
|
create_lw_region(player, 'Zora Waterfall Water', None, ['Waterfall of Wishing Cave Entry', 'Zora Waterfall Landing', 'Zora Whirlpool'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Waterfall of Wishing Cave', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']),
|
create_lw_region(player, 'Waterfall of Wishing Cave', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']),
|
||||||
create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']),
|
create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']),
|
||||||
create_lw_region(player, 'Lost Woods Pass West Area', None, ['Skull Woods Pass West Mirror Spot', 'Lost Woods Pass NW', 'Lost Woods Pass SW']),
|
create_lw_region(player, 'Lost Woods Pass West Area', None, ['Skull Woods Pass West Mirror Spot', 'Lost Woods Pass NW', 'Lost Woods Pass SW']),
|
||||||
@@ -47,13 +47,13 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave Inner Rocks', 'Kings Grave', 'Dark Graveyard Grave Mirror Spot']),
|
create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave Inner Rocks', 'Kings Grave', 'Dark Graveyard Grave Mirror Spot']),
|
||||||
create_lw_region(player, 'River Bend Area', None, ['North Fairy Cave Drop', 'River Bend Water Drop', 'North Fairy Cave', 'Qirn Jump Mirror Spot', 'River Bend WC', 'River Bend SW']),
|
create_lw_region(player, 'River Bend Area', None, ['North Fairy Cave Drop', 'River Bend Water Drop', 'North Fairy Cave', 'Qirn Jump Mirror Spot', 'River Bend WC', 'River Bend SW']),
|
||||||
create_lw_region(player, 'River Bend East Bank', None, ['River Bend East Water Drop', 'Qirn Jump East Mirror Spot', 'River Bend SE', 'River Bend EC', 'River Bend ES']),
|
create_lw_region(player, 'River Bend East Bank', None, ['River Bend East Water Drop', 'Qirn Jump East Mirror Spot', 'River Bend SE', 'River Bend EC', 'River Bend ES']),
|
||||||
create_lw_region(player, 'River Bend Water', None, ['River Bend West Pier', 'River Bend East Pier', 'River Bend EN', 'River Bend SC', 'River Bend Whirlpool'], Terrain.Water),
|
create_lw_region(player, 'River Bend Water', None, ['River Bend West Pier', 'River Bend East Pier', 'River Bend EN', 'River Bend SC', 'River Bend Whirlpool'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop Water Drop', 'Potion Shop Rock (South)', 'Potion Shop', 'Dark Witch Mirror Spot', 'Potion Shop WC', 'Potion Shop WS']),
|
create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop Water Drop', 'Potion Shop Rock (South)', 'Potion Shop', 'Dark Witch Mirror Spot', 'Potion Shop WC', 'Potion Shop WS']),
|
||||||
create_lw_region(player, 'Potion Shop Northeast', None, ['Potion Shop Northeast Water Drop', 'Potion Shop Rock (North)', 'Dark Witch Northeast Mirror Spot', 'Potion Shop EC']),
|
create_lw_region(player, 'Potion Shop Northeast', None, ['Potion Shop Northeast Water Drop', 'Potion Shop Rock (North)', 'Dark Witch Northeast Mirror Spot', 'Potion Shop EC']),
|
||||||
create_lw_region(player, 'Potion Shop Water', None, ['Potion Shop WN', 'Potion Shop EN'], Terrain.Water),
|
create_lw_region(player, 'Potion Shop Water', None, ['Potion Shop WN', 'Potion Shop EN'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Zora Approach Area', None, ['Zora Approach Rocks (West)', 'Zora Approach Bottom Ledge Drop', 'Zora Approach Water Drop', 'Catfish Approach Mirror Spot', 'Zora Approach WC']),
|
create_lw_region(player, 'Zora Approach Area', None, ['Zora Approach Rocks (West)', 'Zora Approach Bottom Ledge Drop', 'Zora Approach Water Drop', 'Catfish Approach Mirror Spot', 'Zora Approach WC']),
|
||||||
create_lw_region(player, 'Zora Approach Ledge', None, ['Zora Approach Rocks (East)', 'Zora Approach Ledge Drop', 'Catfish Approach Ledge Mirror Spot', 'Zora Approach NE']),
|
create_lw_region(player, 'Zora Approach Ledge', None, ['Zora Approach Rocks (East)', 'Zora Approach Ledge Drop', 'Catfish Approach Ledge Mirror Spot', 'Zora Approach NE']),
|
||||||
create_lw_region(player, 'Zora Approach Water', None, ['Zora Approach WN'], Terrain.Water),
|
create_lw_region(player, 'Zora Approach Water', None, ['Zora Approach WN'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Kakariko Area', ['Bottle Merchant'], ['Kakariko Southwest Bush (North)', 'Kakariko Yard Bush (South)', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blinds Hideout',
|
create_lw_region(player, 'Kakariko Area', ['Bottle Merchant'], ['Kakariko Southwest Bush (North)', 'Kakariko Yard Bush (South)', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Blinds Hideout',
|
||||||
'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House',
|
'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House',
|
||||||
'Kakariko Shop', 'Tavern (Front)', 'Tavern North', 'Village of Outcasts Mirror Spot', 'Kakariko NW', 'Kakariko NC', 'Kakariko NE', 'Kakariko ES', 'Kakariko SE']),
|
'Kakariko Shop', 'Tavern (Front)', 'Tavern North', 'Village of Outcasts Mirror Spot', 'Kakariko NW', 'Kakariko NC', 'Kakariko NE', 'Kakariko ES', 'Kakariko SE']),
|
||||||
@@ -64,18 +64,18 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Hyrule Castle Southwest', None, ['Hyrule Castle Southwest Bush (South)', 'Hyrule Castle SW']),
|
create_lw_region(player, 'Hyrule Castle Southwest', None, ['Hyrule Castle Southwest Bush (South)', 'Hyrule Castle SW']),
|
||||||
create_lw_region(player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Entrance (South)', 'Pyramid Courtyard Mirror Spot', 'Top of Pyramid (Inner)']),
|
create_lw_region(player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Courtyard Bush (South)', 'Hyrule Castle Main Gate (North)', 'Hyrule Castle Entrance (South)', 'Pyramid Courtyard Mirror Spot', 'Top of Pyramid (Inner)']),
|
||||||
create_lw_region(player, 'Hyrule Castle Courtyard Northeast', None, ['Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Secret Entrance Stairs', 'Pyramid Uncle Mirror Spot']),
|
create_lw_region(player, 'Hyrule Castle Courtyard Northeast', None, ['Hyrule Castle Courtyard Bush (North)', 'Hyrule Castle Secret Entrance Stairs', 'Pyramid Uncle Mirror Spot']),
|
||||||
create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Ledge Drop', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Entrance', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Entrance (East)', 'Inverted Pyramid Hole', 'Pyramid From Ledge Mirror Spot']),
|
create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Ledge Drop', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Entrance', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Entrance (East)', 'Inverted Pyramid Hole', 'Pyramid From Ledge Mirror Spot'], 'the castle rampart'),
|
||||||
create_lw_region(player, 'Hyrule Castle East Entry', None, ['Hyrule Castle Outer East Rock', 'Pyramid Entry Mirror Spot', 'Hyrule Castle ES']),
|
create_lw_region(player, 'Hyrule Castle East Entry', None, ['Hyrule Castle Outer East Rock', 'Pyramid Entry Mirror Spot', 'Hyrule Castle ES']),
|
||||||
create_lw_region(player, 'Wooden Bridge Area', None, ['Wooden Bridge Bush (South)', 'Wooden Bridge Water Drop', 'Broken Bridge West Mirror Spot', 'Broken Bridge East Mirror Spot', 'Wooden Bridge NW', 'Wooden Bridge SW']),
|
create_lw_region(player, 'Wooden Bridge Area', None, ['Wooden Bridge Bush (South)', 'Wooden Bridge Water Drop', 'Broken Bridge West Mirror Spot', 'Broken Bridge East Mirror Spot', 'Wooden Bridge NW', 'Wooden Bridge SW']),
|
||||||
create_lw_region(player, 'Wooden Bridge Northeast', None, ['Wooden Bridge Bush (North)', 'Wooden Bridge Northeast Water Drop', 'Broken Bridge Northeast Mirror Spot', 'Wooden Bridge NE']),
|
create_lw_region(player, 'Wooden Bridge Northeast', None, ['Wooden Bridge Bush (North)', 'Wooden Bridge Northeast Water Drop', 'Broken Bridge Northeast Mirror Spot', 'Wooden Bridge NE']),
|
||||||
create_lw_region(player, 'Wooden Bridge Water', None, ['Wooden Bridge NC'], Terrain.Water),
|
create_lw_region(player, 'Wooden Bridge Water', None, ['Wooden Bridge NC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Eastern Palace Area', None, ['Sahasrahlas Hut', 'Eastern Palace', 'Palace of Darkness Mirror Spot', 'Eastern Palace SW', 'Eastern Palace SE']),
|
create_lw_region(player, 'Eastern Palace Area', None, ['Sahasrahlas Hut', 'Eastern Palace', 'Palace of Darkness Mirror Spot', 'Eastern Palace SW', 'Eastern Palace SE']),
|
||||||
create_lw_region(player, 'Eastern Cliff', None, ['Sand Dunes Ledge Drop', 'Stone Bridge East Ledge Drop', 'Tree Line Ledge Drop', 'Eastern Palace Ledge Drop']),
|
create_lw_region(player, 'Eastern Cliff', None, ['Sand Dunes Ledge Drop', 'Stone Bridge East Ledge Drop', 'Tree Line Ledge Drop', 'Eastern Palace Ledge Drop']),
|
||||||
create_lw_region(player, 'Blacksmith Area', None, ['Blacksmiths Hut', 'Bat Cave Cave', 'Bat Cave Ledge Peg', 'Hammer Pegs Mirror Spot', 'Hammer Pegs Entry Mirror Spot', 'Blacksmith WS']),
|
create_lw_region(player, 'Blacksmith Area', None, ['Blacksmiths Hut', 'Bat Cave Cave', 'Bat Cave Ledge Peg', 'Hammer Pegs Mirror Spot', 'Hammer Pegs Entry Mirror Spot', 'Blacksmith WS']),
|
||||||
create_lw_region(player, 'Bat Cave Ledge', None, ['Bat Cave Ledge Peg (East)', 'Bat Cave Drop']),
|
create_lw_region(player, 'Bat Cave Ledge', None, ['Bat Cave Ledge Peg (East)', 'Bat Cave Drop']),
|
||||||
create_lw_region(player, 'Sand Dunes Area', None, ['Dark Dunes Mirror Spot', 'Sand Dunes NW', 'Sand Dunes WN', 'Sand Dunes SC']),
|
create_lw_region(player, 'Sand Dunes Area', None, ['Dark Dunes Mirror Spot', 'Sand Dunes NW', 'Sand Dunes WN', 'Sand Dunes SC']),
|
||||||
create_lw_region(player, 'Maze Race Area', None, ['Dig Game Mirror Spot', 'Maze Race ES']),
|
create_lw_region(player, 'Maze Race Area', None, ['Dig Game Mirror Spot', 'Maze Race ES']),
|
||||||
create_lw_region(player, 'Maze Race Ledge', None, ['Two Brothers House (West)', 'Maze Race Game', 'Dig Game Ledge Mirror Spot']),
|
create_lw_region(player, 'Maze Race Ledge', None, ['Two Brothers House (West)', 'Maze Race Game', 'Dig Game Ledge Mirror Spot'], 'a race against time'),
|
||||||
create_lw_region(player, 'Maze Race Prize', ['Maze Race'], ['Maze Race Ledge Drop']), #this is a separate region to make OWG item get possible without allowing the Entrance access
|
create_lw_region(player, 'Maze Race Prize', ['Maze Race'], ['Maze Race Ledge Drop']), #this is a separate region to make OWG item get possible without allowing the Entrance access
|
||||||
create_lw_region(player, 'Kakariko Suburb Area', None, ['Library', 'Two Brothers House (East)', 'Kakariko Gamble Game', 'Frog Mirror Spot', 'Frog Prison Mirror Spot', 'Archery Game Mirror Spot', 'Kakariko Suburb NE', 'Kakariko Suburb WS', 'Kakariko Suburb ES']),
|
create_lw_region(player, 'Kakariko Suburb Area', None, ['Library', 'Two Brothers House (East)', 'Kakariko Gamble Game', 'Frog Mirror Spot', 'Frog Prison Mirror Spot', 'Archery Game Mirror Spot', 'Kakariko Suburb NE', 'Kakariko Suburb WS', 'Kakariko Suburb ES']),
|
||||||
create_lw_region(player, 'Flute Boy Area', ['Flute Spot'], ['Stumpy Mirror Spot', 'Flute Boy SC']),
|
create_lw_region(player, 'Flute Boy Area', ['Flute Spot'], ['Stumpy Mirror Spot', 'Flute Boy SC']),
|
||||||
@@ -83,17 +83,17 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Central Bonk Rocks Area', None, ['Bonk Fairy (Light)', 'Dark Bonk Rocks Mirror Spot', 'Central Bonk Rocks NW', 'Central Bonk Rocks SW', 'Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES']),
|
create_lw_region(player, 'Central Bonk Rocks Area', None, ['Bonk Fairy (Light)', 'Dark Bonk Rocks Mirror Spot', 'Central Bonk Rocks NW', 'Central Bonk Rocks SW', 'Central Bonk Rocks EN', 'Central Bonk Rocks EC', 'Central Bonk Rocks ES']),
|
||||||
create_lw_region(player, 'Links House Area', None, ['Links House', 'Big Bomb Shop Mirror Spot', 'Links House NE', 'Links House WN', 'Links House WC', 'Links House WS', 'Links House SC', 'Links House ES']),
|
create_lw_region(player, 'Links House Area', None, ['Links House', 'Big Bomb Shop Mirror Spot', 'Links House NE', 'Links House WN', 'Links House WC', 'Links House WS', 'Links House SC', 'Links House ES']),
|
||||||
create_lw_region(player, 'Stone Bridge Area', None, ['Hammer Bridge North Mirror Spot', 'Hammer Bridge South Mirror Spot', 'Stone Bridge NC', 'Stone Bridge EN', 'Stone Bridge WS', 'Stone Bridge SC']),
|
create_lw_region(player, 'Stone Bridge Area', None, ['Hammer Bridge North Mirror Spot', 'Hammer Bridge South Mirror Spot', 'Stone Bridge NC', 'Stone Bridge EN', 'Stone Bridge WS', 'Stone Bridge SC']),
|
||||||
create_lw_region(player, 'Stone Bridge Water', None, ['Dark Hobo Mirror Spot', 'Stone Bridge WC', 'Stone Bridge EC'], Terrain.Water),
|
create_lw_region(player, 'Stone Bridge Water', None, ['Dark Hobo Mirror Spot', 'Stone Bridge WC', 'Stone Bridge EC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Hobo Bridge', ['Hobo'], ['Hobo EC'], Terrain.Water),
|
create_lw_region(player, 'Hobo Bridge', ['Hobo'], ['Hobo EC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'South Teleporter Cliff Ledge Drop', 'Statues Cliff Ledge Drop']),
|
create_lw_region(player, 'Central Cliffs', None, ['Central Bonk Rocks Cliff Ledge Drop', 'Links House Cliff Ledge Drop', 'Stone Bridge Cliff Ledge Drop', 'Lake Hylia Area Cliff Ledge Drop', 'Lake Hylia Island FAWT Ledge Drop', 'Stone Bridge EC Cliff Water Drop', 'Tree Line WC Cliff Water Drop', 'C Whirlpool Outer Cliff Ledge Drop', 'C Whirlpool Cliff Ledge Drop', 'South Teleporter Cliff Ledge Drop', 'Statues Cliff Ledge Drop']),
|
||||||
create_lw_region(player, 'Tree Line Area', None, ['Lake Hylia Fairy', 'Dark Tree Line Mirror Spot', 'Tree Line WN', 'Tree Line NW', 'Tree Line SE']),
|
create_lw_region(player, 'Tree Line Area', None, ['Lake Hylia Fairy', 'Dark Tree Line Mirror Spot', 'Tree Line WN', 'Tree Line NW', 'Tree Line SE']),
|
||||||
create_lw_region(player, 'Tree Line Water', None, ['Tree Line WC', 'Tree Line SC'], Terrain.Water),
|
create_lw_region(player, 'Tree Line Water', None, ['Tree Line WC', 'Tree Line SC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Eastern Nook Area', None, ['Long Fairy Cave', 'Darkness Nook Mirror Spot', 'East Hyrule Teleporter', 'Eastern Nook NE']),
|
create_lw_region(player, 'Eastern Nook Area', None, ['Long Fairy Cave', 'Darkness Nook Mirror Spot', 'East Hyrule Teleporter', 'Eastern Nook NE']),
|
||||||
create_lw_region(player, 'Desert Area', None, ['Desert Palace Statue Move', 'Checkerboard Ledge Approach', 'Aginahs Cave', 'Misery Mire Mirror Spot', 'Desert ES']),
|
create_lw_region(player, 'Desert Area', None, ['Desert Palace Statue Move', 'Checkerboard Ledge Approach', 'Aginahs Cave', 'Misery Mire Mirror Spot', 'Desert ES']),
|
||||||
create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Ledge Outer Rocks', 'Desert Ledge Drop', 'Desert Palace Entrance (West)', 'Misery Mire Ledge Mirror Spot']),
|
create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Ledge Outer Rocks', 'Desert Ledge Drop', 'Desert Palace Entrance (West)', 'Misery Mire Ledge Mirror Spot'], 'the desert ledge'),
|
||||||
create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Ledge Inner Rocks', 'Desert Palace Entrance (North)', 'Misery Mire Blocked Mirror Spot']),
|
create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Ledge Inner Rocks', 'Desert Palace Entrance (North)', 'Misery Mire Blocked Mirror Spot'], 'the desert ledge'),
|
||||||
create_lw_region(player, 'Desert Checkerboard Ledge', None, ['Checkerboard Ledge Leave', 'Checkerboard Ledge Drop', 'Checkerboard Cave']),
|
create_lw_region(player, 'Desert Checkerboard Ledge', None, ['Checkerboard Ledge Leave', 'Checkerboard Ledge Drop', 'Checkerboard Cave']),
|
||||||
create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Misery Mire Main Mirror Spot']),
|
create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Misery Mire Main Mirror Spot'], 'a sandy vista'),
|
||||||
create_lw_region(player, 'Desert Palace Mouth', None, ['Desert Mouth Drop', 'Desert Palace Entrance (East)']),
|
create_lw_region(player, 'Desert Palace Mouth', None, ['Desert Mouth Drop', 'Desert Palace Entrance (East)']),
|
||||||
create_lw_region(player, 'Desert Palace Teleporter Ledge', None, ['Desert Teleporter Drop', 'Desert Teleporter']),
|
create_lw_region(player, 'Desert Palace Teleporter Ledge', None, ['Desert Teleporter Drop', 'Desert Teleporter']),
|
||||||
create_lw_region(player, 'Desert Northeast Cliffs', None, ['Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Desert Pass Southeast Cliff Ledge Drop', 'Dam Cliff Ledge Drop']),
|
create_lw_region(player, 'Desert Northeast Cliffs', None, ['Desert Boss Cliff Ledge Drop', 'Checkerboard Cliff Ledge Drop', 'Suburb Cliff Ledge Drop', 'Cave 45 Cliff Ledge Drop', 'Desert C Whirlpool Cliff Ledge Drop', 'Desert Pass Cliff Ledge Drop', 'Desert Pass Southeast Cliff Ledge Drop', 'Dam Cliff Ledge Drop']),
|
||||||
@@ -102,17 +102,17 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Flute Boy Bush Entry', None, ['Flute Boy Bush (North)', 'Stumpy Bush Entry Mirror Spot', 'Flute Boy Approach NC']),
|
create_lw_region(player, 'Flute Boy Bush Entry', None, ['Flute Boy Bush (North)', 'Stumpy Bush Entry Mirror Spot', 'Flute Boy Approach NC']),
|
||||||
create_lw_region(player, 'Cave 45 Ledge', None, ['Cave 45 Inverted Leave', 'Cave 45 Ledge Drop', 'Cave 45']),
|
create_lw_region(player, 'Cave 45 Ledge', None, ['Cave 45 Inverted Leave', 'Cave 45 Ledge Drop', 'Cave 45']),
|
||||||
create_lw_region(player, 'C Whirlpool Area', None, ['C Whirlpool Rock (Bottom)', 'C Whirlpool Water Entry', 'Dark C Whirlpool Mirror Spot', 'South Hyrule Teleporter', 'C Whirlpool EN', 'C Whirlpool ES', 'C Whirlpool SC']),
|
create_lw_region(player, 'C Whirlpool Area', None, ['C Whirlpool Rock (Bottom)', 'C Whirlpool Water Entry', 'Dark C Whirlpool Mirror Spot', 'South Hyrule Teleporter', 'C Whirlpool EN', 'C Whirlpool ES', 'C Whirlpool SC']),
|
||||||
create_lw_region(player, 'C Whirlpool Water', None, ['C Whirlpool Landing', 'C Whirlpool', 'C Whirlpool EC'], Terrain.Water),
|
create_lw_region(player, 'C Whirlpool Water', None, ['C Whirlpool Landing', 'C Whirlpool', 'C Whirlpool EC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'C Whirlpool Outer Area', None, ['C Whirlpool Rock (Top)', 'Dark C Whirlpool Outer Mirror Spot', 'C Whirlpool WC', 'C Whirlpool NW']),
|
create_lw_region(player, 'C Whirlpool Outer Area', None, ['C Whirlpool Rock (Top)', 'Dark C Whirlpool Outer Mirror Spot', 'C Whirlpool WC', 'C Whirlpool NW']),
|
||||||
create_lw_region(player, 'Statues Area', None, ['Statues Water Entry', 'Light Hype Fairy', 'Hype Cave Mirror Spot', 'Statues NC', 'Statues WN', 'Statues WS', 'Statues SC']),
|
create_lw_region(player, 'Statues Area', None, ['Statues Water Entry', 'Light Hype Fairy', 'Hype Cave Mirror Spot', 'Statues NC', 'Statues WN', 'Statues WS', 'Statues SC']),
|
||||||
create_lw_region(player, 'Statues Water', None, ['Statues Landing', 'Statues WC'], Terrain.Water),
|
create_lw_region(player, 'Statues Water', None, ['Statues Landing', 'Statues WC'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Lake Hylia Area', None, ['Lake Hylia Water Drop', 'Lake Hylia Fortune Teller', 'Cave Shop (Lake Hylia)', 'Ice Lake Mirror Spot', 'Lake Hylia NW']),
|
create_lw_region(player, 'Lake Hylia Area', None, ['Lake Hylia Water Drop', 'Lake Hylia Fortune Teller', 'Cave Shop (Lake Hylia)', 'Ice Lake Mirror Spot', 'Lake Hylia NW']),
|
||||||
create_lw_region(player, 'Lake Hylia South Shore', None, ['Lake Hylia South Water Drop', 'Mini Moldorm Cave', 'Ice Lake Southwest Mirror Spot', 'Ice Lake Southeast Mirror Spot', 'Lake Hylia WS', 'Lake Hylia ES']),
|
create_lw_region(player, 'Lake Hylia South Shore', None, ['Lake Hylia South Water Drop', 'Mini Moldorm Cave', 'Ice Lake Southwest Mirror Spot', 'Ice Lake Southeast Mirror Spot', 'Lake Hylia WS', 'Lake Hylia ES']),
|
||||||
create_lw_region(player, 'Lake Hylia Northeast Bank', None, ['Lake Hylia Northeast Water Drop', 'Ice Lake Northeast Mirror Spot', 'Lake Hylia NE']),
|
create_lw_region(player, 'Lake Hylia Northeast Bank', None, ['Lake Hylia Northeast Water Drop', 'Ice Lake Northeast Mirror Spot', 'Lake Hylia NE']),
|
||||||
create_lw_region(player, 'Lake Hylia Central Island', None, ['Lake Hylia Central Water Drop', 'Capacity Upgrade', 'Ice Palace Mirror Spot', 'Lake Hylia Teleporter']),
|
create_lw_region(player, 'Lake Hylia Central Island', None, ['Lake Hylia Central Water Drop', 'Capacity Upgrade', 'Ice Palace Mirror Spot', 'Lake Hylia Teleporter']),
|
||||||
create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island'], ['Lake Hylia Island Water Drop']),
|
create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island'], ['Lake Hylia Island Water Drop']),
|
||||||
create_lw_region(player, 'Lake Hylia Water', None, ['Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia West Pier', 'Lake Hylia East Pier', 'Lake Hylia NC', 'Lake Hylia EC', 'Lake Hylia Whirlpool'], Terrain.Water),
|
create_lw_region(player, 'Lake Hylia Water', None, ['Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia West Pier', 'Lake Hylia East Pier', 'Lake Hylia NC', 'Lake Hylia EC', 'Lake Hylia Whirlpool'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Lake Hylia Water D', None, ['Lake Hylia Water D Entry', 'Ice Lake Moat Mirror Spot'], Terrain.Water),
|
create_lw_region(player, 'Lake Hylia Water D', None, ['Lake Hylia Water D Entry', 'Ice Lake Moat Mirror Spot'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Ice Cave Area', None, ['Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', 'Shopping Mall Mirror Spot', 'Ice Cave SE', 'Ice Cave SW']),
|
create_lw_region(player, 'Ice Cave Area', None, ['Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave', 'Shopping Mall Mirror Spot', 'Ice Cave SE', 'Ice Cave SW']),
|
||||||
create_lw_region(player, 'Desert Pass Area', ['Middle Aged Man'], ['Desert Pass Ladder (South)', 'Middle Aged Man', 'Desert Fairy', '50 Rupee Cave', 'Swamp Nook Mirror Spot', 'Desert Pass WS', 'Desert Pass EC', 'Desert Pass Rocks (North)']),
|
create_lw_region(player, 'Desert Pass Area', ['Middle Aged Man'], ['Desert Pass Ladder (South)', 'Middle Aged Man', 'Desert Fairy', '50 Rupee Cave', 'Swamp Nook Mirror Spot', 'Desert Pass WS', 'Desert Pass EC', 'Desert Pass Rocks (North)']),
|
||||||
create_lw_region(player, 'Middle Aged Man', ['Purple Chest'], None),
|
create_lw_region(player, 'Middle Aged Man', ['Purple Chest'], None),
|
||||||
@@ -121,13 +121,13 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Dam Area', ['Sunken Treasure'], ['Dam', 'Swamp Mirror Spot', 'Dam WC', 'Dam WS', 'Dam NC', 'Dam EC']),
|
create_lw_region(player, 'Dam Area', ['Sunken Treasure'], ['Dam', 'Swamp Mirror Spot', 'Dam WC', 'Dam WS', 'Dam NC', 'Dam EC']),
|
||||||
create_lw_region(player, 'South Pass Area', None, ['Dark South Pass Mirror Spot', 'South Pass WC', 'South Pass NC', 'South Pass ES']),
|
create_lw_region(player, 'South Pass Area', None, ['Dark South Pass Mirror Spot', 'South Pass WC', 'South Pass NC', 'South Pass ES']),
|
||||||
create_lw_region(player, 'Octoballoon Area', None, ['Octoballoon Water Drop', 'Bomber Corner Mirror Spot', 'Octoballoon WS', 'Octoballoon NE']),
|
create_lw_region(player, 'Octoballoon Area', None, ['Octoballoon Water Drop', 'Bomber Corner Mirror Spot', 'Octoballoon WS', 'Octoballoon NE']),
|
||||||
create_lw_region(player, 'Octoballoon Water', None, ['Octoballoon Pier', 'Octoballoon WC', 'Octoballoon Whirlpool'], Terrain.Water),
|
create_lw_region(player, 'Octoballoon Water', None, ['Octoballoon Pier', 'Octoballoon WC', 'Octoballoon Whirlpool'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Octoballoon Water Ledge', None, ['Octoballoon Waterfall Water Drop', 'Octoballoon NW'], Terrain.Water),
|
create_lw_region(player, 'Octoballoon Water Ledge', None, ['Octoballoon Waterfall Water Drop', 'Octoballoon NW'], 'Light World', Terrain.Water),
|
||||||
|
|
||||||
create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods Bush Rock (East)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
|
create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods Bush Rock (East)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
|
||||||
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Lost Woods East Mirror Spot', 'Skull Woods SE']),
|
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Lost Woods East Mirror Spot', 'Skull Woods SE']),
|
||||||
create_dw_region(player, 'Skull Woods Portal Entry', None, ['Skull Woods Bush Rock (West)', 'Lost Woods Entry Mirror Spot', 'Skull Woods SC']),
|
create_dw_region(player, 'Skull Woods Portal Entry', None, ['Skull Woods Bush Rock (West)', 'Lost Woods Entry Mirror Spot', 'Skull Woods SC']),
|
||||||
create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Lost Woods Pedestal Mirror Spot']),
|
create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Lost Woods Pedestal Mirror Spot'], 'a deep, dark forest'),
|
||||||
create_dw_region(player, 'Skull Woods Forgotten Path (Southwest)', None, ['Skull Woods Forgotten Bush (West)', 'Lost Woods Southwest Mirror Spot', 'Skull Woods SW']),
|
create_dw_region(player, 'Skull Woods Forgotten Path (Southwest)', None, ['Skull Woods Forgotten Bush (West)', 'Lost Woods Southwest Mirror Spot', 'Skull Woods SW']),
|
||||||
create_dw_region(player, 'Skull Woods Forgotten Path (Northeast)', None, ['Skull Woods Forgotten Bush (East)', 'Lost Woods East (Forgotten) Mirror Spot', 'Lost Woods West (Forgotten) Mirror Spot', 'Skull Woods EN']),
|
create_dw_region(player, 'Skull Woods Forgotten Path (Northeast)', None, ['Skull Woods Forgotten Bush (East)', 'Lost Woods East (Forgotten) Mirror Spot', 'Lost Woods West (Forgotten) Mirror Spot', 'Skull Woods EN']),
|
||||||
create_dw_region(player, 'Dark Lumberjack Area', None, ['Dark World Lumberjack Shop', 'Lumberjack Mirror Spot', 'Dark Lumberjack WN', 'Dark Lumberjack SW']),
|
create_dw_region(player, 'Dark Lumberjack Area', None, ['Dark World Lumberjack Shop', 'Lumberjack Mirror Spot', 'Dark Lumberjack WN', 'Dark Lumberjack SW']),
|
||||||
@@ -137,14 +137,14 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'East Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Superbunny Cave (Top)', 'Hookshot Cave', 'East Death Mountain (Top West) Mirror Spot', 'East Death Mountain (Top East) Mirror Spot', 'East Dark Death Mountain WN', 'East Dark Death Mountain EN']),
|
create_dw_region(player, 'East Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Superbunny Cave (Top)', 'Hookshot Cave', 'East Death Mountain (Top West) Mirror Spot', 'East Death Mountain (Top East) Mirror Spot', 'East Dark Death Mountain WN', 'East Dark Death Mountain EN']),
|
||||||
create_dw_region(player, 'East Dark Death Mountain (Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Teleporter (East)', 'Fairy Ascension Mirror Spot']),
|
create_dw_region(player, 'East Dark Death Mountain (Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Teleporter (East)', 'Fairy Ascension Mirror Spot']),
|
||||||
create_dw_region(player, 'East Dark Death Mountain (Bottom Left)', None, ['Death Mountain Bridge Mirror Spot', 'East Dark Death Mountain WS']),
|
create_dw_region(player, 'East Dark Death Mountain (Bottom Left)', None, ['Death Mountain Bridge Mirror Spot', 'East Dark Death Mountain WS']),
|
||||||
create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Spiral Cave Mirror Spot', 'Mimic Cave Mirror Spot']),
|
create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Spiral Cave Mirror Spot', 'Mimic Cave Mirror Spot'], 'a dark ledge'),
|
||||||
create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance', 'Isolated Ledge Mirror Spot']),
|
create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance', 'Isolated Ledge Mirror Spot'], 'a dark vista'),
|
||||||
create_dw_region(player, 'Dark Death Mountain Floating Island', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']),
|
create_dw_region(player, 'Dark Death Mountain Floating Island', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot'], 'a dark floating island'),
|
||||||
create_dw_region(player, 'Turtle Rock Area', None, ['Turtle Rock Tail Ledge Drop', 'Turtle Rock', 'TR Pegs Area Mirror Spot', 'Turtle Rock WN']),
|
create_dw_region(player, 'Turtle Rock Area', None, ['Turtle Rock Tail Ledge Drop', 'Turtle Rock', 'TR Pegs Area Mirror Spot', 'Turtle Rock WN']),
|
||||||
create_dw_region(player, 'Turtle Rock Ledge', None, ['Turtle Rock Ledge Drop', 'Turtle Rock Teleporter']),
|
create_dw_region(player, 'Turtle Rock Ledge', None, ['Turtle Rock Ledge Drop', 'Turtle Rock Teleporter']),
|
||||||
create_dw_region(player, 'Bumper Cave Area', None, ['Bumper Cave Entrance Rock', 'Mountain Entry Mirror Spot', 'Bumper Cave NW', 'Bumper Cave SE']),
|
create_dw_region(player, 'Bumper Cave Area', None, ['Bumper Cave Entrance Rock', 'Mountain Entry Mirror Spot', 'Bumper Cave NW', 'Bumper Cave SE']),
|
||||||
create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave Ledge Drop', 'Bumper Cave (Bottom)', 'Mountain Entry Entrance Mirror Spot']),
|
create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave Ledge Drop', 'Bumper Cave (Bottom)', 'Mountain Entry Entrance Mirror Spot']),
|
||||||
create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Entrance Drop', 'Bumper Cave (Top)', 'Mountain Entry Ledge Mirror Spot']),
|
create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Entrance Drop', 'Bumper Cave (Top)', 'Mountain Entry Ledge Mirror Spot'], 'a ledge with an item'),
|
||||||
create_dw_region(player, 'Catfish Area', ['Catfish'], ['Zora Waterfall Mirror Spot', 'Catfish SE']),
|
create_dw_region(player, 'Catfish Area', ['Catfish'], ['Zora Waterfall Mirror Spot', 'Catfish SE']),
|
||||||
create_dw_region(player, 'Skull Woods Pass West Area', None, ['Skull Woods Pass Bush Row (West)', 'Lost Woods Pass West Mirror Spot', 'Skull Woods Pass NW', 'Skull Woods Pass SW']),
|
create_dw_region(player, 'Skull Woods Pass West Area', None, ['Skull Woods Pass Bush Row (West)', 'Lost Woods Pass West Mirror Spot', 'Skull Woods Pass NW', 'Skull Woods Pass SW']),
|
||||||
create_dw_region(player, 'Skull Woods Pass East Top Area', None, ['Lost Woods Pass East Top Mirror Spot', 'Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Bush (North)', 'Skull Woods Pass NE']),
|
create_dw_region(player, 'Skull Woods Pass East Top Area', None, ['Lost Woods Pass East Top Mirror Spot', 'Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Bush (North)', 'Skull Woods Pass NE']),
|
||||||
@@ -157,13 +157,13 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'Dark Graveyard North', None, ['Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Dark Graveyard Bush (North)']),
|
create_dw_region(player, 'Dark Graveyard North', None, ['Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Dark Graveyard Bush (North)']),
|
||||||
create_dw_region(player, 'Qirn Jump Area', None, ['Qirn Jump Water Drop', 'River Bend Mirror Spot', 'Qirn Jump WC', 'Qirn Jump SW']),
|
create_dw_region(player, 'Qirn Jump Area', None, ['Qirn Jump Water Drop', 'River Bend Mirror Spot', 'Qirn Jump WC', 'Qirn Jump SW']),
|
||||||
create_dw_region(player, 'Qirn Jump East Bank', None, ['Qirn Jump East Water Drop', 'River Bend East Mirror Spot', 'Qirn Jump SE', 'Qirn Jump EC', 'Qirn Jump ES']),
|
create_dw_region(player, 'Qirn Jump East Bank', None, ['Qirn Jump East Water Drop', 'River Bend East Mirror Spot', 'Qirn Jump SE', 'Qirn Jump EC', 'Qirn Jump ES']),
|
||||||
create_dw_region(player, 'Qirn Jump Water', None, ['Qirn Jump Pier', 'Qirn Jump Whirlpool', 'Qirn Jump EN', 'Qirn Jump SC'], Terrain.Water),
|
create_dw_region(player, 'Qirn Jump Water', None, ['Qirn Jump Pier', 'Qirn Jump Whirlpool', 'Qirn Jump EN', 'Qirn Jump SC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Dark Witch Area', None, ['Dark Witch Water Drop', 'Dark Witch Rock (South)', 'Dark World Potion Shop', 'Potion Shop Mirror Spot', 'Dark Witch WC', 'Dark Witch WS']),
|
create_dw_region(player, 'Dark Witch Area', None, ['Dark Witch Water Drop', 'Dark Witch Rock (South)', 'Dark World Potion Shop', 'Potion Shop Mirror Spot', 'Dark Witch WC', 'Dark Witch WS']),
|
||||||
create_dw_region(player, 'Dark Witch Northeast', None, ['Dark Witch Northeast Water Drop', 'Dark Witch Rock (North)', 'Potion Shop Northeast Mirror Spot', 'Dark Witch EC']),
|
create_dw_region(player, 'Dark Witch Northeast', None, ['Dark Witch Northeast Water Drop', 'Dark Witch Rock (North)', 'Potion Shop Northeast Mirror Spot', 'Dark Witch EC']),
|
||||||
create_dw_region(player, 'Dark Witch Water', None, ['Dark Witch WN', 'Dark Witch EN'], Terrain.Water),
|
create_dw_region(player, 'Dark Witch Water', None, ['Dark Witch WN', 'Dark Witch EN'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Catfish Approach Area', None, ['Catfish Approach Rocks (West)', 'Catfish Approach Bottom Ledge Drop', 'Catfish Approach Water Drop', 'Zora Approach Mirror Spot', 'Catfish Approach WC']),
|
create_dw_region(player, 'Catfish Approach Area', None, ['Catfish Approach Rocks (West)', 'Catfish Approach Bottom Ledge Drop', 'Catfish Approach Water Drop', 'Zora Approach Mirror Spot', 'Catfish Approach WC']),
|
||||||
create_dw_region(player, 'Catfish Approach Ledge', None, ['Catfish Approach Rocks (East)', 'Catfish Approach Ledge Drop', 'Zora Approach Ledge Mirror Spot', 'Catfish Approach NE']),
|
create_dw_region(player, 'Catfish Approach Ledge', None, ['Catfish Approach Rocks (East)', 'Catfish Approach Ledge Drop', 'Zora Approach Ledge Mirror Spot', 'Catfish Approach NE']),
|
||||||
create_dw_region(player, 'Catfish Approach Water', None, ['Catfish Approach WN'], Terrain.Water),
|
create_dw_region(player, 'Catfish Approach Water', None, ['Catfish Approach WN'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Village of Outcasts Area', None, ['Village of Outcasts Pegs', 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Brewery', 'Kakariko Mirror Spot', 'Village of Outcasts NW', 'Village of Outcasts NC', 'Village of Outcasts NE', 'Village of Outcasts ES', 'Village of Outcasts SE']),
|
create_dw_region(player, 'Village of Outcasts Area', None, ['Village of Outcasts Pegs', 'Chest Game', 'Thieves Town', 'C-Shaped House', 'Brewery', 'Kakariko Mirror Spot', 'Village of Outcasts NW', 'Village of Outcasts NC', 'Village of Outcasts NE', 'Village of Outcasts ES', 'Village of Outcasts SE']),
|
||||||
create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop', 'Kakariko Grass Mirror Spot']),
|
create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop', 'Kakariko Grass Mirror Spot']),
|
||||||
create_dw_region(player, 'Shield Shop Area', None, ['Shield Shop Fence (Outer) Ledge Drop', 'Forgotton Forest Mirror Spot', 'Shield Shop NW', 'Shield Shop NE']),
|
create_dw_region(player, 'Shield Shop Area', None, ['Shield Shop Fence (Outer) Ledge Drop', 'Forgotton Forest Mirror Spot', 'Shield Shop NW', 'Shield Shop NE']),
|
||||||
@@ -175,7 +175,7 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Wooden Bridge Mirror Spot', 'Broken Bridge SW']),
|
create_dw_region(player, 'Broken Bridge Area', None, ['Broken Bridge Hammer Rock (South)', 'Broken Bridge Water Drop', 'Wooden Bridge Mirror Spot', 'Broken Bridge SW']),
|
||||||
create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Wooden Bridge Northeast Mirror Spot', 'Broken Bridge NE']),
|
create_dw_region(player, 'Broken Bridge Northeast', None, ['Broken Bridge Hammer Rock (North)', 'Broken Bridge Hookshot Gap', 'Broken Bridge Northeast Water Drop', 'Wooden Bridge Northeast Mirror Spot', 'Broken Bridge NE']),
|
||||||
create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Wooden Bridge West Mirror Spot', 'Broken Bridge NW']),
|
create_dw_region(player, 'Broken Bridge West', None, ['Broken Bridge West Water Drop', 'Wooden Bridge West Mirror Spot', 'Broken Bridge NW']),
|
||||||
create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], Terrain.Water),
|
create_dw_region(player, 'Broken Bridge Water', None, ['Broken Bridge NC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Eastern Palace Mirror Spot', 'Palace of Darkness SW', 'Palace of Darkness SE']),
|
create_dw_region(player, 'Palace of Darkness Area', None, ['Palace of Darkness Hint', 'Palace of Darkness', 'Eastern Palace Mirror Spot', 'Palace of Darkness SW', 'Palace of Darkness SE']),
|
||||||
create_dw_region(player, 'Darkness Cliff', None, ['Dark Dunes Ledge Drop', 'Hammer Bridge North Ledge Drop', 'Dark Tree Line Ledge Drop', 'Palace of Darkness Ledge Drop']),
|
create_dw_region(player, 'Darkness Cliff', None, ['Dark Dunes Ledge Drop', 'Hammer Bridge North Ledge Drop', 'Dark Tree Line Ledge Drop', 'Palace of Darkness Ledge Drop']),
|
||||||
create_dw_region(player, 'Hammer Pegs Entry', None, ['Peg Area Rocks (West)', 'Blacksmith Entry Mirror Spot', 'Hammer Pegs WS']),
|
create_dw_region(player, 'Hammer Pegs Entry', None, ['Peg Area Rocks (West)', 'Blacksmith Entry Mirror Spot', 'Hammer Pegs WS']),
|
||||||
@@ -192,11 +192,11 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'Big Bomb Shop Area', None, ['Big Bomb Shop', 'Links House Mirror Spot', 'Big Bomb Shop NE', 'Big Bomb Shop WN', 'Big Bomb Shop WC', 'Big Bomb Shop WS', 'Big Bomb Shop SC', 'Big Bomb Shop ES']),
|
create_dw_region(player, 'Big Bomb Shop Area', None, ['Big Bomb Shop', 'Links House Mirror Spot', 'Big Bomb Shop NE', 'Big Bomb Shop WN', 'Big Bomb Shop WC', 'Big Bomb Shop WS', 'Big Bomb Shop SC', 'Big Bomb Shop ES']),
|
||||||
create_dw_region(player, 'Hammer Bridge North Area', None, ['Hammer Bridge Pegs (North)', 'Hammer Bridge Water Drop', 'Stone Bridge Mirror Spot', 'Hammer Bridge NC', 'Hammer Bridge EN']),
|
create_dw_region(player, 'Hammer Bridge North Area', None, ['Hammer Bridge Pegs (North)', 'Hammer Bridge Water Drop', 'Stone Bridge Mirror Spot', 'Hammer Bridge NC', 'Hammer Bridge EN']),
|
||||||
create_dw_region(player, 'Hammer Bridge South Area', None, ['Hammer Bridge Pegs (South)', 'Stone Bridge South Mirror Spot', 'Hammer Bridge WS', 'Hammer Bridge SC']),
|
create_dw_region(player, 'Hammer Bridge South Area', None, ['Hammer Bridge Pegs (South)', 'Stone Bridge South Mirror Spot', 'Hammer Bridge WS', 'Hammer Bridge SC']),
|
||||||
create_dw_region(player, 'Hammer Bridge Water', None, ['Hammer Bridge Pier', 'Hobo Mirror Spot', 'Hammer Bridge EC'], Terrain.Water),
|
create_dw_region(player, 'Hammer Bridge Water', None, ['Hammer Bridge Pier', 'Hobo Mirror Spot', 'Hammer Bridge EC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Dark Central Cliffs', None, ['Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', 'Ice Lake Area Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop',
|
create_dw_region(player, 'Dark Central Cliffs', None, ['Dark Bonk Rocks Cliff Ledge Drop', 'Bomb Shop Cliff Ledge Drop', 'Hammer Bridge South Cliff Ledge Drop', 'Ice Lake Area Cliff Ledge Drop', 'Ice Palace Island FAWT Ledge Drop',
|
||||||
'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Hype Cliff Ledge Drop', 'Dark South Teleporter Cliff Ledge Drop']),
|
'Hammer Bridge EC Cliff Water Drop', 'Dark Tree Line WC Cliff Water Drop', 'Dark C Whirlpool Outer Cliff Ledge Drop', 'Dark C Whirlpool Cliff Ledge Drop', 'Hype Cliff Ledge Drop', 'Dark South Teleporter Cliff Ledge Drop']),
|
||||||
create_dw_region(player, 'Dark Tree Line Area', None, ['Dark Lake Hylia Fairy', 'Tree Line Mirror Spot', 'Dark Tree Line WN', 'Dark Tree Line NW', 'Dark Tree Line SE']),
|
create_dw_region(player, 'Dark Tree Line Area', None, ['Dark Lake Hylia Fairy', 'Tree Line Mirror Spot', 'Dark Tree Line WN', 'Dark Tree Line NW', 'Dark Tree Line SE']),
|
||||||
create_dw_region(player, 'Dark Tree Line Water', None, ['Dark Tree Line WC', 'Dark Tree Line SC'], Terrain.Water),
|
create_dw_region(player, 'Dark Tree Line Water', None, ['Dark Tree Line WC', 'Dark Tree Line SC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Palace of Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Eastern Nook Mirror Spot', 'Palace of Darkness Nook NE']),
|
create_dw_region(player, 'Palace of Darkness Nook Area', None, ['East Dark World Hint', 'East Dark World Teleporter', 'Eastern Nook Mirror Spot', 'Palace of Darkness Nook NE']),
|
||||||
create_dw_region(player, 'Misery Mire Area', None, ['Mire Shed', 'Misery Mire', 'Dark Desert Fairy', 'Dark Desert Hint', 'Desert Mirror Spot', 'Desert Ledge Mirror Spot', 'Checkerboard Mirror Spot', 'DP Stairs Mirror Spot', 'DP Entrance (North) Mirror Spot']),
|
create_dw_region(player, 'Misery Mire Area', None, ['Mire Shed', 'Misery Mire', 'Dark Desert Fairy', 'Dark Desert Hint', 'Desert Mirror Spot', 'Desert Ledge Mirror Spot', 'Checkerboard Mirror Spot', 'DP Stairs Mirror Spot', 'DP Entrance (North) Mirror Spot']),
|
||||||
create_dw_region(player, 'Misery Mire Teleporter Ledge', None, ['Misery Mire Teleporter Ledge Drop', 'Misery Mire Teleporter']),
|
create_dw_region(player, 'Misery Mire Teleporter Ledge', None, ['Misery Mire Teleporter Ledge Drop', 'Misery Mire Teleporter']),
|
||||||
@@ -204,15 +204,15 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'Stumpy Approach Area', None, ['Stumpy Approach Bush (South)', 'Cave 45 Mirror Spot', 'Stumpy Approach NW', 'Stumpy Approach EC']),
|
create_dw_region(player, 'Stumpy Approach Area', None, ['Stumpy Approach Bush (South)', 'Cave 45 Mirror Spot', 'Stumpy Approach NW', 'Stumpy Approach EC']),
|
||||||
create_dw_region(player, 'Stumpy Approach Bush Entry', None, ['Stumpy Approach Bush (North)', 'Flute Boy Entry Mirror Spot', 'Stumpy Approach NC']),
|
create_dw_region(player, 'Stumpy Approach Bush Entry', None, ['Stumpy Approach Bush (North)', 'Flute Boy Entry Mirror Spot', 'Stumpy Approach NC']),
|
||||||
create_dw_region(player, 'Dark C Whirlpool Area', None, ['Dark C Whirlpool Rock (Bottom)', 'South Dark World Teleporter', 'C Whirlpool Mirror Spot', 'Dark C Whirlpool Water Entry', 'Dark C Whirlpool EN', 'Dark C Whirlpool ES', 'Dark C Whirlpool SC']),
|
create_dw_region(player, 'Dark C Whirlpool Area', None, ['Dark C Whirlpool Rock (Bottom)', 'South Dark World Teleporter', 'C Whirlpool Mirror Spot', 'Dark C Whirlpool Water Entry', 'Dark C Whirlpool EN', 'Dark C Whirlpool ES', 'Dark C Whirlpool SC']),
|
||||||
create_dw_region(player, 'Dark C Whirlpool Water', None, ['Dark C Whirlpool Landing', 'Dark C Whirlpool EC'], Terrain.Water),
|
create_dw_region(player, 'Dark C Whirlpool Water', None, ['Dark C Whirlpool Landing', 'Dark C Whirlpool EC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Dark C Whirlpool Outer Area', None, ['Dark C Whirlpool Rock (Top)', 'C Whirlpool Outer Mirror Spot', 'Dark C Whirlpool WC', 'Dark C Whirlpool NW']),
|
create_dw_region(player, 'Dark C Whirlpool Outer Area', None, ['Dark C Whirlpool Rock (Top)', 'C Whirlpool Outer Mirror Spot', 'Dark C Whirlpool WC', 'Dark C Whirlpool NW']),
|
||||||
create_dw_region(player, 'Hype Cave Area', None, ['Hype Cave Water Entry', 'Hype Cave', 'Statues Mirror Spot', 'Hype Cave NC', 'Hype Cave WN', 'Hype Cave WS', 'Hype Cave SC']),
|
create_dw_region(player, 'Hype Cave Area', None, ['Hype Cave Water Entry', 'Hype Cave', 'Statues Mirror Spot', 'Hype Cave NC', 'Hype Cave WN', 'Hype Cave WS', 'Hype Cave SC']),
|
||||||
create_dw_region(player, 'Hype Cave Water', None, ['Hype Cave Landing', 'Hype Cave WC'], Terrain.Water),
|
create_dw_region(player, 'Hype Cave Water', None, ['Hype Cave Landing', 'Hype Cave WC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Ice Lake Area', None, ['Ice Lake Water Drop', 'Dark Lake Hylia Shop', 'Lake Hylia Mirror Spot', 'Ice Lake NW']),
|
create_dw_region(player, 'Ice Lake Area', None, ['Ice Lake Water Drop', 'Dark Lake Hylia Shop', 'Lake Hylia Mirror Spot', 'Ice Lake NW']),
|
||||||
create_dw_region(player, 'Ice Lake Northeast Bank', None, ['Ice Lake Northeast Water Drop', 'Lake Hylia Northeast Mirror Spot', 'Ice Lake NE']),
|
create_dw_region(player, 'Ice Lake Northeast Bank', None, ['Ice Lake Northeast Water Drop', 'Lake Hylia Northeast Mirror Spot', 'Ice Lake NE']),
|
||||||
create_dw_region(player, 'Ice Lake Ledge (West)', None, ['Ice Lake Southwest Water Drop', 'South Shore Mirror Spot', 'Ice Lake WS']),
|
create_dw_region(player, 'Ice Lake Ledge (West)', None, ['Ice Lake Southwest Water Drop', 'South Shore Mirror Spot', 'Ice Lake WS']),
|
||||||
create_dw_region(player, 'Ice Lake Ledge (East)', None, ['Ice Lake Southeast Water Drop', 'South Shore East Mirror Spot', 'Ice Lake ES']),
|
create_dw_region(player, 'Ice Lake Ledge (East)', None, ['Ice Lake Southeast Water Drop', 'South Shore East Mirror Spot', 'Ice Lake ES']),
|
||||||
create_dw_region(player, 'Ice Lake Water', None, ['Ice Lake Northeast Pier', 'Ice Lake Moat Bomb Jump', 'Lake Hylia Island Mirror Spot', 'Ice Lake NC', 'Ice Lake EC'], Terrain.Water),
|
create_dw_region(player, 'Ice Lake Water', None, ['Ice Lake Northeast Pier', 'Ice Lake Moat Bomb Jump', 'Lake Hylia Island Mirror Spot', 'Ice Lake NC', 'Ice Lake EC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Ice Lake Moat', None, ['Ice Lake Moat Water Entry', 'Ice Lake Northeast Pier Hop', 'Lake Hylia Water Mirror Spot']),
|
create_dw_region(player, 'Ice Lake Moat', None, ['Ice Lake Moat Water Entry', 'Ice Lake Northeast Pier Hop', 'Lake Hylia Water Mirror Spot']),
|
||||||
create_dw_region(player, 'Ice Palace Area', None, ['Ice Palace', 'Ice Palace Teleporter', 'Lake Hylia Central Island Mirror Spot']),
|
create_dw_region(player, 'Ice Palace Area', None, ['Ice Palace', 'Ice Palace Teleporter', 'Lake Hylia Central Island Mirror Spot']),
|
||||||
create_dw_region(player, 'Shopping Mall Area', None, ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Ice Cave Mirror Spot', 'Shopping Mall SW', 'Shopping Mall SE']),
|
create_dw_region(player, 'Shopping Mall Area', None, ['Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Ice Cave Mirror Spot', 'Shopping Mall SW', 'Shopping Mall SE']),
|
||||||
@@ -220,8 +220,8 @@ def create_regions(world, player):
|
|||||||
create_dw_region(player, 'Swamp Area', None, ['Swamp Palace', 'Dam Mirror Spot', 'Swamp WC', 'Swamp WS', 'Swamp NC', 'Swamp EC']),
|
create_dw_region(player, 'Swamp Area', None, ['Swamp Palace', 'Dam Mirror Spot', 'Swamp WC', 'Swamp WS', 'Swamp NC', 'Swamp EC']),
|
||||||
create_dw_region(player, 'Dark South Pass Area', None, ['South Pass Mirror Spot', 'Dark South Pass WC', 'Dark South Pass NC', 'Dark South Pass ES']),
|
create_dw_region(player, 'Dark South Pass Area', None, ['South Pass Mirror Spot', 'Dark South Pass WC', 'Dark South Pass NC', 'Dark South Pass ES']),
|
||||||
create_dw_region(player, 'Bomber Corner Area', None, ['Bomber Corner Water Drop', 'Octoballoon Mirror Spot', 'Bomber Corner WS', 'Bomber Corner NE']),
|
create_dw_region(player, 'Bomber Corner Area', None, ['Bomber Corner Water Drop', 'Octoballoon Mirror Spot', 'Bomber Corner WS', 'Bomber Corner NE']),
|
||||||
create_dw_region(player, 'Bomber Corner Water', None, ['Bomber Corner Pier', 'Bomber Corner Whirlpool', 'Bomber Corner WC'], Terrain.Water),
|
create_dw_region(player, 'Bomber Corner Water', None, ['Bomber Corner Pier', 'Bomber Corner Whirlpool', 'Bomber Corner WC'], 'Dark World', Terrain.Water),
|
||||||
create_dw_region(player, 'Bomber Corner Water Ledge', None, ['Bomber Corner Waterfall Water Drop', 'Bomber Corner NW'], Terrain.Water),
|
create_dw_region(player, 'Bomber Corner Water Ledge', None, ['Bomber Corner Waterfall Water Drop', 'Bomber Corner NW'], 'Dark World', Terrain.Water),
|
||||||
|
|
||||||
create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'),
|
create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'),
|
||||||
create_cave_region(player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
|
create_cave_region(player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
|
||||||
@@ -1021,14 +1021,14 @@ def create_menu_region(player, name, locations=None, exits=None):
|
|||||||
return _create_region(player, name, RegionType.Menu, 'Menu', locations, exits)
|
return _create_region(player, name, RegionType.Menu, 'Menu', locations, exits)
|
||||||
|
|
||||||
|
|
||||||
def create_lw_region(player, name, locations=None, exits=None, terrain=Terrain.Land):
|
def create_lw_region(player, name, locations=None, exits=None, hint='Light World', terrain=Terrain.Land):
|
||||||
region = _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
|
region = _create_region(player, name, RegionType.LightWorld, hint, locations, exits)
|
||||||
region.terrain = terrain
|
region.terrain = terrain
|
||||||
return region
|
return region
|
||||||
|
|
||||||
|
|
||||||
def create_dw_region(player, name, locations=None, exits=None, terrain=Terrain.Land):
|
def create_dw_region(player, name, locations=None, exits=None, hint='Dark World', terrain=Terrain.Land):
|
||||||
region = _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits)
|
region = _create_region(player, name, RegionType.DarkWorld, hint, locations, exits)
|
||||||
region.terrain = terrain
|
region.terrain = terrain
|
||||||
return region
|
return region
|
||||||
|
|
||||||
@@ -1171,6 +1171,15 @@ def adjust_locations(world, player):
|
|||||||
world.get_location(location, player).address = 0x400000 + index
|
world.get_location(location, player).address = 0x400000 + index
|
||||||
# player address? it is in the shop table
|
# player address? it is in the shop table
|
||||||
index += 1
|
index += 1
|
||||||
|
# unreal events:
|
||||||
|
for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Dark Blacksmith Ruins', 'Middle Aged Man',
|
||||||
|
'Frog', 'Missing Smith', 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain',
|
||||||
|
'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack',
|
||||||
|
'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off']:
|
||||||
|
location = world.get_location_unsafe(l, player)
|
||||||
|
if location:
|
||||||
|
location.real = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
@@ -1510,16 +1519,16 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
|||||||
'Ice Block Drop': (None, None, False, None),
|
'Ice Block Drop': (None, None, False, None),
|
||||||
'Zelda Pickup': (None, None, False, None),
|
'Zelda Pickup': (None, None, False, None),
|
||||||
'Zelda Drop Off': (None, None, False, None),
|
'Zelda Drop Off': (None, None, False, None),
|
||||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
|
'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE], None, True, 'Eastern Palace'),
|
||||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
|
'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF], None, True, 'Desert Palace'),
|
||||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
|
'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706], None, True, 'Tower of Hera'),
|
||||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
|
'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702], None, True, 'Palace of Darkness'),
|
||||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
|
'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701], None, True, 'Swamp Palace'),
|
||||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves Town'),
|
'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707], None, True, 'Thieves\' Town'),
|
||||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
|
'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704], None, True, 'Skull Woods'),
|
||||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
|
'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705], None, True, 'Ice Palace'),
|
||||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
|
'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703], None, True, 'Misery Mire'),
|
||||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock'),
|
'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708], None, True, 'Turtle Rock'),
|
||||||
'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'),
|
||||||
'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'),
|
||||||
'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'),
|
||||||
|
|||||||
140
Rom.py
140
Rom.py
@@ -16,8 +16,8 @@ except ImportError:
|
|||||||
raise Exception('Could not load BPS module')
|
raise Exception('Could not load BPS module')
|
||||||
|
|
||||||
from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, PotItem
|
from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, PotItem
|
||||||
from DoorShuffle import compass_data, DROptions, boss_indicator
|
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
||||||
from Dungeons import dungeon_music_addresses
|
from Dungeons import dungeon_music_addresses, dungeon_table
|
||||||
from Regions import location_table, shop_to_location_table, retro_shops
|
from Regions import location_table, shop_to_location_table, retro_shops
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
||||||
@@ -26,14 +26,14 @@ from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
|
|||||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
||||||
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
|
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from EntranceShuffle import door_addresses, exit_ids
|
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
|
||||||
from OverworldShuffle import default_flute_connections, flute_data
|
from OverworldShuffle import default_flute_connections, flute_data
|
||||||
|
|
||||||
from source.classes.SFX import randomize_sfx
|
from source.classes.SFX import randomize_sfx
|
||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '6497ab1cbede637dbaea53b9be8247e3'
|
RANDOMIZERBASEHASH = 'aa5868d654074a921fa11e491732cb2a'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -1513,14 +1513,48 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
|
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
|
||||||
|
|
||||||
# compasses showing dungeon count
|
# compasses showing dungeon count
|
||||||
|
compass_mode = 0x00
|
||||||
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
|
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
|
||||||
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
|
compass_mode = 0x00 # Currently must be off if timer is on, because they use same HUD location
|
||||||
elif world.dungeon_counters[player] == 'on':
|
|
||||||
rom.write_byte(0x18003C, 0x02) # always on
|
|
||||||
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
|
|
||||||
rom.write_byte(0x18003C, 0x01) # show on pickup
|
|
||||||
else:
|
|
||||||
rom.write_byte(0x18003C, 0x00)
|
rom.write_byte(0x18003C, 0x00)
|
||||||
|
elif world.dungeon_counters[player] == 'on':
|
||||||
|
compass_mode = 0x02 # always on
|
||||||
|
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
|
||||||
|
compass_mode = 0x01 # show on pickup
|
||||||
|
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
|
||||||
|
compass_mode |= 0x80 # turn on locating dungeons
|
||||||
|
x_map_position_generic = [0x3c0, 0xbc0, 0x7c0, 0x1c0, 0x5c0, 0xdc0, 0x7c0, 0xbc0, 0x9c0, 0x3c0]
|
||||||
|
for idx, x_map in enumerate(x_map_position_generic):
|
||||||
|
rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map))
|
||||||
|
rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0))
|
||||||
|
if world.compassshuffle[player] and world.overworld_map[player] == 'compass':
|
||||||
|
compass_mode |= 0x40 # compasses are wild
|
||||||
|
for dungeon, portal_list in dungeon_portals.items():
|
||||||
|
ow_map_index = dungeon_table[dungeon].map_index
|
||||||
|
if len(portal_list) == 1:
|
||||||
|
portal_idx = 0
|
||||||
|
else:
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
# the random choice excludes sanctuary
|
||||||
|
portal_idx = next((i for i, elem in enumerate(portal_list)
|
||||||
|
if world.get_portal(elem, player).chosen), random.choice([1, 2, 3]))
|
||||||
|
else:
|
||||||
|
portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon]
|
||||||
|
portal = world.get_portal(portal_list[portal_idx], player)
|
||||||
|
entrance = portal.find_portal_entrance()
|
||||||
|
world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00
|
||||||
|
coords = ow_prize_table[entrance.name]
|
||||||
|
# figure out compass entrances and what world (light/dark)
|
||||||
|
rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0]))
|
||||||
|
rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1]))
|
||||||
|
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
|
||||||
|
# in crossed doors - flip the compass exists flags
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
|
||||||
|
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
|
||||||
|
|
||||||
|
|
||||||
|
rom.write_byte(0x18003C, compass_mode)
|
||||||
|
|
||||||
# Bitfield - enable free items to show up in menu
|
# Bitfield - enable free items to show up in menu
|
||||||
#
|
#
|
||||||
@@ -2228,6 +2262,7 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
|
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
|
||||||
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0
|
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0
|
||||||
|
hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0
|
||||||
for entrance in all_entrances:
|
for entrance in all_entrances:
|
||||||
if entrance.name in entrances_to_hint:
|
if entrance.name in entrances_to_hint:
|
||||||
if hint_count > 0:
|
if hint_count > 0:
|
||||||
@@ -2334,11 +2369,67 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
|
|
||||||
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice.
|
hint_candidates = []
|
||||||
junk_hints = junk_texts.copy()
|
for name, district in world.districts[player].items():
|
||||||
random.shuffle(junk_hints)
|
hint_type = 'foolish'
|
||||||
for location in hint_locations:
|
choice_set = set()
|
||||||
tt[location] = junk_hints.pop(0)
|
item_count, item_type = 0, 'useful'
|
||||||
|
for loc_name in district.locations:
|
||||||
|
location_item = world.get_location(loc_name, player).item
|
||||||
|
if location_item.advancement:
|
||||||
|
if 'Heart Container' in location_item.name:
|
||||||
|
continue
|
||||||
|
itm_type = 'useful' if useful_item_for_hint(location_item, world) else 'vital'
|
||||||
|
hint_type = 'path'
|
||||||
|
if item_type == itm_type:
|
||||||
|
choice_set.add(location_item)
|
||||||
|
item_count += 1
|
||||||
|
elif itm_type == 'vital':
|
||||||
|
item_type = 'vital'
|
||||||
|
item_count = 1
|
||||||
|
choice_set.clear()
|
||||||
|
choice_set.add(location_item)
|
||||||
|
if hint_type == 'foolish':
|
||||||
|
if district.dungeons and world.shuffle[player] != 'vanilla':
|
||||||
|
choice_set.update(district.dungeons)
|
||||||
|
hint_type = 'dungeon_path'
|
||||||
|
elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple',
|
||||||
|
'dungeonsfull']:
|
||||||
|
choice_set.update([x.hint_text for x in district.access_points])
|
||||||
|
hint_type = 'connector'
|
||||||
|
if hint_type == 'foolish':
|
||||||
|
hint_candidates.append((hint_type, f'{name} is a foolish choice'))
|
||||||
|
elif hint_type == 'dungeon_path':
|
||||||
|
choices = sorted(list(choice_set))
|
||||||
|
dungeon_choice = random.choice(choices) # prefer required dungeons...
|
||||||
|
hint_candidates.append((hint_type, f'{name} is on the path to {dungeon_choice}'))
|
||||||
|
elif hint_type == 'connector':
|
||||||
|
choices = sorted(list(choice_set))
|
||||||
|
access_point = random.choice(choices) # prefer required access...
|
||||||
|
hint_candidates.append((hint_type, f'{name} can reach {access_point}'))
|
||||||
|
elif hint_type == 'path':
|
||||||
|
if item_count == 1:
|
||||||
|
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
||||||
|
hint_candidates.append((hint_type, f'{name} conceals {the_item}'))
|
||||||
|
else:
|
||||||
|
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
||||||
|
district_hints = min(len(hint_candidates), len(hint_locations))
|
||||||
|
random.shuffle(hint_candidates)
|
||||||
|
hint_candidates.sort(key=lambda x: 1 if x[0] == 'foolish' else 0)
|
||||||
|
foolish_only = min(2, district_hints) # 2 foolish only
|
||||||
|
for i in range(0, foolish_only):
|
||||||
|
tt[hint_locations.pop(0)] = hint_candidates.pop(0)[1]
|
||||||
|
random.shuffle(hint_candidates)
|
||||||
|
district_hints -= foolish_only # the rest can be anything
|
||||||
|
for i in range(0, district_hints):
|
||||||
|
tt[hint_locations.pop(0)] = hint_candidates.pop(0)[1]
|
||||||
|
if len(hint_locations) > 0:
|
||||||
|
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint
|
||||||
|
# isn't selected twice.
|
||||||
|
junk_hints = junk_texts.copy()
|
||||||
|
random.shuffle(junk_hints)
|
||||||
|
for location in hint_locations:
|
||||||
|
tt[location] = junk_hints.pop(0)
|
||||||
|
|
||||||
# We still need the older hints of course. Those are done here.
|
# We still need the older hints of course. Those are done here.
|
||||||
|
|
||||||
@@ -2489,6 +2580,25 @@ def write_strings(rom, world, player, team):
|
|||||||
rom.write_bytes(0x181500, data)
|
rom.write_bytes(0x181500, data)
|
||||||
rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]])
|
rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]])
|
||||||
|
|
||||||
|
|
||||||
|
useful_item_names = {
|
||||||
|
'Mushroom', 'Shovel', 'Magic Powder', 'Progressive Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail',
|
||||||
|
'Mirror Shield', 'Blue Boomerang', 'Red Boomerang', 'Bug Catching Net', 'Cane of Byrna', 'Cape',
|
||||||
|
'Magic Upgrade (1/2)', 'Magic Upgrade (1/4)', 'Ether', 'Quake'}
|
||||||
|
|
||||||
|
|
||||||
|
def useful_item_for_hint(item, world):
|
||||||
|
return 'Bottle' in item.name or (item.name in useful_item_names
|
||||||
|
and item.name not in world.required_medallions[item.player])
|
||||||
|
|
||||||
|
|
||||||
|
def text_for_item(item, world, player, team):
|
||||||
|
if item.player == player:
|
||||||
|
return item.hint_text
|
||||||
|
else:
|
||||||
|
return f'{item.hint_text} for {world.player_names[item.player][team]}'
|
||||||
|
|
||||||
|
|
||||||
def set_inverted_mode(world, player, rom, inverted_buffer):
|
def set_inverted_mode(world, player, rom, inverted_buffer):
|
||||||
if world.mode[player] == 'inverted':
|
if world.mode[player] == 'inverted':
|
||||||
# load inverted maps
|
# load inverted maps
|
||||||
|
|||||||
21
Rules.py
21
Rules.py
@@ -4,6 +4,7 @@ from collections import deque
|
|||||||
|
|
||||||
import OverworldGlitchRules
|
import OverworldGlitchRules
|
||||||
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType
|
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType
|
||||||
|
from Dungeons import dungeon_table
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
from OverworldGlitchRules import overworld_glitches_rules
|
from OverworldGlitchRules import overworld_glitches_rules
|
||||||
|
|
||||||
@@ -591,15 +592,33 @@ def global_rules(world, player):
|
|||||||
add_key_logic_rules(world, player)
|
add_key_logic_rules(world, player)
|
||||||
# End of door rando rules.
|
# End of door rando rules.
|
||||||
|
|
||||||
|
if world.restrict_boss_items[player] != 'none':
|
||||||
|
def add_mc_rule(l):
|
||||||
|
boss_location = world.get_location(l, player)
|
||||||
|
d_name = boss_location.parent_region.dungeon.name
|
||||||
|
compass_name = f'Compass ({d_name})'
|
||||||
|
map_name = f'Map ({d_name})'
|
||||||
|
add_rule(boss_location, lambda state: state.has(compass_name, player) and state.has(map_name, player))
|
||||||
|
|
||||||
|
for dungeon, info in dungeon_table.items():
|
||||||
|
if info.prize:
|
||||||
|
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||||
|
for loc in [info.prize, f'{d_name} - Boss']:
|
||||||
|
add_mc_rule(loc)
|
||||||
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
add_mc_rule('Agahnim 1')
|
||||||
|
add_mc_rule('Agahnim 2')
|
||||||
|
|
||||||
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)
|
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 bomb_rules(world, player):
|
def bomb_rules(world, player):
|
||||||
bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended.
|
bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended.
|
||||||
bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Light Hype Fairy', 'Mini Moldorm Cave',
|
bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Light Hype Fairy', 'Mini Moldorm Cave',
|
||||||
'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back',
|
'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back',
|
||||||
'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery']
|
'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery', 'Light Hype Fairy']
|
||||||
for entrance in bonkable_doors:
|
for entrance in bonkable_doors:
|
||||||
add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
||||||
add_bunny_rule(world.get_entrance(entrance, player), player)
|
add_bunny_rule(world.get_entrance(entrance, player), player)
|
||||||
|
|||||||
10
Utils.py
10
Utils.py
@@ -1,12 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import operator as op
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import reduce
|
from math import factorial
|
||||||
from itertools import count
|
from itertools import count
|
||||||
|
|
||||||
|
|
||||||
@@ -133,12 +132,9 @@ def kth_combination(k, l, r):
|
|||||||
|
|
||||||
|
|
||||||
def ncr(n, r):
|
def ncr(n, r):
|
||||||
if r == 0:
|
if r == 0 or r >= n:
|
||||||
return 1
|
return 1
|
||||||
r = min(r, n-r)
|
return factorial(n) // factorial(r) // factorial(n-r)
|
||||||
numerator = reduce(op.mul, range(n, n-r, -1), 1)
|
|
||||||
denominator = reduce(op.mul, range(1, r+1), 1)
|
|
||||||
return numerator / denominator
|
|
||||||
|
|
||||||
|
|
||||||
entrance_offsets = {
|
entrance_offsets = {
|
||||||
|
|||||||
Binary file not shown.
@@ -59,6 +59,10 @@
|
|||||||
shuffleganon:
|
shuffleganon:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
|
overworld_map: # control how the overworld map operates when entrance shuffle is on
|
||||||
|
default: 1
|
||||||
|
compass: 10
|
||||||
|
map: 5
|
||||||
world_state:
|
world_state:
|
||||||
standard: 1
|
standard: 1
|
||||||
open: 1
|
open: 1
|
||||||
@@ -79,6 +83,16 @@
|
|||||||
triforce_pool_min: 20
|
triforce_pool_min: 20
|
||||||
triforce_pool_max: 40
|
triforce_pool_max: 40
|
||||||
triforce_min_difference: 10
|
triforce_min_difference: 10
|
||||||
|
algorithm:
|
||||||
|
major_only: 1
|
||||||
|
dungeon_only: 1
|
||||||
|
vanilla_fill: 1
|
||||||
|
balanced: 10
|
||||||
|
district: 1
|
||||||
|
restrict_boss_items:
|
||||||
|
none: 1
|
||||||
|
mapcompass: 1
|
||||||
|
dungeon: 1
|
||||||
dungeon_items:
|
dungeon_items:
|
||||||
standard: 10
|
standard: 10
|
||||||
mc: 3
|
mc: 3
|
||||||
|
|||||||
164
mystery_testsuite.yml
Normal file
164
mystery_testsuite.yml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
description: A test suite for testing various combinations
|
||||||
|
# Not yet in this branch
|
||||||
|
algorithm:
|
||||||
|
major_only: 1
|
||||||
|
dungeon_only: 1
|
||||||
|
vanilla_fill: 1
|
||||||
|
balanced: 10
|
||||||
|
district: 1
|
||||||
|
door_shuffle:
|
||||||
|
vanilla: 1
|
||||||
|
basic: 2
|
||||||
|
crossed: 3 # crossed yield more errors so is preferred
|
||||||
|
intensity:
|
||||||
|
1: 1
|
||||||
|
2: 1
|
||||||
|
3: 2 # intensity 3 usuall yield more errors
|
||||||
|
keydropshuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
shopsanity:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
pot_shuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
entrance_shuffle:
|
||||||
|
none: 1
|
||||||
|
dungeonssimple: 1
|
||||||
|
dungeonsfull: 1
|
||||||
|
simple: 1
|
||||||
|
restricted: 1
|
||||||
|
full: 1
|
||||||
|
crossed: 1
|
||||||
|
insanity: 1
|
||||||
|
shufflelinks:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
world_state:
|
||||||
|
standard: 1
|
||||||
|
open: 1
|
||||||
|
inverted: 1
|
||||||
|
retro: 0
|
||||||
|
retro:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
goals:
|
||||||
|
ganon: 1
|
||||||
|
fast_ganon: 1
|
||||||
|
dungeons: 2 # this yields more errors so is preferred
|
||||||
|
pedestal: 1
|
||||||
|
triforce-hunt: 1
|
||||||
|
triforce_goal_min: 20
|
||||||
|
triforce_goal_max: 30
|
||||||
|
triforce_pool_min: 30
|
||||||
|
triforce_pool_max: 40
|
||||||
|
triforce_min_difference: 10
|
||||||
|
map_shuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
compass_shuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
smallkey_shuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
bigkey_shuffle:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
dungeon_counters:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
default: 1
|
||||||
|
experimental:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
glitches_required:
|
||||||
|
none: 10 # i'm more interest in testing shuffles with more restrictive logic
|
||||||
|
owg: 1
|
||||||
|
no_logic: 1
|
||||||
|
accessibility:
|
||||||
|
items: 1
|
||||||
|
locations: 1
|
||||||
|
none: 0 # i'm not really interested in this yet
|
||||||
|
restrict_boss_items:
|
||||||
|
none: 1
|
||||||
|
mapcompass: 1
|
||||||
|
dungeon: 1
|
||||||
|
tower_open:
|
||||||
|
"0": 1
|
||||||
|
"1": 1
|
||||||
|
"2": 1
|
||||||
|
"3": 1
|
||||||
|
"4": 1
|
||||||
|
"5": 1
|
||||||
|
"6": 1
|
||||||
|
"7": 10 # more restrictions is usually best for testing
|
||||||
|
random: 1
|
||||||
|
ganon_open:
|
||||||
|
"0": 1
|
||||||
|
"1": 1
|
||||||
|
"2": 1
|
||||||
|
"3": 1
|
||||||
|
"4": 1
|
||||||
|
"5": 1
|
||||||
|
"6": 1
|
||||||
|
"7": 10 # more restrictions is usually best for testing
|
||||||
|
random: 1
|
||||||
|
boss_shuffle:
|
||||||
|
none: 1
|
||||||
|
simple: 1
|
||||||
|
full: 1
|
||||||
|
random: 1
|
||||||
|
enemy_shuffle: # shouldn't affect generation
|
||||||
|
none: 1
|
||||||
|
shuffled: 1
|
||||||
|
random: 1
|
||||||
|
legacy: 0
|
||||||
|
hints:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
pseudoboots: # shouldn't affect generation
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
|
weapons:
|
||||||
|
randomized: 1
|
||||||
|
assured: 1
|
||||||
|
vanilla: 1
|
||||||
|
swordless: 1
|
||||||
|
item_pool:
|
||||||
|
normal: 1
|
||||||
|
hard: 1
|
||||||
|
expert: 1
|
||||||
|
item_functionality: # shouldn't affect generation
|
||||||
|
normal: 1
|
||||||
|
hard: 0
|
||||||
|
expert: 0
|
||||||
|
enemy_damage: # shouldn't affect generation
|
||||||
|
default: 1
|
||||||
|
shuffled: 0
|
||||||
|
random: 0
|
||||||
|
enemy_health: # shouldn't affect generation
|
||||||
|
default: 1
|
||||||
|
easy: 0
|
||||||
|
hard: 0
|
||||||
|
expert: 0
|
||||||
|
rom:
|
||||||
|
quickswap: # shouldn't affect generation
|
||||||
|
on: 1
|
||||||
|
off: 0
|
||||||
|
# reduce_flashing: should affect generation at this point
|
||||||
|
heartcolor: # shouldn't affect generation
|
||||||
|
red: 1
|
||||||
|
blue: 1
|
||||||
|
green: 1
|
||||||
|
yellow: 1
|
||||||
|
heartbeep: # shouldn't affect generation
|
||||||
|
double: 0
|
||||||
|
normal: 0
|
||||||
|
half: 0
|
||||||
|
quarter: 1
|
||||||
|
off: 0
|
||||||
|
shuffle_sfx:
|
||||||
|
on: 1
|
||||||
|
off: 1
|
||||||
@@ -102,12 +102,11 @@
|
|||||||
"algorithm": {
|
"algorithm": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"balanced",
|
"balanced",
|
||||||
"freshness",
|
"equitable",
|
||||||
"flood",
|
"vanilla_fill",
|
||||||
"vt21",
|
"major_only",
|
||||||
"vt22",
|
"dungeon_only",
|
||||||
"vt25",
|
"district"
|
||||||
"vt26"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ow_shuffle": {
|
"ow_shuffle": {
|
||||||
@@ -308,6 +307,13 @@
|
|||||||
"none"
|
"none"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"restrict_boss_items": {
|
||||||
|
"choices": [
|
||||||
|
"none",
|
||||||
|
"mapcompass",
|
||||||
|
"dungeon"
|
||||||
|
]
|
||||||
|
},
|
||||||
"hints": {
|
"hints": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
@@ -372,6 +378,13 @@
|
|||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
|
"overworld_map": {
|
||||||
|
"choices": [
|
||||||
|
"default",
|
||||||
|
"compass",
|
||||||
|
"map"
|
||||||
|
]
|
||||||
|
},
|
||||||
"pseudoboots": {
|
"pseudoboots": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"shuffling.overworld": "Shuffling overworld",
|
"shuffling.overworld": "Shuffling overworld",
|
||||||
"shuffling.world": "Shuffling entrances",
|
"shuffling.world": "Shuffling entrances",
|
||||||
|
"shuffling.prep": "Dungeon and Item prep",
|
||||||
"shuffling.dungeons": "Shuffling dungeons",
|
"shuffling.dungeons": "Shuffling dungeons",
|
||||||
"shuffling.pots": "Shuffling pots",
|
"shuffling.pots": "Shuffling pots",
|
||||||
"basic.traversal": "--Basic Traversal",
|
"basic.traversal": "--Basic Traversal",
|
||||||
@@ -156,22 +157,28 @@
|
|||||||
"balanced: vt26 derivative that aims to strike a balance between",
|
"balanced: vt26 derivative that aims to strike a balance between",
|
||||||
" the overworld heavy vt25 and the dungeon heavy vt26",
|
" the overworld heavy vt25 and the dungeon heavy vt26",
|
||||||
" algorithm.",
|
" algorithm.",
|
||||||
"vt26: Shuffle items and place them in a random location",
|
"equitable: does not place dungeon items first allowing new potential",
|
||||||
" that it is not impossible to be in. This includes",
|
" but mixed with the normal advancement pool",
|
||||||
" dungeon keys and items.",
|
"restricted placements: these consider all major items to be special and attempts",
|
||||||
"vt25: Shuffle items and place them in a random location",
|
"to place items from fixed to semi-random locations. For purposes of these shuffles, all",
|
||||||
" that it is not impossible to be in.",
|
"Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and",
|
||||||
"vt21: Unbiased in its selection, but has tendency to put",
|
"1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool",
|
||||||
" Ice Rod in Turtle Rock.",
|
"if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),",
|
||||||
"vt22: Drops off stale locations after 1/3 of progress",
|
"1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and",
|
||||||
" items were placed to try to circumvent vt21\\'s",
|
"triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.",
|
||||||
" shortcomings.",
|
"vanilla_fill As above, but attempts to place items in their vanilla",
|
||||||
"Freshness: Keep track of stale locations (ones that cannot be",
|
" location first. Major items that cannot be placed that way",
|
||||||
" reached yet) and decrease likeliness of selecting",
|
" will attempt to be placed in other failed locations first.",
|
||||||
" them the more often they were found unreachable.",
|
" Also attempts to place all items in vanilla locations",
|
||||||
"Flood: Push out items starting from Link\\'s House and",
|
"major_only As above, but uses the major items' location preferentially",
|
||||||
" slightly biased to placing progression items with",
|
" major item location are defined as the group of location where",
|
||||||
" less restrictions."
|
" the items are found in the vanilla game.",
|
||||||
|
"dungeon_only As above, but major items are preferentially placed",
|
||||||
|
" in dungeons locations first",
|
||||||
|
"district As above, but groups of locations are chosen randomly",
|
||||||
|
" from a pool of fixed locations designed to be interesting",
|
||||||
|
" and give major clues about the location of other",
|
||||||
|
" advancement items. These fixed groups will be documented."
|
||||||
],
|
],
|
||||||
"shuffle": [
|
"shuffle": [
|
||||||
"Select Entrance Shuffling Algorithm. (default: %(default)s)",
|
"Select Entrance Shuffling Algorithm. (default: %(default)s)",
|
||||||
@@ -317,6 +324,13 @@
|
|||||||
"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."
|
||||||
],
|
],
|
||||||
|
"restrict_boss_items": [
|
||||||
|
"Select which dungeon are not allowed on bosses (default: %(default)s)",
|
||||||
|
"None: All items allowed",
|
||||||
|
"Mapcompass: Map and Compass are required before you defeat the boss.",
|
||||||
|
"Dungeon: Same as above and keys too cannot be on the boss. Small key shuffle",
|
||||||
|
" and big key shuffle override this behavior"
|
||||||
|
],
|
||||||
"hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ],
|
"hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ],
|
||||||
"no_shuffleganon": [
|
"no_shuffleganon": [
|
||||||
"Don't include the Ganon's Tower and Pyramid Hole in the",
|
"Don't include the Ganon's Tower and Pyramid Hole in the",
|
||||||
@@ -325,6 +339,9 @@
|
|||||||
"shufflelinks": [
|
"shufflelinks": [
|
||||||
"Include Link's House in the entrance shuffle pool. (default: %(default)s)"
|
"Include Link's House in the entrance shuffle pool. (default: %(default)s)"
|
||||||
],
|
],
|
||||||
|
"overworld_map": [
|
||||||
|
"Control if and how the overworld map indicators show the locations of dungeons (default: %(default)s)"
|
||||||
|
],
|
||||||
"heartbeep": [
|
"heartbeep": [
|
||||||
"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)"
|
||||||
|
|||||||
@@ -140,6 +140,10 @@
|
|||||||
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
||||||
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",
|
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",
|
||||||
"randomizer.entrance.shufflelinks": "Include Link's House in the shuffle pool",
|
"randomizer.entrance.shufflelinks": "Include Link's House in the shuffle pool",
|
||||||
|
"randomizer.entrance.overworld_map": "Overworld Map",
|
||||||
|
"randomizer.entrance.overworld_map.default": "Default (no location display)",
|
||||||
|
"randomizer.entrance.overworld_map.compass": "Compass indicates location",
|
||||||
|
"randomizer.entrance.overworld_map.map": "Map indicates location",
|
||||||
|
|
||||||
"randomizer.entrance.entranceshuffle": "Entrance Shuffle",
|
"randomizer.entrance.entranceshuffle": "Entrance Shuffle",
|
||||||
"randomizer.entrance.entranceshuffle.vanilla": "Vanilla",
|
"randomizer.entrance.entranceshuffle.vanilla": "Vanilla",
|
||||||
@@ -307,14 +311,17 @@
|
|||||||
"randomizer.item.accessibility.none": "Beatable",
|
"randomizer.item.accessibility.none": "Beatable",
|
||||||
|
|
||||||
"randomizer.item.sortingalgo": "Item Sorting",
|
"randomizer.item.sortingalgo": "Item Sorting",
|
||||||
"randomizer.item.sortingalgo.freshness": "Freshness",
|
|
||||||
"randomizer.item.sortingalgo.flood": "Flood",
|
|
||||||
"randomizer.item.sortingalgo.vt21": "VT8.21",
|
|
||||||
"randomizer.item.sortingalgo.vt22": "VT8.22",
|
|
||||||
"randomizer.item.sortingalgo.vt25": "VT8.25",
|
|
||||||
"randomizer.item.sortingalgo.vt26": "VT8.26",
|
|
||||||
"randomizer.item.sortingalgo.balanced": "Balanced",
|
"randomizer.item.sortingalgo.balanced": "Balanced",
|
||||||
|
"randomizer.item.sortingalgo.equitable": "Equitable",
|
||||||
|
"randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill",
|
||||||
|
"randomizer.item.sortingalgo.major_only": "Major Location Restriction",
|
||||||
|
"randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction",
|
||||||
|
"randomizer.item.sortingalgo.district": "District Restriction",
|
||||||
|
|
||||||
|
"randomizer.item.restrict_boss_items": "Forbidden Boss Items",
|
||||||
|
"randomizer.item.restrict_boss_items.none": "None",
|
||||||
|
"randomizer.item.restrict_boss_items.mapcompass": "Map & Compass",
|
||||||
|
"randomizer.item.restrict_boss_items.dungeon": "Map & Compass & Keys",
|
||||||
|
|
||||||
"bottom.content.worlds": "Worlds",
|
"bottom.content.worlds": "Worlds",
|
||||||
"bottom.content.names": "Player names",
|
"bottom.content.names": "Player names",
|
||||||
|
|||||||
@@ -3,6 +3,17 @@
|
|||||||
"openpyramid": { "type": "checkbox" },
|
"openpyramid": { "type": "checkbox" },
|
||||||
"shuffleganon": { "type": "checkbox" },
|
"shuffleganon": { "type": "checkbox" },
|
||||||
"shufflelinks": { "type": "checkbox" },
|
"shufflelinks": { "type": "checkbox" },
|
||||||
|
"overworld_map": {
|
||||||
|
"type": "selectbox",
|
||||||
|
"options": [
|
||||||
|
"default",
|
||||||
|
"compass",
|
||||||
|
"map"
|
||||||
|
],
|
||||||
|
"config": {
|
||||||
|
"width": 45
|
||||||
|
}
|
||||||
|
},
|
||||||
"entranceshuffle": {
|
"entranceshuffle": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
"options": [
|
"options": [
|
||||||
|
|||||||
@@ -117,13 +117,21 @@
|
|||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
"default": "balanced",
|
"default": "balanced",
|
||||||
"options": [
|
"options": [
|
||||||
"freshness",
|
"balanced",
|
||||||
"flood",
|
"equitable",
|
||||||
"vt21",
|
"vanilla_fill",
|
||||||
"vt22",
|
"major_only",
|
||||||
"vt25",
|
"dungeon_only",
|
||||||
"vt26",
|
"district"
|
||||||
"balanced"
|
]
|
||||||
|
},
|
||||||
|
"restrict_boss_items": {
|
||||||
|
"type": "selectbox",
|
||||||
|
"default": "none",
|
||||||
|
"options": [
|
||||||
|
"none",
|
||||||
|
"mapcompass",
|
||||||
|
"dungeon"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ def shuffle_sfx_data():
|
|||||||
random.shuffle(candidates)
|
random.shuffle(candidates)
|
||||||
|
|
||||||
# place chained sfx first
|
# place chained sfx first
|
||||||
random.shuffle(chained_sfx) # todo: sort largest to smallest
|
random.shuffle(chained_sfx)
|
||||||
chained_sfx = sorted(chained_sfx, key=lambda x: len(x.chain), reverse=True)
|
chained_sfx = sorted(chained_sfx, key=lambda x: len(x.chain), reverse=True)
|
||||||
for chained in chained_sfx:
|
for chained in chained_sfx:
|
||||||
chosen_slot = next(x for x in candidates if len(accompaniment_map[x[0]]) - len(chained.chain) >= 0)
|
chosen_slot = next(x for x in candidates if len(accompaniment_map[x[0]]) - len(chained.chain) >= 0)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
|
import ssl
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
import webbrowser
|
import webbrowser
|
||||||
@@ -149,7 +150,7 @@ class SpriteSelector(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
task.update_status("Downloading official sprites list")
|
task.update_status("Downloading official sprites list")
|
||||||
with urlopen('https://alttpr.com/sprites') as response:
|
with urlopen('https://alttpr.com/sprites', context=ssl._create_unverified_context()) as response:
|
||||||
sprites_arr = json.loads(response.read().decode("utf-8"))
|
sprites_arr = json.loads(response.read().decode("utf-8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ SETTINGSTOPROCESS = {
|
|||||||
"progressives": "progressive",
|
"progressives": "progressive",
|
||||||
"accessibility": "accessibility",
|
"accessibility": "accessibility",
|
||||||
"sortingalgo": "algorithm",
|
"sortingalgo": "algorithm",
|
||||||
"beemizer": "beemizer"
|
"beemizer": "beemizer",
|
||||||
|
"restrict_boss_items": "restrict_boss_items"
|
||||||
},
|
},
|
||||||
"overworld": {
|
"overworld": {
|
||||||
"overworldshuffle": "ow_shuffle",
|
"overworldshuffle": "ow_shuffle",
|
||||||
@@ -86,7 +87,8 @@ SETTINGSTOPROCESS = {
|
|||||||
"openpyramid": "openpyramid",
|
"openpyramid": "openpyramid",
|
||||||
"shuffleganon": "shuffleganon",
|
"shuffleganon": "shuffleganon",
|
||||||
"shufflelinks": "shufflelinks",
|
"shufflelinks": "shufflelinks",
|
||||||
"entranceshuffle": "shuffle"
|
"entranceshuffle": "shuffle",
|
||||||
|
"overworld_map": "overworld_map",
|
||||||
},
|
},
|
||||||
"enemizer": {
|
"enemizer": {
|
||||||
"enemyshuffle": "shuffleenemies",
|
"enemyshuffle": "shuffleenemies",
|
||||||
|
|||||||
159
source/item/District.py
Normal file
159
source/item/District.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from BaseClasses import CollectionState, RegionType
|
||||||
|
from Dungeons import dungeon_table
|
||||||
|
from OWEdges import OWTileRegions
|
||||||
|
|
||||||
|
class District(object):
|
||||||
|
|
||||||
|
def __init__(self, name, locations, entrances=None, dungeon=None):
|
||||||
|
self.name = name
|
||||||
|
self.dungeon = dungeon
|
||||||
|
self.locations = locations
|
||||||
|
self.entrances = entrances if entrances else []
|
||||||
|
self.sphere_one = False
|
||||||
|
|
||||||
|
self.dungeons = set()
|
||||||
|
self.access_points = set()
|
||||||
|
|
||||||
|
|
||||||
|
def create_districts(world):
|
||||||
|
world.districts = {}
|
||||||
|
for p in range(1, world.players + 1):
|
||||||
|
create_district_helper(world, p)
|
||||||
|
|
||||||
|
|
||||||
|
def create_district_helper(world, player):
|
||||||
|
districts = {}
|
||||||
|
kak_locations = {'Bottle Merchant', 'Kakariko Tavern', 'Maze Race'}
|
||||||
|
nw_lw_locations = {'Mushroom', 'Master Sword Pedestal'}
|
||||||
|
central_lw_locations = {'Sunken Treasure', 'Flute Spot'}
|
||||||
|
desert_locations = {'Purple Chest', 'Desert Ledge', 'Bombos Tablet'}
|
||||||
|
lake_locations = {'Hobo', 'Lake Hylia Island'}
|
||||||
|
east_lw_locations = {"Zora's Ledge", 'King Zora'}
|
||||||
|
lw_dm_locations = {'Old Man', 'Spectacle Rock', 'Ether Tablet', 'Floating Island'}
|
||||||
|
east_dw_locations = {'Pyramid', 'Catfish'}
|
||||||
|
south_dw_locations = {'Stumpy', 'Digging Game'}
|
||||||
|
voo_north_locations = {'Bumper Cave Ledge'}
|
||||||
|
ddm_locations = set()
|
||||||
|
|
||||||
|
kak_entrances = ['Kakariko Well Cave', 'Bat Cave Cave', 'Elder House (East)', 'Elder House (West)',
|
||||||
|
'Two Brothers House (East)', 'Two Brothers House (West)', 'Blinds Hideout', 'Chicken House',
|
||||||
|
'Blacksmiths Hut', 'Sick Kids House', 'Snitch Lady (East)', 'Snitch Lady (West)',
|
||||||
|
'Bush Covered House', 'Tavern (Front)', 'Light World Bomb Hut', 'Kakariko Shop', 'Library',
|
||||||
|
'Kakariko Gamble Game', 'Kakariko Well Drop', 'Bat Cave Drop', 'Tavern North']
|
||||||
|
nw_lw_entrances = ['North Fairy Cave', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary',
|
||||||
|
'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Kings Grave', 'Lost Woods Gamble',
|
||||||
|
'Fortune Teller (Light)', 'Bonk Rock Cave', 'Lumberjack House', 'North Fairy Cave Drop',
|
||||||
|
'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'Graveyard Cave']
|
||||||
|
central_lw_entrances = ['Links House', 'Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (West)',
|
||||||
|
'Hyrule Castle Entrance (East)', 'Agahnims Tower', 'Hyrule Castle Secret Entrance Stairs',
|
||||||
|
'Dam', 'Bonk Fairy (Light)', 'Light Hype Fairy', 'Hyrule Castle Secret Entrance Drop',
|
||||||
|
'Cave 45']
|
||||||
|
desert_entrances = ['Desert Palace Entrance (South)', 'Desert Palace Entrance (West)',
|
||||||
|
'Desert Palace Entrance (North)', 'Desert Palace Entrance (East)', 'Desert Fairy',
|
||||||
|
'Aginahs Cave', '50 Rupee Cave', 'Checkerboard Cave']
|
||||||
|
lake_entrances = ['Capacity Upgrade', 'Mini Moldorm Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Rod Cave',
|
||||||
|
'Cave Shop (Lake Hylia)', 'Lake Hylia Fortune Teller']
|
||||||
|
east_lw_entrances = ['Eastern Palace', 'Waterfall of Wishing', 'Lake Hylia Fairy', 'Sahasrahlas Hut',
|
||||||
|
'Long Fairy Cave', 'Potion Shop']
|
||||||
|
lw_dm_entrances = ['Tower of Hera', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
|
||||||
|
'Death Mountain Return Cave (East)', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave',
|
||||||
|
'Spectacle Rock Cave (Bottom)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)',
|
||||||
|
'Paradox Cave (Top)', 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)',
|
||||||
|
'Spiral Cave', 'Spiral Cave (Bottom)', 'Hookshot Fairy', 'Mimic Cave']
|
||||||
|
east_dw_entrances = ['Palace of Darkness', 'Pyramid Entrance', 'Pyramid Fairy', 'East Dark World Hint',
|
||||||
|
'Palace of Darkness Hint', 'Dark Lake Hylia Fairy', 'Dark World Potion Shop', 'Pyramid Hole']
|
||||||
|
south_dw_entrances = ['Ice Palace', 'Swamp Palace', 'Dark Lake Hylia Ledge Fairy',
|
||||||
|
'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave',
|
||||||
|
'Bonk Fairy (Dark)', 'Archery Game', 'Big Bomb Shop', 'Dark Lake Hylia Shop', ]
|
||||||
|
voo_north_entrances = ['Thieves Town', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
||||||
|
'Skull Woods Second Section Door (West)', 'Skull Woods Final Section',
|
||||||
|
'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Brewery', 'C-Shaped House', 'Chest Game',
|
||||||
|
'Dark World Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint',
|
||||||
|
'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop',
|
||||||
|
'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)',
|
||||||
|
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
|
||||||
|
mire_entrances = ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy']
|
||||||
|
ddm_entrances = ['Turtle Rock', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)',
|
||||||
|
'Turtle Rock Isolated Ledge Entrance', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)',
|
||||||
|
'Hookshot Cave', 'Hookshot Cave Back Entrance', 'Ganons Tower', 'Spike Cave',
|
||||||
|
'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Fairy']
|
||||||
|
|
||||||
|
if world.is_tile_swapped(0x1b, player):
|
||||||
|
east_dw_entrances.remove('Pyramid Entrance')
|
||||||
|
east_dw_entrances.remove('Pyramid Hole')
|
||||||
|
central_lw_entrances.append('Inverted Pyramid Entrance')
|
||||||
|
central_lw_entrances.append('Inverted Pyramid Hole')
|
||||||
|
|
||||||
|
districts['Kakariko'] = District('Kakariko', kak_locations, entrances=kak_entrances)
|
||||||
|
districts['Northwest Hyrule'] = District('Northwest Hyrule', nw_lw_locations, entrances=nw_lw_entrances)
|
||||||
|
districts['Central Hyrule'] = District('Central Hyrule', central_lw_locations, entrances=central_lw_entrances)
|
||||||
|
districts['The Desert Area'] = District('Desert', desert_locations, entrances=desert_entrances)
|
||||||
|
districts['Lake Hylia'] = District('Lake Hylia', lake_locations, entrances=lake_entrances)
|
||||||
|
districts['Eastern Hyrule'] = District('Eastern Hyrule', east_lw_locations, entrances=east_lw_entrances)
|
||||||
|
districts['Death Mountain'] = District('Death Mountain', lw_dm_locations, entrances=lw_dm_entrances)
|
||||||
|
districts['East Dark World'] = District('East Dark World', east_dw_locations, entrances=east_dw_entrances)
|
||||||
|
districts['South Dark World'] = District('South Dark World', south_dw_locations, entrances=south_dw_entrances)
|
||||||
|
districts['Northwest Dark World'] = District('Northwest Dark World', voo_north_locations,
|
||||||
|
entrances=voo_north_entrances)
|
||||||
|
districts['The Mire Area'] = District('The Mire', set(), entrances=mire_entrances)
|
||||||
|
districts['Dark Death Mountain'] = District('Dark Death Mountain', ddm_locations, entrances=ddm_entrances)
|
||||||
|
districts.update({x: District(x, set(), dungeon=x) for x in dungeon_table.keys()})
|
||||||
|
|
||||||
|
world.districts[player] = districts
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_districts(world):
|
||||||
|
create_districts(world)
|
||||||
|
state = CollectionState(world)
|
||||||
|
state.sweep_for_events()
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
# these are not static for OWR - but still important
|
||||||
|
inaccessible = [r for r in inaccessible_regions_std if not world.is_tile_swapped(OWTileRegions[r], player)]
|
||||||
|
inaccessible = inaccessible + [r for r in inaccessible_regions_inv if world.is_tile_swapped(OWTileRegions[r], player)]
|
||||||
|
check_set = find_reachable_locations(state, player)
|
||||||
|
for name, district in world.districts[player].items():
|
||||||
|
if district.dungeon:
|
||||||
|
layout = world.dungeon_layouts[player][district.dungeon]
|
||||||
|
district.locations.update([l.name for r in layout.master_sector.regions
|
||||||
|
for l in r.locations if not l.item and l.real])
|
||||||
|
else:
|
||||||
|
for entrance in district.entrances:
|
||||||
|
ent = world.get_entrance(entrance, player)
|
||||||
|
queue = deque([ent.connected_region])
|
||||||
|
visited = set()
|
||||||
|
while len(queue) > 0:
|
||||||
|
region = queue.pop()
|
||||||
|
visited.add(region)
|
||||||
|
if region.type == RegionType.Cave:
|
||||||
|
for location in region.locations:
|
||||||
|
if not location.item and location.real:
|
||||||
|
district.locations.add(location.name)
|
||||||
|
for ext in region.exits:
|
||||||
|
if ext.connected_region not in visited:
|
||||||
|
queue.appendleft(ext.connected_region)
|
||||||
|
elif region.type == RegionType.Dungeon and region.dungeon:
|
||||||
|
district.dungeons.add(region.dungeon.name)
|
||||||
|
elif region.name in inaccessible:
|
||||||
|
district.access_points.add(region)
|
||||||
|
|
||||||
|
district.sphere_one = len(check_set.intersection(district.locations)) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def find_reachable_locations(state, player):
|
||||||
|
check_set = set()
|
||||||
|
for region in state.reachable_regions[player]:
|
||||||
|
for location in region.locations:
|
||||||
|
if location.can_reach(state) and not location.forced_item and location.real:
|
||||||
|
check_set.add(location.name)
|
||||||
|
return check_set
|
||||||
|
|
||||||
|
|
||||||
|
inaccessible_regions_std = {'Desert Palace Lone Stairs', 'Bumper Cave Ledge', 'Skull Woods Forest (West)',
|
||||||
|
'Dark Death Mountain Ledge', 'Dark Death Mountain Isolated Ledge',
|
||||||
|
'Death Mountain Floating Island (Dark World)'}
|
||||||
|
|
||||||
|
|
||||||
|
inaccessible_regions_inv = {'Desert Palace Lone Stairs', 'Maze Race Ledge', 'Desert Ledge',
|
||||||
|
'Desert Palace Entrance (North) Spot', 'Hyrule Castle Ledge', 'Death Mountain Return Ledge'}
|
||||||
821
source/item/FillUtil.py
Normal file
821
source/item/FillUtil.py
Normal file
@@ -0,0 +1,821 @@
|
|||||||
|
import RaceRandom as random
|
||||||
|
import logging
|
||||||
|
from math import ceil
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from source.item.District import resolve_districts
|
||||||
|
from DoorShuffle import validate_vanilla_reservation
|
||||||
|
from Dungeons import dungeon_table
|
||||||
|
from Items import item_table, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ItemPoolConfig(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.location_groups = None
|
||||||
|
self.static_placement = None
|
||||||
|
self.item_pool = None
|
||||||
|
self.placeholders = None
|
||||||
|
self.reserved_locations = defaultdict(set)
|
||||||
|
|
||||||
|
self.recorded_choices = []
|
||||||
|
|
||||||
|
|
||||||
|
class LocationGroup(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.locations = []
|
||||||
|
|
||||||
|
# flags
|
||||||
|
self.keyshuffle = False
|
||||||
|
self.keydropshuffle = False
|
||||||
|
self.shopsanity = False
|
||||||
|
self.retro = False
|
||||||
|
|
||||||
|
def locs(self, locs):
|
||||||
|
self.locations = list(locs)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def flags(self, k, d=False, s=False, r=False):
|
||||||
|
self.keyshuffle = k
|
||||||
|
self.keydropshuffle = d
|
||||||
|
self.shopsanity = s
|
||||||
|
self.retro = r
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def create_item_pool_config(world):
|
||||||
|
world.item_pool_config = config = ItemPoolConfig()
|
||||||
|
player_set = set()
|
||||||
|
for player in range(1, world.players+1):
|
||||||
|
if world.restrict_boss_items[player] != 'none':
|
||||||
|
player_set.add(player)
|
||||||
|
if world.restrict_boss_items[player] == 'dungeon':
|
||||||
|
for dungeon, info in dungeon_table.items():
|
||||||
|
if info.prize:
|
||||||
|
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
|
||||||
|
config.reserved_locations[player].add(f'{d_name} - Boss')
|
||||||
|
for dungeon in world.dungeons:
|
||||||
|
if world.restrict_boss_items[dungeon.player] != 'none':
|
||||||
|
for item in dungeon.all_items:
|
||||||
|
if item.map or item.compass:
|
||||||
|
item.advancement = True
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
config.static_placement = {}
|
||||||
|
config.location_groups = {}
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.static_placement[player] = vanilla_mapping.copy()
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
|
if item in config.static_placement[player]:
|
||||||
|
config.static_placement[player][item].extend(locs)
|
||||||
|
else:
|
||||||
|
config.static_placement[player][item] = list(locs)
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
for item, locs in shop_vanilla_mapping.items():
|
||||||
|
if item in config.static_placement[player]:
|
||||||
|
config.static_placement[player][item].extend(locs)
|
||||||
|
else:
|
||||||
|
config.static_placement[player][item] = list(locs)
|
||||||
|
if world.retro[player]:
|
||||||
|
for item, locs in retro_vanilla_mapping.items():
|
||||||
|
if item in config.static_placement[player]:
|
||||||
|
config.static_placement[player][item].extend(locs)
|
||||||
|
else:
|
||||||
|
config.static_placement[player][item] = list(locs)
|
||||||
|
# universal keys
|
||||||
|
universal_key_locations = []
|
||||||
|
for item, locs in vanilla_mapping.items():
|
||||||
|
if 'Small Key' in item:
|
||||||
|
universal_key_locations.extend(locs)
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
|
if 'Small Key' in item:
|
||||||
|
universal_key_locations.extend(locs)
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
||||||
|
single_arrow_placement.append('Red Shield Shop - Right')
|
||||||
|
config.static_placement[player]['Single Arrow'] = single_arrow_placement
|
||||||
|
universal_key_locations.extend(shop_vanilla_mapping['Small Heart'])
|
||||||
|
universal_key_locations.extend(shop_vanilla_mapping['Blue Shield'])
|
||||||
|
config.static_placement[player]['Small Key (Universal)'] = universal_key_locations
|
||||||
|
config.location_groups[player] = [
|
||||||
|
LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']),
|
||||||
|
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
|
||||||
|
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
|
||||||
|
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
|
||||||
|
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
|
||||||
|
config.reserved_locations[player].add(loc_name)
|
||||||
|
elif world.algorithm == 'major_only':
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('MajorItems'),
|
||||||
|
LocationGroup('Backup')
|
||||||
|
]
|
||||||
|
config.item_pool = {}
|
||||||
|
init_set = mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
groups = LocationGroup('Major').locs(init_set)
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Big Keys'])
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Big Key Drops'])
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Small Keys'])
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Key Drops'])
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Compasses'])
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
groups.locations.extend(mode_grouping['Maps'])
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
groups.locations.append('Capacity Upgrade - Left')
|
||||||
|
groups.locations.append('Capacity Upgrade - Right')
|
||||||
|
if world.retro[player]:
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
groups.locations.extend(retro_vanilla_mapping['Heart Container'])
|
||||||
|
groups.locations.append('Old Man Sword Cave Item 1')
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
config.location_groups[0].locations = set(groups.locations)
|
||||||
|
config.reserved_locations[player].update(groups.locations)
|
||||||
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
|
||||||
|
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
|
||||||
|
config.location_groups[1].locations = set(backup)
|
||||||
|
elif world.algorithm == 'dungeon_only':
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('Dungeons'),
|
||||||
|
LocationGroup('Backup')
|
||||||
|
]
|
||||||
|
config.item_pool = {}
|
||||||
|
dungeon_set = (mode_grouping['Big Chests'] + mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||||
|
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||||
|
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||||
|
mode_grouping['Big Key Drops'])
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
config.location_groups[0].locations = set(dungeon_set)
|
||||||
|
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
|
||||||
|
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
|
||||||
|
config.location_groups[1].locations = set(backup)
|
||||||
|
|
||||||
|
|
||||||
|
def district_item_pool_config(world):
|
||||||
|
resolve_districts(world)
|
||||||
|
if world.algorithm == 'district':
|
||||||
|
config = world.item_pool_config
|
||||||
|
config.location_groups = [
|
||||||
|
LocationGroup('Districts'),
|
||||||
|
]
|
||||||
|
item_cnt = 0
|
||||||
|
config.item_pool = {}
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
config.item_pool[player] = determine_major_items(world, player)
|
||||||
|
item_cnt += count_major_items(config, world, player)
|
||||||
|
# set district choices
|
||||||
|
district_choices = {}
|
||||||
|
for p in range(1, world.players + 1):
|
||||||
|
for name, district in world.districts[p].items():
|
||||||
|
adjustment = 0
|
||||||
|
if district.dungeon:
|
||||||
|
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
||||||
|
if i.is_inside_dungeon_item(world)])
|
||||||
|
dist_len = len(district.locations) - adjustment
|
||||||
|
if name not in district_choices:
|
||||||
|
district_choices[name] = (district.sphere_one, dist_len)
|
||||||
|
else:
|
||||||
|
so, amt = district_choices[name]
|
||||||
|
district_choices[name] = (so or district.sphere_one, amt + dist_len)
|
||||||
|
|
||||||
|
chosen_locations = defaultdict(set)
|
||||||
|
location_cnt = 0
|
||||||
|
|
||||||
|
# choose a sphere one district
|
||||||
|
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
||||||
|
sphere_one = random.choice(sphere_one_choices)
|
||||||
|
so, amt = district_choices[sphere_one]
|
||||||
|
location_cnt += amt
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
for location in world.districts[player][sphere_one].locations:
|
||||||
|
chosen_locations[location].add(player)
|
||||||
|
del district_choices[sphere_one]
|
||||||
|
config.recorded_choices.append(sphere_one)
|
||||||
|
|
||||||
|
scale_factors = defaultdict(int)
|
||||||
|
scale_total = 0
|
||||||
|
for p in range(1, world.players + 1):
|
||||||
|
ent = 'Agahnims Tower' if world.is_tile_swapped(0x1b, player) and world.is_tile_swapped(0x1b, player) else 'Ganons Tower'
|
||||||
|
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
||||||
|
if dungeon:
|
||||||
|
scale = world.crystals_needed_for_gt[p]
|
||||||
|
scale_total += scale
|
||||||
|
scale_factors[dungeon.name] += scale
|
||||||
|
scale_total = max(1, scale_total)
|
||||||
|
scale_divisors = defaultdict(lambda: 1)
|
||||||
|
scale_divisors.update(scale_factors)
|
||||||
|
|
||||||
|
while location_cnt < item_cnt:
|
||||||
|
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
||||||
|
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
||||||
|
so, amt = district_choices[choice]
|
||||||
|
location_cnt += amt
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
for location in world.districts[player][choice].locations:
|
||||||
|
chosen_locations[location].add(player)
|
||||||
|
del district_choices[choice]
|
||||||
|
config.recorded_choices.append(choice)
|
||||||
|
config.placeholders = location_cnt - item_cnt
|
||||||
|
config.location_groups[0].locations = chosen_locations
|
||||||
|
|
||||||
|
|
||||||
|
def location_prefilled(location, world, player):
|
||||||
|
if world.swords[player] == 'vanilla':
|
||||||
|
return location in vanilla_swords
|
||||||
|
if world.goal[player] in ['pedestal', 'trinity']:
|
||||||
|
return location == 'Master Sword Pedestal'
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def previously_reserved(location, world, player):
|
||||||
|
if '- Boss' in location.name:
|
||||||
|
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
|
||||||
|
or not world.mapshuffle[player]):
|
||||||
|
return True
|
||||||
|
if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player]
|
||||||
|
or not world.mapshuffle[player]
|
||||||
|
or not world.bigkeyshuffle[player]
|
||||||
|
or not (world.keyshuffle[player] or world.retro[player])):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def massage_item_pool(world):
|
||||||
|
player_pool = defaultdict(list)
|
||||||
|
for item in world.itempool:
|
||||||
|
player_pool[item.player].append(item)
|
||||||
|
for dungeon in world.dungeons:
|
||||||
|
for item in dungeon.all_items:
|
||||||
|
if item.is_inside_dungeon_item(world):
|
||||||
|
player_pool[item.player].append(item)
|
||||||
|
player_locations = defaultdict(list)
|
||||||
|
for player in player_pool:
|
||||||
|
player_locations[player] = [x for x in world.get_unfilled_locations(player) if '- Prize' not in x.name]
|
||||||
|
discrepancy = len(player_pool[player]) - len(player_locations[player])
|
||||||
|
if discrepancy:
|
||||||
|
trash_options = [x for x in player_pool[player] if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
while discrepancy > 0 and len(trash_options) > 0:
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
world.itempool.remove(deleted)
|
||||||
|
discrepancy -= 1
|
||||||
|
if discrepancy > 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, something will be removed at random')
|
||||||
|
if world.item_pool_config.placeholders is not None:
|
||||||
|
removed = 0
|
||||||
|
single_rupees = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
|
removed += len(single_rupees)
|
||||||
|
for x in single_rupees:
|
||||||
|
world.itempool.remove(x)
|
||||||
|
if removed < world.item_pool_config.placeholders:
|
||||||
|
trash_options = [x for x in world.itempool if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
while removed < world.item_pool_config.placeholders:
|
||||||
|
if len(trash_options) == 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
world.itempool.remove(deleted)
|
||||||
|
removed += 1
|
||||||
|
if world.item_pool_config.placeholders > len(single_rupees):
|
||||||
|
for _ in range(world.item_pool_config.placeholders-len(single_rupees)):
|
||||||
|
single_rupees.append(ItemFactory('Rupee (1)', random.randint(1, world.players)))
|
||||||
|
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
|
||||||
|
world.itempool += placeholders
|
||||||
|
removed -= len(placeholders)
|
||||||
|
for _ in range(removed):
|
||||||
|
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
|
||||||
|
|
||||||
|
|
||||||
|
def replace_trash_item(item_pool, replacement):
|
||||||
|
trash_options = [x for x in item_pool if x.name in trash_items]
|
||||||
|
random.shuffle(trash_options)
|
||||||
|
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
|
||||||
|
if len(trash_options) == 0:
|
||||||
|
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
|
||||||
|
deleted = trash_options.pop()
|
||||||
|
item_pool.remove(deleted)
|
||||||
|
replace_item = ItemFactory(replacement, deleted.player)
|
||||||
|
item_pool.append(replace_item)
|
||||||
|
return replace_item
|
||||||
|
|
||||||
|
|
||||||
|
def validate_reservation(location, dungeon, world, player):
|
||||||
|
world.item_pool_config.reserved_locations[player].add(location.name)
|
||||||
|
if world.doorShuffle[player] != 'vanilla':
|
||||||
|
return True # we can generate the dungeon somehow most likely
|
||||||
|
if validate_vanilla_reservation(dungeon, world, player):
|
||||||
|
return True
|
||||||
|
world.item_pool_config.reserved_locations[player].remove(location.name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def count_major_items(config, world, player):
|
||||||
|
return sum(1 for x in world.itempool if x.name in config.item_pool[player] and x.player == player)
|
||||||
|
|
||||||
|
|
||||||
|
def calc_dungeon_limits(world, player):
|
||||||
|
b, s, c, m, k, r, bi = (world.bigkeyshuffle[player], world.keyshuffle[player], world.compassshuffle[player],
|
||||||
|
world.mapshuffle[player], world.keydropshuffle[player], world.retro[player],
|
||||||
|
world.restrict_boss_items[player])
|
||||||
|
if world.doorShuffle[player] in ['vanilla', 'basic']:
|
||||||
|
limits = {}
|
||||||
|
for dungeon, info in dungeon_table.items():
|
||||||
|
val = info.free_items
|
||||||
|
if bi != 'none' and info.prize:
|
||||||
|
if bi == 'mapcompass' and (not c or not m):
|
||||||
|
val -= 1
|
||||||
|
if bi == 'dungeon' and (not c or not m or not (s or r) or not b):
|
||||||
|
val -= 1
|
||||||
|
if b:
|
||||||
|
val += 1 if info.bk_present else 0
|
||||||
|
if k:
|
||||||
|
val += 1 if info.bk_drops else 0
|
||||||
|
if s or r:
|
||||||
|
val += info.key_num
|
||||||
|
if k:
|
||||||
|
val += info.key_drops
|
||||||
|
if c:
|
||||||
|
val += 1 if info.compass_present else 0
|
||||||
|
if m:
|
||||||
|
val += 1 if info.map_present else 0
|
||||||
|
limits[dungeon] = val
|
||||||
|
else:
|
||||||
|
limits = 60
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
limits += 11
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
limits += 1
|
||||||
|
if world.keyshuffle[player] or world.retro[player]:
|
||||||
|
limits += 29
|
||||||
|
if world.keydropshuffle[player]:
|
||||||
|
limits += 32
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
limits += 11
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
limits += 12
|
||||||
|
return limits
|
||||||
|
|
||||||
|
|
||||||
|
def determine_major_items(world, player):
|
||||||
|
major_item_set = set(major_items)
|
||||||
|
if world.progressive == 'off':
|
||||||
|
pass # now what?
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'SmallKey'})
|
||||||
|
if world.compassshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Compass'})
|
||||||
|
if world.mapshuffle[player]:
|
||||||
|
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Map'})
|
||||||
|
if world.shopsanity[player]:
|
||||||
|
major_item_set.add('Bomb Upgrade (+5)')
|
||||||
|
major_item_set.add('Arrow Upgrade (+5)')
|
||||||
|
if world.retro[player]:
|
||||||
|
major_item_set.add('Single Arrow')
|
||||||
|
major_item_set.add('Small Key (Universal)')
|
||||||
|
if world.goal in ['triforcehunt', 'trinity']:
|
||||||
|
major_item_set.add('Triforce Piece')
|
||||||
|
if world.bombbag[player]:
|
||||||
|
major_item_set.add('Bomb Upgrade (+10)')
|
||||||
|
return major_item_set
|
||||||
|
|
||||||
|
|
||||||
|
def classify_major_items(world):
|
||||||
|
if world.algorithm in ['major_only', 'dungeon_only', 'district']:
|
||||||
|
config = world.item_pool_config
|
||||||
|
for item in world.itempool:
|
||||||
|
if item.name in config.item_pool[item.player]:
|
||||||
|
if not item.advancement and not item.priority:
|
||||||
|
if item.smallkey or item.bigkey:
|
||||||
|
item.advancement = True
|
||||||
|
else:
|
||||||
|
item.priority = True
|
||||||
|
else:
|
||||||
|
if item.priority:
|
||||||
|
item.priority = False
|
||||||
|
|
||||||
|
|
||||||
|
def vanilla_fallback(item_to_place, locations, world):
|
||||||
|
if item_to_place.is_inside_dungeon_item(world):
|
||||||
|
return [x for x in locations if x.name in vanilla_fallback_dungeon_set
|
||||||
|
and x.parent_region.dungeon and x.parent_region.dungeon.name == item_to_place.dungeon]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def filter_locations(item_to_place, locations, world, vanilla_skip=False):
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
config, filtered = world.item_pool_config, []
|
||||||
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
|
if item_name in config.static_placement[item_to_place.player]:
|
||||||
|
restricted = config.static_placement[item_to_place.player][item_name]
|
||||||
|
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||||
|
if vanilla_skip and len(filtered) == 0:
|
||||||
|
return filtered
|
||||||
|
i = 0
|
||||||
|
while len(filtered) <= 0:
|
||||||
|
if i >= len(config.location_groups[item_to_place.player]):
|
||||||
|
return locations
|
||||||
|
restricted = config.location_groups[item_to_place.player][i].locations
|
||||||
|
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
|
||||||
|
i += 1
|
||||||
|
return filtered
|
||||||
|
if world.algorithm in ['major_only', 'dungeon_only']:
|
||||||
|
config = world.item_pool_config
|
||||||
|
if item_to_place.name in config.item_pool[item_to_place.player]:
|
||||||
|
restricted = config.location_groups[0].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted]
|
||||||
|
if len(filtered) == 0:
|
||||||
|
restricted = config.location_groups[1].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted]
|
||||||
|
# bias toward certain location in overflow? (thinking about this for major_bias)
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
if world.algorithm == 'district':
|
||||||
|
config = world.item_pool_config
|
||||||
|
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
|
||||||
|
restricted = config.location_groups[0].locations
|
||||||
|
filtered = [l for l in locations if l.name in restricted and l.player in restricted[l.name]]
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
vanilla_mapping = {
|
||||||
|
'Green Pendant': ['Eastern Palace - Prize'],
|
||||||
|
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
'Blue Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
'Crystal 1': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 2': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 3': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 4': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 7': ['Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Thieves\' Town - Prize',
|
||||||
|
'Skull Woods - Prize', 'Turtle Rock - Prize'],
|
||||||
|
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
|
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
|
||||||
|
'Bow': ['Eastern Palace - Big Chest'],
|
||||||
|
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Right'],
|
||||||
|
'Book of Mudora': ['Library'],
|
||||||
|
'Hammer': ['Palace of Darkness - Big Chest'],
|
||||||
|
'Hookshot': ['Swamp Palace - Big Chest'],
|
||||||
|
'Magic Mirror': ['Old Man'],
|
||||||
|
'Ocarina': ['Flute Spot'],
|
||||||
|
'Pegasus Boots': ['Sahasrahla'],
|
||||||
|
'Power Glove': ['Desert Palace - Big Chest'],
|
||||||
|
'Cape': ["King's Tomb"],
|
||||||
|
'Mushroom': ['Mushroom'],
|
||||||
|
'Shovel': ['Stumpy'],
|
||||||
|
'Lamp': ["Link's House"],
|
||||||
|
'Magic Powder': ['Potion Shop'],
|
||||||
|
'Moon Pearl': ['Tower of Hera - Big Chest'],
|
||||||
|
'Cane of Somaria': ['Misery Mire - Big Chest'],
|
||||||
|
'Fire Rod': ['Skull Woods - Big Chest'],
|
||||||
|
'Flippers': ['King Zora'],
|
||||||
|
'Ice Rod': ['Ice Rod Cave'],
|
||||||
|
'Titans Mitts': ["Thieves' Town - Big Chest"],
|
||||||
|
'Bombos': ['Bombos Tablet'],
|
||||||
|
'Ether': ['Ether Tablet'],
|
||||||
|
'Quake': ['Catfish'],
|
||||||
|
'Bottle': ['Bottle Merchant', 'Kakariko Tavern', 'Purple Chest', 'Hobo'],
|
||||||
|
'Master Sword': ['Master Sword Pedestal'],
|
||||||
|
'Tempered Sword': ['Blacksmith'],
|
||||||
|
'Fighter Sword': ["Link's Uncle"],
|
||||||
|
'Golden Sword': ['Pyramid Fairy - Left'],
|
||||||
|
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Left'],
|
||||||
|
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
|
||||||
|
'Silver Arrows': ['Pyramid Fairy - Right'],
|
||||||
|
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
|
||||||
|
'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right',
|
||||||
|
'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left',
|
||||||
|
'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - DMs Room - Top Right',
|
||||||
|
'Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
||||||
|
"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Left'],
|
||||||
|
'Bombs (3)': ['Floodgate Chest', "Sahasrahla's Hut - Middle", 'Kakariko Well - Bottom', 'Superbunny Cave - Top',
|
||||||
|
'Mini Moldorm Cave - Far Left', 'Sewers - Secret Room - Left', 'Paradox Cave Upper - Left',
|
||||||
|
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Maze - Top',
|
||||||
|
'Ganons Tower - Hope Room - Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||||
|
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Mini Helmasaur Room - Left',
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Right'],
|
||||||
|
'Blue Mail': ['Ice Palace - Big Chest'],
|
||||||
|
'Red Mail': ['Ganons Tower - Big Chest'],
|
||||||
|
'Progressive Armor': ['Ice Palace - Big Chest', 'Ganons Tower - Big Chest'],
|
||||||
|
'Blue Boomerang': ['Hyrule Castle - Boomerang Chest'],
|
||||||
|
'Red Boomerang': ['Waterfall Fairy - Left'],
|
||||||
|
'Blue Shield': ['Secret Passage'],
|
||||||
|
'Red Shield': ['Waterfall Fairy - Right'],
|
||||||
|
'Mirror Shield': ['Turtle Rock - Big Chest'],
|
||||||
|
'Progressive Shield': ['Secret Passage', 'Waterfall Fairy - Right', 'Turtle Rock - Big Chest'],
|
||||||
|
'Bug Catching Net': ['Sick Kid'],
|
||||||
|
'Cane of Byrna': ['Spike Cave'],
|
||||||
|
'Boss Heart Container': ['Desert Palace - Boss', 'Eastern Palace - Boss', 'Tower of Hera - Boss',
|
||||||
|
'Swamp Palace - Boss', "Thieves' Town - Boss", 'Skull Woods - Boss', 'Ice Palace - Boss',
|
||||||
|
'Misery Mire - Boss', 'Turtle Rock - Boss', 'Palace of Darkness - Boss'],
|
||||||
|
'Sanctuary Heart Container': ['Sanctuary'],
|
||||||
|
'Piece of Heart': ['Sunken Treasure', "Blind's Hideout - Top", "Zora's Ledge", "Aginah's Cave", 'Maze Race',
|
||||||
|
'Kakariko Well - Top', 'Lost Woods Hideout', 'Lumberjack Tree', 'Cave 45', 'Graveyard Cave',
|
||||||
|
'Checkerboard Cave', 'Bonk Rock Cave', 'Lake Hylia Island', 'Desert Ledge', 'Spectacle Rock',
|
||||||
|
'Spectacle Rock Cave', 'Pyramid', 'Digging Game', 'Peg Cave', 'Chest Game', 'Bumper Cave Ledge',
|
||||||
|
'Mire Shed - Left', 'Floating Island', 'Mimic Cave'],
|
||||||
|
'Rupee (1)': ['Turtle Rock - Eye Bridge - Top Right', 'Ganons Tower - Compass Room - Top Right'],
|
||||||
|
'Rupees (5)': ["Hyrule Castle - Zelda's Chest", 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
|
# 'Palace of Darkness - Harmless Hellway',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom',
|
||||||
|
'Ganons Tower - Validation Chest'],
|
||||||
|
'Rupees (20)': ["Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||||
|
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||||
|
'Kakariko Well - Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||||
|
'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right',
|
||||||
|
'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle', 'Hype Cave - Top',
|
||||||
|
'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||||
|
'Swamp Palace - West Chest', 'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Waterfall Room',
|
||||||
|
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Right', 'Ganons Tower - Compass Room - Bottom Left',
|
||||||
|
'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest",
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
|
||||||
|
'Rupees (50)': ["Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", 'Spiral Cave', 'Superbunny Cave - Bottom',
|
||||||
|
'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
|
||||||
|
'Hookshot Cave - Bottom Left'],
|
||||||
|
'Rupees (100)': ['Eastern Palace - Cannonball Chest'],
|
||||||
|
'Rupees (300)': ['Mini Moldorm Cave - Generous Guy', 'Sewers - Secret Room - Middle', 'Hype Cave - Generous Guy',
|
||||||
|
'Brewery', 'C-Shaped House'],
|
||||||
|
'Magic Upgrade (1/2)': ['Magic Bat'],
|
||||||
|
'Big Key (Eastern Palace)': ['Eastern Palace - Big Key Chest'],
|
||||||
|
'Compass (Eastern Palace)': ['Eastern Palace - Compass Chest'],
|
||||||
|
'Map (Eastern Palace)': ['Eastern Palace - Map Chest'],
|
||||||
|
'Small Key (Desert Palace)': ['Desert Palace - Torch'],
|
||||||
|
'Big Key (Desert Palace)': ['Desert Palace - Big Key Chest'],
|
||||||
|
'Compass (Desert Palace)': ['Desert Palace - Compass Chest'],
|
||||||
|
'Map (Desert Palace)': ['Desert Palace - Map Chest'],
|
||||||
|
'Small Key (Tower of Hera)': ['Tower of Hera - Basement Cage'],
|
||||||
|
'Big Key (Tower of Hera)': ['Tower of Hera - Big Key Chest'],
|
||||||
|
'Compass (Tower of Hera)': ['Tower of Hera - Compass Chest'],
|
||||||
|
'Map (Tower of Hera)': ['Tower of Hera - Map Chest'],
|
||||||
|
'Small Key (Escape)': ['Sewers - Dark Cross'],
|
||||||
|
'Map (Escape)': ['Hyrule Castle - Map Chest'],
|
||||||
|
'Small Key (Agahnims Tower)': ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'],
|
||||||
|
'Small Key (Palace of Darkness)': ['Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge',
|
||||||
|
'Palace of Darkness - Stalfos Basement',
|
||||||
|
'Palace of Darkness - The Arena - Ledge',
|
||||||
|
'Palace of Darkness - Dark Basement - Right',
|
||||||
|
'Palace of Darkness - Harmless Hellway'],
|
||||||
|
# 'Palace of Darkness - Dark Maze - Bottom'],
|
||||||
|
'Big Key (Palace of Darkness)': ['Palace of Darkness - Big Key Chest'],
|
||||||
|
'Compass (Palace of Darkness)': ['Palace of Darkness - Compass Chest'],
|
||||||
|
'Map (Palace of Darkness)': ['Palace of Darkness - Map Chest'],
|
||||||
|
'Small Key (Thieves Town)': ["Thieves' Town - Blind's Cell"],
|
||||||
|
'Big Key (Thieves Town)': ["Thieves' Town - Big Key Chest"],
|
||||||
|
'Compass (Thieves Town)': ["Thieves' Town - Compass Chest"],
|
||||||
|
'Map (Thieves Town)': ["Thieves' Town - Map Chest"],
|
||||||
|
'Small Key (Skull Woods)': ['Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Skull Woods - Bridge Room'],
|
||||||
|
'Big Key (Skull Woods)': ['Skull Woods - Big Key Chest'],
|
||||||
|
'Compass (Skull Woods)': ['Skull Woods - Compass Chest'],
|
||||||
|
'Map (Skull Woods)': ['Skull Woods - Map Chest'],
|
||||||
|
'Small Key (Swamp Palace)': ['Swamp Palace - Entrance'],
|
||||||
|
'Big Key (Swamp Palace)': ['Swamp Palace - Big Key Chest'],
|
||||||
|
'Compass (Swamp Palace)': ['Swamp Palace - Compass Chest'],
|
||||||
|
'Map (Swamp Palace)': ['Swamp Palace - Map Chest'],
|
||||||
|
'Small Key (Ice Palace)': ['Ice Palace - Iced T Room', 'Ice Palace - Spike Room'],
|
||||||
|
'Big Key (Ice Palace)': ['Ice Palace - Big Key Chest'],
|
||||||
|
'Compass (Ice Palace)': ['Ice Palace - Compass Chest'],
|
||||||
|
'Map (Ice Palace)': ['Ice Palace - Map Chest'],
|
||||||
|
'Small Key (Misery Mire)': ['Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'],
|
||||||
|
'Big Key (Misery Mire)': ['Misery Mire - Big Key Chest'],
|
||||||
|
'Compass (Misery Mire)': ['Misery Mire - Compass Chest'],
|
||||||
|
'Map (Misery Mire)': ['Misery Mire - Map Chest'],
|
||||||
|
'Small Key (Turtle Rock)': ['Turtle Rock - Roller Room - Right', 'Turtle Rock - Chain Chomps',
|
||||||
|
'Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left'],
|
||||||
|
'Big Key (Turtle Rock)': ['Turtle Rock - Big Key Chest'],
|
||||||
|
'Compass (Turtle Rock)': ['Turtle Rock - Compass Chest'],
|
||||||
|
'Map (Turtle Rock)': ['Turtle Rock - Roller Room - Left'],
|
||||||
|
'Small Key (Ganons Tower)': ["Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||||
|
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'],
|
||||||
|
'Big Key (Ganons Tower)': ['Ganons Tower - Big Key Chest'],
|
||||||
|
'Compass (Ganons Tower)': ['Ganons Tower - Compass Room - Top Left'],
|
||||||
|
'Map (Ganons Tower)': ['Ganons Tower - Map Chest']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
keydrop_vanilla_mapping = {
|
||||||
|
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||||
|
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||||
|
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop'],
|
||||||
|
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
|
'Hyrule Castle - Key Rat Key Drop'],
|
||||||
|
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
||||||
|
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
||||||
|
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
||||||
|
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop'],
|
||||||
|
'Small Key (Swamp Palace)': ['Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
||||||
|
'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||||
|
'Swamp Palace - Waterway Pot Key'],
|
||||||
|
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||||
|
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||||
|
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key',
|
||||||
|
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
|
||||||
|
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||||
|
'Small Key (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
||||||
|
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasuar Key Drop'],
|
||||||
|
}
|
||||||
|
|
||||||
|
shop_vanilla_mapping = {
|
||||||
|
'Red Potion': ['Dark Death Mountain Shop - Left', 'Dark Lake Hylia Shop - Left', 'Dark Lumberjack Shop - Left',
|
||||||
|
'Village of Outcasts Shop - Left', 'Dark Potion Shop - Left', 'Paradox Shop - Left',
|
||||||
|
'Kakariko Shop - Left', 'Lake Hylia Shop - Left', 'Potion Shop - Left'],
|
||||||
|
'Small Heart': ['Dark Death Mountain Shop - Middle', 'Paradox Shop - Middle', 'Kakariko Shop - Middle',
|
||||||
|
'Lake Hylia Shop - Middle'],
|
||||||
|
'Bombs (10)': ['Dark Death Mountain Shop - Right', 'Dark Lake Hylia Shop - Right', 'Dark Lumberjack Shop - Right',
|
||||||
|
'Village of Outcasts Shop - Right', 'Dark Potion Shop - Right', 'Paradox Shop - Right',
|
||||||
|
'Kakariko Shop - Right', 'Lake Hylia Shop - Right'],
|
||||||
|
'Blue Shield': ['Dark Lake Hylia Shop - Middle', 'Dark Lumberjack Shop - Middle',
|
||||||
|
'Village of Outcasts Shop - Middle', 'Dark Potion Shop - Middle'],
|
||||||
|
'Red Shield': ['Red Shield Shop - Left'],
|
||||||
|
'Bee': ['Red Shield Shop - Middle'],
|
||||||
|
'Arrows (10)': ['Red Shield Shop - Right'],
|
||||||
|
'Bomb Upgrade (+5)': ['Capacity Upgrade - Left'],
|
||||||
|
'Arrow Upgrade (+5)': ['Capacity Upgrade - Right'],
|
||||||
|
'Blue Potion': ['Potion Shop - Right'],
|
||||||
|
'Green Potion': ['Potion Shop - Middle'],
|
||||||
|
}
|
||||||
|
|
||||||
|
retro_vanilla_mapping = {
|
||||||
|
'Heart Container': ['Take-Any #1 Item 1', 'Take-Any #2 Item 1', 'Take-Any #3 Item 1', 'Take-Any #4 Item 1'],
|
||||||
|
'Blue Potion': ['Take-Any #1 Item 2', 'Take-Any #2 Item 2', 'Take-Any #3 Item 2', 'Take-Any #4 Item 2'],
|
||||||
|
'Progressive Sword': ['Old Man Sword Cave Item 1']
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_grouping = {
|
||||||
|
'Overworld Major': [
|
||||||
|
"Link's Uncle", 'King Zora', "Link's House", 'Sahasrahla', 'Ice Rod Cave', 'Library',
|
||||||
|
'Master Sword Pedestal', 'Old Man', 'Ether Tablet', 'Catfish', 'Stumpy', 'Bombos Tablet', 'Mushroom',
|
||||||
|
'Bottle Merchant', 'Kakariko Tavern', 'Secret Passage', 'Flute Spot', 'Purple Chest',
|
||||||
|
'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith', 'Magic Bat', 'Sick Kid', 'Hobo',
|
||||||
|
'Potion Shop', 'Spike Cave', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right', "King's Tomb",
|
||||||
|
],
|
||||||
|
'Big Chests': ['Eastern Palace - Big Chest','Desert Palace - Big Chest', 'Tower of Hera - Big Chest',
|
||||||
|
'Palace of Darkness - Big Chest', 'Swamp Palace - Big Chest', 'Skull Woods - Big Chest',
|
||||||
|
"Thieves' Town - Big Chest", 'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest',
|
||||||
|
'Ice Palace - Big Chest', 'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'],
|
||||||
|
'Heart Containers': ['Sanctuary', 'Eastern Palace - Boss','Desert Palace - Boss', 'Tower of Hera - Boss',
|
||||||
|
'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Skull Woods - Boss',
|
||||||
|
"Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss', 'Turtle Rock - Boss'],
|
||||||
|
'Heart Pieces': [
|
||||||
|
'Bumper Cave Ledge', 'Desert Ledge', 'Lake Hylia Island', 'Floating Island',
|
||||||
|
'Maze Race', 'Spectacle Rock', 'Pyramid', "Zora's Ledge", 'Lumberjack Tree',
|
||||||
|
'Sunken Treasure', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', 'Peg Cave', 'Cave 45',
|
||||||
|
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
|
||||||
|
'Chest Game', 'Digging Game', 'Mire Shed - Right', 'Mimic Cave'
|
||||||
|
],
|
||||||
|
'Big Keys': [
|
||||||
|
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
||||||
|
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
|
||||||
|
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest',
|
||||||
|
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest',
|
||||||
|
],
|
||||||
|
'Compasses': [
|
||||||
|
'Eastern Palace - Compass Chest', 'Desert Palace - Compass Chest', 'Tower of Hera - Compass Chest',
|
||||||
|
'Palace of Darkness - Compass Chest', 'Swamp Palace - Compass Chest', 'Skull Woods - Compass Chest',
|
||||||
|
"Thieves' Town - Compass Chest", 'Ice Palace - Compass Chest', 'Misery Mire - Compass Chest',
|
||||||
|
'Turtle Rock - Compass Chest', 'Ganons Tower - Compass Room - Top Left'
|
||||||
|
],
|
||||||
|
'Maps': [
|
||||||
|
'Hyrule Castle - Map Chest', 'Eastern Palace - Map Chest', 'Desert Palace - Map Chest',
|
||||||
|
'Tower of Hera - Map Chest', 'Palace of Darkness - Map Chest', 'Swamp Palace - Map Chest',
|
||||||
|
'Skull Woods - Map Chest', "Thieves' Town - Map Chest", 'Ice Palace - Map Chest', 'Misery Mire - Map Chest',
|
||||||
|
'Turtle Rock - Roller Room - Left', 'Ganons Tower - Map Chest'
|
||||||
|
],
|
||||||
|
'Small Keys': [
|
||||||
|
'Sewers - Dark Cross', 'Desert Palace - Torch', 'Tower of Hera - Basement Cage',
|
||||||
|
'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
|
||||||
|
'Palace of Darkness - Stalfos Basement', 'Palace of Darkness - Dark Basement - Right',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Shooter Room',
|
||||||
|
'Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - The Arena - Ledge',
|
||||||
|
"Thieves' Town - Blind's Cell", 'Skull Woods - Bridge Room', 'Ice Palace - Spike Room',
|
||||||
|
'Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Misery Mire - Spike Chest',
|
||||||
|
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest', 'Swamp Palace - Entrance',
|
||||||
|
'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room', 'Turtle Rock - Roller Room - Right',
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Left', "Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
|
||||||
|
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'
|
||||||
|
],
|
||||||
|
'Dungeon Trash': [
|
||||||
|
'Sewers - Secret Room - Right', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||||
|
"Hyrule Castle - Zelda's Chest", 'Eastern Palace - Cannonball Chest', "Thieves' Town - Ambush Chest",
|
||||||
|
"Thieves' Town - Attic", 'Ice Palace - Freezor Chest', 'Palace of Darkness - Dark Basement - Left',
|
||||||
|
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Dark Maze - Top',
|
||||||
|
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', 'Swamp Palace - Waterfall Room',
|
||||||
|
'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
|
||||||
|
'Turtle Rock - Eye Bridge - Top Right', 'Swamp Palace - West Chest',
|
||||||
|
],
|
||||||
|
'Overworld Trash': [
|
||||||
|
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
|
||||||
|
"Blind's Hideout - Far Right", 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||||
|
'Kakariko Well - Bottom', 'Chicken House', 'Floodgate Chest', 'Mini Moldorm Cave - Left',
|
||||||
|
'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Mini Moldorm Cave - Far Left',
|
||||||
|
'Mini Moldorm Cave - Far Right', "Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right",
|
||||||
|
"Sahasrahla's Hut - Middle", 'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left',
|
||||||
|
'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle',
|
||||||
|
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Brewery', 'C-Shaped House',
|
||||||
|
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||||
|
'Hype Cave - Generous Guy', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top', 'Hookshot Cave - Top Right',
|
||||||
|
'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left', 'Mire Shed - Left'
|
||||||
|
],
|
||||||
|
'GT Trash': [
|
||||||
|
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
|
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||||
|
'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Right',
|
||||||
|
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Hope Room - Left',
|
||||||
|
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Randomizer Room - Top Left',
|
||||||
|
'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Right',
|
||||||
|
'Ganons Tower - Randomizer Room - Bottom Left', "Ganons Tower - Bob's Chest",
|
||||||
|
'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right',
|
||||||
|
'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||||
|
'Ganons Tower - Validation Chest',
|
||||||
|
],
|
||||||
|
'Key Drops': [
|
||||||
|
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
|
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Square Pot Key',
|
||||||
|
'Eastern Palace - Dark Eyegore Key Drop', 'Desert Palace - Desert Tiles 1 Pot Key',
|
||||||
|
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||||
|
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
||||||
|
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
|
||||||
|
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
|
||||||
|
'Skull Woods - Spike Corner Key Drop', "Thieves' Town - Hallway Pot Key",
|
||||||
|
"Thieves' Town - Spike Switch Pot Key", 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||||
|
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Misery Mire - Spikes Pot Key',
|
||||||
|
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
|
||||||
|
'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Conveyor Cross Pot Key',
|
||||||
|
'Ganons Tower - Double Switch Pot Key', 'Ganons Tower - Conveyor Star Pits Pot Key',
|
||||||
|
'Ganons Tower - Mini Helmasuar Key Drop',
|
||||||
|
],
|
||||||
|
'Big Key Drops': ['Hyrule Castle - Big Key Drop'],
|
||||||
|
'Shops': [
|
||||||
|
'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
|
||||||
|
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right', 'Dark Lake Hylia Shop - Left',
|
||||||
|
'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right', 'Dark Lumberjack Shop - Left',
|
||||||
|
'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right', 'Village of Outcasts Shop - Left',
|
||||||
|
'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right', 'Dark Potion Shop - Left',
|
||||||
|
'Dark Potion Shop - Middle', 'Dark Potion Shop - Right', 'Paradox Shop - Left', 'Paradox Shop - Middle',
|
||||||
|
'Paradox Shop - Right', 'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right',
|
||||||
|
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Capacity Upgrade - Left',
|
||||||
|
'Capacity Upgrade - Right'
|
||||||
|
],
|
||||||
|
'RetroShops': [
|
||||||
|
'Old Man Sword Cave Item 1', 'Take-Any #1 Item 1', 'Take-Any #1 Item 2', 'Take-Any #2 Item 1',
|
||||||
|
'Take-Any #2 Item 2', 'Take-Any #3 Item 1', 'Take-Any #3 Item 2','Take-Any #4 Item 1', 'Take-Any #4 Item 2'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
vanilla_fallback_dungeon_set = set(mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||||
|
mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||||
|
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||||
|
mode_grouping['Big Key Drops'])
|
||||||
|
|
||||||
|
|
||||||
|
major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer',
|
||||||
|
'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel',
|
||||||
|
'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove',
|
||||||
|
'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror',
|
||||||
|
'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)',
|
||||||
|
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield',
|
||||||
|
'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword',
|
||||||
|
'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
|
||||||
|
'Progressive Bow', 'Progressive Bow (Alt)'}
|
||||||
|
|
||||||
|
vanilla_swords = {"Link's Uncle", 'Master Sword Pedestal', 'Blacksmith', 'Pyramid Fairy - Left'}
|
||||||
|
|
||||||
|
trash_items = {
|
||||||
|
'Nothing': -1,
|
||||||
|
'Bee Trap': 0,
|
||||||
|
'Rupee (1)': 1,
|
||||||
|
'Rupees (5)': 1,
|
||||||
|
'Rupees (20)': 1,
|
||||||
|
|
||||||
|
'Small Heart': 2,
|
||||||
|
'Bee': 2,
|
||||||
|
'Arrows (5)': 2,
|
||||||
|
'Chicken': 2,
|
||||||
|
'Single Bomb': 2,
|
||||||
|
|
||||||
|
'Bombs (3)': 3,
|
||||||
|
'Arrows (10)': 3,
|
||||||
|
'Bombs (10)': 3,
|
||||||
|
|
||||||
|
'Red Potion': 4,
|
||||||
|
'Blue Shield': 4,
|
||||||
|
'Rupees (50)': 4,
|
||||||
|
'Rupees (100)': 4,
|
||||||
|
'Rupees (300)': 5,
|
||||||
|
|
||||||
|
'Piece of Heart': 17
|
||||||
|
}
|
||||||
124
source/test/MysteryTestSuite.py
Normal file
124
source/test/MysteryTestSuite.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import multiprocessing
|
||||||
|
import concurrent.futures
|
||||||
|
import argparse
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
cpu_threads = multiprocessing.cpu_count()
|
||||||
|
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
successes = []
|
||||||
|
errors = []
|
||||||
|
task_mapping = []
|
||||||
|
tests = OrderedDict()
|
||||||
|
|
||||||
|
successes.append(f"Testing {args.dr} DR with {args.count} Tests" + (f" (intensity={args.tense})" if args.dr in ['basic', 'crossed'] else ""))
|
||||||
|
print(successes[0])
|
||||||
|
|
||||||
|
max_attempts = args.count
|
||||||
|
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
|
||||||
|
dead_or_alive = 0
|
||||||
|
alive = 0
|
||||||
|
|
||||||
|
def test(testname: str, command: str):
|
||||||
|
tests[testname] = [command]
|
||||||
|
basecommand = f"python3.8 Mystery.py --suppress_rom"
|
||||||
|
|
||||||
|
def gen_seed():
|
||||||
|
taskcommand = basecommand + " " + command
|
||||||
|
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
|
||||||
|
|
||||||
|
for x in range(1, max_attempts + 1):
|
||||||
|
task = pool.submit(gen_seed)
|
||||||
|
task.success = False
|
||||||
|
task.name = testname
|
||||||
|
task.mode = "Mystery"
|
||||||
|
task.cmd = basecommand + " " + command
|
||||||
|
task_mapping.append(task)
|
||||||
|
|
||||||
|
for i in range(0, 100):
|
||||||
|
test("Mystery", "--weights mystery_testsuite.yml")
|
||||||
|
|
||||||
|
from tqdm import tqdm
|
||||||
|
with tqdm(concurrent.futures.as_completed(task_mapping),
|
||||||
|
total=len(task_mapping), unit="seed(s)",
|
||||||
|
desc=f"Success rate: 0.00%") as progressbar:
|
||||||
|
for task in progressbar:
|
||||||
|
dead_or_alive += 1
|
||||||
|
try:
|
||||||
|
result = task.result()
|
||||||
|
if result.returncode:
|
||||||
|
errors.append([task.name, task.cmd, result.stderr])
|
||||||
|
else:
|
||||||
|
alive += 1
|
||||||
|
task.success = True
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}")
|
||||||
|
|
||||||
|
def get_results(testname: str):
|
||||||
|
result = ""
|
||||||
|
for mode in ['Mystery']:
|
||||||
|
dead_or_alive = [task.success for task in task_mapping if task.name == testname and task.mode == mode]
|
||||||
|
alive = [x for x in dead_or_alive if x]
|
||||||
|
success = f"{testname} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
|
||||||
|
successes.append(success)
|
||||||
|
print(success)
|
||||||
|
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
|
||||||
|
return result.strip()
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for t in tests.keys():
|
||||||
|
results.append(get_results(t))
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
print(result)
|
||||||
|
successes.append(result)
|
||||||
|
|
||||||
|
return successes, errors
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
successes = []
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
|
||||||
|
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
|
||||||
|
parser.add_argument('--help', default=False, action='store_true')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.help:
|
||||||
|
parser.print_help()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
cpu_threads = args.cpu_threads
|
||||||
|
|
||||||
|
for dr in [['mystery', args.count if args.count else 1, 1]]:
|
||||||
|
|
||||||
|
for tense in range(1, dr[2] + 1):
|
||||||
|
args = argparse.Namespace()
|
||||||
|
args.dr = dr[0]
|
||||||
|
args.tense = tense
|
||||||
|
args.count = dr[1]
|
||||||
|
s, errors = main(args=args)
|
||||||
|
if successes:
|
||||||
|
successes += [""] * 2
|
||||||
|
successes += s
|
||||||
|
print()
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
|
||||||
|
for error in errors:
|
||||||
|
stream.write(error[0] + "\n")
|
||||||
|
stream.write(error[1] + "\n")
|
||||||
|
stream.write(error[2] + "\n\n")
|
||||||
|
|
||||||
|
with open("success.txt", "w") as stream:
|
||||||
|
stream.write(str.join("\n", successes))
|
||||||
|
|
||||||
|
input("Press enter to continue")
|
||||||
0
source/test/__init__.py
Normal file
0
source/test/__init__.py
Normal file
Reference in New Issue
Block a user