Generation refinement

This commit is contained in:
aerinon
2020-01-31 16:11:46 -07:00
parent e9a55c8cf4
commit 4b48c5e125
4 changed files with 306 additions and 158 deletions

View File

@@ -1234,6 +1234,7 @@ class Sector(object):
self.name = None self.name = None
self.r_name_set = None self.r_name_set = None
self.chest_locations = 0 self.chest_locations = 0
self.big_chest_present = False
self.key_only_locations = 0 self.key_only_locations = 0
self.c_switch = False self.c_switch = False
self.orange_barrier = False self.orange_barrier = False
@@ -1242,6 +1243,7 @@ class Sector(object):
self.bk_provided = False self.bk_provided = False
self.conn_balance = None self.conn_balance = None
self.branch_factor = None self.branch_factor = None
self.dead_end_cnt = None
self.entrance_sector = None self.entrance_sector = None
self.equations = None self.equations = None
@@ -1281,6 +1283,9 @@ class Sector(object):
def branching_factor(self): def branching_factor(self):
if self.branch_factor is None: if self.branch_factor is None:
self.branch_factor = len(self.outstanding_doors) self.branch_factor = len(self.outstanding_doors)
cnt_dead = len([x for x in self.outstanding_doors if x.dead])
if cnt_dead > 1:
self.branch_factor -= cnt_dead - 1
for region in self.regions: for region in self.regions:
for ent in region.entrances: for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
@@ -1289,6 +1294,18 @@ class Sector(object):
self.branch_factor += 1 self.branch_factor += 1
return self.branch_factor return self.branch_factor
def branches(self):
return max(0, self.branching_factor() - 2)
def dead_ends(self):
if self.dead_end_cnt is None:
if self.branching_factor() <= 1:
self.dead_end_cnt = 1
else:
dead_cnt = len([x for x in self.outstanding_doors if x.dead])
self.dead_end_cnt = dead_cnt - 1 if dead_cnt > 2 else 0
return self.dead_end_cnt
def is_entrance_sector(self): def is_entrance_sector(self):
if self.entrance_sector is None: if self.entrance_sector is None:
self.entrance_sector = False self.entrance_sector = False

View File

@@ -115,7 +115,7 @@ def vanilla_key_logic(world, player):
sector = Sector() sector = Sector()
sector.name = dungeon.name sector.name = dungeon.name
sector.regions.extend(convert_regions(dungeon.regions, world, player)) sector.regions.extend(convert_regions(dungeon.regions, world, player))
builder = simple_dungeon_builder(sector.name, [sector], world, player) builder = simple_dungeon_builder(sector.name, [sector])
builder.master_sector = sector builder.master_sector = sector
builders.append(builder) builders.append(builder)
@@ -298,7 +298,7 @@ def within_dungeon(world, player):
dungeon_builders = {} dungeon_builders = {}
for key in dungeon_regions.keys(): for key in dungeon_regions.keys():
sector_list = convert_to_sectors(dungeon_regions[key], world, player) sector_list = convert_to_sectors(dungeon_regions[key], world, player)
dungeon_builders[key] = simple_dungeon_builder(key, sector_list, world, player) dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
dungeon_builders[key].entrance_list = list(entrances_map[key]) dungeon_builders[key].entrance_list = list(entrances_map[key])
recombinant_builders = {} recombinant_builders = {}
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map) handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
@@ -322,6 +322,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
dungeon_builders.update(split_builders) dungeon_builders.update(split_builders)
for sub_name, split_entrances in split_list.items(): for sub_name, split_entrances in split_list.items():
sub_builder = dungeon_builders[name+' '+sub_name] sub_builder = dungeon_builders[name+' '+sub_name]
sub_builder.split_flag = True
entrance_list = list(split_entrances) entrance_list = list(split_entrances)
if name in flexible_starts.keys(): if name in flexible_starts.keys():
add_shuffled_entrances(sub_builder.sectors, flexible_starts[name], entrance_list) add_shuffled_entrances(sub_builder.sectors, flexible_starts[name], entrance_list)

View File

@@ -58,7 +58,9 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
builder = queue.popleft() builder = queue.popleft()
stonewall = check_for_stonewall(builder, finished_stonewalls) stonewall = check_for_stonewall(builder, finished_stonewalls)
if stonewall is not None: if stonewall is not None:
dungeon_map = stonewall_dungeon_builder(builder, stonewall, entrance_region_names) # todo: kill drop exceptions
entrances = [x for x in entrance_region_names if x not in ['Skull Back Drop']]
dungeon_map = stonewall_dungeon_builder(builder, stonewall, entrances, world, player)
builder_list.remove(builder) builder_list.remove(builder)
for sub_builder in dungeon_map.values(): for sub_builder in dungeon_map.values():
builder_list.append(sub_builder) builder_list.append(sub_builder)
@@ -984,8 +986,8 @@ def valid_region_to_explore(region, name, world, player):
def get_doors(world, region, player): def get_doors(world, region, player):
res = [] res = []
for exit in region.exits: for ext in region.exits:
door = world.check_for_door(exit.name, player) door = world.check_for_door(ext.name, player)
if door is not None: if door is not None:
res.append(door) res.append(door)
return res return res
@@ -1002,8 +1004,8 @@ def get_dungeon_doors(region, world, player):
def get_entrance_doors(world, region, player): def get_entrance_doors(world, region, player):
res = [] res = []
for exit in region.entrances: for ext in region.entrances:
door = world.check_for_door(exit.name, player) door = world.check_for_door(ext.name, player)
if door is not None: if door is not None:
res.append(door) res.append(door)
return res return res
@@ -1037,11 +1039,12 @@ class DungeonBuilder(object):
self.conn_balance = defaultdict(int) self.conn_balance = defaultdict(int)
self.mag_needed = {} self.mag_needed = {}
self.unfulfilled = defaultdict(int) self.unfulfilled = defaultdict(int)
self.all_entrances = None # used for sector segration/branching self.all_entrances = None # used for sector segregation/branching
self.entrance_list = None # used for overworld accessibility self.entrance_list = None # used for overworld accessibility
self.layout_starts = None # used for overworld accessibility self.layout_starts = None # used for overworld accessibility
self.master_sector = None self.master_sector = None
self.path_entrances = None # used for pathing/key doors, I think self.path_entrances = None # used for pathing/key doors, I think
self.split_flag = False
self.stonewall_entrances = [] # used by stonewall system self.stonewall_entrances = [] # used by stonewall system
@@ -1050,6 +1053,17 @@ class DungeonBuilder(object):
self.combo_size = None self.combo_size = None
self.flex = 0 self.flex = 0
if name in dungeon_dead_end_allowance.keys():
self.allowance = dungeon_dead_end_allowance[name]
elif 'Stonewall' in name:
self.allowance = 1
elif 'Prewall' in name:
orig_name = name[:-8]
if orig_name in dungeon_dead_end_allowance.keys():
self.allowance = dungeon_dead_end_allowance[orig_name]
if self.allowance is None:
self.allowance = 1
def polarity_complement(self): def polarity_complement(self):
pol = Polarity() pol = Polarity()
for sector in self.sectors: for sector in self.sectors:
@@ -1063,12 +1077,13 @@ class DungeonBuilder(object):
return pol return pol
def simple_dungeon_builder(name, sector_list, world, player): def simple_dungeon_builder(name, sector_list):
define_sector_features(sector_list) define_sector_features(sector_list)
builder = DungeonBuilder(name) builder = DungeonBuilder(name)
dummy_pool = dict.fromkeys(sector_list) dummy_pool = dict.fromkeys(sector_list)
global_pole = GlobalPolarity(dummy_pool)
for sector in sector_list: for sector in sector_list:
assign_sector(sector, builder, dummy_pool) assign_sector(sector, builder, dummy_pool, global_pole)
return builder return builder
@@ -1079,6 +1094,7 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
dungeon_entrances = default_dungeon_entrances dungeon_entrances = default_dungeon_entrances
define_sector_features(all_sectors) define_sector_features(all_sectors)
candidate_sectors = dict.fromkeys(all_sectors) candidate_sectors = dict.fromkeys(all_sectors)
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map = {} dungeon_map = {}
for key in dungeon_regions.keys(): for key in dungeon_regions.keys():
@@ -1086,16 +1102,17 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
for key in dungeon_boss_sectors.keys(): for key in dungeon_boss_sectors.keys():
current_dungeon = dungeon_map[key] current_dungeon = dungeon_map[key]
for r_name in dungeon_boss_sectors[key]: for r_name in dungeon_boss_sectors[key]:
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors) assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole)
if key == 'Hyrule Castle' and world.mode[player] == 'standard': if key == 'Hyrule Castle' and world.mode[player] == 'standard':
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors) assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole)
for key in dungeon_entrances.keys(): for key in dungeon_entrances.keys():
current_dungeon = dungeon_map[key] current_dungeon = dungeon_map[key]
current_dungeon.all_entrances = dungeon_entrances[key] current_dungeon.all_entrances = dungeon_entrances[key]
for r_name in current_dungeon.all_entrances: for r_name in current_dungeon.all_entrances:
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors) assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, candidate_sectors, global_pole)
# categorize sectors # categorize sectors
free_location_sectors = {} free_location_sectors = {}
crystal_switches = {} crystal_switches = {}
crystal_barriers = {} crystal_barriers = {}
@@ -1113,23 +1130,23 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
else: else:
polarized_sectors[sector] = None polarized_sectors[sector] = None
logger.info('-Assigning Chest Locations') logger.info('-Assigning Chest Locations')
assign_location_sectors(dungeon_map, free_location_sectors) assign_location_sectors(dungeon_map, free_location_sectors, global_pole)
logger.info('-Assigning Crystal Switches and Barriers') logger.info('-Assigning Crystal Switches and Barriers')
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches) leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole)
for sector in leftover: for sector in leftover:
if sector.polarity().is_neutral(): if sector.polarity().is_neutral():
neutral_sectors[sector] = None neutral_sectors[sector] = None
else: else:
polarized_sectors[sector] = None polarized_sectors[sector] = None
# blue barriers # blue barriers
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers) assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
# polarity: # polarity:
if not globally_valid(dungeon_map, None, [], polarized_sectors): if not global_pole.is_valid(dungeon_map):
raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!') raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!')
logger.info('-Balancing Doors') logger.info('-Balancing Doors')
assign_polarized_sectors(dungeon_map, polarized_sectors, logger) assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
# the rest # the rest
assign_the_rest(dungeon_map, neutral_sectors) assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map return dungeon_map
@@ -1149,6 +1166,7 @@ def define_sector_features(sectors):
sector.chest_locations += 1 sector.chest_locations += 1
if '- Big Chest' in loc.name: if '- Big Chest' in loc.name:
sector.bk_required = True sector.bk_required = True
sector.big_chest_present = True
for ext in region.exits: for ext in region.exits:
door = ext.door door = ext.door
if door is not None: if door is not None:
@@ -1162,10 +1180,11 @@ def define_sector_features(sectors):
sector.bk_required = True sector.bk_required = True
def assign_sector(sector, dungeon, candidate_sectors): def assign_sector(sector, dungeon, candidate_sectors, global_pole):
if sector is not None: if sector is not None:
del candidate_sectors[sector] del candidate_sectors[sector]
dungeon.sectors.append(sector) dungeon.sectors.append(sector)
global_pole.consume(sector)
dungeon.location_cnt += sector.chest_locations dungeon.location_cnt += sector.chest_locations
dungeon.key_drop_cnt += sector.key_only_locations dungeon.key_drop_cnt += sector.key_only_locations
if sector.c_switch: if sector.c_switch:
@@ -1177,11 +1196,8 @@ def assign_sector(sector, dungeon, candidate_sectors):
if sector.bk_provided: if sector.bk_provided:
dungeon.bk_provided = True dungeon.bk_provided = True
count_conn_needed_supplied(sector, dungeon.conn_needed, dungeon.conn_supplied) count_conn_needed_supplied(sector, dungeon.conn_needed, dungeon.conn_supplied)
factor = sector.branching_factor() dungeon.dead_ends += sector.dead_ends()
if factor <= 1: dungeon.branches += sector.branches()
dungeon.dead_ends += 1
if factor > 2:
dungeon.branches += factor - 2
def count_conn_needed_supplied(sector, conn_needed, conn_supplied): def count_conn_needed_supplied(sector, conn_needed, conn_supplied):
@@ -1201,7 +1217,7 @@ def find_sector(r_name, sectors):
return None return None
def assign_location_sectors(dungeon_map, free_location_sectors): def assign_location_sectors(dungeon_map, free_location_sectors, global_pole):
valid = False valid = False
choices = None choices = None
sector_list = list(free_location_sectors) sector_list = list(free_location_sectors)
@@ -1218,7 +1234,7 @@ def assign_location_sectors(dungeon_map, free_location_sectors):
break break
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice.name] builder = dungeon_map[choice.name]
assign_sector(sector_list[i], builder, free_location_sectors) assign_sector(sector_list[i], builder, free_location_sectors, global_pole)
def weighted_random_locations(dungeon_map, free_location_sectors): def weighted_random_locations(dungeon_map, free_location_sectors):
@@ -1256,7 +1272,7 @@ def minimal_locations(dungeon_name):
return 5 return 5
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, assign_one=False): def assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole, assign_one=False):
population = [] population = []
some_c_switches_present = False some_c_switches_present = False
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
@@ -1266,19 +1282,33 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, assign_one=Fals
some_c_switches_present = True some_c_switches_present = True
if len(population) == 0: # nothing needs a switch if len(population) == 0: # nothing needs a switch
if assign_one and not some_c_switches_present: # something should have one if assign_one and not some_c_switches_present: # something should have one
choice = random.choice(list(dungeon_map.keys())) valid, builder_choice, switch_choice = False, None, None
builder = dungeon_map[choice] switch_candidates = list(crystal_switches)
assign_sector(random.choice(list(crystal_switches)), builder, crystal_switches) switch_choice = random.choice(switch_candidates)
switch_candidates.remove(switch_choice)
builder_candidates = list(dungeon_map.keys())
while not valid:
if len(builder_candidates) == 0:
if len(switch_candidates) == 0:
raise Exception('No where to assign crystal switch. Ref %s' % next(iter(dungeon_map.keys())))
switch_choice = random.choice(switch_candidates)
switch_candidates.remove(switch_choice)
builder_candidates = list(dungeon_map.keys())
choice = random.choice(builder_candidates)
builder_candidates.remove(choice)
builder_choice = dungeon_map[choice]
valid = global_pole.is_valid_choice(dungeon_map, builder_choice, [switch_choice])
assign_sector(switch_choice, builder_choice, crystal_switches, global_pole)
return crystal_switches return crystal_switches
sector_list = list(crystal_switches) sector_list = list(crystal_switches)
choices = random.sample(sector_list, k=len(population)) choices = random.sample(sector_list, k=len(population))
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[population[i]] builder = dungeon_map[population[i]]
assign_sector(choice, builder, crystal_switches) assign_sector(choice, builder, crystal_switches, global_pole)
return crystal_switches return crystal_switches
def assign_crystal_barrier_sectors(dungeon_map, crystal_barriers): def assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole):
population = [] population = []
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
if builder.c_switch_present: if builder.c_switch_present:
@@ -1288,7 +1318,7 @@ def assign_crystal_barrier_sectors(dungeon_map, crystal_barriers):
choices = random.choices(population, k=len(sector_list)) choices = random.choices(population, k=len(sector_list))
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice] builder = dungeon_map[choice]
assign_sector(sector_list[i], builder, crystal_barriers) assign_sector(sector_list[i], builder, crystal_barriers, global_pole)
def identify_polarity_issues(dungeon_map): def identify_polarity_issues(dungeon_map):
@@ -1347,10 +1377,10 @@ def check_flags(sector_mag, connection_flags):
def identify_simple_branching_issues(dungeon_map): def identify_simple_branching_issues(dungeon_map):
problem_builders = {} problem_builders = {}
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
if name == 'Skull Woods 2': # i dislike this special case if name == 'Skull Woods 2': # i dislike this special case todo: identify destination entrances
builder.conn_supplied[Hook.West] += 1 builder.conn_supplied[Hook.West] += 1
builder.conn_needed[Hook.East] -= 1 builder.conn_needed[Hook.East] -= 1
if builder.dead_ends > builder.branches + 1: # todo: if entrances need to link like skull 2 then this is reduced for each linkage necessary if builder.dead_ends > builder.branches + builder.allowance:
problem_builders[name] = builder problem_builders[name] = builder
for h_type in Hook: for h_type in Hook:
lack = builder.conn_balance[h_type] = builder.conn_supplied[h_type] - builder.conn_needed[h_type] lack = builder.conn_balance[h_type] = builder.conn_supplied[h_type] - builder.conn_needed[h_type]
@@ -1398,22 +1428,21 @@ def sum_polarity(sector_list):
return pol return pol
def assign_polarized_sectors(dungeon_map, polarized_sectors, logger): def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger):
# step 1: fix polarity connection issues # step 1: fix polarity connection issues
logger.info('--Basic Traversal') logger.info('--Basic Traversal')
unconnected_builders = identify_polarity_issues(dungeon_map) unconnected_builders = identify_polarity_issues(dungeon_map)
while len(unconnected_builders) > 0: while len(unconnected_builders) > 0:
for name, builder in unconnected_builders.items(): for name, builder in unconnected_builders.items():
candidates = find_connection_candidates(builder.mag_needed, polarized_sectors) candidates = find_connection_candidates(builder.mag_needed, polarized_sectors)
if len(candidates) == 0: valid, sector = False, None
raise Exception('Cross Dungeon Builder: Cannot find a candidate for connectedness - restart?') while not valid:
sector = random.choice(candidates)
while not globally_valid(dungeon_map, builder, [sector], polarized_sectors):
candidates.remove(sector)
if len(candidates) == 0: if len(candidates) == 0:
raise Exception('Cross Dungeon Builder: Cannot find a candidate for connectedness - globally invalid') raise Exception('Cross Dungeon Builder: Cannot find a candidate for connectedness. %s' % name)
sector = random.choice(candidates) sector = random.choice(candidates)
assign_sector(sector, builder, polarized_sectors) candidates.remove(sector)
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
assign_sector(sector, builder, polarized_sectors, global_pole)
builder.mag_needed = {} builder.mag_needed = {}
unconnected_builders = identify_polarity_issues(unconnected_builders) unconnected_builders = identify_polarity_issues(unconnected_builders)
@@ -1422,16 +1451,24 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, logger):
while len(problem_builders) > 0: while len(problem_builders) > 0:
for name, builder in problem_builders.items(): for name, builder in problem_builders.items():
candidates, charges = find_simple_branching_candidates(builder, polarized_sectors) candidates, charges = find_simple_branching_candidates(builder, polarized_sectors)
# todo: could use the smaller charges as weights to help pre-balance biggest = max(charges) + 1
choice = random.choice(candidates) weights = [biggest-x for x in charges]
if valid_connected_assignment(builder, [choice]) and globally_valid(dungeon_map, builder, [choice], polarized_sectors): valid, choice = False, None
assign_sector(choice, builder, polarized_sectors) while not valid:
if len(candidates) == 0:
raise Exception('Cross Dungeon Builder: Simple branch problems: %s' % name)
choice = random.choices(candidates, weights)[0]
i = candidates.index(choice)
candidates.pop(i)
weights.pop(i)
valid = global_pole.is_valid_choice(dungeon_map, builder, [choice]) and valid_connected_assignment(builder, [choice])
assign_sector(choice, builder, polarized_sectors, global_pole)
builder.total_conn_lack = 0 builder.total_conn_lack = 0
builder.conn_balance.clear() builder.conn_balance.clear()
problem_builders = identify_simple_branching_issues(problem_builders) problem_builders = identify_simple_branching_issues(problem_builders)
# step 3: fix neutrality issues # step 3: fix neutrality issues
polarity_step_3(dungeon_map, polarized_sectors, logger) polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
# step 4: fix dead ends again # step 4: fix dead ends again
neutral_choices: List[List] = neutralize_the_rest(polarized_sectors) neutral_choices: List[List] = neutralize_the_rest(polarized_sectors)
@@ -1446,7 +1483,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, logger):
if valid_polarized_assignment(builder, choice): if valid_polarized_assignment(builder, choice):
neutral_choices.remove(choice) neutral_choices.remove(choice)
for sector in choice: for sector in choice:
assign_sector(sector, builder, polarized_sectors) assign_sector(sector, builder, polarized_sectors, global_pole)
builder.unfulfilled.clear() builder.unfulfilled.clear()
problem_builders = identify_branching_issues_2(problem_builders) problem_builders = identify_branching_issues_2(problem_builders)
@@ -1454,72 +1491,116 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, logger):
tries = 0 tries = 0
while len(polarized_sectors) > 0: while len(polarized_sectors) > 0:
if tries > 100: if tries > 100:
raise Exception('No valid assignment found') raise Exception('No valid assignment found. Ref: %s' % next(iter(dungeon_map.keys())))
choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices)) choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices))
valid = [] valid = []
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice] builder = dungeon_map[choice]
if valid_assignment(builder, neutral_choices[i]): if valid_assignment(builder, neutral_choices[i]):
for sector in neutral_choices[i]: for sector in neutral_choices[i]:
assign_sector(sector, builder, polarized_sectors) assign_sector(sector, builder, polarized_sectors, global_pole)
valid.append(neutral_choices[i]) valid.append(neutral_choices[i])
for c in valid: for c in valid:
neutral_choices.remove(c) neutral_choices.remove(c)
tries += 1 tries += 1
def polarity_step_3(dungeon_map, polarized_sectors, logger): def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger):
builder_order = list(dungeon_map.values()) builder_order = list(dungeon_map.values())
random.shuffle(builder_order) random.shuffle(builder_order)
for builder in builder_order: for builder in builder_order:
logger.info('--Balancing %s', builder.name) logger.info('--Balancing %s', builder.name)
globally_valid(dungeon_map, builder, [], polarized_sectors)
while not builder.polarity().is_neutral(): while not builder.polarity().is_neutral():
candidates = find_neutralizing_candidates(builder, polarized_sectors) candidates = find_neutralizing_candidates(builder, polarized_sectors)
sectors = random.choice(candidates) valid, sectors = False, None
while not globally_valid(dungeon_map, builder, sectors, polarized_sectors): while not valid:
candidates.remove(sectors)
if len(candidates) == 0: if len(candidates) == 0:
raise NeutralizingException('Unable to find a globally valid neutralizer') raise NeutralizingException('Unable to find a globally valid neutralizer: %s' % builder.name)
sectors = random.choice(candidates) sectors = random.choice(candidates)
candidates.remove(sectors)
valid = global_pole.is_valid_choice(dungeon_map, builder, sectors)
for sector in sectors: for sector in sectors:
assign_sector(sector, builder, polarized_sectors) assign_sector(sector, builder, polarized_sectors, global_pole)
def globally_valid(dungeon_map, builder, sectors, polarized_sectors): class GlobalPolarity:
non_neutral_polarities = [x.polarity() for x in dungeon_map.values() if not x.polarity().is_neutral() and x != builder]
remaining = [x for x in polarized_sectors if x not in sectors] def __init__(self, candidate_sectors):
positives = [0, 0, 0] self.positives = [0, 0, 0]
negatives = [0, 0, 0] self.negatives = [0, 0, 0]
for sector in remaining: for sector in candidate_sectors:
pol = sector.polarity() pol = sector.polarity()
for slot in PolSlot: for slot in PolSlot:
if pol.vector[slot.value] < 0: if pol.vector[slot.value] < 0:
negatives[slot.value] += 1 self.negatives[slot.value] += -pol.vector[slot.value]
elif pol.vector[slot.value] > 0: elif pol.vector[slot.value] > 0:
positives[slot.value] += 1 self.positives[slot.value] += pol.vector[slot.value]
if builder is not None:
current_polarity = builder.polarity() + sum_polarity(sectors) def copy(self):
non_neutral_polarities.append(current_polarity) gp = GlobalPolarity([])
for polarity in non_neutral_polarities: gp.positives = self.positives.copy()
gp.negatives = self.negatives.copy()
return gp
def is_valid(self, dungeon_map):
polarities = [x.polarity() for x in dungeon_map.values()]
return self._is_valid_polarities(polarities)
def _is_valid_polarities(self, polarities):
positives = self.positives.copy()
negatives = self.negatives.copy()
for polarity in polarities:
for slot in PolSlot:
if polarity[slot.value] > 0 and slot != PolSlot.Stairs:
if negatives[slot.value] >= polarity[slot.value]:
negatives[slot.value] -= polarity[slot.value]
else:
return False
elif polarity[slot.value] < 0 and slot != PolSlot.Stairs:
if positives[slot.value] >= -polarity[slot.value]:
positives[slot.value] += polarity[slot.value]
else:
return False
elif slot == PolSlot.Stairs:
if positives[slot.value] >= polarity[slot.value]:
positives[slot.value] -= polarity[slot.value]
else:
return False
return True
def consume(self, sector):
polarity = sector.polarity()
for slot in PolSlot: for slot in PolSlot:
if polarity[slot.value] > 0 and slot != PolSlot.Stairs: if polarity[slot.value] > 0 and slot != PolSlot.Stairs:
if negatives[slot.value] >= polarity[slot.value]: if self.positives[slot.value] >= polarity[slot.value]:
negatives[slot.value] -= polarity[slot.value] self.positives[slot.value] -= polarity[slot.value]
else: else:
return False raise Exception('Invalid assignment of %s' % sector.name)
elif polarity[slot.value] < 0 or slot == PolSlot.Stairs: elif polarity[slot.value] < 0 and slot != PolSlot.Stairs:
if positives[slot.value] >= -polarity[slot.value]: if self.negatives[slot.value] >= -polarity[slot.value]:
positives[slot.value] += polarity[slot.value] self.negatives[slot.value] += polarity[slot.value]
else: else:
return False raise Exception('Invalid assignment of %s' % sector.name)
return True elif slot == PolSlot.Stairs:
if self.positives[slot.value] >= polarity[slot.value]:
self.positives[slot.value] -= polarity[slot.value]
else:
raise Exception('Invalid assignment of %s' % sector.name)
def is_valid_choice(self, dungeon_map, builder, sectors):
proposal = self.copy()
non_neutral_polarities = [x.polarity() for x in dungeon_map.values() if not x.polarity().is_neutral() and x != builder]
current_polarity = builder.polarity() + sum_polarity(sectors)
non_neutral_polarities.append(current_polarity)
for sector in sectors:
proposal.consume(sector)
return proposal._is_valid_polarities(non_neutral_polarities)
def find_connection_candidates(mag_needed, sector_pool): def find_connection_candidates(mag_needed, sector_pool):
candidates = [] candidates = []
for sector in sector_pool: for sector in sector_pool:
if sector.outflow() < 2: if sector.branching_factor() < 2:
continue continue
mag = sector.magnitude() mag = sector.magnitude()
matches = False matches = False
@@ -1537,7 +1618,7 @@ def find_connection_candidates(mag_needed, sector_pool):
def find_simple_branching_candidates(builder, sector_pool): def find_simple_branching_candidates(builder, sector_pool):
candidates = defaultdict(list) candidates = defaultdict(list)
charges = defaultdict(list) charges = defaultdict(list)
outflow_needed = builder.dead_ends > builder.branches + 1 outflow_needed = builder.dead_ends > builder.branches + builder.allowance
original_lack = builder.total_conn_lack original_lack = builder.total_conn_lack
best_lack = original_lack best_lack = original_lack
for sector in sector_pool: for sector in sector_pool:
@@ -1561,13 +1642,12 @@ def find_simple_branching_candidates(builder, sector_pool):
return candidates[best_lack], charges[best_lack] return candidates[best_lack], charges[best_lack]
def calc_sector_balance(sector): # move to base class? def calc_sector_balance(sector): # todo: move to base class?
if sector.conn_balance is None: if sector.conn_balance is None:
sector.conn_balance = defaultdict(int) sector.conn_balance = defaultdict(int)
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if door.blocked or door.dead or sector.adj_outflow() <= 1: if door.blocked or door.dead or sector.branching_factor() <= 1:
sector.conn_balance[hook_from_door(door)] -= 1 sector.conn_balance[hook_from_door(door)] -= 1
# todo: stonewall - not a great candidate anyway yet
else: else:
sector.conn_balance[hanger_from_door(door)] += 1 sector.conn_balance[hanger_from_door(door)] += 1
@@ -1584,7 +1664,7 @@ def find_neutralizing_candidates(builder, sector_pool):
for r in r_range: for r in r_range:
if r > len(main_pool): if r > len(main_pool):
if len(candidates) == 0: if len(candidates) == 0:
raise NeutralizingException('Cross Dungeon Builder: No possible neutralizers left') raise NeutralizingException('Cross Dungeon Builder: No possible neutralizers left %s' % builder.name)
else: else:
continue continue
last_r = r last_r = r
@@ -1600,7 +1680,7 @@ def find_neutralizing_candidates(builder, sector_pool):
official_cand = [] official_cand = []
while len(official_cand) == 0: while len(official_cand) == 0:
if len(candidates.keys()) == 0: if len(candidates.keys()) == 0:
raise NeutralizingException('Cross Dungeon Builder: Weeded out all candidates') raise NeutralizingException('Cross Dungeon Builder: Weeded out all candidates %s' % builder.name)
while best_charge not in candidates.keys(): while best_charge not in candidates.keys():
best_charge += 1 best_charge += 1
candidate_list = candidates.pop(best_charge) candidate_list = candidates.pop(best_charge)
@@ -1610,11 +1690,8 @@ def find_neutralizing_candidates(builder, sector_pool):
ttl_branches = 0 ttl_branches = 0
for sector in cand: for sector in cand:
calc_sector_balance(sector) calc_sector_balance(sector)
factor = sector.branching_factor() ttl_deads += sector.dead_ends()
if factor <= 1: ttl_branches += sector.branches()
ttl_deads += 1
elif factor > 2:
ttl_branches += factor - 2
ttl_lack = 0 ttl_lack = 0
ttl_balance = 0 ttl_balance = 0
for hook in Hook: for hook in Hook:
@@ -1625,7 +1702,7 @@ def find_neutralizing_candidates(builder, sector_pool):
ttl_balance += lack ttl_balance += lack
if lack < 0: if lack < 0:
ttl_lack += -lack ttl_lack += -lack
if ttl_balance >= 0 and builder.dead_ends + ttl_deads <= builder.branches + ttl_branches + 1: # todo: calc for different entrances if ttl_balance >= 0 and builder.dead_ends + ttl_deads <= builder.branches + ttl_branches + builder.allowance:
if best_lack is None or ttl_lack < best_lack: if best_lack is None or ttl_lack < best_lack:
best_lack = ttl_lack best_lack = ttl_lack
official_cand = [cand] official_cand = [cand]
@@ -1659,7 +1736,7 @@ def find_branching_candidates(builder, neutral_choices):
if door_match and flow_match: if door_match and flow_match:
candidates.append(choice) candidates.append(choice)
if len(candidates) == 0: if len(candidates) == 0:
raise Exception('Cross Dungeon Builder: No more branching candidates!') raise Exception('Cross Dungeon Builder: No more branching candidates! %s' % builder.name)
return candidates return candidates
@@ -1723,32 +1800,24 @@ def valid_connected_assignment(builder, sector_list):
def valid_polarized_assignment(builder, sector_list): def valid_polarized_assignment(builder, sector_list):
if not valid_connected_assignment(builder, sector_list): if not valid_connected_assignment(builder, sector_list):
return False return False
# dead_ends = 0
# branches = 0
# for sector in sector_list:
# if sector.outflow == 1:
# dead_ends += 1
# if sector.outflow() > 2:
# branches += sector.outflow() - 2
# if builder.dead_ends + dead_ends > builder.branches + branches:
# return False
return (sum_polarity(sector_list) + sum_polarity(builder.sectors)).is_neutral() return (sum_polarity(sector_list) + sum_polarity(builder.sectors)).is_neutral()
def assign_the_rest(dungeon_map, neutral_sectors): def assign_the_rest(dungeon_map, neutral_sectors, global_pole):
while len(neutral_sectors) > 0: while len(neutral_sectors) > 0:
sector_list = list(neutral_sectors) sector_list = list(neutral_sectors)
choices = random.choices(list(dungeon_map.keys()), k=len(sector_list)) choices = random.choices(list(dungeon_map.keys()), k=len(sector_list))
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice] builder = dungeon_map[choice]
if valid_polarized_assignment(builder, [sector_list[i]]): if valid_polarized_assignment(builder, [sector_list[i]]):
assign_sector(sector_list[i], builder, neutral_sectors) assign_sector(sector_list[i], builder, neutral_sectors, global_pole)
def split_dungeon_builder(builder, split_list): def split_dungeon_builder(builder, split_list):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.info('Splitting Up Desert/Skull') logger.info('Splitting Up Desert/Skull')
candidate_sectors = dict.fromkeys(builder.sectors) candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map = {} dungeon_map = {}
for name, split_entrances in split_list.items(): for name, split_entrances in split_list.items():
@@ -1756,25 +1825,31 @@ def split_dungeon_builder(builder, split_list):
dungeon_map[key] = sub_builder = DungeonBuilder(key) dungeon_map[key] = sub_builder = DungeonBuilder(key)
sub_builder.all_entrances = split_entrances sub_builder.all_entrances = split_entrances
for r_name in split_entrances: for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors) assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
return balance_split(candidate_sectors, dungeon_map) return balance_split(candidate_sectors, dungeon_map, global_pole)
def balance_split(candidate_sectors, dungeon_map): def balance_split(candidate_sectors, dungeon_map, global_pole):
logger = logging.getLogger('') logger = logging.getLogger('')
# categorize sectors # categorize sectors
crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors, dungeon_map) crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole, len(crystal_barriers) > 0)
for sector in leftover:
if sector.polarity().is_neutral():
neutral_sectors[sector] = None
else:
polarized_sectors[sector] = None
# blue barriers # blue barriers
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers) assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
# polarity: # polarity:
logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al') logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al')
assign_polarized_sectors(dungeon_map, polarized_sectors, logger) assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
# the rest # the rest
assign_the_rest(dungeon_map, neutral_sectors) assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map return dungeon_map
def categorize_sectors(candidate_sectors, dungeon_map): def categorize_sectors(candidate_sectors):
crystal_switches = {} crystal_switches = {}
crystal_barriers = {} crystal_barriers = {}
polarized_sectors = {} polarized_sectors = {}
@@ -1788,16 +1863,10 @@ def categorize_sectors(candidate_sectors, dungeon_map):
neutral_sectors[sector] = None neutral_sectors[sector] = None
else: else:
polarized_sectors[sector] = None polarized_sectors[sector] = None
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, len(crystal_barriers) > 0) return crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors
for sector in leftover:
if sector.polarity().is_neutral():
neutral_sectors[sector] = None
else:
polarized_sectors[sector] = None
return crystal_barriers, neutral_sectors, polarized_sectors
def stonewall_dungeon_builder(builder, stonewall, entrance_region_names): def stonewall_dungeon_builder(builder, stonewall, entrance_region_names, world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.info('Stonewall treatment') logger.info('Stonewall treatment')
candidate_sectors = dict.fromkeys(builder.sectors) candidate_sectors = dict.fromkeys(builder.sectors)
@@ -1818,9 +1887,29 @@ def stonewall_dungeon_builder(builder, stonewall, entrance_region_names):
define_sector_features([stonewall_connector, stonewall_start]) define_sector_features([stonewall_connector, stonewall_start])
candidate_sectors[stonewall_start] = None candidate_sectors[stonewall_start] = None
candidate_sectors[stonewall_connector] = None candidate_sectors[stonewall_connector] = None
create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors) global_pole = GlobalPolarity(candidate_sectors)
create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors) create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors, global_pole)
return balance_split(candidate_sectors, dungeon_map) origin_builder = create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors, global_pole)
if stonewall.name == 'Desert Wall Slide NW':
# not true if big shuffled or split
location_needed = not builder.split_flag and not world.bigkeyshuffle[player]
for sector in origin_builder.sectors:
location_needed &= sector.chest_locations == 0 or (sector.chest_locations == 1 and sector.big_chest_present)
if location_needed:
free_location_sectors = []
for sector in candidate_sectors:
if sector.chest_locations > 1 or (sector.chest_locations == 1 and not sector.big_chest_present):
free_location_sectors.append(sector)
valid = False
while not valid:
if len(free_location_sectors) == 0:
raise Exception('Cannot place a big key sector before the wall slide, ouch')
sector = random.choice(free_location_sectors)
free_location_sectors.remove(sector)
valid = global_pole.is_valid_choice(dungeon_map, origin_builder, [sector])
assign_sector(sector, origin_builder, candidate_sectors, global_pole)
return balance_split(candidate_sectors, dungeon_map, global_pole)
# dependent sector splits # dependent sector splits
# dependency_list = [] # dependency_list = []
@@ -1861,47 +1950,53 @@ def stonewall_dungeon_builder(builder, stonewall, entrance_region_names):
# raise NeutralizingException('Unable to find a valid combination') # raise NeutralizingException('Unable to find a valid combination')
def stonewall_split(candidate_sectors, dungeon_map): def stonewall_split(candidate_sectors, dungeon_map, global_pole):
logger = logging.getLogger('') logger = logging.getLogger('')
# categorize sectors # categorize sectors
crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors, dungeon_map) crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole, len(crystal_barriers) > 0)
for sector in leftover:
if sector.polarity().is_neutral():
neutral_sectors[sector] = None
else:
polarized_sectors[sector] = None
# blue barriers # blue barriers
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers) assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
# polarity: # polarity:
logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al') logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al')
polarity_step_3(dungeon_map, polarized_sectors, logger) polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
assign_polarized_sectors(dungeon_map, polarized_sectors, logger) assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
# the rest # the rest
assign_the_rest(dungeon_map, neutral_sectors) assign_the_rest(dungeon_map, neutral_sectors, global_pole)
return dungeon_map return dungeon_map
def create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors): def create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors, global_pole):
key = builder.name + ' Prewall' key = builder.name + ' Prewall'
dungeon_map[key] = origin_builder = DungeonBuilder(key) dungeon_map[key] = origin_builder = DungeonBuilder(key)
origin_builder.stonewall_entrances += entrance_region_names origin_builder.stonewall_entrances += entrance_region_names
origin_builder.all_entrances = [] origin_builder.all_entrances = []
for ent in builder.all_entrances: for ent in entrance_region_names:
sector = find_sector(ent, candidate_sectors) sector = find_sector(ent, candidate_sectors)
if sector is not None: if sector is not None:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if not door.blocked: if not door.blocked:
origin_builder.all_entrances.append(ent) origin_builder.all_entrances.append(ent)
assign_sector(sector, origin_builder, candidate_sectors) assign_sector(sector, origin_builder, candidate_sectors, global_pole)
break break
else: # already got assigned else: # already got assigned
origin_builder.all_entrances.append(ent) origin_builder.all_entrances.append(ent)
assign_sector(stonewall_connector, origin_builder, candidate_sectors) assign_sector(stonewall_connector, origin_builder, candidate_sectors, global_pole)
return origin_builder return origin_builder
def create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors): def create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors, global_pole):
key = builder.name + ' Stonewall' key = builder.name + ' Stonewall'
dungeon_map[key] = stone_builder = DungeonBuilder(key) dungeon_map[key] = stone_builder = DungeonBuilder(key)
stone_builder.stonewall_entrances += [region.name] stone_builder.stonewall_entrances += [region.name]
stone_builder.all_entrances = [region.name] stone_builder.all_entrances = [region.name]
stone_builder.branch_factor = 2 stone_builder.branch_factor = 2
assign_sector(stonewall_start, stone_builder, candidate_sectors) assign_sector(stonewall_start, stone_builder, candidate_sectors, global_pole)
return stone_builder return stone_builder
@@ -2045,9 +2140,8 @@ def resolve_equations(builder, sector_list):
def find_priority_equation(equations, current_access): def find_priority_equation(equations, current_access):
flex = calc_flex(equations, current_access) flex = calc_flex(equations, current_access)
best_profit = None best_profit = None
best_flex = False triplet_candidates = []
best_local = False local_profit_map = {}
selected_triplet = None, None, None
for sector, eq_list in equations.items(): for sector, eq_list in equations.items():
eq_list.sort(key=lambda eq: eq.profit(), reverse=True) eq_list.sort(key=lambda eq: eq.profit(), reverse=True)
best_local_profit = None best_local_profit = None
@@ -2058,21 +2152,34 @@ def find_priority_equation(equations, current_access):
if eq.can_cover_cost(current_access): if eq.can_cover_cost(current_access):
if eq.neutral(): if eq.neutral():
return eq, eq_list, sector # don't need to compare return eq, eq_list, sector # don't need to compare
flexible = eq.can_cover_cost(flex) if best_profit is None or profit >= best_profit:
good_local = profit == best_local_profit if best_profit is None or profit > best_profit:
if best_profit is None or profit > best_profit: triplet_candidates = [(eq, eq_list, sector)]
best_profit = profit best_profit = profit
best_flex = flexible else:
best_local = good_local triplet_candidates.append((eq, eq_list, sector))
selected_triplet = eq, eq_list, sector local_profit_map[sector] = best_local_profit
elif profit == best_profit and (flexible and not best_flex): if len(triplet_candidates) == 0:
best_flex = flexible return None, None, None # can't pay for anything
best_local = good_local if len(triplet_candidates) == 1:
selected_triplet = eq, eq_list, sector return triplet_candidates[0]
elif profit == best_profit and flexible == best_flex and (good_local and not best_local):
best_local = good_local required_candidates = [x for x in triplet_candidates if x[0].required]
selected_triplet = eq, eq_list, sector if len(required_candidates) == 0:
return selected_triplet required_candidates = triplet_candidates
if len(required_candidates) == 1:
return required_candidates[0]
flexible_candidates = [x for x in required_candidates if x[0].can_cover_cost(flex)]
if len(flexible_candidates) == 0:
flexible_candidates = required_candidates
if len(flexible_candidates) == 1:
return flexible_candidates[0]
good_local_candidates = [x for x in flexible_candidates if local_profit_map[x[2]] == x[0].profit()]
if len(good_local_candidates) == 0:
good_local_candidates = flexible_candidates
return good_local_candidates[0] # just pick one I guess
def calc_flex(equations, current_access): def calc_flex(equations, current_access):
@@ -2261,3 +2368,26 @@ default_dungeon_entrances = {
'Turtle Rock': ['TR Main Lobby', 'TR Eye Bridge', 'TR Big Chest Entrance', 'TR Lazy Eyes'], 'Turtle Rock': ['TR Main Lobby', 'TR Eye Bridge', 'TR Big Chest Entrance', 'TR Lazy Eyes'],
'Ganons Tower': ['GT Lobby'] 'Ganons Tower': ['GT Lobby']
} }
# todo: calculate these for ER - the multi entrance dungeons anyway
dungeon_dead_end_allowance = {
'Hyrule Castle': 6,
'Eastern Palace': 1,
'Desert Palace': 2,
'Tower of Hera': 1,
'Agahnims Tower': 1,
'Palace of Darkness': 1,
'Swamp Palace': 1,
'Skull Woods': 3, # two allowed in skull 1, 1 in skull 3, 0 in skull 2
'Thieves Town': 1,
'Ice Palace': 1,
'Misery Mire': 1,
'Turtle Rock': 2, # this assumes one overworld connection
'Ganons Tower': 1,
'Desert Palace Back': 1,
'Desert Palace Main': 1,
'Skull Woods 1': 2,
'Skull Woods 2': 0,
'Skull Woods 3': 1,
}

View File

@@ -23,7 +23,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.a-pre' __version__ = '0.0.b-pre'
def main(args, seed=None): def main(args, seed=None):