@@ -229,15 +229,11 @@ class World(object):
|
||||
|
||||
if keys:
|
||||
for p in range(1, self.players + 1):
|
||||
key_list = []
|
||||
player_dungeons = [x for x in self.dungeons if x.player == p]
|
||||
for dungeon in player_dungeons:
|
||||
if dungeon.big_key is not None:
|
||||
key_list += [dungeon.big_key.name]
|
||||
if len(dungeon.small_keys) > 0:
|
||||
key_list += [x.name for x in dungeon.small_keys]
|
||||
from Items import ItemFactory
|
||||
for item in ItemFactory(key_list, p):
|
||||
for item in ItemFactory(['Small Key (Escape)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)',
|
||||
'Big Key (Palace of Darkness)'] + ['Small Key (Palace of Darkness)'] * 6 + ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Skull Woods)'] + ['Small Key (Skull Woods)'] * 3 + ['Big Key (Swamp Palace)',
|
||||
'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + ['Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', 'Big Key (Ganons Tower)'] + ['Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + ['Small Key (Ganons Tower)'] * 4,
|
||||
p):
|
||||
soft_collect(item)
|
||||
ret.sweep_for_events()
|
||||
return ret
|
||||
@@ -893,30 +889,12 @@ class Polarity:
|
||||
def __getitem__(self, item):
|
||||
return self.vector[item]
|
||||
|
||||
def __eq__(self, other):
|
||||
for i in range(len(self.vector)):
|
||||
if self.vector[i] != other.vector[i]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_neutral(self):
|
||||
for i in range(len(self.vector)):
|
||||
if self.vector[i] != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def complement(self):
|
||||
result = Polarity()
|
||||
for i in range(len(self.vector)):
|
||||
result.vector[i] = pol_comp[pol_idx_2[i]](self.vector[i])
|
||||
return result
|
||||
|
||||
def charge(self):
|
||||
result = 0
|
||||
for i in range(len(self.vector)):
|
||||
result += abs(self.vector[i])
|
||||
return result
|
||||
|
||||
|
||||
pol_idx = {
|
||||
Direction.North: (0, 'Pos'),
|
||||
@@ -940,11 +918,6 @@ pol_add = {
|
||||
'Add': lambda x, y: x + y,
|
||||
'Mod': lambda x, y: (x + y) % 2
|
||||
}
|
||||
pol_comp = {
|
||||
'Add': lambda x: -x,
|
||||
'Mod': lambda x: 0 if x == 0 else 1
|
||||
}
|
||||
|
||||
|
||||
@unique
|
||||
class CrystalBarrier(Enum):
|
||||
@@ -988,7 +961,6 @@ class Door(object):
|
||||
self.req_event = None # if a dungeon event is required for this door - swamp palace mostly
|
||||
self.controller = None
|
||||
self.dependents = []
|
||||
self.dead = False
|
||||
|
||||
def getAddress(self):
|
||||
if self.type == DoorType.Normal:
|
||||
@@ -1063,10 +1035,6 @@ class Door(object):
|
||||
self.crystal = CrystalBarrier.Either
|
||||
return self
|
||||
|
||||
def kill(self):
|
||||
self.dead = True
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.name == other.name
|
||||
|
||||
@@ -1086,19 +1054,6 @@ class Sector(object):
|
||||
self.regions = []
|
||||
self.outstanding_doors = []
|
||||
self.name = None
|
||||
self.r_name_set = None
|
||||
self.chest_locations = 0
|
||||
self.key_only_locations = 0
|
||||
self.c_switch = False
|
||||
self.orange_barrier = False
|
||||
self.blue_barrier = False
|
||||
self.bk_required = False
|
||||
self.bk_provided = False
|
||||
|
||||
def region_set(self):
|
||||
if self.r_name_set is None:
|
||||
self.r_name_set = dict.fromkeys(map(lambda r: r.name, self.regions))
|
||||
return self.r_name_set.keys()
|
||||
|
||||
def polarity(self):
|
||||
pol = Polarity()
|
||||
@@ -1121,19 +1076,6 @@ class Sector(object):
|
||||
outflow = outflow + 1
|
||||
return outflow
|
||||
|
||||
def adj_outflow(self):
|
||||
outflow = 0
|
||||
for door in self.outstanding_doors:
|
||||
if not door.blocked and not door.dead:
|
||||
outflow = outflow + 1
|
||||
return outflow
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s' % next(iter(self.region_set()))
|
||||
|
||||
|
||||
class Boss(object):
|
||||
def __init__(self, name, enemizer_name, defeat_rule, player):
|
||||
@@ -1393,9 +1335,14 @@ class Spoiler(object):
|
||||
self.bosses[str(player)]["Ice Palace"] = self.world.get_dungeon("Ice Palace", player).boss.name
|
||||
self.bosses[str(player)]["Misery Mire"] = self.world.get_dungeon("Misery Mire", player).boss.name
|
||||
self.bosses[str(player)]["Turtle Rock"] = self.world.get_dungeon("Turtle Rock", player).boss.name
|
||||
self.bosses[str(player)]["Ganons Tower Basement"] = [x for x in self.world.dungeons if x.player == player and 'bottom' in x.bosses.keys()][0].bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = [x for x in self.world.dungeons if x.player == player and 'middle' in x.bosses.keys()][0].bosses['middle'].name
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = [x for x in self.world.dungeons if x.player == player and 'top' in x.bosses.keys()][0].bosses['top'].name
|
||||
if self.world.mode != 'inverted':
|
||||
self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Ganons Tower', player).bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Ganons Tower', player).bosses['middle'].name
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Ganons Tower', player).bosses['top'].name
|
||||
else:
|
||||
self.bosses[str(player)]["Ganons Tower Basement"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['bottom'].name
|
||||
self.bosses[str(player)]["Ganons Tower Middle"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].name
|
||||
self.bosses[str(player)]["Ganons Tower Top"] = self.world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].name
|
||||
|
||||
self.bosses[str(player)]["Ganons Tower"] = "Agahnim 2"
|
||||
self.bosses[str(player)]["Ganon"] = "Ganon"
|
||||
|
||||
559
DoorShuffle.py
559
DoorShuffle.py
@@ -6,16 +6,13 @@ import operator as op
|
||||
import time
|
||||
|
||||
from functools import reduce
|
||||
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier
|
||||
from Regions import key_only_locations
|
||||
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, Polarity, CrystalBarrier
|
||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
|
||||
from Dungeons import dungeon_regions, region_starts, split_region_starts, flexible_starts
|
||||
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys
|
||||
from Items import ItemFactory
|
||||
from Dungeons import drop_entrances
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon
|
||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder
|
||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
|
||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout_ex
|
||||
|
||||
|
||||
def link_doors(world, player):
|
||||
@@ -56,15 +53,15 @@ def link_doors(world, player):
|
||||
elif world.doorShuffle == 'experimental':
|
||||
experiment(world, player)
|
||||
|
||||
mark_regions(world, player)
|
||||
if world.doorShuffle != 'vanilla':
|
||||
create_door_spoiler(world, player)
|
||||
|
||||
|
||||
# todo: I think this function is not necessary
|
||||
def mark_regions(world, player):
|
||||
# traverse dungeons and make sure dungeon property is assigned
|
||||
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
||||
for dungeon in player_dungeons:
|
||||
playerDungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
||||
for dungeon in playerDungeons:
|
||||
queue = collections.deque(dungeon.regions)
|
||||
while len(queue) > 0:
|
||||
region = world.get_region(queue.popleft(), player)
|
||||
@@ -104,31 +101,34 @@ def create_door_spoiler(world, player):
|
||||
logger.debug('Door not found in queue: %s connected to %s', door_b.name, door_a.name)
|
||||
else:
|
||||
logger.warning('Door not connected: %s', door_a.name)
|
||||
# for dp in world.paired_doors[player]:
|
||||
# if dp.pair:
|
||||
# logger.debug('Paired Doors: %s with %s (p%d)', dp.door_a, dp.door_b, player)
|
||||
# else:
|
||||
# logger.debug('Unpaired Doors: %s not paired with %s (p%d)', dp.door_a, dp.door_b, player)
|
||||
|
||||
|
||||
def vanilla_key_logic(world, player):
|
||||
builders = []
|
||||
sectors = []
|
||||
for dungeon in [dungeon for dungeon in world.dungeons if dungeon.player == player]:
|
||||
sector = Sector()
|
||||
sector.name = dungeon.name
|
||||
sector.regions.extend(convert_regions(dungeon.regions, world, player))
|
||||
builder = simple_dungeon_builder(sector.name, [sector], world, player)
|
||||
builder.master_sector = sector
|
||||
builders.append(builder)
|
||||
sectors.append(sector)
|
||||
|
||||
overworld_prep(world, player)
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
for builder in builders:
|
||||
start_regions = convert_regions(entrances_map[builder.name], world, 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)
|
||||
for sector in sectors:
|
||||
start_regions = convert_regions(entrances_map[sector.name], world, player)
|
||||
doors = convert_key_doors(default_small_key_doors[sector.name], world, player)
|
||||
key_layout = build_key_layout(sector, start_regions, doors, world, player)
|
||||
valid = validate_key_layout_ex(key_layout, world, player)
|
||||
if not valid:
|
||||
raise Exception('Vanilla key layout not valid %s' % builder.name)
|
||||
raise Exception('Vanilla key layout not valid %s' % sector.name)
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
key_layout = analyze_dungeon(key_layout, world, player)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
world.key_logic[player][sector.name] = key_layout.key_logic
|
||||
validate_vanilla_key_logic(world, player)
|
||||
|
||||
|
||||
@@ -271,78 +271,61 @@ def pair_existing_key_doors(world, player, door_a, door_b):
|
||||
def within_dungeon(world, player):
|
||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||
overworld_prep(world, player)
|
||||
dungeon_sectors = []
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
connections_tuple = (entrances_map, potentials, connections)
|
||||
|
||||
dungeon_builders = {}
|
||||
for key in dungeon_regions.keys():
|
||||
sector_list = convert_to_sectors(dungeon_regions[key], world, player)
|
||||
dungeon_builders[key] = simple_dungeon_builder(key, sector_list, world, player)
|
||||
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
||||
recombinant_builders = {}
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||
if key in split_region_starts.keys():
|
||||
split_sectors = split_up_sectors(sector_list, split_region_starts[key])
|
||||
for idx, sub_sector_list in enumerate(split_sectors):
|
||||
entrance_list = list(split_region_starts[key][idx])
|
||||
# shuffable entrances like pinball, left pit need to be added to entrance list
|
||||
if key in flexible_starts.keys():
|
||||
add_shuffled_entrances(sub_sector_list, flexible_starts[key], entrance_list)
|
||||
filtered_list = [x for x in entrance_list if x in entrances_map[key]]
|
||||
dungeon_sectors.append((key, sub_sector_list, filtered_list))
|
||||
else:
|
||||
dungeon_sectors.append((key, sector_list, list(entrances_map[key])))
|
||||
|
||||
enabled_entrances = {}
|
||||
dungeon_layouts = []
|
||||
sector_queue = collections.deque(dungeon_sectors)
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
key, sector_list, entrance_list = sector_queue.popleft()
|
||||
split_dungeon = key in split_region_starts.keys()
|
||||
origin_list = list(entrance_list)
|
||||
find_enabled_origins(sector_list, enabled_entrances, origin_list, entrances_map, key)
|
||||
origin_list_sans_drops = remove_drop_origins(origin_list)
|
||||
if len(origin_list_sans_drops) <= 0:
|
||||
if last_key == key:
|
||||
raise Exception('Infinte loop detected %s' % key)
|
||||
sector_queue.append((key, sector_list, entrance_list))
|
||||
last_key = key
|
||||
else:
|
||||
ds = generate_dungeon(key, sector_list, origin_list_sans_drops, split_dungeon, world, player)
|
||||
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
||||
ds.name = key
|
||||
layout_starts = origin_list if len(entrance_list) <= 0 else entrance_list
|
||||
dungeon_layouts.append((ds, layout_starts))
|
||||
last_key = None
|
||||
|
||||
combine_layouts(dungeon_layouts, entrances_map)
|
||||
world.dungeon_layouts[player] = {}
|
||||
for sector, entrances in dungeon_layouts:
|
||||
find_enabled_origins([sector], enabled_entrances, entrances, entrances_map, sector.name)
|
||||
world.dungeon_layouts[player][sector.name] = (sector, entrances_map[sector.name])
|
||||
|
||||
paths = determine_required_paths(world)
|
||||
check_required_paths(paths, world, player)
|
||||
|
||||
# shuffle_key_doors for dungeons
|
||||
start = time.process_time()
|
||||
for builder in world.dungeon_layouts[player].values():
|
||||
shuffle_key_doors(builder, world, player)
|
||||
for sector, entrances in world.dungeon_layouts[player].values():
|
||||
shuffle_key_doors(sector, entrances, world, player)
|
||||
logging.getLogger('').info('Key door shuffle time: %s', time.process_time()-start)
|
||||
|
||||
|
||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
||||
for name, split_list in split_region_starts.items():
|
||||
builder = dungeon_builders.pop(name)
|
||||
recombinant_builders[name] = builder
|
||||
split_builders = split_dungeon_builder(builder, split_list)
|
||||
dungeon_builders.update(split_builders)
|
||||
for sub_name, split_entrances in split_list.items():
|
||||
sub_builder = dungeon_builders[name+' '+sub_name]
|
||||
entrance_list = list(split_entrances)
|
||||
if name in flexible_starts.keys():
|
||||
add_shuffled_entrances(sub_builder.sectors, flexible_starts[name], entrance_list)
|
||||
filtered_entrance_list = [x for x in entrance_list if x in entrances_map[name]]
|
||||
sub_builder.entrance_list = filtered_entrance_list
|
||||
|
||||
|
||||
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
||||
entrances_map, potentials, connections = connections_tuple
|
||||
enabled_entrances = {}
|
||||
sector_queue = collections.deque(dungeon_builders.values())
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
builder = sector_queue.popleft()
|
||||
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
|
||||
name = builder.name
|
||||
if split_dungeon:
|
||||
name = ' '.join(builder.name.split(' ')[:-1])
|
||||
origin_list = list(builder.entrance_list)
|
||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||
origin_list_sans_drops = remove_drop_origins(origin_list)
|
||||
if len(origin_list_sans_drops) <= 0:
|
||||
if last_key == builder.name:
|
||||
raise Exception('Infinte loop detected %s' % builder.name)
|
||||
sector_queue.append(builder)
|
||||
last_key = builder.name
|
||||
else:
|
||||
logging.getLogger('').info('Generating dungeon: %s', builder.name)
|
||||
ds = generate_dungeon(name, builder.sectors, origin_list_sans_drops, split_dungeon, world, player)
|
||||
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
||||
ds.name = name
|
||||
builder.master_sector = ds
|
||||
builder.layout_starts = origin_list if len(builder.entrance_list) <= 0 else builder.entrance_list
|
||||
last_key = None
|
||||
combine_layouts(recombinant_builders, dungeon_builders, entrances_map)
|
||||
world.dungeon_layouts[player] = {}
|
||||
for builder in dungeon_builders.values():
|
||||
find_enabled_origins([builder.master_sector], enabled_entrances, builder.layout_starts, entrances_map, builder.name)
|
||||
builder.path_entrances = entrances_map[builder.name]
|
||||
world.dungeon_layouts[player] = dungeon_builders
|
||||
|
||||
|
||||
def determine_entrance_list(world, player):
|
||||
entrance_map = {}
|
||||
potential_entrances = {}
|
||||
@@ -364,7 +347,6 @@ def determine_entrance_list(world, player):
|
||||
return entrance_map, potential_entrances, connections
|
||||
|
||||
|
||||
# todo: kill drop exceptions
|
||||
def drop_exception(name):
|
||||
return name in ['Skull Pot Circle', 'Skull Back Drop']
|
||||
|
||||
@@ -383,8 +365,6 @@ def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key):
|
||||
entrance_list.append(region.name)
|
||||
origin_reg, origin_dungeon = enabled[region.name]
|
||||
if origin_reg != region.name and origin_dungeon != region.dungeon:
|
||||
if key not in entrance_map.keys():
|
||||
key = ' '.join(key.split(' ')[:-1])
|
||||
entrance_map[key].append(region.name)
|
||||
if drop_exception(region.name): # only because they have unique regions
|
||||
entrance_list.append(region.name)
|
||||
@@ -613,128 +593,34 @@ def doors_fit_mandatory_pair(pair_list, a, b):
|
||||
|
||||
|
||||
def cross_dungeon(world, player):
|
||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||
overworld_prep(world, player)
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
connections_tuple = (entrances_map, potentials, connections)
|
||||
|
||||
all_sectors = []
|
||||
for key in dungeon_regions.keys():
|
||||
all_sectors.extend(convert_to_sectors(dungeon_regions[key], world, player))
|
||||
dungeon_builders = create_dungeon_builders(all_sectors, world, player)
|
||||
for builder in dungeon_builders.values():
|
||||
builder.entrance_list = list(entrances_map[builder.name])
|
||||
dungeon_obj = world.get_dungeon(builder.name, player)
|
||||
for sector in builder.sectors:
|
||||
for region in sector.regions:
|
||||
region.dungeon = dungeon_obj
|
||||
for loc in region.locations:
|
||||
if loc.name in key_only_locations:
|
||||
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
|
||||
loc.forced_item = loc.item = ItemFactory(key_name, player)
|
||||
recombinant_builders = {}
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||
|
||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||
|
||||
paths = determine_required_paths(world)
|
||||
check_required_paths(paths, world, player)
|
||||
|
||||
start = time.process_time()
|
||||
total_keys = remaining = 29
|
||||
total_candidates = 0
|
||||
start_regions_map = {}
|
||||
# Step 1: Find Small Key Door Candidates
|
||||
for name, builder in dungeon_builders.items():
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if not builder.bk_required or builder.bk_provided:
|
||||
dungeon.big_key = None
|
||||
elif builder.bk_required and not builder.bk_provided:
|
||||
dungeon.big_key = ItemFactory(dungeon_bigs[name], player)
|
||||
start_regions = convert_regions(builder.path_entrances, world, player)
|
||||
find_small_key_door_candidates(builder, start_regions, world, player)
|
||||
builder.key_doors_num = max(0, len(builder.candidates) - builder.key_drop_cnt)
|
||||
total_candidates += builder.key_doors_num
|
||||
start_regions_map[name] = start_regions
|
||||
|
||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||
for name, builder in dungeon_builders.items():
|
||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||
max_keys = builder.location_cnt - calc_used_dungeon_items(builder)
|
||||
cand_len = max(0, len(builder.candidates) - builder.key_drop_cnt)
|
||||
limit = min(max_keys, cand_len)
|
||||
suggested = min(calculated, limit)
|
||||
combo_size = ncr(len(builder.candidates), suggested + builder.key_drop_cnt)
|
||||
while combo_size > 500000 and suggested > 0:
|
||||
suggested -= 1
|
||||
combo_size = ncr(len(builder.candidates), suggested + builder.key_drop_cnt)
|
||||
builder.key_doors_num = suggested + builder.key_drop_cnt
|
||||
remaining -= suggested
|
||||
builder.combo_size = combo_size
|
||||
if suggested < limit:
|
||||
builder.flex = limit - suggested
|
||||
|
||||
# Step 3: Initial valid combination find - reduce flex if needed
|
||||
for name, builder in dungeon_builders.items():
|
||||
suggested = builder.key_doors_num - builder.key_drop_cnt
|
||||
find_valid_combination(builder, start_regions_map[name], world, player)
|
||||
actual_chest_keys = builder.key_doors_num - builder.key_drop_cnt
|
||||
if actual_chest_keys < suggested:
|
||||
remaining += suggested - actual_chest_keys
|
||||
builder.flex = 0
|
||||
|
||||
# Step 4: Try to assign remaining keys
|
||||
builder_order = [x for x in dungeon_builders.values() if x.flex > 0]
|
||||
builder_order.sort(key=lambda b: b.combo_size)
|
||||
queue = collections.deque(builder_order)
|
||||
logger = logging.getLogger('')
|
||||
while len(queue) > 0 and remaining > 0:
|
||||
builder = queue.popleft()
|
||||
name = builder.name
|
||||
logger.info('Cross Dungeon: Increasing key count by 1 for %s', name)
|
||||
builder.key_doors_num += 1
|
||||
result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False)
|
||||
if result:
|
||||
remaining -= 1
|
||||
builder.flex -= 1
|
||||
if builder.flex > 0:
|
||||
builder.combo_size = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
queue.append(builder)
|
||||
queue = collections.deque(sorted(queue, key=lambda b: b.combo_size))
|
||||
dungeon_split = split_up_sectors(all_sectors, default_dungeon_sets)
|
||||
dungeon_sectors = []
|
||||
for idx, sector_list in enumerate(dungeon_split):
|
||||
name = dungeon_x_idx_to_name[idx]
|
||||
if name in split_region_starts.keys():
|
||||
split = split_up_sectors(sector_list, split_region_starts[name])
|
||||
for sub_idx, sub_sector_list in enumerate(split):
|
||||
dungeon_sectors.append((name, sub_sector_list, split_region_starts[name][sub_idx]))
|
||||
else:
|
||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
||||
builder.flex = 0
|
||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||
dungeon_sectors.append((name, sector_list, region_starts[name]))
|
||||
# todo - adjust dungeon item pools -- ?
|
||||
dungeon_layouts = []
|
||||
# for key, sector_list, entrance_list in dungeon_sectors:
|
||||
# ds = shuffle_dungeon_no_repeats_new(world, player, sector_list, entrance_list)
|
||||
# ds.name = key
|
||||
# dungeon_layouts.append((ds, entrance_list))
|
||||
|
||||
# Last Step: Adjust Small Key Dungeon Pool
|
||||
for name, builder in dungeon_builders.items():
|
||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
logging.getLogger('').info('Cross Dungeon: Key door shuffle time: %s', time.process_time()-start)
|
||||
# todo: pair down paired doors - excessive rom writes ATM
|
||||
combine_layouts(dungeon_layouts)
|
||||
|
||||
# Re-assign dungeon bosses
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
for name, builder in dungeon_builders.items():
|
||||
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
|
||||
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
|
||||
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
|
||||
|
||||
|
||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
if boss_region in builder.master_sector.region_set():
|
||||
new_dungeon = world.get_dungeon(builder.name, player)
|
||||
if new_dungeon != gt:
|
||||
gt_boss = gt.bosses.pop(boss_key)
|
||||
new_dungeon.bosses[boss_key] = gt_boss
|
||||
for layout in dungeon_layouts:
|
||||
shuffle_key_doors(layout[0], layout[1], world, player)
|
||||
|
||||
|
||||
def experiment(world, player):
|
||||
cross_dungeon(world, player)
|
||||
within_dungeon(world, player)
|
||||
|
||||
|
||||
def convert_to_sectors(region_names, world, player):
|
||||
@@ -742,12 +628,12 @@ def convert_to_sectors(region_names, world, player):
|
||||
sectors = []
|
||||
while len(region_list) > 0:
|
||||
region = region_list.pop()
|
||||
sector = None
|
||||
new_sector = True
|
||||
region_chunk = [region]
|
||||
exits = []
|
||||
exits.extend(region.exits)
|
||||
outstanding_doors = []
|
||||
matching_sectors = []
|
||||
while len(exits) > 0:
|
||||
ext = exits.pop()
|
||||
if ext.connected_region is not None:
|
||||
@@ -759,55 +645,182 @@ def convert_to_sectors(region_names, world, player):
|
||||
if connect_region not in region_chunk:
|
||||
for existing in sectors:
|
||||
if connect_region in existing.regions:
|
||||
sector = existing
|
||||
new_sector = False
|
||||
if existing not in matching_sectors:
|
||||
matching_sectors.append(existing)
|
||||
else:
|
||||
door = world.check_for_door(ext.name, player)
|
||||
if door is not None and door.controller is None and door.dest is None:
|
||||
outstanding_doors.append(door)
|
||||
sector = Sector()
|
||||
if not new_sector:
|
||||
for match in matching_sectors:
|
||||
sector.regions.extend(match.regions)
|
||||
sector.outstanding_doors.extend(match.outstanding_doors)
|
||||
sectors.remove(match)
|
||||
if new_sector:
|
||||
sector = Sector()
|
||||
sector.regions.extend(region_chunk)
|
||||
sector.outstanding_doors.extend(outstanding_doors)
|
||||
sectors.append(sector)
|
||||
if new_sector:
|
||||
sectors.append(sector)
|
||||
return sectors
|
||||
|
||||
|
||||
# those with split region starts like Desert/Skull combine for key layouts
|
||||
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||
for recombine in recombinant_builders.values():
|
||||
queue = collections.deque(dungeon_builders.values())
|
||||
while len(queue) > 0:
|
||||
builder = queue.pop()
|
||||
if builder.name.startswith(recombine.name):
|
||||
del dungeon_builders[builder.name]
|
||||
if recombine.master_sector is None:
|
||||
recombine.master_sector = builder.master_sector
|
||||
recombine.master_sector.name = recombine.name
|
||||
else:
|
||||
recombine.master_sector.regions.extend(builder.master_sector.regions)
|
||||
recombine.layout_starts = list(entrances_map[recombine.name])
|
||||
dungeon_builders[recombine.name] = recombine
|
||||
def combine_layouts(dungeon_layouts, entrances_map):
|
||||
combined = {}
|
||||
queue = collections.deque(dungeon_layouts)
|
||||
while len(queue) > 0:
|
||||
sector, entrance_list = queue.pop()
|
||||
if sector.name in split_region_starts:
|
||||
dungeon_layouts.remove((sector, entrance_list))
|
||||
# desert_entrances.extend(entrance_list)
|
||||
if sector.name not in combined:
|
||||
combined[sector.name] = sector
|
||||
else:
|
||||
combined[sector.name].regions.extend(sector.regions)
|
||||
for key in combined.keys():
|
||||
dungeon_layouts.append((combined[key], list(entrances_map[key])))
|
||||
|
||||
|
||||
def split_up_sectors(sector_list, entrance_sets):
|
||||
new_sector_grid = []
|
||||
leftover_sectors = []
|
||||
leftover_sectors.extend(sector_list)
|
||||
for entrance_set in entrance_sets:
|
||||
new_sector_list = []
|
||||
for sector in sector_list:
|
||||
s_regions = list(map(lambda r: r.name, sector.regions))
|
||||
for entrance in entrance_set:
|
||||
if entrance in s_regions:
|
||||
new_sector_list.append(sector)
|
||||
leftover_sectors.remove(sector)
|
||||
break
|
||||
new_sector_grid.append(new_sector_list)
|
||||
shuffle_sectors(new_sector_grid, leftover_sectors)
|
||||
return new_sector_grid
|
||||
|
||||
|
||||
def sum_vector(sector_list, func):
|
||||
result = [0, 0, 0]
|
||||
for sector in sector_list:
|
||||
vector = func(sector)
|
||||
for i in range(len(result)):
|
||||
result[i] = result[i] + vector[i]
|
||||
return result
|
||||
|
||||
|
||||
def is_polarity_neutral(sector_list):
|
||||
pol = Polarity()
|
||||
for sector in sector_list:
|
||||
pol += sector.polarity()
|
||||
return pol.is_neutral()
|
||||
|
||||
|
||||
search_iterations = 0
|
||||
|
||||
|
||||
def is_proposal_valid(proposal, buckets, candidates):
|
||||
logger = logging.getLogger('')
|
||||
global search_iterations
|
||||
search_iterations = search_iterations + 1
|
||||
if search_iterations % 100 == 0:
|
||||
logger.debug('Iteration %s', search_iterations)
|
||||
# check that proposal is complete
|
||||
for i in range(len(proposal)):
|
||||
if proposal[i] is -1:
|
||||
return False # indicates an incomplete proposal
|
||||
test_bucket = []
|
||||
for bucket_idx in range(len(buckets)):
|
||||
test_bucket.append(list(buckets[bucket_idx]))
|
||||
for i in range(len(proposal)):
|
||||
test_bucket[proposal[i]].append(candidates[i])
|
||||
for test in test_bucket:
|
||||
valid = is_polarity_neutral(test)
|
||||
if not valid:
|
||||
return False
|
||||
for sector in test:
|
||||
other_sectors = list(test)
|
||||
other_sectors.remove(sector)
|
||||
sector_mag = sector.magnitude()
|
||||
other_mag = sum_vector(other_sectors, lambda x: x.magnitude())
|
||||
for i in range(len(sector_mag)):
|
||||
if sector_mag[i] > 0 and other_mag[i] == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def shuffle_sectors(buckets, candidates):
|
||||
# for a faster search - instead of random - put the most likely culprits to cause problems at the end, least likely at the front
|
||||
# unless we start checking for failures earlier in the algo
|
||||
random.shuffle(candidates)
|
||||
proposal = [-1]*len(candidates)
|
||||
|
||||
solution = find_proposal_monte_carlo(proposal, buckets, candidates)
|
||||
if solution is None:
|
||||
raise Exception('Unable to find a proposal')
|
||||
for i in range(len(solution)):
|
||||
buckets[solution[i]].append(candidates[i])
|
||||
|
||||
|
||||
# monte carlo proposal generation
|
||||
def find_proposal_monte_carlo(proposal, buckets, candidates):
|
||||
n = len(candidates)
|
||||
k = len(buckets)
|
||||
hashes = set()
|
||||
collisions = 0
|
||||
|
||||
while collisions < 10000:
|
||||
hash = ''
|
||||
for i in range(n):
|
||||
proposal[i] = random.randrange(k)
|
||||
hash = hash + str(proposal[i])
|
||||
if hash not in hashes:
|
||||
collisions = 0
|
||||
if is_proposal_valid(proposal, buckets, candidates):
|
||||
return proposal
|
||||
hashes.add(hash)
|
||||
else:
|
||||
collisions = collisions + 1
|
||||
raise Exception('Too many collisions in a row, solutions space is sparse')
|
||||
|
||||
|
||||
# this is a DFS search - fairly slow
|
||||
def find_proposal(proposal, buckets, candidates):
|
||||
size = len(candidates)
|
||||
combination_grid = []
|
||||
for i in range(size):
|
||||
combination_grid.append(list(range(len(buckets))))
|
||||
# randomize which bucket
|
||||
for possible_buckets in combination_grid:
|
||||
random.shuffle(possible_buckets)
|
||||
|
||||
idx = 0
|
||||
while idx != size or not is_proposal_valid(proposal, buckets, candidates):
|
||||
if idx == size:
|
||||
idx = idx - 1
|
||||
while len(combination_grid[idx]) == 0:
|
||||
if idx == -1: # this is the failure case - we shouldn't hit it
|
||||
return None
|
||||
combination_grid[idx] = list(range(len(buckets)))
|
||||
idx = idx - 1
|
||||
proposal[idx] = combination_grid[idx].pop()
|
||||
# can we detect a bad choice at this stage
|
||||
idx = idx + 1
|
||||
return proposal
|
||||
|
||||
|
||||
def valid_region_to_explore(region, world, player):
|
||||
return region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player]
|
||||
|
||||
|
||||
def shuffle_key_doors(builder, world, player):
|
||||
start_regions = convert_regions(builder.path_entrances, world, player)
|
||||
def shuffle_key_doors(dungeon_sector, entrances, world, player):
|
||||
logger = logging.getLogger('')
|
||||
logger.info('Shuffling Key doors for %s', dungeon_sector.name)
|
||||
start_regions = convert_regions(entrances, world, player)
|
||||
# count number of key doors - this could be a table?
|
||||
num_key_doors = 0
|
||||
current_doors = []
|
||||
skips = []
|
||||
for region in builder.master_sector.regions:
|
||||
for region in dungeon_sector.regions:
|
||||
for ext in region.exits:
|
||||
d = world.check_for_door(ext.name, player)
|
||||
if d is not None and d.smallKey:
|
||||
current_doors.append(d)
|
||||
if d not in skips:
|
||||
if d.type == DoorType.Interior:
|
||||
skips.append(d.dest)
|
||||
@@ -820,22 +833,7 @@ def shuffle_key_doors(builder, world, player):
|
||||
skips.append(world.get_door(dp.door_a, player))
|
||||
break
|
||||
num_key_doors += 1
|
||||
builder.key_doors_num = num_key_doors
|
||||
find_small_key_door_candidates(builder, start_regions, world, player)
|
||||
find_valid_combination(builder, start_regions, world, player)
|
||||
|
||||
|
||||
def find_current_key_doors(builder, world, player):
|
||||
current_doors = []
|
||||
for region in builder.master_sector.regions:
|
||||
for ext in region.exits:
|
||||
d = world.check_for_door(ext.name, player)
|
||||
if d is not None and d.smallKey:
|
||||
current_doors.append(d)
|
||||
return current_doors
|
||||
|
||||
|
||||
def find_small_key_door_candidates(builder, start_regions, world, player):
|
||||
# traverse dungeon and find candidates
|
||||
candidates = []
|
||||
checked_doors = set()
|
||||
@@ -849,73 +847,42 @@ def find_small_key_door_candidates(builder, start_regions, world, player):
|
||||
if candidate.type != DoorType.Normal or candidate.dest not in checked_doors or candidate.dest in candidates:
|
||||
flat_candidates.append(candidate)
|
||||
|
||||
paired_candidates = build_pair_list(flat_candidates)
|
||||
builder.candidates = paired_candidates
|
||||
|
||||
|
||||
def calc_used_dungeon_items(builder):
|
||||
base = 4
|
||||
if builder.bk_required and not builder.bk_provided:
|
||||
base += 1
|
||||
if builder.name == 'Hyrule Castle':
|
||||
base -= 1 # Missing compass/map
|
||||
if builder.name == 'Agahnims Tower':
|
||||
base -= 2 # Missing both compass/map
|
||||
# gt can lose map once compasses work
|
||||
return base
|
||||
|
||||
|
||||
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
||||
logger = logging.getLogger('')
|
||||
logger.info('Shuffling Key doors for %s', builder.name)
|
||||
# find valid combination of candidates
|
||||
if len(builder.candidates) < builder.key_doors_num:
|
||||
if not drop_keys:
|
||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||
return False
|
||||
builder.key_doors_num = len(builder.candidates) # reduce number of key doors
|
||||
logger.info('Lowering key door count because not enough candidates: %s', builder.name)
|
||||
combinations = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
paired_candidates = build_pair_list(flat_candidates)
|
||||
if len(paired_candidates) < num_key_doors:
|
||||
num_key_doors = len(paired_candidates) # reduce number of key doors
|
||||
logger.info('Lowering key door count because not enough candidates: %s', dungeon_sector.name)
|
||||
combinations = ncr(len(paired_candidates), num_key_doors)
|
||||
itr = 0
|
||||
start = time.process_time()
|
||||
sample_list = list(range(0, int(combinations)))
|
||||
random.shuffle(sample_list)
|
||||
proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num)
|
||||
proposal = kth_combination(sample_list[itr], paired_candidates, num_key_doors)
|
||||
|
||||
key_layout = build_key_layout(builder, start_regions, proposal, world, player)
|
||||
while not validate_key_layout(key_layout, world, player):
|
||||
key_layout = build_key_layout(dungeon_sector, start_regions, proposal, world, player)
|
||||
while not validate_key_layout_ex(key_layout, world, player):
|
||||
itr += 1
|
||||
stop_early = False
|
||||
if itr % 1000 == 0:
|
||||
mark = time.process_time()-start
|
||||
if (mark > 10 and itr*100/combinations > 50) or (mark > 20 and itr*100/combinations > 25) or mark > 30:
|
||||
stop_early = True
|
||||
if itr >= combinations or stop_early:
|
||||
if not drop_keys:
|
||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||
return False
|
||||
logger.info('Lowering key door count because no valid layouts: %s', builder.name)
|
||||
builder.key_doors_num -= 1
|
||||
if builder.key_doors_num < 0:
|
||||
raise Exception('Bad dungeon %s - 0 key doors not valid' % builder.name)
|
||||
combinations = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
if itr >= combinations:
|
||||
logger.info('Lowering key door count because no valid layouts: %s', dungeon_sector.name)
|
||||
num_key_doors -= 1
|
||||
if num_key_doors < 0:
|
||||
raise Exception('Bad dungeon %s - 0 key doors not valid' % dungeon_sector.name)
|
||||
combinations = ncr(len(paired_candidates), num_key_doors)
|
||||
sample_list = list(range(0, int(combinations)))
|
||||
random.shuffle(sample_list)
|
||||
itr = 0
|
||||
start = time.process_time() # reset time since itr reset
|
||||
proposal = kth_combination(sample_list[itr], builder.candidates, builder.key_doors_num)
|
||||
key_layout.reset(proposal, builder, world, player)
|
||||
proposal = kth_combination(sample_list[itr], paired_candidates, num_key_doors)
|
||||
key_layout.reset(proposal)
|
||||
if (itr+1) % 1000 == 0:
|
||||
mark = time.process_time()-start
|
||||
logger.info('%s time elapsed. %s iterations/s', mark, itr/mark)
|
||||
# make changes
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
reassign_key_doors(builder, proposal, world, player)
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
return True
|
||||
key_layout_new = analyze_dungeon(key_layout, world, player)
|
||||
reassign_key_doors(current_doors, proposal, world, player)
|
||||
log_key_logic(dungeon_sector.name, key_layout_new.key_logic)
|
||||
world.key_logic[player][dungeon_sector.name] = key_layout_new.key_logic
|
||||
|
||||
|
||||
def log_key_logic(d_name, key_logic):
|
||||
@@ -943,7 +910,7 @@ def build_pair_list(flat_list):
|
||||
queue = collections.deque(flat_list)
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.dest in queue and d.type != DoorType.SpiralStairs:
|
||||
if d.dest in queue:
|
||||
paired_list.append((d, d.dest))
|
||||
queue.remove(d.dest)
|
||||
else:
|
||||
@@ -1022,10 +989,10 @@ def ncr(n, r):
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
def reassign_key_doors(builder, proposal, world, player):
|
||||
def reassign_key_doors(current_doors, proposal, world, player):
|
||||
logger = logging.getLogger('')
|
||||
flat_proposal = flatten_pair_list(proposal)
|
||||
queue = collections.deque(find_current_key_doors(builder, world, player))
|
||||
queue = collections.deque(current_doors)
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.type is DoorType.SpiralStairs and d not in proposal:
|
||||
@@ -1115,7 +1082,7 @@ def determine_required_paths(world):
|
||||
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
|
||||
# noinspection PyTypeChecker
|
||||
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
||||
if world.doorShuffle in ['basic', 'experimental']: # todo: crossed?
|
||||
if world.doorShuffle in ['basic', 'experimental']:
|
||||
paths['Thieves Town'].append('Thieves Attic Window')
|
||||
return paths
|
||||
|
||||
@@ -1197,19 +1164,19 @@ def create_door(world, player, entName, region_name):
|
||||
|
||||
def check_required_paths(paths, world, player):
|
||||
for dungeon_name in paths.keys():
|
||||
builder = world.dungeon_layouts[player][dungeon_name]
|
||||
sector, entrances = world.dungeon_layouts[player][dungeon_name]
|
||||
if len(paths[dungeon_name]) > 0:
|
||||
states_to_explore = defaultdict(list)
|
||||
for path in paths[dungeon_name]:
|
||||
if type(path) is tuple:
|
||||
states_to_explore[tuple([path[0]])].append(path[1])
|
||||
else:
|
||||
states_to_explore[tuple(builder.path_entrances)].append(path)
|
||||
states_to_explore[tuple(entrances)].append(path)
|
||||
cached_initial_state = None
|
||||
for start_regs, dest_regs in states_to_explore.items():
|
||||
check_paths = convert_regions(dest_regs, world, player)
|
||||
start_regions = convert_regions(start_regs, world, player)
|
||||
initial = start_regs == tuple(builder.path_entrances)
|
||||
initial = start_regs == tuple(entrances)
|
||||
if not initial or cached_initial_state is None:
|
||||
init = determine_init_crystal(initial, cached_initial_state, start_regions)
|
||||
state = ExplorationState(init, dungeon_name)
|
||||
|
||||
38
Doors.py
38
Doors.py
@@ -145,7 +145,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Eastern Compass Room EN', Intr).dir(Ea, 0xa8, Top, High).pos(3),
|
||||
create_door(player, 'Eastern Hint Tile WN', Intr).dir(We, 0xa8, Top, High).pos(3),
|
||||
create_door(player, 'Eastern Hint Tile EN', Nrml).dir(Ea, 0xa8, Top, Low).pos(4),
|
||||
create_door(player, 'Eastern Hint Tile Blocked Path SE', Nrml).dir(So, 0xa8, Right, High).small_key().pos(2).kill(),
|
||||
create_door(player, 'Eastern Hint Tile Blocked Path SE', Nrml).dir(So, 0xa8, Right, High).small_key().pos(2),
|
||||
create_door(player, 'Eastern Hint Tile Push Block', Lgcl),
|
||||
create_door(player, 'Eastern Courtyard WN', Nrml).dir(We, 0xa9, Top, Low).pos(3),
|
||||
create_door(player, 'Eastern Courtyard EN', Nrml).dir(Ea, 0xa9, Top, Low).pos(4),
|
||||
@@ -226,7 +226,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Desert Four Statues ES', Intr).dir(Ea, 0x53, Bot, High).pos(1),
|
||||
create_door(player, 'Desert Beamos Hall WS', Intr).dir(We, 0x53, Bot, High).pos(1),
|
||||
create_door(player, 'Desert Beamos Hall NE', Nrml).dir(No, 0x53, Right, High).small_key().pos(2),
|
||||
create_door(player, 'Desert Tiles 2 SE', Nrml).dir(So, 0x43, Right, High).small_key().pos(2).kill(),
|
||||
create_door(player, 'Desert Tiles 2 SE', Nrml).dir(So, 0x43, Right, High).small_key().pos(2),
|
||||
create_door(player, 'Desert Tiles 2 NE', Intr).dir(No, 0x43, Right, High).small_key().pos(1),
|
||||
create_door(player, 'Desert Wall Slide SE', Intr).dir(So, 0x43, Right, High).small_key().pos(1),
|
||||
create_door(player, 'Desert Wall Slide NW', Nrml).dir(No, 0x43, Left, High).big_key().pos(0).no_entrance(),
|
||||
@@ -263,7 +263,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Hera 5F Pothole Chain', Hole),
|
||||
create_door(player, 'Hera 5F Normal Holes', Hole),
|
||||
create_door(player, 'Hera Fairies\' Warp', Warp),
|
||||
create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0).kill(),
|
||||
create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0),
|
||||
create_door(player, 'Hera Boss Outer Hole', Hole),
|
||||
create_door(player, 'Hera Boss Inner Hole', Hole),
|
||||
|
||||
@@ -322,12 +322,12 @@ def create_doors(world, player):
|
||||
create_door(player, 'PoD Pit Room Freefall', Hole),
|
||||
create_door(player, 'PoD Pit Room Bomb Hole', Hole),
|
||||
create_door(player, 'PoD Big Key Landing Hole', Hole),
|
||||
create_door(player, 'PoD Big Key Landing Down Stairs', Sprl).dir(Dn, 0x3a, 0, HTH).ss(A, 0x11, 0x00).kill(),
|
||||
create_door(player, 'PoD Big Key Landing Down Stairs', Sprl).dir(Dn, 0x3a, 0, HTH).ss(A, 0x11, 0x00),
|
||||
create_door(player, 'PoD Basement Ledge Up Stairs', Sprl).dir(Up, 0x0a, 0, HTH).ss(A, 0x1a, 0xec).small_key().pos(0),
|
||||
create_door(player, 'PoD Basement Ledge Drop Down', Lgcl),
|
||||
create_door(player, 'PoD Stalfos Basement Warp', Warp),
|
||||
create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4),
|
||||
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5).kill(),
|
||||
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5),
|
||||
create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1),
|
||||
create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0),
|
||||
create_door(player, 'PoD Arena Main Crystal Path', Lgcl),
|
||||
@@ -365,7 +365,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'PoD Dark Basement W Up Stairs', Sprl).dir(Up, 0x6a, 0, HTH).ss(S, 0x1b, 0x3c, True),
|
||||
create_door(player, 'PoD Dark Basement E Up Stairs', Sprl).dir(Up, 0x6a, 1, HTH).ss(S, 0x1b, 0x9c, True),
|
||||
create_door(player, 'PoD Dark Alley NE', Nrml).dir(No, 0x6a, Right, High).big_key().pos(0),
|
||||
create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).kill(),
|
||||
create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1),
|
||||
create_door(player, 'PoD Mimics 2 NW', Intr).dir(No, 0x1b, Left, High).pos(0),
|
||||
create_door(player, 'PoD Bow Statue SW', Intr).dir(So, 0x1b, Left, High).pos(0),
|
||||
create_door(player, 'PoD Bow Statue Down Ladder', Lddr).no_entrance(),
|
||||
@@ -443,7 +443,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Swamp Barrier EN', Nrml).dir(Ea, 0x34, Top, High).pos(0),
|
||||
create_door(player, 'Swamp Barrier - Orange', Lgcl),
|
||||
create_door(player, 'Swamp Barrier Ledge Hook Path', Lgcl),
|
||||
create_door(player, 'Swamp Attic Down Stairs', Sprl).dir(Dn, 0x54, 0, HTH).ss(Z, 0x12, 0x80).kill(),
|
||||
create_door(player, 'Swamp Attic Down Stairs', Sprl).dir(Dn, 0x54, 0, HTH).ss(Z, 0x12, 0x80),
|
||||
create_door(player, 'Swamp Attic Left Pit', Hole),
|
||||
create_door(player, 'Swamp Attic Right Pit', Hole),
|
||||
create_door(player, 'Swamp Push Statue S', Nrml).dir(So, 0x26, Mid, High).small_key().pos(0),
|
||||
@@ -459,7 +459,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Swamp Drain Left Up Stairs', Sprl).dir(Up, 0x76, 0, HTH).ss(S, 0x1b, 0x2c, True, True),
|
||||
create_door(player, 'Swamp Drain WN', Intr).dir(We, 0x76, Top, Low).pos(0),
|
||||
create_door(player, 'Swamp Drain Right Switch', Lgcl),
|
||||
create_door(player, 'Swamp Drain Right Up Stairs', Sprl).dir(Up, 0x76, 1, HTH).ss(S, 0x1b, 0x9c, True, True).kill(),
|
||||
create_door(player, 'Swamp Drain Right Up Stairs', Sprl).dir(Up, 0x76, 1, HTH).ss(S, 0x1b, 0x9c, True, True),
|
||||
create_door(player, 'Swamp Flooded Room Up Stairs', Sprl).dir(Up, 0x76, 2, HTH).ss(X, 0x1a, 0xac, True, True),
|
||||
create_door(player, 'Swamp Flooded Room WS', Intr).dir(We, 0x76, Bot, Low).pos(1),
|
||||
create_door(player, 'Swamp Flooded Spot Ladder', Lgcl),
|
||||
@@ -648,7 +648,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Ice Spike Room WS', Nrml).dir(We, 0x5f, Bot, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Spike Room Down Stairs', Sprl).dir(Dn, 0x5f, 3, HTH).ss(Z, 0x11, 0x48, True, True),
|
||||
create_door(player, 'Ice Spike Room Up Stairs', Sprl).dir(Up, 0x5f, 4, HTH).ss(Z, 0x1a, 0xa4, True, True),
|
||||
create_door(player, 'Ice Hammer Block Down Stairs', Sprl).dir(Dn, 0x3f, 0, HTH).ss(Z, 0x11, 0xb8, True, True).kill(),
|
||||
create_door(player, 'Ice Hammer Block Down Stairs', Sprl).dir(Dn, 0x3f, 0, HTH).ss(Z, 0x11, 0xb8, True, True),
|
||||
create_door(player, 'Ice Hammer Block ES', Intr).dir(Ea, 0x3f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Tongue Pull WS', Intr).dir(We, 0x3f, Bot, High).pos(0),
|
||||
create_door(player, 'Ice Tongue Pull Up Ladder', Lddr),
|
||||
@@ -660,7 +660,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).no_exit().trap(0x4).pos(0).kill(),
|
||||
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).no_exit().trap(0x4).pos(0),
|
||||
create_door(player, 'Ice Hookshot Ledge Path', Lgcl),
|
||||
create_door(player, 'Ice Hookshot Balcony Path', Lgcl),
|
||||
create_door(player, 'Ice Hookshot Balcony SW', Intr).dir(So, 0x7f, Left, High).pos(1),
|
||||
@@ -786,7 +786,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'Mire BK Chest Ledge WS', Intr).dir(We, 0xd1, Bot, High).pos(0),
|
||||
create_door(player, 'Mire Warping Pool ES', Intr).dir(Ea, 0xd1, Bot, High).no_exit().pos(0),
|
||||
create_door(player, 'Mire Warping Pool Warp', Warp),
|
||||
create_door(player, 'Mire Torches Top Down Stairs', Sprl).dir(Dn, 0x97, 0, HTH).ss(A, 0x11, 0xb0, True).kill(),
|
||||
create_door(player, 'Mire Torches Top Down Stairs', Sprl).dir(Dn, 0x97, 0, HTH).ss(A, 0x11, 0xb0, True),
|
||||
create_door(player, 'Mire Torches Top SW', Intr).dir(So, 0x97, Left, High).pos(1),
|
||||
create_door(player, 'Mire Torches Bottom NW', Intr).dir(No, 0x97, Left, High).pos(1),
|
||||
create_door(player, 'Mire Torches Bottom WS', Intr).dir(We, 0x97, Bot, High).pos(0),
|
||||
@@ -900,10 +900,10 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Hope Room WN', Intr).dir(We, 0x8c, Top, High).small_key().pos(2),
|
||||
create_door(player, 'GT Torch SW', Intr).dir(So, 0x8c, Left, High).no_exit().pos(1),
|
||||
create_door(player, 'GT Big Chest NW', Intr).dir(So, 0x8c, Left, High).pos(1),
|
||||
create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True).kill(),
|
||||
create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True),
|
||||
create_door(player, 'GT Blocked Stairs Block Path', Lgcl),
|
||||
create_door(player, 'GT Big Chest SW', Nrml).dir(So, 0x8c, Left, High).pos(4),
|
||||
create_door(player, 'GT Bob\'s Room SE', Nrml).dir(So, 0x8c, Right, High).pos(5).kill(),
|
||||
create_door(player, 'GT Bob\'s Room SE', Nrml).dir(So, 0x8c, Right, High).pos(5),
|
||||
create_door(player, 'GT Bob\'s Room Hole', Hole),
|
||||
create_door(player, 'GT Tile Room WN', Nrml).dir(We, 0x8d, Top, High).pos(2),
|
||||
create_door(player, 'GT Tile Room EN', Intr).dir(Ea, 0x8d, Top, High).small_key().pos(1),
|
||||
@@ -912,7 +912,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Speed Torch WS', Intr).dir(We, 0x8d, Bot, High).pos(4),
|
||||
create_door(player, 'GT Pots n Blocks ES', Intr).dir(Ea, 0x8d, Bot, High).pos(4),
|
||||
create_door(player, 'GT Speed Torch SE', Nrml).dir(So, 0x8d, Right, High).trap(0x4).pos(0),
|
||||
create_door(player, 'GT Crystal Conveyor NE', Nrml).dir(No, 0x9d, Right, High).pos(0).kill(),
|
||||
create_door(player, 'GT Crystal Conveyor NE', Nrml).dir(No, 0x9d, Right, High).pos(0),
|
||||
create_door(player, 'GT Crystal Conveyor WN', Intr).dir(We, 0x9d, Top, High).pos(2),
|
||||
create_door(player, 'GT Compass Room EN', Intr).dir(Ea, 0x9d, Top, High).pos(2),
|
||||
create_door(player, 'GT Compass Room Warp', Warp),
|
||||
@@ -936,7 +936,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Hookshot ES', Intr).dir(Ea, 0x8b, Bot, High).small_key().pos(1),
|
||||
create_door(player, 'GT Map Room WS', Intr).dir(We, 0x8b, Bot, High).small_key().pos(1),
|
||||
create_door(player, 'GT Hookshot SW', Nrml).dir(So, 0x8b, Left, High).pos(3),
|
||||
create_door(player, 'GT Double Switch NW', Nrml).dir(No, 0x9b, Left, High).pos(1).kill(),
|
||||
create_door(player, 'GT Double Switch NW', Nrml).dir(No, 0x9b, Left, High).pos(1),
|
||||
create_door(player, 'GT Double Switch Orange Barrier', Lgcl),
|
||||
create_door(player, 'GT Double Switch Orange Barrier 2', Lgcl),
|
||||
create_door(player, 'GT Double Switch Blue Path', Lgcl),
|
||||
@@ -971,7 +971,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Warp Maze (Rails) WS', Nrml).dir(We, 0x7d, Bot, High).pos(1),
|
||||
create_door(player, 'GT Trap Room SE', Nrml).dir(So, 0x7d, Right, High).trap(0x4).pos(0),
|
||||
create_door(player, 'GT Conveyor Star Pits EN', Nrml).dir(Ea, 0x7b, Top, High).small_key().pos(1),
|
||||
create_door(player, 'GT Hidden Star ES', Nrml).dir(Ea, 0x7b, Bot, High).pos(2).kill(),
|
||||
create_door(player, 'GT Hidden Star ES', Nrml).dir(Ea, 0x7b, Bot, High).pos(2),
|
||||
create_door(player, 'GT Hidden Star Warp', Warp),
|
||||
create_door(player, 'GT DMs Room SW', Nrml).dir(So, 0x7b, Left, High).trap(0x4).pos(0),
|
||||
create_door(player, 'GT Falling Bridge WN', Nrml).dir(We, 0x7c, Top, High).small_key().pos(2),
|
||||
@@ -1008,7 +1008,7 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Gauntlet 4 SW', Intr).dir(So, 0x6d, Left, High).pos(1),
|
||||
create_door(player, 'GT Gauntlet 5 NW', Intr).dir(No, 0x6d, Left, High).pos(1),
|
||||
create_door(player, 'GT Gauntlet 5 WS', Nrml).dir(We, 0x6d, Bot, High).pos(2),
|
||||
create_door(player, 'GT Beam Dash ES', Nrml).dir(Ea, 0x6c, Bot, High).pos(2).kill(),
|
||||
create_door(player, 'GT Beam Dash ES', Nrml).dir(Ea, 0x6c, Bot, High).pos(2),
|
||||
create_door(player, 'GT Beam Dash WS', Intr).dir(We, 0x6c, Bot, High).pos(0),
|
||||
create_door(player, 'GT Lanmolas 2 ES', Intr).dir(Ea, 0x6c, Bot, High).pos(0),
|
||||
create_door(player, 'GT Lanmolas 2 NW', Intr).dir(No, 0x6c, Left, High).pos(1),
|
||||
@@ -1035,14 +1035,14 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Bomb Conveyor SW', Intr).dir(So, 0x3d, Left, High).pos(3),
|
||||
create_door(player, 'GT Crystal Circles NW', Intr).dir(No, 0x3d, Left, High).pos(3),
|
||||
create_door(player, 'GT Crystal Circles SW', Nrml).dir(So, 0x3d, Left, High).small_key().pos(2),
|
||||
create_door(player, 'GT Left Moldorm Ledge NW', Nrml).dir(No, 0x4d, Left, High).small_key().pos(0).kill(),
|
||||
create_door(player, 'GT Left Moldorm Ledge NW', Nrml).dir(No, 0x4d, Left, High).small_key().pos(0),
|
||||
create_door(player, 'GT Left Moldorm Ledge Drop Down', Lgcl),
|
||||
create_door(player, 'GT Right Moldorm Ledge Drop Down', Lgcl),
|
||||
create_door(player, 'GT Moldorm Gap', Lgcl),
|
||||
create_door(player, 'GT Moldorm Hole', Hole),
|
||||
create_door(player, 'GT Validation Block Path', Lgcl),
|
||||
create_door(player, 'GT Validation WS', Nrml).dir(We, 0x4d, Bot, High).pos(1),
|
||||
create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80).kill(),
|
||||
create_door(player, 'GT Right Moldorm Ledge Down Stairs', Sprl).dir(Dn, 0x4d, 0, HTH).ss(S, 0x12, 0x80),
|
||||
create_door(player, 'GT Moldorm Pit Up Stairs', Sprl).dir(Up, 0xa6, 0, HTH).ss(S, 0x1b, 0x6c),
|
||||
create_door(player, 'GT Frozen Over ES', Nrml).dir(Ea, 0x4c, Bot, High).no_exit().trap(0x4).pos(0),
|
||||
create_door(player, 'GT Frozen Over Up Stairs', Sprl).dir(Up, 0x4c, 0, HTH).ss(S, 0x1a, 0x6c, True),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
18
Dungeons.py
18
Dungeons.py
@@ -333,15 +333,15 @@ region_starts = {
|
||||
}
|
||||
|
||||
split_region_starts = {
|
||||
'Desert Palace': {
|
||||
'Back': ['Desert Back Lobby'],
|
||||
'Main': ['Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
|
||||
},
|
||||
'Skull Woods': {
|
||||
'1': ['Skull 1 Lobby', 'Skull Pot Circle'],
|
||||
'2': ['Skull 2 West Lobby', 'Skull 2 East Lobby', 'Skull Back Drop'],
|
||||
'3': ['Skull 3 Lobby']
|
||||
}
|
||||
'Desert Palace': [
|
||||
['Desert Back Lobby'],
|
||||
['Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
|
||||
],
|
||||
'Skull Woods': [
|
||||
['Skull 1 Lobby', 'Skull Pot Circle'],
|
||||
['Skull 2 West Lobby', 'Skull 2 East Lobby', 'Skull Back Drop'],
|
||||
['Skull 3 Lobby']
|
||||
]
|
||||
}
|
||||
|
||||
flexible_starts = {
|
||||
|
||||
1
Items.py
1
Items.py
@@ -127,7 +127,6 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||
'Compass (Escape)': (False, True, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Hyrule Castle'),
|
||||
'Map (Escape)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'),
|
||||
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'),
|
||||
'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
|
||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
|
||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
|
||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
||||
|
||||
@@ -6,6 +6,37 @@ from Dungeons import dungeon_keys, dungeon_bigs
|
||||
from DungeonGenerator import ExplorationState
|
||||
|
||||
|
||||
class KeySphere(object):
|
||||
|
||||
def __init__(self):
|
||||
self.access_door = None
|
||||
self.free_locations = {}
|
||||
self.prize_region = False
|
||||
self.key_only_locations = {}
|
||||
self.child_doors = {}
|
||||
self.bk_locked = False
|
||||
self.parent_sphere = None
|
||||
self.other_locations = {}
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.prize_region != other.prize_region:
|
||||
return False
|
||||
# already have merge function for this
|
||||
# if self.bk_locked != other.bk_locked:
|
||||
# return False
|
||||
if len(self.free_locations) != len(other.free_locations):
|
||||
return False
|
||||
if len(self.key_only_locations) != len(other.key_only_locations):
|
||||
return False
|
||||
if len(set(self.free_locations).symmetric_difference(set(other.free_locations))) > 0:
|
||||
return False
|
||||
if len(set(self.key_only_locations).symmetric_difference(set(other.key_only_locations))) > 0:
|
||||
return False
|
||||
if len(set(self.child_doors).symmetric_difference(set(other.child_doors))) > 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class KeyLayout(object):
|
||||
|
||||
def __init__(self, sector, starts, proposal):
|
||||
@@ -14,21 +45,20 @@ class KeyLayout(object):
|
||||
self.proposal = proposal
|
||||
self.key_logic = KeyLogic(sector.name)
|
||||
|
||||
self.key_spheres = None
|
||||
self.key_counters = None
|
||||
self.flat_prop = None
|
||||
self.max_chests = None
|
||||
self.max_drops = None
|
||||
self.all_chest_locations = {}
|
||||
self.big_key_special = False
|
||||
|
||||
# bk special?
|
||||
# bk required? True if big chests or big doors exists
|
||||
|
||||
def reset(self, proposal, builder, world, player):
|
||||
def reset(self, proposal):
|
||||
self.proposal = proposal
|
||||
self.flat_prop = flatten_pair_list(self.proposal)
|
||||
self.key_logic = KeyLogic(self.sector.name)
|
||||
self.max_chests = calc_max_chests(builder, self, world, player)
|
||||
|
||||
|
||||
class KeyLogic(object):
|
||||
@@ -39,8 +69,6 @@ class KeyLogic(object):
|
||||
self.sm_restricted = set()
|
||||
self.small_key_name = dungeon_keys[dungeon_name]
|
||||
self.bk_name = dungeon_bigs[dungeon_name]
|
||||
self.bk_doors = set()
|
||||
self.bk_chests = set()
|
||||
self.logic_min = {}
|
||||
self.logic_max = {}
|
||||
|
||||
@@ -68,7 +96,34 @@ class KeyCounter(object):
|
||||
self.used_keys = 0
|
||||
self.big_key_opened = False
|
||||
self.important_location = False
|
||||
self.other_locations = {}
|
||||
|
||||
def update(self, key_sphere):
|
||||
self.free_locations.update(key_sphere.free_locations)
|
||||
self.key_only_locations.update(key_sphere.key_only_locations)
|
||||
self.child_doors.update(dict.fromkeys([x for x in key_sphere.child_doors if x not in self.open_doors and x.dest not in self.open_doors]))
|
||||
self.important_location = self.important_location or key_sphere.prize_region or self.special_region(key_sphere)
|
||||
|
||||
@staticmethod
|
||||
def special_region(key_sphere):
|
||||
for other in key_sphere.other_locations:
|
||||
# todo: zelda's cell is special in standard, and probably crossed too
|
||||
if other.name in ['Attic Cracked Floor', 'Suspicious Maiden']:
|
||||
return True
|
||||
return False
|
||||
|
||||
def open_door(self, door, flat_proposal):
|
||||
if door in flat_proposal:
|
||||
self.used_keys += 1
|
||||
del self.child_doors[door]
|
||||
self.open_doors[door] = None
|
||||
if door.dest in flat_proposal:
|
||||
self.open_doors[door.dest] = None
|
||||
if door.dest in self.child_doors:
|
||||
del self.child_doors[door.dest]
|
||||
elif door.bigKey:
|
||||
self.big_key_opened = True
|
||||
del self.child_doors[door]
|
||||
self.open_doors[door] = None
|
||||
|
||||
def used_smalls_loc(self, reserve=0):
|
||||
return max(self.used_keys + reserve - len(self.key_only_locations), 0)
|
||||
@@ -85,35 +140,29 @@ class KeyCounter(object):
|
||||
return ret
|
||||
|
||||
|
||||
def build_key_layout(builder, start_regions, proposal, world, player):
|
||||
key_layout = KeyLayout(builder.master_sector, start_regions, proposal)
|
||||
def build_key_layout(sector, start_regions, proposal, world, player):
|
||||
key_layout = KeyLayout(sector, start_regions, proposal)
|
||||
key_layout.flat_prop = flatten_pair_list(key_layout.proposal)
|
||||
key_layout.max_chests = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
key_layout.max_drops = count_key_drops(key_layout.sector)
|
||||
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
|
||||
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
|
||||
return key_layout
|
||||
|
||||
|
||||
def calc_max_chests(builder, key_layout, world, player):
|
||||
if world.doorShuffle != 'crossed':
|
||||
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
return builder.key_doors_num - key_layout.max_drops
|
||||
|
||||
|
||||
def analyze_dungeon(key_layout, world, player):
|
||||
key_layout.key_counters = create_key_counters(key_layout, world, player)
|
||||
key_layout.key_spheres = create_key_spheres(key_layout, world, player)
|
||||
key_logic = key_layout.key_logic
|
||||
|
||||
find_bk_locked_sections(key_layout, world)
|
||||
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||
|
||||
original_key_counter = find_counter({}, False, key_layout)
|
||||
queue = collections.deque([(None, original_key_counter)])
|
||||
init_bk = check_special_locations(key_layout.key_spheres['Origin'].free_locations.keys())
|
||||
key_counter = key_layout.key_counters[counter_id({}, init_bk, key_layout.flat_prop)]
|
||||
queue = collections.deque([(key_layout.key_spheres['Origin'], key_counter)])
|
||||
doors_completed = set()
|
||||
|
||||
while len(queue) > 0:
|
||||
queue = collections.deque(sorted(queue, key=queue_sorter))
|
||||
parent_door, key_counter = queue.popleft()
|
||||
key_sphere, key_counter = queue.popleft()
|
||||
chest_keys = available_chest_small_keys(key_counter, world)
|
||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||
available = raw_avail - key_counter.used_keys
|
||||
@@ -121,28 +170,29 @@ def analyze_dungeon(key_layout, world, player):
|
||||
if not key_counter.big_key_opened:
|
||||
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls:
|
||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
||||
if not key_sphere.bk_locked and big_chest_in_locations(key_counter.free_locations):
|
||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
||||
# todo: detect forced subsequent keys - see keypuzzles
|
||||
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
||||
child_queue = collections.deque()
|
||||
for child in key_counter.child_doors.keys():
|
||||
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
||||
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world)
|
||||
if not empty_counter(odd_counter) and child not in doors_completed:
|
||||
child_queue.append((child, odd_counter))
|
||||
for child in sorted(list(key_sphere.child_doors), key=lambda x: x.name):
|
||||
next_sphere = key_layout.key_spheres[child.name]
|
||||
# todo: empty_sphere are not always empty, Mire spike barrier is not empty if other doors open first
|
||||
if not empty_sphere(next_sphere) and child not in doors_completed:
|
||||
child_queue.append((child, next_sphere))
|
||||
while len(child_queue) > 0:
|
||||
child, odd_counter = child_queue.popleft()
|
||||
child, next_sphere = child_queue.popleft()
|
||||
if not child.bigKey:
|
||||
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, False)
|
||||
best_counter = find_best_counter(child, key_counter, key_layout, world, False)
|
||||
rule = create_rule(best_counter, key_counter, key_layout, world)
|
||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world)
|
||||
bk_restricted_rules(rule, child, odd_counter, key_counter, key_layout, world)
|
||||
check_for_self_lock_key(rule, next_sphere, key_layout, world)
|
||||
bk_restricted_rules(rule, next_sphere, key_counter, key_layout, world)
|
||||
key_logic.door_rules[child.name] = rule
|
||||
doors_completed.add(child)
|
||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||
queue.append((child, next_counter))
|
||||
check_rules(original_key_counter, key_layout)
|
||||
doors_completed.add(next_sphere.access_door)
|
||||
next_counter = find_next_counter(child, key_counter, next_sphere, key_layout)
|
||||
queue.append((next_sphere, next_counter))
|
||||
check_rules(key_layout)
|
||||
return key_layout
|
||||
|
||||
|
||||
def count_key_drops(sector):
|
||||
@@ -155,42 +205,43 @@ def count_key_drops(sector):
|
||||
|
||||
|
||||
def queue_sorter(queue_item):
|
||||
door, counter = queue_item
|
||||
if door is None:
|
||||
sphere, counter = queue_item
|
||||
if sphere.access_door is None:
|
||||
return 0
|
||||
return 1 if door.bigKey else 0
|
||||
return 1 if sphere.access_door.bigKey else 0
|
||||
|
||||
|
||||
def find_bk_locked_sections(key_layout, world):
|
||||
key_counters = key_layout.key_counters
|
||||
key_spheres = key_layout.key_spheres
|
||||
key_logic = key_layout.key_logic
|
||||
|
||||
bk_key_not_required = set()
|
||||
big_chest_allowed_big_key = world.accessibility != 'locations'
|
||||
for counter in key_counters.values():
|
||||
key_layout.all_chest_locations.update(counter.free_locations)
|
||||
if counter.big_key_opened and counter.important_location:
|
||||
for key in key_spheres.keys():
|
||||
sphere = key_spheres[key]
|
||||
key_layout.all_chest_locations.update(sphere.free_locations)
|
||||
if sphere.bk_locked and (sphere.prize_region or KeyCounter.special_region(sphere)):
|
||||
big_chest_allowed_big_key = False
|
||||
if not counter.big_key_opened:
|
||||
bk_key_not_required.update(counter.free_locations)
|
||||
if not sphere.bk_locked:
|
||||
bk_key_not_required.update(sphere.free_locations)
|
||||
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_key_not_required)))
|
||||
if not big_chest_allowed_big_key:
|
||||
key_logic.bk_restricted.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||
|
||||
|
||||
def empty_counter(counter):
|
||||
if len(counter.key_only_locations) != 0 or len(counter.free_locations) != 0 or len(counter.child_doors) != 0:
|
||||
def empty_sphere(sphere):
|
||||
if len(sphere.key_only_locations) != 0 or len(sphere.free_locations) != 0 or len(sphere.child_doors) != 0:
|
||||
return False
|
||||
return not counter.important_location
|
||||
return not sphere.prize_region
|
||||
|
||||
|
||||
def relative_empty_counter(odd_counter, key_counter):
|
||||
if len(set(odd_counter.key_only_locations).difference(key_counter.key_only_locations)) > 0:
|
||||
def relative_empty_sphere(sphere, key_counter):
|
||||
if len(set(sphere.key_only_locations).difference(key_counter.key_only_locations)) > 0:
|
||||
return False
|
||||
if len(set(odd_counter.free_locations).difference(key_counter.free_locations)) > 0:
|
||||
if len(set(sphere.free_locations).difference(key_counter.free_locations)) > 0:
|
||||
return False
|
||||
new_child_door = False
|
||||
for child in odd_counter.child_doors:
|
||||
for child in sphere.child_doors:
|
||||
if unique_child_door(child, key_counter):
|
||||
new_child_door = True
|
||||
break
|
||||
@@ -209,15 +260,23 @@ def unique_child_door(child, key_counter):
|
||||
return True
|
||||
|
||||
|
||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk): # try to waste as many keys as possible?
|
||||
ignored_doors = {door, door.dest} if door is not None else {}
|
||||
def increment_key_counter(door, sphere, key_counter, flat_proposal):
|
||||
new_counter = key_counter.copy()
|
||||
new_counter.open_door(door, flat_proposal)
|
||||
new_counter.update(sphere)
|
||||
return new_counter
|
||||
|
||||
|
||||
def find_best_counter(door, key_counter, key_layout, world, skip_bk): # try to waste as many keys as possible?
|
||||
door_sphere = key_layout.key_spheres[door.name]
|
||||
ignored_doors = {door, door.dest}
|
||||
finished = False
|
||||
opened_doors = dict(key_counter.open_doors)
|
||||
bk_opened = key_counter.big_key_opened
|
||||
# new_counter = key_counter
|
||||
last_counter = key_counter
|
||||
while not finished:
|
||||
door_set = find_potential_open_doors(last_counter, ignored_doors, key_layout, skip_bk)
|
||||
door_set = find_potential_open_doors(last_counter, ignored_doors, skip_bk)
|
||||
if door_set is None or len(door_set) == 0:
|
||||
finished = True
|
||||
continue
|
||||
@@ -227,7 +286,7 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk
|
||||
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
||||
bk_open = new_counter.big_key_opened
|
||||
# this means the new_door invalidates the door / leads to the same stuff
|
||||
if relative_empty_counter(odd_counter, new_counter):
|
||||
if relative_empty_sphere(door_sphere, new_counter):
|
||||
ignored_doors.add(new_door)
|
||||
else:
|
||||
if not key_wasted(new_door, last_counter, new_counter, key_layout, world):
|
||||
@@ -239,20 +298,17 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, skip_bk
|
||||
return last_counter
|
||||
|
||||
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
||||
def find_potential_open_doors(key_counter, ignored_doors, skip_bk):
|
||||
small_doors = []
|
||||
big_doors = []
|
||||
for other in key_counter.child_doors:
|
||||
if other not in ignored_doors and other.dest not in ignored_doors:
|
||||
if other.bigKey:
|
||||
if not skip_bk and (not key_layout.big_key_special or key_counter.big_key_opened):
|
||||
if not skip_bk:
|
||||
big_doors.append(other)
|
||||
elif other.dest not in small_doors:
|
||||
small_doors.append(other)
|
||||
if key_layout.big_key_special:
|
||||
big_key_available = key_counter.big_key_opened
|
||||
else:
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
|
||||
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
|
||||
return None
|
||||
return small_doors + big_doors
|
||||
@@ -282,10 +338,10 @@ def key_wasted(new_door, old_counter, new_counter, key_layout, world):
|
||||
return False
|
||||
|
||||
|
||||
def find_next_counter(new_door, old_counter, key_layout):
|
||||
def find_next_counter(new_door, old_counter, next_sphere, key_layout):
|
||||
proposed_doors = {**old_counter.open_doors, **dict.fromkeys([new_door, new_door.dest])}
|
||||
bk_open = old_counter.big_key_opened or new_door.bigKey
|
||||
return find_counter(proposed_doors, bk_open, key_layout)
|
||||
bk_open = old_counter.big_key_opened or new_door.bigKey or check_special_locations(next_sphere.free_locations)
|
||||
return key_layout.key_counters[counter_id(proposed_doors, bk_open, key_layout.flat_prop)]
|
||||
|
||||
|
||||
def check_special_locations(locations):
|
||||
@@ -302,8 +358,8 @@ def calc_avail_keys(key_counter, world):
|
||||
|
||||
|
||||
def create_rule(key_counter, prev_counter, key_layout, world):
|
||||
# prev_chest_keys = available_chest_small_keys(prev_counter, world)
|
||||
# prev_avail = prev_chest_keys + len(prev_counter.key_only_locations)
|
||||
prev_chest_keys = available_chest_small_keys(prev_counter, world)
|
||||
prev_avail = prev_chest_keys + len(prev_counter.key_only_locations)
|
||||
chest_keys = available_chest_small_keys(key_counter, world)
|
||||
key_gain = len(key_counter.key_only_locations) - len(prev_counter.key_only_locations)
|
||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||
@@ -321,78 +377,29 @@ def create_rule(key_counter, prev_counter, key_layout, world):
|
||||
return DoorRules(rule_num)
|
||||
|
||||
|
||||
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world):
|
||||
def check_for_self_lock_key(rule, sphere, key_layout, world):
|
||||
if world.accessibility != 'locations':
|
||||
counter = find_inverted_counter(door, parent_counter, key_layout, world)
|
||||
counter = KeyCounter(key_layout.max_chests)
|
||||
counter.update(sphere)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
queue = collections.deque(counter.child_doors)
|
||||
already_queued = set(counter.child_doors)
|
||||
while len(queue) > 0:
|
||||
child = queue.popleft()
|
||||
if child not in counter.open_doors:
|
||||
counter = increment_key_counter(child, key_layout.key_spheres[child.name], counter, key_layout.flat_prop)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
for new_door in counter.child_doors:
|
||||
if new_door not in already_queued:
|
||||
queue.append(new_door)
|
||||
already_queued.add(new_door)
|
||||
if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
|
||||
rule.allow_small = True
|
||||
rule.small_location = next(iter(counter.free_locations))
|
||||
|
||||
|
||||
def find_inverted_counter(door, parent_counter, key_layout, world):
|
||||
# open all doors in counter
|
||||
counter = open_all_counter(parent_counter, key_layout, door=door)
|
||||
max_counter = find_max_counter(key_layout)
|
||||
# find the difference
|
||||
inverted_counter = KeyCounter(key_layout.max_chests)
|
||||
inverted_counter.free_locations = dict_difference(max_counter.free_locations, counter.free_locations)
|
||||
inverted_counter.key_only_locations = dict_difference(max_counter.key_only_locations, counter.key_only_locations)
|
||||
# child doors? used_keys?
|
||||
inverted_counter.open_doors = dict_difference(max_counter.open_doors, counter.open_doors)
|
||||
inverted_counter.other_locations = dict_difference(max_counter.other_locations, counter.other_locations)
|
||||
for loc in inverted_counter.other_locations:
|
||||
if important_location(loc, world):
|
||||
inverted_counter.important_location = True
|
||||
return inverted_counter
|
||||
|
||||
|
||||
def open_all_counter(parent_counter, key_layout, door=None, skipBk=False):
|
||||
changed = True
|
||||
counter = parent_counter
|
||||
proposed_doors = dict.fromkeys(parent_counter.open_doors.keys())
|
||||
while changed:
|
||||
changed = False
|
||||
doors_to_open = {}
|
||||
for child in counter.child_doors:
|
||||
if door is None or (child != door and child != door.dest):
|
||||
if skipBk:
|
||||
if not child.bigKey:
|
||||
doors_to_open[child] = None
|
||||
elif not child.bigKey or not key_layout.big_key_special or counter.big_key_opened:
|
||||
doors_to_open[child] = None
|
||||
if len(doors_to_open.keys()) > 0:
|
||||
proposed_doors = {**proposed_doors, **doors_to_open}
|
||||
bk_hint = counter.big_key_opened
|
||||
for d in doors_to_open.keys():
|
||||
bk_hint = bk_hint or d.bigKey
|
||||
counter = find_counter(proposed_doors, bk_hint, key_layout)
|
||||
changed = True
|
||||
return counter
|
||||
|
||||
|
||||
def open_some_counter(parent_counter, key_layout, ignored_doors):
|
||||
changed = True
|
||||
counter = parent_counter
|
||||
proposed_doors = dict.fromkeys(parent_counter.open_doors.keys())
|
||||
while changed:
|
||||
changed = False
|
||||
doors_to_open = {}
|
||||
for child in counter.child_doors:
|
||||
if child not in ignored_doors:
|
||||
if not child.bigKey:
|
||||
doors_to_open[child] = None
|
||||
if len(doors_to_open.keys()) > 0:
|
||||
proposed_doors = {**proposed_doors, **doors_to_open}
|
||||
bk_hint = counter.big_key_opened
|
||||
for d in doors_to_open.keys():
|
||||
bk_hint = bk_hint or d.bigKey
|
||||
counter = find_counter(proposed_doors, bk_hint, key_layout)
|
||||
changed = True
|
||||
return counter
|
||||
|
||||
|
||||
def self_lock_possible(counter):
|
||||
return len(counter.free_locations) <= 1 and len(counter.key_only_locations) == 0 and not counter.important_location
|
||||
|
||||
@@ -408,27 +415,136 @@ def available_chest_small_keys(key_counter, world):
|
||||
return key_counter.max_chests
|
||||
|
||||
|
||||
def bk_restricted_rules(rule, door, odd_counter, key_counter, key_layout, world):
|
||||
if key_counter.big_key_opened:
|
||||
def bk_restricted_rules(rule, sphere, key_counter, key_layout, world):
|
||||
if sphere.bk_locked:
|
||||
return
|
||||
best_counter = find_best_counter(door, odd_counter, key_counter, key_layout, world, True)
|
||||
best_counter = find_best_counter(sphere.access_door, key_counter, key_layout, world, True)
|
||||
bk_number = create_rule(best_counter, key_counter, key_layout, world).small_key_num
|
||||
if bk_number == rule.small_key_num:
|
||||
return
|
||||
door_open = find_next_counter(door, best_counter, key_layout)
|
||||
ignored_doors = dict_intersection(best_counter.child_doors, door_open.child_doors)
|
||||
dest_ignored = []
|
||||
for door in ignored_doors.keys():
|
||||
if door.dest not in ignored_doors:
|
||||
dest_ignored.append(door.dest)
|
||||
ignored_doors = {**ignored_doors, **dict.fromkeys(dest_ignored)}
|
||||
post_counter = open_some_counter(door_open, key_layout, ignored_doors.keys())
|
||||
unique_loc = dict_difference(post_counter.free_locations, best_counter.free_locations)
|
||||
post_counter = KeyCounter(key_layout.max_chests)
|
||||
post_counter.update(sphere)
|
||||
other_doors_beyond_me = [x for x in post_counter.child_doors if not x.bigKey]
|
||||
queue = collections.deque(other_doors_beyond_me)
|
||||
already_queued = set(other_doors_beyond_me)
|
||||
while len(queue) > 0:
|
||||
child = queue.popleft()
|
||||
if child not in post_counter.open_doors:
|
||||
post_counter = increment_key_counter(child, key_layout.key_spheres[child.name], post_counter, key_layout.flat_prop)
|
||||
for new_door in post_counter.child_doors:
|
||||
if not new_door.bigKey and new_door not in already_queued and new_door.dest not in already_queued:
|
||||
queue.append(new_door)
|
||||
already_queued.add(new_door)
|
||||
unique_loc = set(post_counter.free_locations).difference(set(best_counter.free_locations))
|
||||
if len(unique_loc) > 0:
|
||||
rule.alternate_small_key = bk_number
|
||||
rule.alternate_big_key_loc.update(unique_loc)
|
||||
|
||||
|
||||
def expand_counter_no_big_doors(door, key_counter, key_layout, ignored_doors):
|
||||
door_sphere = key_layout.key_spheres[door.name]
|
||||
small_doors = set()
|
||||
for other in key_counter.child_doors:
|
||||
if other != door and other not in ignored_doors:
|
||||
if other.dest not in small_doors and not other.bigKey:
|
||||
small_doors.add(other)
|
||||
if len(small_doors) == 0:
|
||||
return key_counter
|
||||
new_counter = key_counter
|
||||
last_counter = key_counter
|
||||
new_ignored = set(ignored_doors)
|
||||
for new_door in small_doors:
|
||||
new_sphere = key_layout.key_spheres[new_door.name]
|
||||
new_counter = increment_key_counter(new_door, new_sphere, new_counter, key_layout.flat_prop)
|
||||
# this means the new_door invalidates the door / leads to the same stuff
|
||||
if relative_empty_sphere(door_sphere, new_counter):
|
||||
new_counter = last_counter
|
||||
new_ignored.add(new_door)
|
||||
else:
|
||||
last_counter = new_counter
|
||||
old_counter = None
|
||||
while old_counter != new_counter:
|
||||
old_counter = new_counter
|
||||
new_counter = expand_counter_no_big_doors(door, old_counter, key_layout, new_ignored)
|
||||
return new_counter
|
||||
|
||||
|
||||
def create_key_spheres(key_layout, world, player):
|
||||
key_spheres = {}
|
||||
flat_proposal = key_layout.flat_prop
|
||||
state = ExplorationState(dungeon=key_layout.sector.name)
|
||||
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
|
||||
for region in key_layout.start_regions:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
expand_key_state(state, flat_proposal, world, player)
|
||||
key_spheres['Origin'] = create_key_sphere(state, None, None)
|
||||
queue = collections.deque([(key_spheres['Origin'], state)])
|
||||
while len(queue) > 0:
|
||||
next_key_sphere, parent_state = queue.popleft()
|
||||
for door in next_key_sphere.child_doors:
|
||||
child_state = parent_state.copy()
|
||||
# open the door
|
||||
open_a_door(door, child_state, flat_proposal)
|
||||
expand_key_state(child_state, flat_proposal, world, player)
|
||||
child_kr = create_key_sphere(child_state, next_key_sphere, door)
|
||||
if door.name not in key_spheres.keys():
|
||||
key_spheres[door.name] = child_kr
|
||||
queue.append((child_kr, child_state))
|
||||
else:
|
||||
merge_sphere = old_sphere = key_spheres[door.name]
|
||||
if empty_sphere(old_sphere) and not empty_sphere(child_kr):
|
||||
key_spheres[door.name] = merge_sphere = child_kr
|
||||
queue.append((child_kr, child_state))
|
||||
if not empty_sphere(old_sphere) and not empty_sphere(child_kr) and not old_sphere == child_kr:
|
||||
# ugly sphere merge function - just union locations - ugh
|
||||
if old_sphere.bk_locked != child_kr.bk_locked:
|
||||
if old_sphere.bk_locked:
|
||||
merge_sphere.child_doors = child_kr.child_doors
|
||||
merge_sphere.free_locations = child_kr.free_locations
|
||||
merge_sphere.key_only_locations = child_kr.key_only_locations
|
||||
else:
|
||||
merge_sphere.child_doors = {**old_sphere.child_doors, **child_kr.child_doors}
|
||||
merge_sphere.free_locations = {**old_sphere.free_locations, **child_kr.free_locations}
|
||||
merge_sphere.key_only_locations = {**old_sphere.key_only_locations, **child_kr.key_only_locations}
|
||||
merge_sphere.bk_locked = old_sphere.bk_locked and child_kr.bk_locked
|
||||
# this feels so ugly, key counters are much smarter than this - would love to get rid of spheres
|
||||
return key_spheres
|
||||
|
||||
|
||||
def create_key_sphere(state, parent_sphere, door):
|
||||
key_sphere = KeySphere()
|
||||
key_sphere.parent_sphere = parent_sphere
|
||||
p_region = parent_sphere
|
||||
parent_doors = set()
|
||||
parent_locations = set()
|
||||
while p_region is not None:
|
||||
parent_doors.update(p_region.child_doors)
|
||||
parent_locations.update(p_region.free_locations)
|
||||
parent_locations.update(p_region.key_only_locations)
|
||||
parent_locations.update(p_region.other_locations)
|
||||
p_region = p_region.parent_sphere
|
||||
u_doors = [x for x in unique_doors(state.small_doors+state.big_doors) if x not in parent_doors]
|
||||
key_sphere.child_doors.update(dict.fromkeys(u_doors))
|
||||
region_locations = [x for x in state.found_locations if x not in parent_locations]
|
||||
for loc in region_locations:
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
|
||||
key_sphere.prize_region = True
|
||||
key_sphere.other_locations[loc] = None
|
||||
elif loc.event and 'Small Key' in loc.item.name:
|
||||
key_sphere.key_only_locations[loc] = None
|
||||
elif loc.name not in dungeon_events:
|
||||
key_sphere.free_locations[loc] = None
|
||||
else:
|
||||
key_sphere.other_locations[loc] = None
|
||||
# todo: Cellblock in a dungeon with a big_key door or chest - Crossed Mode
|
||||
key_sphere.bk_locked = state.big_key_opened if not state.big_key_special else False
|
||||
if door is not None:
|
||||
key_sphere.access_door = door
|
||||
return key_sphere
|
||||
|
||||
|
||||
def open_a_door(door, child_state, flat_proposal):
|
||||
if door.bigKey:
|
||||
child_state.big_key_opened = True
|
||||
@@ -494,7 +610,7 @@ def filter_big_chest(locations):
|
||||
def count_locations_exclude_big_chest(state):
|
||||
cnt = 0
|
||||
for loc in state.found_locations:
|
||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']:
|
||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name:
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
@@ -532,33 +648,21 @@ def flatten_pair_list(paired_list):
|
||||
return flat_list
|
||||
|
||||
|
||||
def check_rules(original_counter, key_layout):
|
||||
def check_rules(key_layout):
|
||||
all_key_only = set()
|
||||
key_only_map = {}
|
||||
queue = collections.deque([(None, original_counter)])
|
||||
completed = set()
|
||||
completed.add(cid(original_counter, key_layout))
|
||||
while len(queue) > 0:
|
||||
queue = collections.deque(sorted(queue, key=queue_sorter))
|
||||
access_door, counter = queue.popleft()
|
||||
for loc in counter.key_only_locations:
|
||||
for sphere in key_layout.key_spheres.values():
|
||||
for loc in sphere.key_only_locations:
|
||||
if loc not in all_key_only:
|
||||
all_key_only.add(loc)
|
||||
access_rules = []
|
||||
key_only_map[loc] = access_rules
|
||||
else:
|
||||
access_rules = key_only_map[loc]
|
||||
if access_door is None or access_door.name not in key_layout.key_logic.door_rules.keys():
|
||||
if sphere.access_door is None or sphere.access_door.name not in key_layout.key_logic.door_rules.keys():
|
||||
access_rules.append(DoorRules(0))
|
||||
else:
|
||||
access_rules.append(key_layout.key_logic.door_rules[access_door.name])
|
||||
for child in counter.child_doors.keys():
|
||||
if not child.bigKey or not key_layout.big_key_special or counter.big_key_opened:
|
||||
next_counter = find_next_counter(child, counter, key_layout)
|
||||
c_id = cid(next_counter, key_layout)
|
||||
if c_id not in completed:
|
||||
completed.add(c_id)
|
||||
queue.append((child, next_counter))
|
||||
access_rules.append(key_layout.key_logic.door_rules[sphere.access_door.name])
|
||||
min_rule_bk = defaultdict(list)
|
||||
min_rule_non_bk = defaultdict(list)
|
||||
check_non_bk = False
|
||||
@@ -608,26 +712,30 @@ def adjust_key_location_mins(key_layout, min_rules, getter, setter):
|
||||
|
||||
|
||||
# Soft lock stuff
|
||||
def validate_key_layout(key_layout, world, player):
|
||||
def validate_key_layout_ex(key_layout, world, player):
|
||||
return validate_key_layout_main_loop(key_layout, world, player)
|
||||
|
||||
|
||||
def validate_key_layout_main_loop(key_layout, world, player):
|
||||
flat_proposal = key_layout.flat_prop
|
||||
state = ExplorationState(dungeon=key_layout.sector.name)
|
||||
state.key_locations = key_layout.max_chests
|
||||
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
|
||||
for region in key_layout.start_regions:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, world, player)
|
||||
return validate_key_layout_sub_loop(state, {}, flat_proposal, world, player)
|
||||
|
||||
|
||||
def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, world, player):
|
||||
def validate_key_layout_sub_loop(state, checked_states, flat_proposal, world, player):
|
||||
expand_key_state(state, flat_proposal, world, player)
|
||||
smalls_avail = len(state.small_doors) > 0 # de-dup crystal repeats
|
||||
smalls_avail = len(state.small_doors) > 0
|
||||
num_bigs = 1 if len(state.big_doors) > 0 else 0 # all or nothing
|
||||
if not smalls_avail and num_bigs == 0:
|
||||
return True # I think that's the end
|
||||
ttl_locations = state.ttl_locations if state.big_key_opened else count_locations_exclude_big_chest(state)
|
||||
available_small_locations = cnt_avail_small_locations(key_layout, ttl_locations, state, world)
|
||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world)
|
||||
available_small_locations = min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
||||
available_big_locations = ttl_locations - state.used_locations if not state.big_key_special else 0
|
||||
if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||
return False
|
||||
else:
|
||||
@@ -639,7 +747,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
state_copy.used_smalls += 1
|
||||
code = state_id(state_copy, flat_proposal)
|
||||
if code not in checked_states.keys():
|
||||
valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, world, player)
|
||||
valid = validate_key_layout_sub_loop(state_copy, checked_states, flat_proposal, world, player)
|
||||
checked_states[code] = valid
|
||||
else:
|
||||
valid = checked_states[code]
|
||||
@@ -651,7 +759,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
state_copy.used_locations += 1
|
||||
code = state_id(state_copy, flat_proposal)
|
||||
if code not in checked_states.keys():
|
||||
valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, world, player)
|
||||
valid = validate_key_layout_sub_loop(state_copy, checked_states, flat_proposal, world, player)
|
||||
checked_states[code] = valid
|
||||
else:
|
||||
valid = checked_states[code]
|
||||
@@ -660,18 +768,6 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
return True
|
||||
|
||||
|
||||
def cnt_avail_small_locations(key_layout, ttl_locations, state, world):
|
||||
if not world.keysanity and world.mode != 'retro':
|
||||
return min(ttl_locations - state.used_locations, state.key_locations - state.used_smalls)
|
||||
return key_layout.max_chests + state.key_locations - state.used_smalls
|
||||
|
||||
|
||||
def cnt_avail_big_locations(ttl_locations, state, world):
|
||||
if not world.keysanity:
|
||||
return ttl_locations - state.used_locations if not state.big_key_special else 0
|
||||
return 1 if not state.big_key_special else 0
|
||||
|
||||
|
||||
def create_key_counters(key_layout, world, player):
|
||||
key_counters = {}
|
||||
flat_proposal = key_layout.flat_prop
|
||||
@@ -683,39 +779,36 @@ def create_key_counters(key_layout, world, player):
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
expand_key_state(state, flat_proposal, world, player)
|
||||
code = state_id(state, key_layout.flat_prop)
|
||||
key_counters[code] = create_key_counter(state, key_layout, world, player)
|
||||
key_counters[code] = create_key_counter_x(state, key_layout, world, player)
|
||||
queue = collections.deque([(key_counters[code], state)])
|
||||
while len(queue) > 0:
|
||||
next_key_counter, parent_state = queue.popleft()
|
||||
for door in next_key_counter.child_doors:
|
||||
next_key_sphere, parent_state = queue.popleft()
|
||||
for door in next_key_sphere.child_doors:
|
||||
child_state = parent_state.copy()
|
||||
if door.bigKey:
|
||||
key_layout.key_logic.bk_doors.add(door)
|
||||
# open the door, if possible
|
||||
if not door.bigKey or not child_state.big_key_special or child_state.big_key_opened:
|
||||
open_a_door(door, child_state, flat_proposal)
|
||||
expand_key_state(child_state, flat_proposal, world, player)
|
||||
code = state_id(child_state, key_layout.flat_prop)
|
||||
if code not in key_counters.keys():
|
||||
child_kr = create_key_counter(child_state, key_layout, world, player)
|
||||
key_counters[code] = child_kr
|
||||
queue.append((child_kr, child_state))
|
||||
# open the door
|
||||
open_a_door(door, child_state, flat_proposal)
|
||||
expand_key_state(child_state, flat_proposal, world, player)
|
||||
code = state_id(child_state, key_layout.flat_prop)
|
||||
if code not in key_counters.keys():
|
||||
child_kr = create_key_counter_x(child_state, key_layout, world, player)
|
||||
key_counters[code] = child_kr
|
||||
queue.append((child_kr, child_state))
|
||||
return key_counters
|
||||
|
||||
|
||||
def create_key_counter(state, key_layout, world, player):
|
||||
def create_key_counter_x(state, key_layout, world, player):
|
||||
key_counter = KeyCounter(key_layout.max_chests)
|
||||
key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors)))
|
||||
for loc in state.found_locations:
|
||||
if important_location(loc, world):
|
||||
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
|
||||
key_counter.important_location = True
|
||||
# todo: zelda's cell is special in standard, and probably crossed too
|
||||
elif loc.name in ['Attic Cracked Floor', 'Suspicious Maiden']:
|
||||
key_counter.important_location = True
|
||||
key_counter.other_locations[loc] = None
|
||||
elif loc.event and 'Small Key' in loc.item.name:
|
||||
key_counter.key_only_locations[loc] = None
|
||||
elif loc.name not in dungeon_events:
|
||||
key_counter.free_locations[loc] = None
|
||||
else:
|
||||
key_counter.other_locations[loc] = None
|
||||
key_counter.open_doors.update(dict.fromkeys(state.opened_doors))
|
||||
key_counter.used_keys = count_unique_sm_doors(state.opened_doors)
|
||||
if state.big_key_special:
|
||||
@@ -730,34 +823,6 @@ def create_key_counter(state, key_layout, world, player):
|
||||
return key_counter
|
||||
|
||||
|
||||
def important_location(loc, world):
|
||||
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
||||
if world.mode == 'standard' or world.doorShuffle == 'crossed':
|
||||
important_locations.append('Hyrule Dungeon Cellblock')
|
||||
return '- Prize' in loc.name or loc.name in important_locations
|
||||
|
||||
|
||||
def create_odd_key_counter(door, parent_counter, key_layout, world):
|
||||
odd_counter = KeyCounter(key_layout.max_chests)
|
||||
next_counter = find_next_counter(door, parent_counter, key_layout)
|
||||
odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations)
|
||||
odd_counter.key_only_locations = dict_difference(next_counter.key_only_locations, parent_counter.key_only_locations)
|
||||
odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors)
|
||||
odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations)
|
||||
for loc in odd_counter.other_locations:
|
||||
if important_location(loc, world):
|
||||
odd_counter.important_location = True
|
||||
return odd_counter
|
||||
|
||||
|
||||
def dict_difference(dict_a, dict_b):
|
||||
return dict.fromkeys([x for x in dict_a.keys() if x not in dict_b.keys()])
|
||||
|
||||
|
||||
def dict_intersection(dict_a, dict_b):
|
||||
return dict.fromkeys([x for x in dict_a.keys() if x in dict_b.keys()])
|
||||
|
||||
|
||||
def state_id(state, flat_proposal):
|
||||
s_id = '1' if state.big_key_opened else '0'
|
||||
for d in flat_proposal:
|
||||
@@ -792,10 +857,6 @@ def find_counter_hint(opened_doors, bk_hint, key_layout):
|
||||
return None
|
||||
|
||||
|
||||
def find_max_counter(key_layout):
|
||||
return find_counter_hint(dict.fromkeys(key_layout.flat_prop), False, key_layout)
|
||||
|
||||
|
||||
def counter_id(opened_doors, bk_unlocked, flat_proposal):
|
||||
s_id = '1' if bk_unlocked else '0'
|
||||
for d in flat_proposal:
|
||||
@@ -803,10 +864,6 @@ def counter_id(opened_doors, bk_unlocked, flat_proposal):
|
||||
return s_id
|
||||
|
||||
|
||||
def cid(counter, key_layout):
|
||||
return counter_id(counter.open_doors, counter.big_key_opened, key_layout.flat_prop)
|
||||
|
||||
|
||||
# class SoftLockException(Exception):
|
||||
# pass
|
||||
|
||||
@@ -843,7 +900,7 @@ def val_hyrule(key_logic, world, player):
|
||||
|
||||
|
||||
def val_eastern(key_logic, world, player):
|
||||
val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 2, True, 'Eastern Palace - Big Key Chest', 1, {'Eastern Palace - Big Key Chest'})
|
||||
val_rule(key_logic.door_rules['Eastern Dark Square Key Door WN'], 2, False, None, 1, {'Eastern Palace - Big Key Chest'})
|
||||
val_rule(key_logic.door_rules['Eastern Darkness Up Stairs'], 2)
|
||||
assert world.get_location('Eastern Palace - Big Chest', player) in key_logic.bk_restricted
|
||||
assert world.get_location('Eastern Palace - Boss', player) in key_logic.bk_restricted
|
||||
@@ -924,7 +981,7 @@ def val_ice(key_logic, world, player):
|
||||
|
||||
def val_mire(key_logic, world, player):
|
||||
mire_west_wing = {'Misery Mire - Big Key Chest', 'Misery Mire - Compass Chest'}
|
||||
val_rule(key_logic.door_rules['Mire Spikes NW'], 5) # todo: is sometimes 3 or 5? best_counter order matters
|
||||
# val_rule(key_logic.door_rules['Mire Spikes NW'], 3) # todo: is sometimes 3 or 5? best_counter order matters
|
||||
val_rule(key_logic.door_rules['Mire Hub WS'], 5, False, None, 3, mire_west_wing)
|
||||
val_rule(key_logic.door_rules['Mire Conveyor Crystal WS'], 6, False, None, 4, mire_west_wing)
|
||||
assert world.get_location('Misery Mire - Boss', player) in key_logic.bk_restricted
|
||||
|
||||
@@ -219,7 +219,7 @@ def create_regions(world, player):
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Armory Boomerang', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Boomerang Guard Key Drop'], ['Hyrule Dungeon Armory Boomerang WS']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'Hyrule Castle', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'Hyrule Castle', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle', ['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest'], ['Hyrule Dungeon Cellblock Up Stairs']),
|
||||
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle', ['Hyrule Castle - Zelda\'s Chest'], ['Hyrule Dungeon Cellblock Up Stairs']),
|
||||
|
||||
|
||||
create_dungeon_region(player, 'Sewers Behind Tapestry', 'Hyrule Castle', None, ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs']),
|
||||
@@ -817,7 +817,6 @@ key_only_locations = {
|
||||
'Hyrule Castle - Map Guard Key Drop': 'Small Key (Escape)',
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': 'Small Key (Escape)',
|
||||
'Hyrule Castle - Key Rat Key Drop': 'Small Key (Escape)',
|
||||
'Hyrule Castle - Big Key Drop': 'Big Key (Escape)',
|
||||
'Eastern Palace - Dark Square Pot Key': 'Small Key (Eastern Palace)',
|
||||
'Eastern Palace - Dark Eyegore Key Drop': 'Small Key (Eastern Palace)',
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': 'Small Key (Desert Palace)',
|
||||
|
||||
33
Rules.py
33
Rules.py
@@ -263,19 +263,26 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Eastern Duo Eyegores NE', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('Eastern Single Eyegore NE', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('Eastern Map Balcony Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||
# Big key rules
|
||||
set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
set_rule(world.get_entrance('Eastern Big Key NE', player), lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
set_rule(world.get_entrance('Eastern Courtyard N', player), lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
|
||||
# Boss rules. Same as below but no BK or arrow requirement.
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Prize', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Eastern Palace - Boss', player))
|
||||
|
||||
# Desert
|
||||
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has_Boots(player))
|
||||
set_rule(world.get_entrance('Desert Wall Slide NW', player), lambda state: state.has_fire_source(player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Prize', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Desert Palace - Boss', player))
|
||||
|
||||
# Tower of Hera
|
||||
set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
|
||||
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player))
|
||||
set_rule(world.get_entrance('Hera Startile Corner NW', player), lambda state: state.has('Big Key (Tower of Hera)', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
|
||||
|
||||
@@ -284,6 +291,8 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('PoD Dark Alley NE', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
|
||||
set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
|
||||
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_entrance('PoD Dark Pegs WN', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_entrance('PoD Dark Pegs Up Ladder', player), lambda state: state.has('Hammer', player))
|
||||
@@ -318,16 +327,20 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player))
|
||||
set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
|
||||
set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: state.has('Flippers', player))
|
||||
set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Swamp Palace - Prize', player))
|
||||
|
||||
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player))
|
||||
set_rule(world.get_entrance('Skull Torch Room EN', player), lambda state: state.has('Fire Rod', player))
|
||||
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player))
|
||||
|
||||
set_rule(world.get_entrance('Thieves BK Corner NE', player), lambda state: state.has('Big Key (Thieves Town)', player))
|
||||
# blind can't have the small key? - not necessarily true anymore - but likely still
|
||||
set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state.has('Big Key (Thieves Town)', player) and state.has('Hammer', player)))
|
||||
for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']:
|
||||
set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player))
|
||||
for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
|
||||
@@ -340,6 +353,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
set_rule(world.get_entrance('Ice Lobby WS', player), lambda state: state.can_melt_things(player))
|
||||
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Hammer Block ES', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
set_rule(world.get_location('Ice Palace - Hammer Block Key Drop', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
set_rule(world.get_location('Ice Palace - Map Chest', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
@@ -360,7 +374,11 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Mire Post-Gap Gap', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Mire Falling Bridge WN', player), lambda state: state.has_Boots(player) or state.has('Hookshot', player)) # this is due to the fact the the door opposite is blocked
|
||||
set_rule(world.get_entrance('Mire 2 NE', player), lambda state: state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player) and (state.has_Boots(player) or state.has('Hookshot', player)))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_entrance('Mire BK Door Room N', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_entrance('Mire Square Rail NW', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_entrance('Mire Antechamber NW', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
||||
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
||||
@@ -377,13 +395,15 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('TR Hub NW', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||
set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player))
|
||||
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('TR Dodgers NE', player), lambda state: state.has('Big Key (Turtle Rock)', player))
|
||||
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Crystal Maze Cane Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Final Abyss South Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Final Abyss NW', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('TR Final Abyss NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Big Key (Turtle Rock)', player))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
@@ -403,6 +423,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('GT Hookshot North-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||
set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||
# I am tempted to stick an invincibility rule for getting across falling bridge
|
||||
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
|
||||
set_rule(world.get_entrance('GT Ice Armos WS', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
|
||||
|
||||
@@ -410,6 +431,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('GT Mimics 1 ES', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('GT Mimics 2 WS', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('GT Mimics 2 NE', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('GT Dash Hall NE', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
# consider access to refill room
|
||||
# consider can_kill_most_things to gauntlet
|
||||
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||
@@ -417,6 +439,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
||||
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
|
||||
set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state))
|
||||
set_rule(world.get_entrance('GT Brightly Lit Hall NW', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||
|
||||
add_key_logic_rules(world, player)
|
||||
@@ -1667,14 +1690,6 @@ def add_key_logic_rules(world, player):
|
||||
forbid_item(location, d_logic.bk_name, player)
|
||||
for location in d_logic.sm_restricted:
|
||||
forbid_item(location, d_logic.small_key_name, player)
|
||||
for door in d_logic.bk_doors:
|
||||
add_rule(world.get_entrance(door.name, player), create_rule(d_logic.bk_name, player))
|
||||
for chest in d_logic.bk_chests:
|
||||
add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player))
|
||||
|
||||
|
||||
def create_rule(item_name, player):
|
||||
return lambda state: state.has(item_name, player)
|
||||
|
||||
|
||||
def create_key_rule(small_key_name, player, keys):
|
||||
|
||||
Reference in New Issue
Block a user