Merged in DR v1.2.0.8

This commit is contained in:
codemann8
2023-02-18 19:33:15 -06:00
33 changed files with 902 additions and 156 deletions
+51 -13
View File
@@ -35,6 +35,8 @@ class World(object):
self.doorShuffle = doorShuffle.copy() self.doorShuffle = doorShuffle.copy()
self.intensity = {} self.intensity = {}
self.door_type_mode = {} self.door_type_mode = {}
self.trap_door_mode = {}
self.key_logic_algorithm = {}
self.logic = logic.copy() self.logic = logic.copy()
self.mode = mode.copy() self.mode = mode.copy()
self.swords = swords.copy() self.swords = swords.copy()
@@ -162,6 +164,8 @@ class World(object):
set_player_attr('pot_pool', {}) set_player_attr('pot_pool', {})
set_player_attr('decoupledoors', False) set_player_attr('decoupledoors', False)
set_player_attr('door_type_mode', 'original') set_player_attr('door_type_mode', 'original')
set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'default')
set_player_attr('shopsanity', False) set_player_attr('shopsanity', False)
set_player_attr('mixed_travel', 'prevent') set_player_attr('mixed_travel', 'prevent')
@@ -585,6 +589,7 @@ class CollectionState(object):
self.world = parent self.world = parent
if not skip_init: if not skip_init:
self.prog_items = Counter() self.prog_items = Counter()
self.forced_keys = Counter()
self.reachable_regions = {player: dict() for player in range(1, parent.players + 1)} self.reachable_regions = {player: dict() for player in range(1, parent.players + 1)}
self.blocked_connections = {player: dict() for player in range(1, parent.players + 1)} self.blocked_connections = {player: dict() for player in range(1, parent.players + 1)}
self.events = [] self.events = []
@@ -617,12 +622,13 @@ class CollectionState(object):
queue = deque(self.blocked_connections[player].items()) queue = deque(self.blocked_connections[player].items())
self.traverse_world(queue, rrp, bc, player) self.traverse_world(queue, rrp, bc, player)
unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations if self.world.key_logic_algorithm[player] == 'default':
if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations
and x not in self.locations_checked and x.can_reach(self)] if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement)
unresolved_events = self._do_not_flood_the_keys(unresolved_events) and x not in self.locations_checked and x.can_reach(self)]
if len(unresolved_events) == 0: unresolved_events = self._do_not_flood_the_keys(unresolved_events)
self.check_key_doors_in_dungeons(rrp, player) if len(unresolved_events) == 0:
self.check_key_doors_in_dungeons(rrp, player)
def traverse_world(self, queue, rrp, bc, player): def traverse_world(self, queue, rrp, bc, player):
# run BFS on all connections, and keep track of those blocked by missing items # run BFS on all connections, and keep track of those blocked by missing items
@@ -706,6 +712,7 @@ class CollectionState(object):
def check_key_doors_in_dungeons(self, rrp, player): def check_key_doors_in_dungeons(self, rrp, player):
for dungeon_name, checklist in self.dungeons_to_check[player].items(): for dungeon_name, checklist in self.dungeons_to_check[player].items():
# todo: optimization idea - abort exploration if there are unresolved events now
if self.apply_dungeon_exploration(rrp, player, dungeon_name, checklist): if self.apply_dungeon_exploration(rrp, player, dungeon_name, checklist):
continue continue
init_door_candidates = self.should_explore_child_state(self, dungeon_name, player) init_door_candidates = self.should_explore_child_state(self, dungeon_name, player)
@@ -905,6 +912,7 @@ class CollectionState(object):
def copy(self): def copy(self):
ret = CollectionState(self.world, skip_init=True) ret = CollectionState(self.world, skip_init=True)
ret.prog_items = self.prog_items.copy() ret.prog_items = self.prog_items.copy()
ret.forced_keys = self.forced_keys.copy()
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)} ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)} ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)}
ret.events = copy.copy(self.events) ret.events = copy.copy(self.events)
@@ -1112,6 +1120,14 @@ class CollectionState(object):
return (item, player) in self.prog_items return (item, player) in self.prog_items
return self.prog_items[item, player] >= count return self.prog_items[item, player] >= count
def has_sm_key_strict(self, item, player, count=1):
if self.world.keyshuffle[player] == 'universal':
if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)':
return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool
return self.can_buy_unlimited('Small Key (Universal)', player)
obtained = self.prog_items[item, player] - self.forced_keys[item, player]
return obtained >= count
def can_buy_unlimited(self, item, player): def can_buy_unlimited(self, item, player):
for shop in self.world.shops[player]: for shop in self.world.shops[player]:
if shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self): if shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self):
@@ -1341,6 +1357,8 @@ class CollectionState(object):
def collect(self, item, event=False, location=None): def collect(self, item, event=False, location=None):
if location: if location:
self.locations_checked.add(location) self.locations_checked.add(location)
if item and item.smallkey and location.forced_item is not None:
self.forced_keys[item.name, item.player] += 1
if not item: if not item:
return return
changed = False changed = False
@@ -1533,10 +1551,10 @@ class Region(object):
or (item.bigkey and not self.world.bigkeyshuffle[item.player]) or (item.bigkey and not self.world.bigkeyshuffle[item.player])
or (item.map and not self.world.mapshuffle[item.player]) or (item.map and not self.world.mapshuffle[item.player])
or (item.compass and not self.world.compassshuffle[item.player])) or (item.compass and not self.world.compassshuffle[item.player]))
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)' # not all small keys to escape must be in escape
if sewer_hack or inside_dungeon_item: # sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)'
if inside_dungeon_item:
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
return True return True
def can_cause_bunny(self, player): def can_cause_bunny(self, player):
@@ -2168,6 +2186,9 @@ class Door(object):
return world.get_room(self.roomIndex, self.player).kind(self) return world.get_room(self.roomIndex, self.player).kind(self)
return None return None
def dungeon_name(self):
return self.entrance.parent_region.dungeon.name if self.entrance.parent_region.dungeon else 'Cave'
def __eq__(self, other): def __eq__(self, other):
return isinstance(other, self.__class__) and self.name == other.name return isinstance(other, self.__class__) and self.name == other.name
@@ -2846,6 +2867,8 @@ class Spoiler(object):
'door_shuffle': self.world.doorShuffle, 'door_shuffle': self.world.doorShuffle,
'intensity': self.world.intensity, 'intensity': self.world.intensity,
'door_type_mode': self.world.door_type_mode, 'door_type_mode': self.world.door_type_mode,
'trap_door_mode': self.world.trap_door_mode,
'key_logic': self.world.key_logic_algorithm,
'decoupledoors': self.world.decoupledoors, 'decoupledoors': self.world.decoupledoors,
'dungeon_counters': self.world.dungeon_counters, 'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty, 'item_pool': self.world.difficulty,
@@ -3070,6 +3093,8 @@ class Spoiler(object):
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('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player]) outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player])) outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][player]))
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player])) outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player]) outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
@@ -3408,15 +3433,19 @@ orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "limited": 3, "chaos": 4}
# byte 12: KMB? FF?? (keep similar, mixed/tile flip, bonk drops, flute spots) # byte 12: KMB? FF?? (keep similar, mixed/tile flip, bonk drops, flute spots)
flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2} flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
# byte 13: FBBB, TTSS (flute_mode, bow_mode, take_any, small_key_mode) # byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode)
flute_mode = {'normal': 0, 'active': 1} flute_mode = {'normal': 0, 'active': 1}
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes? keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
# additions # additions
# psuedoboots does not effect code # byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo)
# sfx_shuffle and other adjust items does not effect settings code overworld_map_mode = {'default': 0, 'compass': 1, 'map': 2}
trap_door_mode = {'vanilla': 0, 'optional': 1, 'boss': 2, 'oneway': 3}
key_logic_algo = {'default': 0, 'partial': 1, 'strict': 2}
# sfx_shuffle and other adjust items does not affect settings code
# Bump this when making changes that are not backwards compatible (nearly all of them) # Bump this when making changes that are not backwards compatible (nearly all of them)
settings_version = 1 settings_version = 1
@@ -3466,7 +3495,10 @@ class Settings(object):
| (0x20 if w.shuffle_bonk_drops[p] else 0) | (flutespot_mode[w.owFluteShuffle[p]] << 4), | (0x20 if w.shuffle_bonk_drops[p] else 0) | (flutespot_mode[w.owFluteShuffle[p]] << 4),
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4 (flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
| take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]) | take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]),
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6
| trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]),
]) ])
return base64.b64encode(code, "+-".encode()).decode() return base64.b64encode(code, "+-".encode()).decode()
@@ -3547,6 +3579,12 @@ class Settings(object):
args.take_any[p] = r(take_any_mode)[(settings[13] & 0xC) >> 2] args.take_any[p] = r(take_any_mode)[(settings[13] & 0xC) >> 2]
args.keyshuffle[p] = r(keyshuffle_mode)[settings[13] & 0x3] args.keyshuffle[p] = r(keyshuffle_mode)[settings[13] & 0x3]
if len(settings) > 14:
args.pseudoboots[p] = True if settings[14] & 0x80 else False
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6]
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4]
args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07]
class KeyRuleType(FastEnum): class KeyRuleType(FastEnum):
WorstCase = 0 WorstCase = 0
+4 -1
View File
@@ -141,7 +141,8 @@ def parse_cli(argv, no_defaults=False):
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', 'bonk_drops']: 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
'bonk_drops', 'trap_door_mode', 'key_logic_algorithm']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})
@@ -223,6 +224,8 @@ def parse_settings():
"door_shuffle": "vanilla", "door_shuffle": "vanilla",
"intensity": 3, "intensity": 3,
"door_type_mode": "original", "door_type_mode": "original",
"trap_door_mode": "optional",
"key_logic_algorithm": "default",
"decoupledoors": False, "decoupledoors": False,
"experimental": False, "experimental": False,
"dungeon_counters": "default", "dungeon_counters": "default",
+127 -92
View File
@@ -226,22 +226,38 @@ def vanilla_key_logic(world, player):
add_inaccessible_doors(world, player) add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player)
for builder in builders: enabled_entrances = world.enabled_entrances[player] = {}
builder_queue = deque(builders)
last_key, loops = None, 0
while len(builder_queue) > 0:
builder = builder_queue.popleft()
origin_list = entrances_map[builder.name] origin_list = entrances_map[builder.name]
start_regions = convert_regions(origin_list, world, player) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name)
doors = convert_key_doors(default_small_key_doors[builder.name], world, player) if len(origin_list) <= 0:
key_layout = build_key_layout(builder, start_regions, doors, {}, world, player) if last_key == builder.name or loops > 1000:
valid = validate_key_layout(key_layout, world, player) origin_name = (world.get_region(origin_list[0], player).entrances[0].parent_region.name
if not valid: if len(origin_list) > 0 else 'no origin')
logging.getLogger('').info('Vanilla key layout not valid %s', builder.name) raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}')
builder.key_door_proposal = doors builder_queue.append(builder)
if player not in world.key_logic.keys(): last_key = builder.name
world.key_logic[player] = {} loops += 1
analyze_dungeon(key_layout, world, player) else:
world.key_logic[player][builder.name] = key_layout.key_logic find_new_entrances(builder.master_sector, entrances_map, connections, potentials,
world.key_layout[player][builder.name] = key_layout enabled_entrances, world, player)
log_key_logic(builder.name, key_layout.key_logic) start_regions = convert_regions(origin_list, world, 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]: doors = convert_key_doors(default_small_key_doors[builder.name], world, player)
key_layout = build_key_layout(builder, start_regions, doors, {}, world, player)
valid = validate_key_layout(key_layout, world, player)
if not valid:
logging.getLogger('').info('Vanilla key layout not valid %s', builder.name)
builder.key_door_proposal = doors
if player not in world.key_logic.keys():
world.key_logic[player] = {}
analyze_dungeon(key_layout, world, player)
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)
# if world.shuffle[player] == 'vanilla' 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)
@@ -1745,12 +1761,12 @@ class DoorTypePool:
self.tricky += counts[6] self.tricky += counts[6]
def chaos_shuffle(self, counts): def chaos_shuffle(self, counts):
weights = [1, 2, 4, 3, 2, 1] weights = [1, 2, 4, 3, 2]
return [random.choices(self.get_choices(counts[i]), weights=weights)[0] for i, c in enumerate(counts)] return [random.choices(self.get_choices(counts[i]), weights=weights)[0] for i, c in enumerate(counts)]
@staticmethod @staticmethod
def get_choices(number): def get_choices(number):
return [max(number+i, 0) for i in range(-1, 5)] return [max(number+i, 0) for i in range(-1, 4)]
class BuilderDoorCandidates: class BuilderDoorCandidates:
@@ -1782,58 +1798,68 @@ def shuffle_door_types(door_type_pools, paths, world, player):
def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player): def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player):
used_doors = set() used_doors = set()
for pool, door_type_pool in door_type_pools: for pool, door_type_pool in door_type_pools:
ttl = 0 if world.trap_door_mode[player] != 'oneway':
suggestion_map, trap_map, flex_map = {}, {}, {} ttl = 0
remaining = door_type_pool.traps suggestion_map, trap_map, flex_map = {}, {}, {}
if player in world.custom_door_types: remaining = door_type_pool.traps
custom_trap_doors = world.custom_door_types[player]['Trap Door'] if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[player]:
else: custom_trap_doors = world.custom_door_types[player]['Trap Door']
custom_trap_doors = defaultdict(list) else:
custom_trap_doors = defaultdict(list)
for dungeon in pool: for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon] builder = world.dungeon_layouts[player][dungeon]
find_trappable_candidates(builder, world, player) if 'Mire Warping Pool' in builder.master_sector.region_set():
if custom_trap_doors[dungeon]: custom_trap_doors[dungeon].append(world.get_door('Mire Warping Pool ES', player))
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, custom_trap_doors[dungeon]) world.custom_door_types[player]['Trap Door'] = custom_trap_doors
remaining -= len(custom_trap_doors[dungeon]) find_trappable_candidates(builder, world, player)
ttl += len(builder.candidates.trap) if custom_trap_doors[dungeon]:
if ttl == 0: builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, custom_trap_doors[dungeon])
continue remaining -= len(custom_trap_doors[dungeon])
for dungeon in pool: ttl += len(builder.candidates.trap)
builder = world.dungeon_layouts[player][dungeon] if ttl == 0:
proportion = len(builder.candidates.trap) continue
calc = int(round(proportion * door_type_pool.traps/ttl)) for dungeon in pool:
suggested = min(proportion, calc) builder = world.dungeon_layouts[player][dungeon]
remaining -= suggested proportion = len(builder.candidates.trap)
suggestion_map[dungeon] = suggested calc = int(round(proportion * door_type_pool.traps/ttl))
flex_map[dungeon] = (proportion - suggested) if suggested < proportion else 0 suggested = min(proportion, calc)
for dungeon in pool: remaining -= suggested
builder = world.dungeon_layouts[player][dungeon] suggestion_map[dungeon] = suggested
valid_traps, trap_number = find_valid_trap_combination(builder, suggestion_map[dungeon], flex_map[dungeon] = (proportion - suggested) if suggested < proportion else 0
start_regions_map[dungeon], paths, world, player, for dungeon in pool:
drop=True) builder = world.dungeon_layouts[player][dungeon]
trap_map[dungeon] = valid_traps valid_traps, trap_number = find_valid_trap_combination(builder, suggestion_map[dungeon],
if trap_number < suggestion_map[dungeon]: start_regions_map[dungeon], paths, world, player,
flex_map[dungeon] = 0 drop=True)
remaining += suggestion_map[dungeon] - trap_number
suggestion_map[dungeon] = trap_number
builder_order = [x for x in pool if flex_map[x] > 0]
random.shuffle(builder_order)
queue = deque(builder_order)
while len(queue) > 0 and remaining > 0:
dungeon = queue.popleft()
builder = world.dungeon_layouts[player][dungeon]
increased = suggestion_map[dungeon] + 1
valid_traps, trap_number = find_valid_trap_combination(builder, increased, start_regions_map[dungeon],
paths, world, player)
if valid_traps:
trap_map[dungeon] = valid_traps trap_map[dungeon] = valid_traps
remaining -= 1 if trap_number < suggestion_map[dungeon]:
suggestion_map[dungeon] = increased flex_map[dungeon] = 0
flex_map[dungeon] -= 1 remaining += suggestion_map[dungeon] - trap_number
if flex_map[dungeon] > 0: suggestion_map[dungeon] = trap_number
queue.append(dungeon) builder_order = [x for x in pool if flex_map[x] > 0]
# time to re-assign random.shuffle(builder_order)
queue = deque(builder_order)
while len(queue) > 0 and remaining > 0:
dungeon = queue.popleft()
builder = world.dungeon_layouts[player][dungeon]
increased = suggestion_map[dungeon] + 1
valid_traps, trap_number = find_valid_trap_combination(builder, increased, start_regions_map[dungeon],
paths, world, player)
if valid_traps:
trap_map[dungeon] = valid_traps
remaining -= 1
suggestion_map[dungeon] = increased
flex_map[dungeon] -= 1
if flex_map[dungeon] > 0:
queue.append(dungeon)
# time to re-assign
else:
trap_map = {dungeon: [] for dungeon in pool}
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
if 'Mire Warping Pool' in builder.master_sector.region_set():
trap_map[dungeon].append(world.get_door('Mire Warping Pool ES', player))
reassign_trap_doors(trap_map, world, player) reassign_trap_doors(trap_map, world, player)
for name, traps in trap_map.items(): for name, traps in trap_map.items():
used_doors.update(traps) used_doors.update(traps)
@@ -1845,7 +1871,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
ttl = 0 ttl = 0
suggestion_map, bk_map, flex_map = {}, {}, {} suggestion_map, bk_map, flex_map = {}, {}, {}
remaining = door_type_pool.bigs remaining = door_type_pool.bigs
if player in world.custom_door_types: if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
custom_bk_doors = world.custom_door_types[player]['Big Key Door'] custom_bk_doors = world.custom_door_types[player]['Big Key Door']
else: else:
custom_bk_doors = defaultdict(list) custom_bk_doors = defaultdict(list)
@@ -1907,7 +1933,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
suggestion_map, small_map, flex_map = {}, {}, {} suggestion_map, small_map, flex_map = {}, {}, {}
remaining = door_type_pool.smalls remaining = door_type_pool.smalls
total_keys = remaining total_keys = remaining
if player in world.custom_door_types: if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
custom_key_doors = world.custom_door_types[player]['Key Door'] custom_key_doors = world.custom_door_types[player]['Key Door']
else: else:
custom_key_doors = defaultdict(list) custom_key_doors = defaultdict(list)
@@ -2007,7 +2033,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, worl
remaining_bomb = door_type_pool.bombable remaining_bomb = door_type_pool.bombable
remaining_dash = door_type_pool.dashable remaining_dash = door_type_pool.dashable
if player in world.custom_door_types: if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
custom_bomb_doors = world.custom_door_types[player]['Bomb Door'] custom_bomb_doors = world.custom_door_types[player]['Bomb Door']
custom_dash_doors = world.custom_door_types[player]['Dash Door'] custom_dash_doors = world.custom_door_types[player]['Dash Door']
else: else:
@@ -2139,14 +2165,14 @@ def find_trappable_candidates(builder, world, player):
for ext in world.get_region(r, player).exits: for ext in world.get_region(r, player).exits:
if ext.door: if ext.door:
d = ext.door d = ext.door
if d.blocked and d.trapFlag != 0 and 'Boss' not in d.name and 'Agahnim' not in d.name: if d.blocked and d.trapFlag != 0 and exclude_boss_traps(d):
builder.candidates.trap.append(d) builder.candidates.trap.append(d)
def find_valid_trap_combination(builder, suggested, start_regions, paths, world, player, drop=True): def find_valid_trap_combination(builder, suggested, start_regions, paths, world, player, drop=True):
trap_door_pool = builder.candidates.trap trap_door_pool = builder.candidates.trap
trap_doors_needed = suggested trap_doors_needed = suggested
if player in world.custom_door_types: if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[player]:
custom_trap_doors = world.custom_door_types[player]['Trap Door'][builder.name] custom_trap_doors = world.custom_door_types[player]['Trap Door'][builder.name]
else: else:
custom_trap_doors = [] custom_trap_doors = []
@@ -2282,7 +2308,7 @@ def reassign_trap_doors(trap_map, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
for name, traps in trap_map.items(): for name, traps in trap_map.items():
builder = world.dungeon_layouts[player][name] builder = world.dungeon_layouts[player][name]
queue = deque(find_current_trap_doors(builder)) queue = deque(find_current_trap_doors(builder, world, player))
while len(queue) > 0: while len(queue) > 0:
d = queue.pop() d = queue.pop()
if d.type is DoorType.Interior and d not in traps: if d.type is DoorType.Interior and d not in traps:
@@ -2301,16 +2327,21 @@ def reassign_trap_doors(trap_map, world, player):
d.blocked = False d.blocked = False
for d in traps: for d in traps:
change_door_to_trap(d, world, player) change_door_to_trap(d, world, player)
world.spoiler.set_door_type(d.name, 'Trap Door', player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
logger.debug('Trap Door: %s', d.name) logger.debug(f'Trap Door: {d.name} ({d.dungeon_name()})')
def find_current_trap_doors(builder): def exclude_boss_traps(d):
return ' Boss ' not in d.name and ' Agahnim ' not in d.name and d.name not in ['Skull Spike Corner SW']
def find_current_trap_doors(builder, world, player):
checker = exclude_boss_traps if world.trap_door_mode[player] in ['vanilla', 'optional'] else (lambda x: True)
current_doors = [] current_doors = []
for region in builder.master_sector.regions: for region in builder.master_sector.regions:
for ext in region.exits: for ext in region.exits:
d = ext.door d = ext.door
if d and d.blocked and d.trapFlag != 0: # could exclude removing boss doors here if d and d.blocked and d.trapFlag != 0 and checker(d):
current_doors.append(d) current_doors.append(d)
return current_doors return current_doors
@@ -2425,7 +2456,7 @@ def find_big_key_door_candidates(region, checked, used, world, player):
def find_valid_bk_combination(builder, suggested, start_regions, world, player, drop=True): def find_valid_bk_combination(builder, suggested, start_regions, world, player, drop=True):
bk_door_pool = builder.candidates.big bk_door_pool = builder.candidates.big
bk_doors_needed = suggested bk_doors_needed = suggested
if player in world.custom_door_types: if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
custom_bk_doors = world.custom_door_types[player]['Big Key Door'][builder.name] custom_bk_doors = world.custom_door_types[player]['Big Key Door'][builder.name]
else: else:
custom_bk_doors = [] custom_bk_doors = []
@@ -2494,12 +2525,14 @@ def reassign_big_key_doors(bk_map, world, player):
if d1.type is DoorType.Interior: if d1.type is DoorType.Interior:
change_door_to_big_key(d1, world, player) change_door_to_big_key(d1, world, player)
d2.bigKey = True # ensure flag is set d2.bigKey = True # ensure flag is set
if d2.smallKey:
d2.smallKey = False
else: else:
world.paired_doors[player].append(PairedDoor(d1.name, d2.name)) world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
change_door_to_big_key(d1, world, player) change_door_to_big_key(d1, world, player)
change_door_to_big_key(d2, world, player) change_door_to_big_key(d2, world, player)
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Big Key Door', player) world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Big Key Door', player)
logger.debug(f'Big Key Door: {d1.name} <-> {d2.name}') logger.debug(f'Big Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
else: else:
d = obj d = obj
if d.type is DoorType.Interior: if d.type is DoorType.Interior:
@@ -2516,12 +2549,14 @@ def reassign_big_key_doors(bk_map, world, player):
if stateful_door(d.dest, dest_room.kind(d.dest)): if stateful_door(d.dest, dest_room.kind(d.dest)):
change_door_to_big_key(d.dest, world, player) change_door_to_big_key(d.dest, world, player)
add_pair(d, d.dest, world, player) add_pair(d, d.dest, world, player)
world.spoiler.set_door_type(d.name, 'Big Key Door', player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Big Key Door', player)
logger.debug(f'Big Key Door: {d.name}') logger.debug(f'Big Key Door: {d.name} ({d.dungeon_name()})')
def change_door_to_big_key(d, world, player): def change_door_to_big_key(d, world, player):
d.bigKey = True d.bigKey = True
if d.smallKey:
d.smallKey = False
room = world.get_room(d.roomIndex, player) room = world.get_room(d.roomIndex, player)
if room.doorList[d.doorListPos][1] != DoorKind.BigKey: if room.doorList[d.doorListPos][1] != DoorKind.BigKey:
verify_door_list_pos(d, room, world, player) verify_door_list_pos(d, room, world, player)
@@ -2565,7 +2600,7 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k
logger = logging.getLogger('') logger = logging.getLogger('')
key_door_pool = list(builder.candidates.small) key_door_pool = list(builder.candidates.small)
key_doors_needed = target key_doors_needed = target
if player in world.custom_door_types: if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
custom_key_doors = world.custom_door_types[player]['Key Door'][builder.name] custom_key_doors = world.custom_door_types[player]['Key Door'][builder.name]
else: else:
custom_key_doors = [] custom_key_doors = []
@@ -2693,7 +2728,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
bd_door_pool = builder.candidates.bomb_dash bd_door_pool = builder.candidates.bomb_dash
bomb_doors_needed, dash_doors_needed = suggested bomb_doors_needed, dash_doors_needed = suggested
ttl_needed = bomb_doors_needed + dash_doors_needed ttl_needed = bomb_doors_needed + dash_doors_needed
if player in world.custom_door_types: if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
custom_bomb_doors = world.custom_door_types[player]['Bomb Door'][builder.name] custom_bomb_doors = world.custom_door_types[player]['Bomb Door'][builder.name]
custom_dash_doors = world.custom_door_types[player]['Dash Door'][builder.name] custom_dash_doors = world.custom_door_types[player]['Dash Door'][builder.name]
else: else:
@@ -2769,7 +2804,7 @@ def do_bombable_dashable(proposal, kind, world, player):
change_door_to_kind(d1, kind, world, player) change_door_to_kind(d1, kind, world, player)
change_door_to_kind(d2, kind, world, player) change_door_to_kind(d2, kind, world, player)
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door' spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, spoiler_type, player) world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', spoiler_type, player)
else: else:
d = obj d = obj
if d.type is DoorType.Interior: if d.type is DoorType.Interior:
@@ -2783,7 +2818,7 @@ def do_bombable_dashable(proposal, kind, world, player):
change_door_to_kind(d.dest, kind, world, player) change_door_to_kind(d.dest, kind, world, player)
add_pair(d, d.dest, world, player) add_pair(d, d.dest, world, player)
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door' spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(d.name, spoiler_type, player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', spoiler_type, player)
def find_current_bd_doors(builder, world): def find_current_bd_doors(builder, world):
@@ -2986,8 +3021,8 @@ def reassign_key_doors(small_map, world, player):
world.paired_doors[player].append(PairedDoor(d1.name, d2.name)) world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
change_door_to_small_key(d1, world, player) change_door_to_small_key(d1, world, player)
change_door_to_small_key(d2, world, player) change_door_to_small_key(d2, world, player)
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Key Door', player) world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Key Door', player)
logger.debug('Key Door: %s', d1.name+' <-> '+d2.name) logger.debug(f'Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
else: else:
d = obj d = obj
if d.type is DoorType.Interior: if d.type is DoorType.Interior:
@@ -3003,8 +3038,8 @@ def reassign_key_doors(small_map, world, player):
if stateful_door(d.dest, dest_room.kind(d.dest)): if stateful_door(d.dest, dest_room.kind(d.dest)):
change_door_to_small_key(d.dest, world, player) change_door_to_small_key(d.dest, world, player)
add_pair(d, d.dest, world, player) add_pair(d, d.dest, world, player)
world.spoiler.set_door_type(d.name, 'Key Door', player) world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Key Door', player)
logger.debug('Key Door: %s', d.name) logger.debug(f'Key Door: {d.name} ({d.dungeon_name()})')
def change_door_to_small_key(d, world, player): def change_door_to_small_key(d, world, player):
@@ -3194,7 +3229,7 @@ def change_pair_type(door, new_type, world, player):
room_b.change(door.dest.doorListPos, new_type) room_b.change(door.dest.doorListPos, new_type)
add_pair(door, door.dest, world, player) add_pair(door, door.dest, world, player)
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door' spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(door.name + ' <-> ' + door.dest.name, spoiler_type, player) world.spoiler.set_door_type(f'{door.name} <-> {door.dest.name} ({door.dungeon_name()})', spoiler_type, player)
def remove_pair_type_if_present(door, world, player): def remove_pair_type_if_present(door, world, player):
@@ -4550,7 +4585,7 @@ door_type_counts = {
'Swamp Palace': (6, 0, 0, 2, 0, 0, 0), 'Swamp Palace': (6, 0, 0, 2, 0, 0, 0),
'Palace of Darkness': (6, 1, 1, 3, 2, 0, 0), 'Palace of Darkness': (6, 1, 1, 3, 2, 0, 0),
'Misery Mire': (6, 3, 5, 2, 0, 0, 0), 'Misery Mire': (6, 3, 5, 2, 0, 0, 0),
'Skull Woods': (5, 0, 2, 2, 0, 1, 0), 'Skull Woods': (5, 0, 1, 2, 0, 1, 0),
'Ice Palace': (6, 1, 3, 0, 0, 0, 0), 'Ice Palace': (6, 1, 3, 0, 0, 0, 0),
'Tower of Hera': (1, 1, 0, 0, 0, 0, 0), 'Tower of Hera': (1, 1, 0, 0, 0, 0, 0),
'Thieves Town': (3, 1, 2, 1, 1, 0, 0), 'Thieves Town': (3, 1, 2, 1, 1, 0, 0),
+13 -2
View File
@@ -772,6 +772,13 @@ def connect_simple_door(exit_door, region):
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"] special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
std_special_big_key_doors = ['Hyrule Castle Throne Room Tapestry'] + special_big_key_doors
def get_special_big_key_doors(world, player):
if world.mode[player] == 'standard':
return std_special_big_key_doors
return special_big_key_doors
class ExplorationState(object): class ExplorationState(object):
@@ -1002,7 +1009,8 @@ class ExplorationState(object):
if self.can_traverse(door): if self.can_traverse(door):
if door.controller: if door.controller:
door = door.controller door = door.controller
if (door in big_key_door_proposal or door.name in special_big_key_doors) and not self.big_key_opened: if (door in big_key_door_proposal
or door.name in get_special_big_key_doors(world, player)) and not self.big_key_opened:
if not self.in_door_list(door, self.big_doors): if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors) self.append_door_to_list(door, self.big_doors)
elif door.req_event is not None and door.req_event not in self.events: elif door.req_event is not None and door.req_event not in self.events:
@@ -3509,6 +3517,8 @@ def identify_branching_issues(dungeon_map, builder_info):
unconnected_builders[name] = builder unconnected_builders[name] = builder
for hook, door_list in unreached_doors.items(): for hook, door_list in unreached_doors.items():
builder.unfulfilled[hook] += len(door_list) builder.unfulfilled[hook] += len(door_list)
elif package:
builder.throne_door, builder.throne_sector, builder.chosen_lobby = package
return unconnected_builders return unconnected_builders
@@ -3554,7 +3564,8 @@ def check_for_valid_layout(builder, sector_list, builder_info):
split_list['Sewers'].remove(temp_builder.throne_door.entrance.parent_region.name) split_list['Sewers'].remove(temp_builder.throne_door.entrance.parent_region.name)
builder.exception_list = list(sector_list) builder.exception_list = list(sector_list)
return True, {}, package return True, {}, package
except (GenerationException, NeutralizingException, OtherGenException): except (GenerationException, NeutralizingException, OtherGenException) as e:
logging.getLogger('').info(f'Bailing on this layout for {builder.name}', exc_info=1)
builder.split_dungeon_map = None builder.split_dungeon_map = None
builder.valid_proposal = None builder.valid_proposal = None
if temp_builder.name == 'Hyrule Castle' and temp_builder.throne_door: if temp_builder.name == 'Hyrule Castle' and temp_builder.throne_door:
+84 -16
View File
@@ -105,6 +105,8 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing
spot_to_fill = None spot_to_fill = None
item_locations = filter_locations(item_to_place, locations, world, vanilla) item_locations = filter_locations(item_to_place, locations, world, vanilla)
verify(item_to_place, item_locations, maximum_exploration_state, single_player_placement,
perform_access_check, key_pool, world)
for location in item_locations: for location in item_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state, spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
single_player_placement, perform_access_check, key_pool, world) single_player_placement, perform_access_check, key_pool, world)
@@ -128,9 +130,6 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing
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)
if item_to_place.smallkey:
with suppress(ValueError):
key_pool.remove(item_to_place)
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) track_dungeon_items(item_to_place, spot_to_fill, world)
locations.remove(spot_to_fill) locations.remove(spot_to_fill)
@@ -143,21 +142,29 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
key_pool, world): key_pool, world):
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there 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 location.item = item_to_place
location.event = True
if item_to_place.smallkey:
with suppress(ValueError):
key_pool.remove(item_to_place)
test_state = max_exp_state.copy() test_state = max_exp_state.copy()
test_state.stale[item_to_place.player] = True test_state.stale[item_to_place.player] = True
else: else:
test_state = max_exp_state test_state = max_exp_state
if not single_player_placement or location.player == item_to_place.player: if not single_player_placement or location.player == item_to_place.player:
test_state.sweep_for_events()
if location.can_fill(test_state, item_to_place, perform_access_check): if location.can_fill(test_state, item_to_place, perform_access_check):
if valid_key_placement(item_to_place, location, key_pool, world): if valid_key_placement(item_to_place, location, key_pool, test_state, world):
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world): if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
return location return location
if item_to_place.smallkey or item_to_place.bigkey: if item_to_place.smallkey or item_to_place.bigkey:
location.item = None location.item = None
location.event = False
if item_to_place.smallkey:
key_pool.append(item_to_place)
return None return None
def valid_key_placement(item, location, key_pool, world): def valid_key_placement(item, location, key_pool, collection_state, world):
if not valid_reserved_placement(item, location, world): if not valid_reserved_placement(item, location, world):
return False return False
if ((not item.smallkey and not item.bigkey) or item.player != location.player if ((not item.smallkey and not item.bigkey) or item.player != location.player
@@ -174,7 +181,15 @@ def valid_key_placement(item, location, key_pool, world):
prize_loc = world.get_location(key_logic.prize_location, location.player) prize_loc = world.get_location(key_logic.prize_location, location.player)
cr_count = world.crystals_needed_for_gt[location.player] cr_count = world.crystals_needed_for_gt[location.player]
wild_keys = world.keyshuffle[item.player] != 'none' wild_keys = world.keyshuffle[item.player] != 'none'
return key_logic.check_placement(unplaced_keys, wild_keys, location if item.bigkey else None, prize_loc, cr_count) if wild_keys:
reached_keys = {x for x in collection_state.locations_checked
if x.item and x.item.name == key_logic.small_key_name and x.item.player == item.player}
else:
reached_keys = set() # will be calculated using key logic in a moment
self_locking_keys = sum(1 for d, rule in key_logic.door_rules.items() if rule.allow_small
and rule.small_location.item and rule.small_location.item.name == key_logic.small_key_name)
return key_logic.check_placement(unplaced_keys, wild_keys, reached_keys, self_locking_keys,
location if item.bigkey else None, prize_loc, cr_count)
else: else:
return not item.is_inside_dungeon_item(world) return not item.is_inside_dungeon_item(world)
@@ -205,6 +220,7 @@ def track_outside_keys(item, location, world):
if loc_dungeon and loc_dungeon.name == item_dungeon: if loc_dungeon and loc_dungeon.name == item_dungeon:
return # this is an inside key return # this is an inside key
world.key_logic[item.player][item_dungeon].outside_keys += 1 world.key_logic[item.player][item_dungeon].outside_keys += 1
world.key_logic[item.player][item_dungeon].outside_keys_locations.add(location)
def track_dungeon_items(item, location, world): def track_dungeon_items(item, location, world):
@@ -345,7 +361,9 @@ def find_spot_for_item(item_to_place, locations, world, base_state, pool,
test_state = maximum_exploration_state test_state = maximum_exploration_state
if (not single_player_placement or location.player == item_to_place.player) \ 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 location.can_fill(test_state, item_to_place, perform_access_check) \
and valid_key_placement(item_to_place, location, pool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world): and valid_key_placement(item_to_place, location,
pool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool,
test_state, world):
return location return location
if item_to_place.smallkey or item_to_place.bigkey: if item_to_place.smallkey or item_to_place.bigkey:
location.item = old_item location.item = old_item
@@ -386,11 +404,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
random.shuffle(fill_locations) random.shuffle(fill_locations)
random.shuffle(world.itempool) random.shuffle(world.itempool)
if world.item_pool_config.preferred: config_sort(world)
pref = list(world.item_pool_config.preferred.keys())
pref_len = len(pref)
world.itempool.sort(key=lambda i: pref_len - pref.index((i.name, i.player))
if (i.name, i.player) in world.item_pool_config.preferred else 0)
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]
@@ -430,10 +444,16 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
random.shuffle(fill_locations) random.shuffle(fill_locations)
fill_locations.reverse() fill_locations.reverse()
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # Make sure the escape keys ire placed first in standard to prevent running out of spots
# todo: crossed def std_item_sort(item):
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' if world.mode[item.player] == 'standard':
and world.keyshuffle[item.player] != 'none' and world.mode[item.player] == 'standard' else 0) if item.name == 'Small Key (Escape)':
return 1
if item.name == 'Big Key (Escape)':
return 2
return 0
progitempool.sort(key=std_item_sort)
key_pool = [x for x in progitempool if x.smallkey] key_pool = [x for x in progitempool if x.smallkey]
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
@@ -482,6 +502,20 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
ensure_good_pots(world) ensure_good_pots(world)
def config_sort(world):
if world.item_pool_config.verify:
config_sort_helper(world, world.item_pool_config.verify)
elif world.item_pool_config.preferred:
config_sort_helper(world, world.item_pool_config.preferred)
def config_sort_helper(world, sort_dict):
pref = list(sort_dict.keys())
pref_len = len(pref)
world.itempool.sort(key=lambda i: pref_len - pref.index((i.name, i.player))
if (i.name, i.player) in sort_dict else 0)
def calc_trash_locations(world, player): def calc_trash_locations(world, player):
total_count, gt_count = 0, 0 total_count, gt_count = 0, 0
for loc in world.get_locations(): for loc in world.get_locations():
@@ -656,6 +690,40 @@ def sell_keys(world, player):
world.itempool.remove(universal_key) world.itempool.remove(universal_key)
def verify(item_to_place, item_locations, state, spp, pac, key_pool, world):
if world.item_pool_config.verify:
logger = logging.getLogger('')
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
item_player = item_to_place.player
config = world.item_pool_config
if (item_name, item_player) in config.verify:
tests = config.verify[(item_name, item_player)]
issues = []
for location in item_locations:
if location.name in tests:
expected = tests[location.name]
spot = verify_spot_to_fill(location, item_to_place, state, spp, pac, key_pool, world)
if spot and (item_to_place.smallkey or item_to_place.bigkey):
location.item = None
location.event = False
if item_to_place.smallkey:
key_pool.append(item_to_place)
if (expected and spot) or (not expected and spot is None):
logger.debug(f'Placing {item_name} ({item_player}) at {location.name} was {expected}')
config.verify_count += 1
if config.verify_count >= config.verify_target:
exit()
else:
issues.append((item_name, item_player, location.name, expected))
if len(issues) > 0:
for name, player, loc, expected in issues:
if expected:
logger.error(f'Could not place {name} ({player}) at {loc}')
else:
logger.error(f'{name} ({player}) should not be allowed at {loc}')
raise Exception(f'Test failed placing {name}')
def balance_multiworld_progression(world): def balance_multiworld_progression(world):
state = CollectionState(world) state = CollectionState(world)
checked_locations = set() checked_locations = set()
+7
View File
@@ -1589,6 +1589,13 @@ def fill_specific_items(world):
item_player = player if len(item_parts) < 2 else int(item_parts[1]) item_player = player if len(item_parts) < 2 else int(item_parts[1])
item_name = item_parts[0] item_name = item_parts[0]
world.item_pool_config.preferred[(item_name, item_player)] = placement['locations'] world.item_pool_config.preferred[(item_name, item_player)] = placement['locations']
elif placement['type'] == 'Verification':
item = placement['item']
item_parts = item.split('#')
item_player = player if len(item_parts) < 2 else int(item_parts[1])
item_name = item_parts[0]
world.item_pool_config.verify[(item_name, item_player)] = placement['locations']
world.item_pool_config.verify_target += len(placement['locations'])
def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool): def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool):
+31 -17
View File
@@ -5,7 +5,7 @@ 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, count_locations_exclude_big_chest, prize_or_event from DungeonGenerator import ExplorationState, get_special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
from DungeonGenerator import reserved_location, blind_boss_unavail from DungeonGenerator import reserved_location, blind_boss_unavail
@@ -59,13 +59,16 @@ class KeyLogic(object):
self.placement_rules = [] self.placement_rules = []
self.location_rules = {} self.location_rules = {}
self.outside_keys = 0 self.outside_keys = 0
self.outside_keys_locations = set()
self.dungeon = dungeon_name self.dungeon = dungeon_name
self.sm_doors = {} self.sm_doors = {}
self.prize_location = None self.prize_location = None
def check_placement(self, unplaced_keys, wild_keys, big_key_loc=None, prize_loc=None, cr_count=7): def check_placement(self, unplaced_keys, wild_keys, reached_keys, self_locking_keys,
big_key_loc=None, prize_loc=None, cr_count=7):
for rule in self.placement_rules: for rule in self.placement_rules:
if not rule.is_satisfiable(self.outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_loc, cr_count): if not rule.is_satisfiable(self.outside_keys_locations, wild_keys, reached_keys, self_locking_keys,
unplaced_keys, big_key_loc, prize_loc, cr_count):
return False return False
if big_key_loc: if big_key_loc:
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2): for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
@@ -159,7 +162,8 @@ class PlacementRule(object):
left -= rule_needed left -= rule_needed
return False return False
def is_satisfiable(self, outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_location, cr_count): def is_satisfiable(self, outside_keys_locations, wild_keys, reached_keys, self_locking_keys, unplaced_keys,
big_key_loc, prize_location, cr_count):
if self.prize_relevance and prize_location: if self.prize_relevance and prize_location:
if self.prize_relevance == 'BigBomb': if self.prize_relevance == 'BigBomb':
if prize_location.item.name not in ['Crystal 5', 'Crystal 6']: if prize_location.item.name not in ['Crystal 5', 'Crystal 6']:
@@ -186,10 +190,11 @@ class PlacementRule(object):
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
if not bk_blocked and check_locations is None: if not bk_blocked and check_locations is None:
return True return True
available_keys = outside_keys available_keys = len(outside_keys_locations)
# todo: sometimes we need an extra empty chest to accomodate the big key too # todo: sometimes we need an extra empty chest to accomodate the big key too
# dungeon bias seed 563518200 for example # 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
threshold -= self_locking_keys
if not wild_keys: if not wild_keys:
empty_chests = 0 empty_chests = 0
for loc in check_locations: for loc in check_locations:
@@ -200,7 +205,8 @@ class PlacementRule(object):
place_able_keys = min(empty_chests, unplaced_keys) place_able_keys = min(empty_chests, unplaced_keys)
available_keys += place_able_keys available_keys += place_able_keys
else: else:
available_keys += unplaced_keys available_keys += len(reached_keys.difference(outside_keys_locations)) # already placed small keys
available_keys += unplaced_keys # small keys not yet placed
return available_keys >= threshold return available_keys >= threshold
@@ -1002,7 +1008,7 @@ def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_c
def open_a_door(door, child_state, flat_proposal, world, player): def open_a_door(door, child_state, flat_proposal, world, player):
if door.bigKey or door.name in special_big_key_doors: if door.bigKey or door.name in get_special_big_key_doors(world, player):
child_state.big_key_opened = True child_state.big_key_opened = True
child_state.avail_doors.extend(child_state.big_doors) child_state.avail_doors.extend(child_state.big_doors)
child_state.opened_doors.extend(set([d.door for d in child_state.big_doors])) child_state.opened_doors.extend(set([d.door for d in child_state.big_doors]))
@@ -1485,7 +1491,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
else: else:
ttl_locations = count_locations_exclude_big_chest(state.found_locations, 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,
key_layout, 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)
if invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player): if invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player):
return False return False
@@ -1615,18 +1622,24 @@ def determine_prize_lock(key_layout, world, player):
key_layout.prize_can_lock = True key_layout.prize_can_lock = True
def cnt_avail_small_locations(free_locations, key_only, state, world, player): def cnt_avail_small_locations(free_locations, key_only, state, key_layout, world, player):
if world.keyshuffle[player] == 'none': std_flag = world.mode[player] == 'standard' and key_layout.sector.name == 'Hyrule Castle'
if world.keyshuffle[player] == 'none' or std_flag:
bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0
avail_chest_keys = min(free_locations - bk_adj, state.key_locations - key_only) # this is the secret passage, could expand to Uncle/Links House with appropriate logic
std_adj = 1 if std_flag and world.keyshuffle[player] != 'none' else 0
avail_chest_keys = min(free_locations + std_adj - bk_adj, state.key_locations - key_only)
return max(0, avail_chest_keys + key_only - state.used_smalls) return max(0, avail_chest_keys + key_only - state.used_smalls)
return state.key_locations - state.used_smalls return state.key_locations - state.used_smalls
def cnt_avail_small_locations_by_ctr(free_locations, counter, layout, world, player): def cnt_avail_small_locations_by_ctr(free_locations, counter, layout, world, player):
if world.keyshuffle[player] == 'none': std_flag = world.mode[player] == 'standard' and layout.sector.name == 'Hyrule Castle'
if world.keyshuffle[player] == 'none' or std_flag:
bk_adj = 1 if counter.big_key_opened and not layout.big_key_special else 0 bk_adj = 1 if counter.big_key_opened and not layout.big_key_special else 0
avail_chest_keys = min(free_locations - bk_adj, layout.max_chests) # this is the secret passage, could expand to Uncle/Links House with appropriate logic
std_adj = 1 if std_flag and world.keyshuffle[player] != 'none' else 0
avail_chest_keys = min(free_locations + std_adj - bk_adj, layout.max_chests)
return max(0, avail_chest_keys + len(counter.key_only_locations) - counter.used_keys) return max(0, avail_chest_keys + len(counter.key_only_locations) - counter.used_keys)
return layout.max_chests + len(counter.key_only_locations) - counter.used_keys return layout.max_chests + len(counter.key_only_locations) - counter.used_keys
@@ -1683,10 +1696,10 @@ def create_key_counters(key_layout, world, player):
if door.dest in flat_proposal and door.type != DoorType.SpiralStairs: if door.dest in flat_proposal and door.type != DoorType.SpiralStairs:
key_layout.found_doors.add(door.dest) key_layout.found_doors.add(door.dest)
child_state = parent_state.copy() child_state = parent_state.copy()
if door.bigKey or door.name in special_big_key_doors: if door.bigKey or door.name in get_special_big_key_doors(world, player):
key_layout.key_logic.bk_doors.add(door) key_layout.key_logic.bk_doors.add(door)
# open the door, if possible # open the door, if possible
if can_open_door(door, child_state, world, player): if can_open_door(door, child_state, key_layout, world, player):
open_a_door(door, child_state, flat_proposal, world, player) open_a_door(door, child_state, flat_proposal, world, player)
expand_key_state(child_state, flat_proposal, world, player) expand_key_state(child_state, flat_proposal, world, player)
code = state_id(child_state, key_layout.flat_prop) code = state_id(child_state, key_layout.flat_prop)
@@ -1707,14 +1720,15 @@ def find_outside_connection(region):
return None, None return None, None
def can_open_door(door, state, world, player): def can_open_door(door, state, key_layout, 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.found_locations, 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,
key_layout, world, player)
return available_small_locations > 0 return available_small_locations > 0
elif door.bigKey: elif door.bigKey:
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player) available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
+3 -1
View File
@@ -35,7 +35,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings from source.classes.CustomSettings import CustomSettings
__version__ = '1.2.0.7-u' __version__ = '1.2.0.8-u'
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
@@ -116,6 +116,8 @@ def main(args, seed=None, fish=None):
world.beemizer = args.beemizer.copy() world.beemizer = args.beemizer.copy()
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)} world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
world.door_type_mode = args.door_type_mode.copy() world.door_type_mode = args.door_type_mode.copy()
world.trap_door_mode = args.trap_door_mode.copy()
world.key_logic_algorithm = args.key_logic_algorithm.copy()
world.decoupledoors = args.decoupledoors.copy() world.decoupledoors = args.decoupledoors.copy()
world.experimental = args.experimental.copy() world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy() world.dungeon_counters = args.dungeon_counters.copy()
+9
View File
@@ -108,6 +108,15 @@ These are now independent of retro mode and have three options: None, Random, an
* Bonk Fairy (Dark) * Bonk Fairy (Dark)
# Bug Fixes and Notes # Bug Fixes and Notes
* 1.2.0.8-u
* New Features: trap_door_mode and key_logic_algorithm
* Change S&Q in door shuffle + standard during escape to spawn as Uncle
* Fix for vanilla doors + certain ER modes
* Fix for unintentional decoupled door in standard
* Fix a problem with BK doors being one-sided
* Change to how wilds keys are placed in standard, better randomization
* Removed a Triforce text
* Fix for Desert Tiles 1 key door
* 1.2.0.7-u * 1.2.0.7-u
* Fix for some misery mire key logic * Fix for some misery mire key logic
* Minor standard generation fix * Minor standard generation fix
+1 -1
View File
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '45aa34e724750862234f9a4b09caa3d6' RANDOMIZERBASEHASH = '1694ba41bf6ab7086f05e914e8d08433'
class JsonRom(object): class JsonRom(object):
+1 -1
View File
@@ -103,7 +103,7 @@ def create_rooms(world, player):
Room(player, 0x60, 0x51309).door(Position.NorthE2, DoorKind.NormalLow2).door(Position.East2, DoorKind.NormalLow2).door(Position.East2, DoorKind.ToggleFlag).door(Position.EastN, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.SouthE, DoorKind.IncognitoEntrance), Room(player, 0x60, 0x51309).door(Position.NorthE2, DoorKind.NormalLow2).door(Position.East2, DoorKind.NormalLow2).door(Position.East2, DoorKind.ToggleFlag).door(Position.EastN, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.SouthE, DoorKind.IncognitoEntrance),
Room(player, 0x61, 0x51454).door(Position.West2, DoorKind.NormalLow).door(Position.West2, DoorKind.ToggleFlag).door(Position.East2, DoorKind.NormalLow).door(Position.East2, DoorKind.ToggleFlag).door(Position.South2, DoorKind.NormalLow).door(Position.South2, DoorKind.IncognitoEntrance).door(Position.WestN, DoorKind.Normal), Room(player, 0x61, 0x51454).door(Position.West2, DoorKind.NormalLow).door(Position.West2, DoorKind.ToggleFlag).door(Position.East2, DoorKind.NormalLow).door(Position.East2, DoorKind.ToggleFlag).door(Position.South2, DoorKind.NormalLow).door(Position.South2, DoorKind.IncognitoEntrance).door(Position.WestN, DoorKind.Normal),
Room(player, 0x62, 0x51577).door(Position.West2, DoorKind.NormalLow2).door(Position.West2, DoorKind.ToggleFlag).door(Position.NorthW2, DoorKind.NormalLow2).door(Position.North, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance), Room(player, 0x62, 0x51577).door(Position.West2, DoorKind.NormalLow2).door(Position.West2, DoorKind.ToggleFlag).door(Position.NorthW2, DoorKind.NormalLow2).door(Position.North, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
Room(player, 0x63, 0xf88ed).door(Position.NorthE, DoorKind.StairKey).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.DungeonEntrance), # looked like a huge typo - I had to guess on StairKey Room(player, 0x63, 0xf88ed).door(Position.NorthW, DoorKind.StairKey).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.DungeonEntrance), # looked like a huge typo - I had to guess on StairKey
Room(player, 0x64, 0xfda53).door(Position.InteriorS, DoorKind.Trap2), Room(player, 0x64, 0xfda53).door(Position.InteriorS, DoorKind.Trap2),
Room(player, 0x65, 0xfdac5).door(Position.InteriorS, DoorKind.Normal), Room(player, 0x65, 0xfdac5).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x66, 0xfa01b).door(Position.InteriorE2, DoorKind.Waterfall).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.SouthW2, DoorKind.ToggleFlag).door(Position.InteriorW2, DoorKind.NormalLow2), Room(player, 0x66, 0xfa01b).door(Position.InteriorE2, DoorKind.Waterfall).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.SouthW2, DoorKind.ToggleFlag).door(Position.InteriorW2, DoorKind.NormalLow2),
+20 -3
View File
@@ -1459,7 +1459,7 @@ def standard_rules(world, player):
# zelda should be saved before agahnim is in play # zelda should be saved before agahnim is in play
add_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player)) add_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player))
# too restrictive for crossed? # uncle can't have keys generally because unplaced items aren't used here
def uncle_item_rule(item): def uncle_item_rule(item):
copy_state = CollectionState(world) copy_state = CollectionState(world)
copy_state.collect(item) copy_state.collect(item)
@@ -1817,13 +1817,16 @@ bunny_impassible_doors = {
def add_key_logic_rules(world, player): def add_key_logic_rules(world, player):
key_logic = world.key_logic[player] key_logic = world.key_logic[player]
eval_func = eval_small_key_door
if world.key_logic_algorithm[player] == 'strict' and world.keyshuffle[player] == 'wild':
eval_func = eval_small_key_door_strict
for d_name, d_logic in key_logic.items(): for d_name, d_logic in key_logic.items():
for door_name, rule in d_logic.door_rules.items(): for door_name, rule in d_logic.door_rules.items():
door_entrance = world.get_entrance(door_name, player) door_entrance = world.get_entrance(door_name, player)
add_rule(door_entrance, eval_small_key_door(door_name, d_name, player)) add_rule(door_entrance, eval_func(door_name, d_name, player))
if door_entrance.door.dependents: if door_entrance.door.dependents:
for dep in door_entrance.door.dependents: for dep in door_entrance.door.dependents:
add_rule(dep.entrance, eval_small_key_door(door_name, d_name, player)) add_rule(dep.entrance, eval_func(door_name, d_name, player))
for location in d_logic.bk_restricted: for location in d_logic.bk_restricted:
if not location.forced_item: if not location.forced_item:
forbid_item(location, d_logic.bk_name, player) forbid_item(location, d_logic.bk_name, player)
@@ -1870,10 +1873,24 @@ def eval_small_key_door_main(state, door_name, dungeon, player):
return door_openable return door_openable
def eval_small_key_door_strict_main(state, door_name, dungeon, player):
if state.is_door_open(door_name, player):
return True
key_layout = state.world.key_layout[player][dungeon]
number = key_layout.max_chests
if number <= 0:
return True
return state.has_sm_key_strict(key_layout.key_logic.small_key_name, player, number)
def eval_small_key_door(door_name, dungeon, player): def eval_small_key_door(door_name, dungeon, player):
return lambda state: eval_small_key_door_main(state, door_name, dungeon, player) return lambda state: eval_small_key_door_main(state, door_name, dungeon, player)
def eval_small_key_door_strict(door_name, dungeon, player):
return lambda state: eval_small_key_door_strict_main(state, door_name, dungeon, player)
def allow_big_key_in_big_chest(bk_name, player): def allow_big_key_in_big_chest(bk_name, player):
return lambda state, item: item.name == bk_name and item.player == player return lambda state, item: item.name == bk_name and item.player == player
-1
View File
@@ -91,7 +91,6 @@ Triforce_texts = [
'Who stole the fourth triangle?', 'Who stole the fourth triangle?',
'Trifource?\nMore Like Tritrice, am I right?' 'Trifource?\nMore Like Tritrice, am I right?'
'\n Well Done!', '\n Well Done!',
'You just wasted 2 hours of your life.',
'This was meant to be a trapezoid', 'This was meant to be a trapezoid',
# these ones are from web randomizer # these ones are from web randomizer
"\n G G", "\n G G",
Binary file not shown.
+8
View File
@@ -41,6 +41,14 @@
big: 2 big: 2
all: 1 all: 1
chaos: 1 chaos: 1
trap_door_mode:
vanilla: 1
boss: 0
oneway: 0
key_logic_algorithm:
default: 1
partial: 0
strict: 0
decoupledoors: off decoupledoors: off
dropshuffle: dropshuffle:
on: 1 on: 1
+19 -1
View File
@@ -7,12 +7,30 @@ algorithm:
district: 1 district: 1
door_shuffle: door_shuffle:
vanilla: 1 vanilla: 1
basic: 2 basic: 1
partitioned: 1
crossed: 3 # crossed yield more errors so is preferred crossed: 3 # crossed yield more errors so is preferred
intensity: intensity:
1: 1 1: 1
2: 1 2: 1
3: 2 # intensity 3 usually yield more errors 3: 2 # intensity 3 usually yield more errors
door_type_mode:
original: 2
big: 2
all: 1
chaos: 1
trap_door_mode:
vanilla: 3 # more errors
optional: 1
boss: 1
oneway: 1
key_logic_algorithm:
default: 1
partial: 0
strict: 0
decoupledoors:
off: 9 # more strict
on: 1
dropshuffle: dropshuffle:
on: 1 on: 1
off: 1 off: 1
+15
View File
@@ -239,6 +239,21 @@
"chaos" "chaos"
] ]
}, },
"trap_door_mode": {
"choices": [
"vanilla",
"optional",
"boss",
"oneway"
]
},
"key_logic_algorithm": {
"choices": [
"default",
"partial",
"strict"
]
},
"decoupledoors": { "decoupledoors": {
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
+13
View File
@@ -284,6 +284,19 @@
"all: Adds traps doors (and any future supported door types)", "all: Adds traps doors (and any future supported door types)",
"chaos: Increases the number of door types in all dungeon pools" "chaos: Increases the number of door types in all dungeon pools"
], ],
"trap_door_mode" : [
"Trap Door Removal (default: %(default)s)",
"vanilla: No trap door removal",
"optional: Trap doors removed if blocking",
"boss: Also remove boss traps",
"oneway: Remove all annoying trap doors"
],
"key_logic_algorithm": [
"Key Logic Algorithm (default: %(default)s)",
"default: Balance between safety and randomization",
"partial: Partial protection when using certain minor glitches",
"strict: Ensure small keys are available"
],
"decoupledoors" : [ "Door entrances and exits are decoupled" ], "decoupledoors" : [ "Door entrances and exits are decoupled" ],
"experimental": [ "Enable experimental features. (default: %(default)s)" ], "experimental": [ "Enable experimental features. (default: %(default)s)" ],
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
+11
View File
@@ -91,6 +91,17 @@
"randomizer.dungeon.door_type_mode.all": "Adds Trap Doors", "randomizer.dungeon.door_type_mode.all": "Adds Trap Doors",
"randomizer.dungeon.door_type_mode.chaos": "Increases all door types", "randomizer.dungeon.door_type_mode.chaos": "Increases all door types",
"randomizer.dungeon.trap_door_mode": "Trap Door Removal",
"randomizer.dungeon.trap_door_mode.vanilla": "No Removal",
"randomizer.dungeon.trap_door_mode.optional": "Removed If Blocking Path",
"randomizer.dungeon.trap_door_mode.boss": "Also Remove Boss Traps",
"randomizer.dungeon.trap_door_mode.oneway": "Remove All Annoying Traps",
"randomizer.dungeon.key_logic_algorithm": "Key Logic Algorithm",
"randomizer.dungeon.key_logic_algorithm.default": "Default",
"randomizer.dungeon.key_logic_algorithm.partial": "Partial Protection",
"randomizer.dungeon.key_logic_algorithm.strict": "Strict",
"randomizer.dungeon.experimental": "Enable Experimental Features", "randomizer.dungeon.experimental": "Enable Experimental Features",
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters", "randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
@@ -33,7 +33,7 @@
}, },
"door_type_mode": { "door_type_mode": {
"type": "selectbox", "type": "selectbox",
"default": "basic", "default": "original",
"options": [ "options": [
"original", "original",
"big", "big",
@@ -44,6 +44,28 @@
"width": 45 "width": 45
} }
}, },
"trap_door_mode": {
"type": "selectbox",
"default": "optional",
"options": [
"vanilla",
"optional",
"boss",
"oneway"
],
"config": {
"width": 30
}
},
"key_logic_algorithm": {
"type": "selectbox",
"default": "default",
"options": [
"default",
"partial",
"strict"
]
},
"decoupledoors": { "type": "checkbox" }, "decoupledoors": { "type": "checkbox" },
"keydropshuffle": { "type": "checkbox" }, "keydropshuffle": { "type": "checkbox" },
"pottery": { "pottery": {
+4
View File
@@ -109,6 +109,8 @@ class CustomSettings(object):
args.standardize_palettes[p]) args.standardize_palettes[p])
args.intensity[p] = get_setting(settings['intensity'], args.intensity[p]) args.intensity[p] = get_setting(settings['intensity'], args.intensity[p])
args.door_type_mode[p] = get_setting(settings['door_type_mode'], args.door_type_mode[p]) args.door_type_mode[p] = get_setting(settings['door_type_mode'], args.door_type_mode[p])
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p]) args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p]) args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
@@ -219,6 +221,8 @@ class CustomSettings(object):
settings_dict[p]['door_shuffle'] = world.doorShuffle[p] settings_dict[p]['door_shuffle'] = world.doorShuffle[p]
settings_dict[p]['intensity'] = world.intensity[p] settings_dict[p]['intensity'] = world.intensity[p]
settings_dict[p]['door_type_mode'] = world.door_type_mode[p] settings_dict[p]['door_type_mode'] = world.door_type_mode[p]
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
settings_dict[p]['decoupledoors'] = world.decoupledoors[p] settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['logic'] = world.logic[p] settings_dict[p]['logic'] = world.logic[p]
settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['mode'] = world.mode[p]
+2
View File
@@ -109,6 +109,8 @@ SETTINGSTOPROCESS = {
"dungeondoorshuffle": "door_shuffle", "dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity", "dungeonintensity": "intensity",
"door_type_mode": "door_type_mode", "door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode",
"key_logic_algorithm": "key_logic_algorithm",
"decoupledoors": "decoupledoors", "decoupledoors": "decoupledoors",
"keydropshuffle": "keydropshuffle", "keydropshuffle": "keydropshuffle",
"dropshuffle": "dropshuffle", "dropshuffle": "dropshuffle",
+41 -3
View File
@@ -320,7 +320,10 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
paths.append('Hyrule Dungeon Cellblock') paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room')) paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South') entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South')
# todo: in non-er, we can use the other portals too
paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name)) paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name))
paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name,
'Hyrule Dungeon Cellblock']))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town': if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window') paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names: elif 'Thieves Attic Window' in all_r_names:
@@ -434,6 +437,13 @@ def connect_simple_door(exit_door, region):
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"] special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
std_special_big_key_doors = ['Hyrule Castle Throne Room Tapestry'] + special_big_key_doors
def get_special_big_key_doors(world, player):
if world.mode[player] == 'standard':
return std_special_big_key_doors
return special_big_key_doors
class ExplorationState(object): class ExplorationState(object):
@@ -621,6 +631,7 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag) self.append_door_to_list(door, self.avail_doors, flag)
# same as above but traps are ignored, and flag is not used
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player): def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors: if door in proposed_map and door.name in valid_doors:
@@ -641,6 +652,27 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors): elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors) self.append_door_to_list(door, self.avail_doors)
# same as above but traps are checked for
def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door)
if self.can_traverse(door):
if door.controller is not None:
door = door.controller
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
else:
other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player): def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player):
for door in get_doors(world, region, player): for door in get_doors(world, region, player):
if self.can_traverse_ignore_traps(door) and door not in proposed_traps: if self.can_traverse_ignore_traps(door) and door not in proposed_traps:
@@ -674,7 +706,7 @@ class ExplorationState(object):
if door in key_door_proposal and door not in self.opened_doors: if door in key_door_proposal and door not in self.opened_doors:
if not self.in_door_list(door, self.small_doors): if not self.in_door_list(door, self.small_doors):
self.append_door_to_list(door, self.small_doors) self.append_door_to_list(door, self.small_doors)
elif (door.bigKey or door.name in special_big_key_doors) and not self.big_key_opened: elif (door.bigKey or door.name in get_special_big_key_doors(world, player)) and not self.big_key_opened:
if not self.in_door_list(door, self.big_doors): if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors) self.append_door_to_list(door, self.big_doors)
elif door.req_event is not None and door.req_event not in self.events: elif door.req_event is not None and door.req_event not in self.events:
@@ -827,7 +859,10 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
local_state = state.copy() local_state = state.copy()
for region in search_regions: for region in search_regions:
local_state.visit_region(region) local_state.visit_region(region)
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player) if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player)
else:
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
while len(local_state.avail_doors) > 0: while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door() explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map: if explorable_door.door in proposed_map:
@@ -838,7 +873,10 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player) if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
and not local_state.visited(connect_region)): and not local_state.visited(connect_region)):
local_state.visit_region(connect_region) local_state.visit_region(connect_region)
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player) if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player)
else:
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
return local_state return local_state
+6
View File
@@ -20,6 +20,9 @@ class ItemPoolConfig(object):
self.reserved_locations = defaultdict(set) self.reserved_locations = defaultdict(set)
self.restricted = {} self.restricted = {}
self.preferred = {} self.preferred = {}
self.verify = {}
self.verify_count = 0
self.verify_target = 0
self.recorded_choices = [] self.recorded_choices = []
@@ -435,6 +438,9 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
if (item_name, item_to_place.player) in config.preferred: if (item_name, item_to_place.player) in config.preferred:
locs = config.preferred[(item_name, item_to_place.player)] locs = config.preferred[(item_name, item_to_place.player)]
return sorted(locations, key=lambda l: 0 if l.name in locs else 1) return sorted(locations, key=lambda l: 0 if l.name in locs else 1)
if (item_name, item_to_place.player) in config.verify:
locs = config.verify[(item_name, item_to_place.player)].keys()
return sorted(locations, key=lambda l: 0 if l.name in locs else 1)
return locations return locations
+3 -2
View File
@@ -1,6 +1,4 @@
import importlib.util import importlib.util
import webbrowser
from tkinter import Tk, Label, Button, Frame
def check_requirements(console=False): def check_requirements(console=False):
@@ -26,6 +24,9 @@ def check_requirements(console=False):
logger.error('See the step about "Installing Platform-specific dependencies":') logger.error('See the step about "Installing Platform-specific dependencies":')
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md') logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
else: else:
import webbrowser
from tkinter import Tk, Label, Button, Frame
master = Tk() master = Tk()
master.title('Error') master.title('Error')
frame = Frame(master) frame = Frame(master)
+2 -1
View File
@@ -304,7 +304,8 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
unused_entrances = set() unused_entrances = set()
if not cross_world: if not cross_world:
lw_entrances, dw_entrances = [], [] lw_entrances, dw_entrances = [], []
for x in rem_entrances: left = sorted(rem_entrances)
for x in left:
if bonk_fairy_exception(x): if bonk_fairy_exception(x):
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x) lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail) do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail)
+2
View File
@@ -95,6 +95,8 @@ def roll_settings(weights):
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')
ret.door_type_mode = get_choice('door_type_mode') ret.door_type_mode = get_choice('door_type_mode')
ret.trap_door_mode = get_choice('trap_door_mode')
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
ret.decoupledoors = get_choice('decoupledoors') == 'on' ret.decoupledoors = get_choice('decoupledoors') == 'on'
ret.experimental = get_choice('experimental') == 'on' ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on' ret.collection_rate = get_choice('collection_rate') == 'on'
+126
View File
@@ -0,0 +1,126 @@
import fnmatch
import os
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 DR (NewTestSuite)")
print(successes[0])
# max_attempts = args.count
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
def test(test_name: str, command: str, test_file: str):
tests[test_name] = [command]
base_command = f"python3.8 DungeonRandomizer.py --suppress_rom --suppress_spoiler"
def gen_seed():
task_command = base_command + " " + command
return subprocess.run(task_command, capture_output=True, shell=True, text=True)
task = pool.submit(gen_seed)
task.success = False
task.name = test_name
task.test_file = test_file
task.cmd = base_command + " " + command
task_mapping.append(task)
for test_suite, test_files in args.test_suite.items():
for test_file in test_files:
test(test_suite, f'--customizer {os.path.join(test_suite, test_file)}', test_file)
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.test_file, 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 = ""
dead_or_alive = [task.success for task in task_mapping if task.name == testname]
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
test_suites = {}
# not sure if it supports subdirectories properly yet
for root, dirnames, filenames in os.walk('test/suite'):
test_suites[root] = fnmatch.filter(filenames, '*.yaml')
args = argparse.Namespace()
args.test_suite = test_suites
s, errors = main(args=args)
if successes:
successes += [""] * 2
successes += s
print()
if errors:
with open(f"new-test-suite-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("new-test-suite-success.txt", "w") as stream:
stream.write(str.join("\n", successes))
input("Press enter to continue")
+44
View File
@@ -0,0 +1,44 @@
# Possible improvements: account for items that are possibly in logic
# Example: Mire Big Key in harmless means all 6 mire smalls required for fire-locked side,
# if you have access to harmless via:
# 2 pod smalls + bow, hammer or 3 pod small
meta:
players: 1
settings:
1:
key_logic_algorithm: default
keysanity: True
crystals_needed_for_gt: 0 # to skip trash fill
placements:
1:
Hobo: Big Key (Misery Mire)
Waterfall Fairy - Left: Small Key (Misery Mire)
Waterfall Fairy - Right: Small Key (Misery Mire)
Palace of Darkness - Big Chest: Hammer
advanced_placements:
1:
# Contrast with partial_2
- type: Verification
item: Flippers
locations:
Misery Mire - Map Chest: True
Misery Mire - Main Lobby: True
Misery Mire - Bridge Chest: True
Misery Mire - Spike Chest: True
Misery Mire - Compass Chest: False
Misery Mire - Big Key Chest: False
Misery Mire - Boss: False
# Contrast with partial_3
- type: Verification
item: Big Key (Ganons Tower)
locations:
Ganons Tower - Big Key Chest: True
Ganons Tower - Big Key Room - Left: True
Ganons Tower - Big Key Room - Right: True
Ganons Tower - Bob's Chest: True
# Normal logic doesn't allow this placement
# unless hammer is placed before it - no algorithm does this in non-keysanity, but possible in keysanity
- type: Verification
item: Small Key (Palace of Darkness)
locations:
Palace of Darkness - Dark Maze - Bottom: True
+24
View File
@@ -0,0 +1,24 @@
# Even though Lamp is Flipper-locked, this logic considers that a key could be wasted in the dark in mire
# Only fire locked mire is off limits
meta:
players: 1
settings:
1:
key_logic_algorithm: partial
keysanity: true
placements:
1:
Hobo: Lamp
Waterfall Fairy - Left: Small Key (Misery Mire)
advanced_placements:
1:
- type: Verification
item: Flippers
locations:
Misery Mire - Map Chest: True
Misery Mire - Main Lobby: True
Misery Mire - Bridge Chest: True
Misery Mire - Spike Chest: True
Misery Mire - Compass Chest: False
Misery Mire - Big Key Chest: False
Misery Mire - Boss: False
+26
View File
@@ -0,0 +1,26 @@
# For contrast with default logic
# This logic is not yet smart enough to allow the crystal blocked chests with two keys (Spike Pot and one other)
meta:
players: 1
settings:
1:
key_logic_algorithm: partial
keysanity: True
placements:
1:
Hobo: Lamp
Waterfall Fairy - Left: Small Key (Misery Mire)
Waterfall Fairy - Right: Small Key (Misery Mire)
Swamp Palace - Entrance: Big Key (Misery Mire)
advanced_placements:
1:
- type: Verification
item: Flippers
locations:
Misery Mire - Map Chest: False
Misery Mire - Main Lobby: False
Misery Mire - Bridge Chest: True
Misery Mire - Spike Chest: True
Misery Mire - Compass Chest: False
Misery Mire - Big Key Chest: False
Misery Mire - Boss: False
+160
View File
@@ -0,0 +1,160 @@
# For contrast with default logic
# Examples of valid big key placement that doesn't work with pure worst case scenarios
# Basically chests that are obtainable two ways+ of spending keys
# (Possible fix: access to the extra door grants access to the mini helma key too)
meta:
players: 1
settings:
1:
key_logic_algorithm: partial
crystals_needed_for_gt: 0 # to skip trash fill
advanced_placements:
1:
- type: Verification
item: Big Key (Desert Palace)
locations:
Desert Palace - Big Chest: False
Desert Palace - Big Key Chest: True
Desert Palace - Boss: False
Desert Palace - Compass Chest: True
Desert Palace - Map Chest: True
Desert Palace - Torch: True
- type: Verification
item: Big Key (Eastern Palace)
locations:
Eastern Palace - Big Chest: False
Eastern Palace - Big Key Chest: True
Eastern Palace - Boss: False
Eastern Palace - Cannonball Chest: True
Eastern Palace - Compass Chest: True
Eastern Palace - Map Chest: True
- type: Verification
item: Big Key (Ganons Tower)
locations:
# These four require not wasting keys upstairs because the big key is down here
Ganons Tower - Big Key Chest: False
Ganons Tower - Big Key Room - Left: False
Ganons Tower - Big Key Room - Right: False
Ganons Tower - Bob's Chest: False
# These are normal
Ganons Tower - Big Chest: False
Ganons Tower - Bob's Torch: True
Ganons Tower - Compass Room - Bottom Left: True
Ganons Tower - Compass Room - Bottom Right: True
Ganons Tower - Compass Room - Top Left: True
Ganons Tower - Compass Room - Top Right: True
Ganons Tower - DMs Room - Bottom Left: True
Ganons Tower - DMs Room - Bottom Right: True
Ganons Tower - DMs Room - Top Left: True
Ganons Tower - DMs Room - Top Right: True
Ganons Tower - Firesnake Room: True
Ganons Tower - Hope Room - Left: True
Ganons Tower - Hope Room - Right: True
Ganons Tower - Map Chest: True
Ganons Tower - Mini Helmasaur Room - Left: False
Ganons Tower - Mini Helmasaur Room - Right: False
Ganons Tower - Pre-Moldorm Chest: False
Ganons Tower - Randomizer Room - Bottom Left: True
Ganons Tower - Randomizer Room - Bottom Right: True
Ganons Tower - Randomizer Room - Top Left: True
Ganons Tower - Randomizer Room - Top Right: True
Ganons Tower - Tile Room: True
Ganons Tower - Validation Chest: False
- type: Verification
item: Big Key (Ice Palace)
locations:
Ice Palace - Big Chest: False
Ice Palace - Big Key Chest: True
Ice Palace - Boss: False
Ice Palace - Compass Chest: True
Ice Palace - Freezor Chest: True
Ice Palace - Iced T Room: True
Ice Palace - Map Chest: True
Ice Palace - Spike Room: True
- type: Verification
item: Big Key (Misery Mire)
locations:
Misery Mire - Big Chest: False
Misery Mire - Big Key Chest: True
Misery Mire - Boss: False
Misery Mire - Bridge Chest: True
Misery Mire - Compass Chest: True
Misery Mire - Main Lobby: True
Misery Mire - Map Chest: True
Misery Mire - Spike Chest: True
- type: Verification
item: Big Key (Palace of Darkness)
locations:
Palace of Darkness - Big Chest: False
Palace of Darkness - Big Key Chest: True
Palace of Darkness - Boss: False
Palace of Darkness - Compass Chest: True
Palace of Darkness - Dark Basement - Left: True
Palace of Darkness - Dark Basement - Right: True
Palace of Darkness - Dark Maze - Bottom: True
Palace of Darkness - Dark Maze - Top: True
Palace of Darkness - Harmless Hellway: True
Palace of Darkness - Map Chest: True
Palace of Darkness - Shooter Room: True
Palace of Darkness - Stalfos Basement: True
Palace of Darkness - The Arena - Bridge: True
Palace of Darkness - The Arena - Ledge: True
- type: Verification
item: Big Key (Skull Woods)
locations:
Skull Woods - Big Chest: True
Skull Woods - Big Key Chest: True
Skull Woods - Boss: True
Skull Woods - Bridge Room: True
Skull Woods - Compass Chest: True
Skull Woods - Map Chest: True
Skull Woods - Pinball Room: True
Skull Woods - Pot Prison: True
- type: Verification
item: Big Key (Swamp Palace)
locations:
Swamp Palace - Big Chest: True
Swamp Palace - Big Key Chest: True
Swamp Palace - Boss: True
Swamp Palace - Compass Chest: True
Swamp Palace - Entrance: False
Swamp Palace - Flooded Room - Left: True
Swamp Palace - Flooded Room - Right: True
Swamp Palace - Map Chest: True
Swamp Palace - Waterfall Room: True
Swamp Palace - West Chest: True
- type: Verification
item: Big Key (Thieves Town)
locations:
Thieves' Town - Ambush Chest: True
Thieves' Town - Attic: False
Thieves' Town - Big Chest: False
Thieves' Town - Big Key Chest: True
Thieves' Town - Blind's Cell: False
Thieves' Town - Boss: False
Thieves' Town - Compass Chest: True
Thieves' Town - Map Chest: True
- type: Verification
item: Big Key (Tower of Hera)
locations:
Tower of Hera - Basement Cage: True
Tower of Hera - Big Chest: False
Tower of Hera - Big Key Chest: True
Tower of Hera - Boss: False
Tower of Hera - Compass Chest: False
Tower of Hera - Map Chest: True
- type: Verification
item: Big Key (Turtle Rock)
locations:
Turtle Rock - Big Chest: False
Turtle Rock - Big Key Chest: True
Turtle Rock - Boss: False
Turtle Rock - Chain Chomps: True
Turtle Rock - Compass Chest: True
Turtle Rock - Crystaroller Room: False
Turtle Rock - Eye Bridge - Bottom Left: False
Turtle Rock - Eye Bridge - Bottom Right: False
Turtle Rock - Eye Bridge - Top Left: False
Turtle Rock - Eye Bridge - Top Right: False
Turtle Rock - Roller Room - Left: True
Turtle Rock - Roller Room - Right: True
+22
View File
@@ -0,0 +1,22 @@
meta:
players: 1
settings:
1:
key_logic_algorithm: strict
keysanity: true
placements:
1:
Hobo: Big Key (Misery Mire)
Waterfall Fairy - Left: Small Key (Misery Mire)
advanced_placements:
1:
- type: Verification
item: Flippers
locations:
Misery Mire - Map Chest: False
Misery Mire - Main Lobby: False
Misery Mire - Bridge Chest: True
Misery Mire - Spike Chest: True
Misery Mire - Compass Chest: False
Misery Mire - Big Key Chest: False
Misery Mire - Boss: False