Stonewall fix and preliminary crossed generation issues
This commit is contained in:
@@ -1084,6 +1084,11 @@ pol_comp = {
|
|||||||
'Mod': lambda x: 0 if x == 0 else 1
|
'Mod': lambda x: 0 if x == 0 else 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class PolSlot(Enum):
|
||||||
|
NorthSouth = 0
|
||||||
|
EastWest = 1
|
||||||
|
Stairs = 2
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class CrystalBarrier(Flag):
|
class CrystalBarrier(Flag):
|
||||||
|
|||||||
@@ -339,14 +339,14 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
|||||||
origin_list = list(builder.entrance_list)
|
origin_list = list(builder.entrance_list)
|
||||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||||
origin_list_sans_drops = remove_drop_origins(origin_list)
|
origin_list_sans_drops = remove_drop_origins(origin_list)
|
||||||
if len(origin_list_sans_drops) <= 0 or name == "Turtle Rock" and not validate_tr(name, builder.sectors, origin_list_sans_drops, world, player):
|
if len(origin_list_sans_drops) <= 0 or name == "Turtle Rock" and not validate_tr(builder, origin_list_sans_drops, world, player):
|
||||||
if last_key == builder.name:
|
if last_key == builder.name:
|
||||||
raise Exception('Infinte loop detected %s' % builder.name)
|
raise Exception('Infinte loop detected %s' % builder.name)
|
||||||
sector_queue.append(builder)
|
sector_queue.append(builder)
|
||||||
last_key = builder.name
|
last_key = builder.name
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').info('Generating dungeon: %s', builder.name)
|
logging.getLogger('').info('Generating dungeon: %s', builder.name)
|
||||||
ds = generate_dungeon(name, builder.sectors, origin_list_sans_drops, split_dungeon, world, player)
|
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
|
||||||
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
||||||
ds.name = name
|
ds.name = name
|
||||||
builder.master_sector = ds
|
builder.master_sector = ds
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from functools import reduce
|
|||||||
import operator as op
|
import operator as op
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, flooded_keys
|
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, Sector, PolSlot, flooded_keys
|
||||||
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
|
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
|
||||||
from Dungeons import dungeon_regions
|
from Dungeons import dungeon_regions
|
||||||
|
|
||||||
@@ -32,32 +32,73 @@ class GraphPiece:
|
|||||||
|
|
||||||
|
|
||||||
# Turtle Rock shouldn't be generated until the Big Chest entrance is reachable
|
# Turtle Rock shouldn't be generated until the Big Chest entrance is reachable
|
||||||
def validate_tr(name, available_sectors, entrance_region_names, world, player):
|
def validate_tr(builder, entrance_region_names, world, player):
|
||||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||||
proposed_map = {}
|
proposed_map = {}
|
||||||
doors_to_connect = {}
|
doors_to_connect = {}
|
||||||
all_regions = set()
|
all_regions = set()
|
||||||
bk_needed = False
|
bk_needed = False
|
||||||
bk_special = False
|
bk_special = False
|
||||||
for sector in available_sectors:
|
for sector in builder.sectors:
|
||||||
for door in sector.outstanding_doors:
|
for door in sector.outstanding_doors:
|
||||||
doors_to_connect[door.name] = door
|
doors_to_connect[door.name] = door
|
||||||
all_regions.update(sector.regions)
|
all_regions.update(sector.regions)
|
||||||
bk_needed = bk_needed or determine_if_bk_needed(sector, False, world, player)
|
bk_needed = bk_needed or determine_if_bk_needed(sector, False, world, player)
|
||||||
bk_special = bk_special or check_for_special(sector)
|
bk_special = bk_special or check_for_special(sector)
|
||||||
dungeon, hangers, hooks = gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map,
|
dungeon, hangers, hooks = gen_dungeon_info(builder.name, builder.sectors, entrance_regions, proposed_map,
|
||||||
doors_to_connect, bk_needed, bk_special, world, player)
|
doors_to_connect, bk_needed, bk_special, world, player)
|
||||||
return check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed, False)
|
return check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed, False)
|
||||||
|
|
||||||
|
|
||||||
def generate_dungeon(name, available_sectors, entrance_region_names, split_dungeon, world, player):
|
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
|
||||||
|
builder_list = [builder]
|
||||||
|
queue = deque(builder_list)
|
||||||
|
finished_stonewalls = []
|
||||||
|
while len(queue) > 0:
|
||||||
|
builder = queue.popleft()
|
||||||
|
stonewall = check_for_stonewall(builder, finished_stonewalls)
|
||||||
|
if stonewall is not None:
|
||||||
|
dungeon_map = stonewall_dungeon_builder(builder, stonewall, entrance_region_names)
|
||||||
|
builder_list.remove(builder)
|
||||||
|
for sub_builder in dungeon_map.values():
|
||||||
|
builder_list.append(sub_builder)
|
||||||
|
queue.append(sub_builder)
|
||||||
|
finished_stonewalls.append(stonewall)
|
||||||
|
if len(builder_list) == 1:
|
||||||
|
return generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player)
|
||||||
|
else:
|
||||||
|
sector_list = []
|
||||||
|
for split_builder in builder_list:
|
||||||
|
sector = generate_dungeon_main(split_builder, split_builder.stonewall_entrances, True, world, player)
|
||||||
|
sector_list.append(sector)
|
||||||
|
master_sector = sector_list.pop()
|
||||||
|
for sub_sector in sector_list:
|
||||||
|
master_sector.regions.extend(sub_sector.regions)
|
||||||
|
master_sector.outstanding_doors.clear()
|
||||||
|
master_sector.r_name_set = None
|
||||||
|
for stonewall in finished_stonewalls:
|
||||||
|
if not stonewall_valid(stonewall):
|
||||||
|
raise Exception('Stonewall still reachable from wrong side')
|
||||||
|
return master_sector
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_stonewall(builder, finished_stonewalls):
|
||||||
|
for sector in builder.sectors:
|
||||||
|
for door in sector.outstanding_doors:
|
||||||
|
if door.stonewall and door not in finished_stonewalls:
|
||||||
|
return door
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
name = builder.name
|
||||||
entrance_regions = convert_regions(entrance_region_names, world, player)
|
entrance_regions = convert_regions(entrance_region_names, world, player)
|
||||||
doors_to_connect = {}
|
doors_to_connect = {}
|
||||||
all_regions = set()
|
all_regions = set()
|
||||||
bk_needed = False
|
bk_needed = False
|
||||||
bk_special = False
|
bk_special = False
|
||||||
for sector in available_sectors:
|
for sector in builder.sectors:
|
||||||
for door in sector.outstanding_doors:
|
for door in sector.outstanding_doors:
|
||||||
doors_to_connect[door.name] = door
|
doors_to_connect[door.name] = door
|
||||||
all_regions.update(sector.regions)
|
all_regions.update(sector.regions)
|
||||||
@@ -78,7 +119,7 @@ def generate_dungeon(name, available_sectors, entrance_region_names, split_dunge
|
|||||||
if itr > 5000:
|
if itr > 5000:
|
||||||
raise Exception('Generation taking too long. Ref %s' % name)
|
raise Exception('Generation taking too long. Ref %s' % name)
|
||||||
if depth not in dungeon_cache.keys():
|
if depth not in dungeon_cache.keys():
|
||||||
dungeon, hangers, hooks = gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map,
|
dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, proposed_map,
|
||||||
doors_to_connect, bk_needed, bk_special, world, player)
|
doors_to_connect, bk_needed, bk_special, world, player)
|
||||||
dungeon_cache[depth] = dungeon, hangers, hooks
|
dungeon_cache[depth] = dungeon, hangers, hooks
|
||||||
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed, std_flag)
|
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions, bk_needed, std_flag)
|
||||||
@@ -120,6 +161,7 @@ def generate_dungeon(name, available_sectors, entrance_region_names, split_dunge
|
|||||||
a, b = queue.pop()
|
a, b = queue.pop()
|
||||||
connect_doors(a, b)
|
connect_doors(a, b)
|
||||||
queue.remove((b, a))
|
queue.remove((b, a))
|
||||||
|
available_sectors = list(builder.sectors)
|
||||||
master_sector = available_sectors.pop()
|
master_sector = available_sectors.pop()
|
||||||
for sub_sector in available_sectors:
|
for sub_sector in available_sectors:
|
||||||
master_sector.regions.extend(sub_sector.regions)
|
master_sector.regions.extend(sub_sector.regions)
|
||||||
@@ -150,6 +192,12 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
|
|||||||
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map,
|
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map,
|
||||||
valid_doors, bk_needed, world, player)
|
valid_doors, bk_needed, world, player)
|
||||||
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
|
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
|
||||||
|
either_crystal = True # if all hooks from the origin are either, explore all bits with either
|
||||||
|
for hook, crystal in dungeon['Origin'].hooks.items():
|
||||||
|
if crystal != CrystalBarrier.Either:
|
||||||
|
either_crystal = False
|
||||||
|
break
|
||||||
|
init_crystal = CrystalBarrier.Either if either_crystal else CrystalBarrier.Orange
|
||||||
hanger_set = set()
|
hanger_set = set()
|
||||||
o_state_cache = {}
|
o_state_cache = {}
|
||||||
for sector in available_sectors:
|
for sector in available_sectors:
|
||||||
@@ -157,7 +205,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, proposed_map, va
|
|||||||
if not door.stonewall and door not in proposed_map.keys():
|
if not door.stonewall and door not in proposed_map.keys():
|
||||||
hanger_set.add(door)
|
hanger_set.add(door)
|
||||||
parent = door.entrance.parent_region
|
parent = door.entrance.parent_region
|
||||||
init_state = ExplorationState(dungeon=name)
|
init_state = ExplorationState(init_crystal, dungeon=name)
|
||||||
init_state.big_key_special = start.big_key_special
|
init_state.big_key_special = start.big_key_special
|
||||||
o_state = extend_reachable_state_improved([parent], init_state, proposed_map,
|
o_state = extend_reachable_state_improved([parent], init_state, proposed_map,
|
||||||
valid_doors, False, world, player)
|
valid_doors, False, world, player)
|
||||||
@@ -197,7 +245,7 @@ def check_blue_states(hanger_set, dungeon, o_state_cache, proposed_map, valid_do
|
|||||||
for door in doors_to_check:
|
for door in doors_to_check:
|
||||||
piece = dungeon[door.name]
|
piece = dungeon[door.name]
|
||||||
for hook, crystal in piece.hooks.items():
|
for hook, crystal in piece.hooks.items():
|
||||||
if crystal == CrystalBarrier.Blue or crystal == CrystalBarrier.Either:
|
if crystal != CrystalBarrier.Orange:
|
||||||
h_type = hook_from_door(hook)
|
h_type = hook_from_door(hook)
|
||||||
if h_type not in blue_hooks:
|
if h_type not in blue_hooks:
|
||||||
new_blues = True
|
new_blues = True
|
||||||
@@ -398,9 +446,6 @@ def cellblock_valid(valid_doors, all_regions, proposed_map):
|
|||||||
return False # couldn't find an outstanding door or the sanctuary
|
return False # couldn't find an outstanding door or the sanctuary
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def winnow_hangers(hangers, hooks):
|
def winnow_hangers(hangers, hooks):
|
||||||
removal_info = []
|
removal_info = []
|
||||||
for hanger, door_set in hangers.items():
|
for hanger, door_set in hangers.items():
|
||||||
@@ -420,54 +465,32 @@ def winnow_hangers(hangers, hooks):
|
|||||||
hangers[hanger].remove(door)
|
hangers[hanger].remove(door)
|
||||||
|
|
||||||
|
|
||||||
def stonewalls_valid(valid_doors, proposed_map):
|
def stonewall_valid(stonewall):
|
||||||
stonewall_doors = []
|
bad_door = stonewall.dest
|
||||||
for door in valid_doors.values():
|
if bad_door.blocked:
|
||||||
if door.stonewall:
|
return True # great we're done with this one
|
||||||
stonewall_doors.append(door)
|
loop_region = stonewall.entrance.parent_region
|
||||||
for stonewall in stonewall_doors:
|
start_region = bad_door.entrance.parent_region
|
||||||
if not stonewall_valid(stonewall, valid_doors, proposed_map):
|
queue = deque([start_region])
|
||||||
return False
|
visited = {start_region}
|
||||||
|
while len(queue) > 0:
|
||||||
|
region = queue.popleft()
|
||||||
|
if region == loop_region:
|
||||||
|
return False # guaranteed loop
|
||||||
|
possible_entrances = list(region.entrances)
|
||||||
|
for entrance in possible_entrances:
|
||||||
|
parent = entrance.parent_region
|
||||||
|
if parent.type != RegionType.Dungeon:
|
||||||
|
return False # you can get stuck from an entrance
|
||||||
|
else:
|
||||||
|
door = entrance.door
|
||||||
|
if door is not None and door != stonewall and not door.blocked and parent not in visited:
|
||||||
|
visited.add(parent)
|
||||||
|
queue.append(parent)
|
||||||
|
# we didn't find anything bad
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def stonewall_valid(stonewall, valid_doors, proposed_map):
|
|
||||||
if stonewall in proposed_map:
|
|
||||||
bad_entrance = proposed_map[stonewall].entrance
|
|
||||||
if bad_entrance.door.blocked:
|
|
||||||
return True # great we're done with this one
|
|
||||||
loop_region = stonewall.entrance.parent_region
|
|
||||||
start_region = proposed_map[stonewall].entrance.parent_region
|
|
||||||
queue = deque([start_region])
|
|
||||||
visited = {start_region}
|
|
||||||
while len(queue) > 0:
|
|
||||||
region = queue.popleft()
|
|
||||||
if region == loop_region:
|
|
||||||
return False # guaranteed loop
|
|
||||||
possible_entrances = list(region.entrances)
|
|
||||||
for d in proposed_map:
|
|
||||||
if d.entrance.parent_region == region:
|
|
||||||
possible_entrances.append(proposed_map[d].entrance)
|
|
||||||
for entrance in possible_entrances:
|
|
||||||
parent = entrance.parent_region
|
|
||||||
if entrance.name in valid_doors:
|
|
||||||
door = entrance.door
|
|
||||||
if not door.blocked and door != stonewall:
|
|
||||||
if door in proposed_map:
|
|
||||||
if parent not in visited:
|
|
||||||
visited.add(parent)
|
|
||||||
queue.append(parent)
|
|
||||||
else:
|
|
||||||
if parent.type != RegionType.Dungeon:
|
|
||||||
return False # you can get stuck from an entrance
|
|
||||||
else:
|
|
||||||
door = entrance.door
|
|
||||||
if door is not None and not door.blocked and parent not in visited:
|
|
||||||
visited.add(parent)
|
|
||||||
queue.append(parent)
|
|
||||||
return True # we didn't find anything bad
|
|
||||||
|
|
||||||
|
|
||||||
def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
|
def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
|
||||||
# todo: info about dungeon events - not sure about that
|
# todo: info about dungeon events - not sure about that
|
||||||
graph_piece = GraphPiece()
|
graph_piece = GraphPiece()
|
||||||
@@ -956,7 +979,7 @@ def special_big_key_found(state, world, player):
|
|||||||
def valid_region_to_explore(region, name, world, player):
|
def valid_region_to_explore(region, name, world, player):
|
||||||
if region is None:
|
if region is None:
|
||||||
return False
|
return False
|
||||||
return (region.type == RegionType.Dungeon and region.dungeon.name == name) or region.name in world.inaccessible_regions[player]
|
return (region.type == RegionType.Dungeon and region.dungeon.name in name) or region.name in world.inaccessible_regions[player]
|
||||||
|
|
||||||
|
|
||||||
def get_doors(world, region, player):
|
def get_doors(world, region, player):
|
||||||
@@ -1016,6 +1039,8 @@ class DungeonBuilder(object):
|
|||||||
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.stonewall_entrances = [] # used by stonewall system
|
||||||
|
|
||||||
self.candidates = None
|
self.candidates = None
|
||||||
self.key_doors_num = None
|
self.key_doors_num = None
|
||||||
self.combo_size = None
|
self.combo_size = None
|
||||||
@@ -1035,7 +1060,7 @@ class DungeonBuilder(object):
|
|||||||
|
|
||||||
|
|
||||||
def simple_dungeon_builder(name, sector_list, world, player):
|
def simple_dungeon_builder(name, sector_list, world, player):
|
||||||
define_sector_features(sector_list, world, player)
|
define_sector_features(sector_list)
|
||||||
builder = DungeonBuilder(name)
|
builder = DungeonBuilder(name)
|
||||||
dummy_pool = dict.fromkeys(sector_list)
|
dummy_pool = dict.fromkeys(sector_list)
|
||||||
for sector in sector_list:
|
for sector in sector_list:
|
||||||
@@ -1048,7 +1073,7 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
|
|||||||
logger.info('Shuffling Dungeon Sectors')
|
logger.info('Shuffling Dungeon Sectors')
|
||||||
if dungeon_entrances is None:
|
if dungeon_entrances is None:
|
||||||
dungeon_entrances = default_dungeon_entrances
|
dungeon_entrances = default_dungeon_entrances
|
||||||
define_sector_features(all_sectors, world, player)
|
define_sector_features(all_sectors)
|
||||||
candidate_sectors = dict.fromkeys(all_sectors)
|
candidate_sectors = dict.fromkeys(all_sectors)
|
||||||
|
|
||||||
dungeon_map = {}
|
dungeon_map = {}
|
||||||
@@ -1102,7 +1127,7 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
|
|||||||
return dungeon_map
|
return dungeon_map
|
||||||
|
|
||||||
|
|
||||||
def define_sector_features(sectors, world, player):
|
def define_sector_features(sectors):
|
||||||
for sector in sectors:
|
for sector in sectors:
|
||||||
if 'Hyrule Dungeon Cellblock' in sector.region_set():
|
if 'Hyrule Dungeon Cellblock' in sector.region_set():
|
||||||
sector.bk_provided = True
|
sector.bk_provided = True
|
||||||
@@ -1119,7 +1144,7 @@ def define_sector_features(sectors, world, player):
|
|||||||
if '- Big Chest' in loc.name:
|
if '- Big Chest' in loc.name:
|
||||||
sector.bk_required = True
|
sector.bk_required = True
|
||||||
for ext in region.exits:
|
for ext in region.exits:
|
||||||
door = world.check_for_door(ext.name, player)
|
door = ext.door
|
||||||
if door is not None:
|
if door is not None:
|
||||||
if door.crystal == CrystalBarrier.Either:
|
if door.crystal == CrystalBarrier.Either:
|
||||||
sector.c_switch = True
|
sector.c_switch = True
|
||||||
@@ -1258,19 +1283,45 @@ def identify_polarity_issues(dungeon_map):
|
|||||||
return x != y
|
return x != y
|
||||||
else:
|
else:
|
||||||
def sector_filter(x, y):
|
def sector_filter(x, y):
|
||||||
return x != y and x.outflow() > 1
|
return x != y and x.outflow() > 1 # todo: entrance sector being filtered
|
||||||
|
connection_flags = {}
|
||||||
|
for slot in PolSlot:
|
||||||
|
connection_flags[slot] = {}
|
||||||
|
for slot2 in PolSlot:
|
||||||
|
connection_flags[slot][slot2] = False
|
||||||
for sector in builder.sectors:
|
for sector in builder.sectors:
|
||||||
others = [x for x in builder.sectors if sector_filter(x, sector)]
|
others = [x for x in builder.sectors if sector_filter(x, sector)]
|
||||||
other_mag = sum_magnitude(others)
|
other_mag = sum_magnitude(others)
|
||||||
sector_mag = sector.magnitude()
|
sector_mag = sector.magnitude()
|
||||||
|
check_flags(sector_mag, connection_flags)
|
||||||
for i in range(len(sector_mag)):
|
for i in range(len(sector_mag)):
|
||||||
if sector_mag[i] > 0 and other_mag[i] == 0:
|
if sector_mag[i] > 0 and other_mag[i] == 0:
|
||||||
builder.mag_needed[i] = True
|
builder.mag_needed[i] = True
|
||||||
if name not in unconnected_builders.keys():
|
if name not in unconnected_builders.keys():
|
||||||
unconnected_builders[name] = builder
|
unconnected_builders[name] = builder
|
||||||
|
ttl_mag = sum_magnitude(builder.sectors)
|
||||||
|
for slot in PolSlot:
|
||||||
|
for slot2 in PolSlot:
|
||||||
|
if ttl_mag[slot.value] > 0 and ttl_mag[slot2.value] > 0 and not connection_flags[slot][slot2]:
|
||||||
|
builder.mag_needed[slot.value] = True
|
||||||
|
builder.mag_needed[slot2.value] = True
|
||||||
|
if name not in unconnected_builders.keys():
|
||||||
|
unconnected_builders[name] = builder
|
||||||
return unconnected_builders
|
return unconnected_builders
|
||||||
|
|
||||||
|
|
||||||
|
def check_flags(sector_mag, connection_flags):
|
||||||
|
for slot in PolSlot:
|
||||||
|
for slot2 in PolSlot:
|
||||||
|
if sector_mag[slot.value] > 0 and sector_mag[slot2.value] > 0:
|
||||||
|
connection_flags[slot][slot2] = True
|
||||||
|
if slot != slot2:
|
||||||
|
for check_slot in PolSlot: # transitivity check
|
||||||
|
if check_slot not in [slot, slot2] and connection_flags[slot2][check_slot]:
|
||||||
|
connection_flags[slot][check_slot] = True
|
||||||
|
connection_flags[check_slot][slot] = True
|
||||||
|
|
||||||
|
|
||||||
def identify_branching_issues(dungeon_map):
|
def identify_branching_issues(dungeon_map):
|
||||||
unconnected_builders = {}
|
unconnected_builders = {}
|
||||||
for name, builder in dungeon_map.items():
|
for name, builder in dungeon_map.items():
|
||||||
@@ -1431,7 +1482,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, logger):
|
|||||||
sector = random.choice(candidates)
|
sector = random.choice(candidates)
|
||||||
assign_sector(sector, builder, polarized_sectors)
|
assign_sector(sector, builder, polarized_sectors)
|
||||||
builder.mag_needed = [False, False, False]
|
builder.mag_needed = [False, False, False]
|
||||||
unconnected_builders = identify_polarity_issues(dungeon_map)
|
unconnected_builders = identify_polarity_issues(unconnected_builders)
|
||||||
|
|
||||||
# step 2: fix neutrality issues
|
# step 2: fix neutrality issues
|
||||||
builder_order = list(dungeon_map.values())
|
builder_order = list(dungeon_map.values())
|
||||||
@@ -1450,13 +1501,16 @@ 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 = find_branching_candidates(builder, neutral_choices)
|
candidates = find_branching_candidates(builder, neutral_choices)
|
||||||
|
# if len(candidates) <= 0:
|
||||||
|
# problem_builders = {}
|
||||||
|
# continue
|
||||||
choice = random.choice(candidates)
|
choice = random.choice(candidates)
|
||||||
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)
|
||||||
builder.unfulfilled.clear()
|
builder.unfulfilled.clear()
|
||||||
problem_builders = identify_branching_issues(dungeon_map)
|
problem_builders = identify_branching_issues(problem_builders)
|
||||||
|
|
||||||
# step 4: assign randomly until gone - must maintain connectedness, neutral polarity
|
# step 4: assign randomly until gone - must maintain connectedness, neutral polarity
|
||||||
while len(polarized_sectors) > 0:
|
while len(polarized_sectors) > 0:
|
||||||
@@ -1495,7 +1549,7 @@ def find_neutralizing_candidates(polarity, 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 Exception('Cross Dungeon Builder: No possible neutralizers left')
|
raise NeutralizingException('Cross Dungeon Builder: No possible neutralizers left')
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
last_r = r
|
last_r = r
|
||||||
@@ -1620,7 +1674,11 @@ def split_dungeon_builder(builder, split_list):
|
|||||||
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)
|
||||||
|
return balance_split(candidate_sectors, dungeon_map)
|
||||||
|
|
||||||
|
|
||||||
|
def balance_split(candidate_sectors, dungeon_map):
|
||||||
|
logger = logging.getLogger('')
|
||||||
# categorize sectors
|
# categorize sectors
|
||||||
crystal_switches = {}
|
crystal_switches = {}
|
||||||
crystal_barriers = {}
|
crystal_barriers = {}
|
||||||
@@ -1644,13 +1702,148 @@ def split_dungeon_builder(builder, split_list):
|
|||||||
# blue barriers
|
# blue barriers
|
||||||
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers)
|
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers)
|
||||||
# polarity:
|
# polarity:
|
||||||
logger.info('-Re-balancing Desert/Skull')
|
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, logger)
|
||||||
# the rest
|
# the rest
|
||||||
assign_the_rest(dungeon_map, neutral_sectors)
|
assign_the_rest(dungeon_map, neutral_sectors)
|
||||||
return dungeon_map
|
return dungeon_map
|
||||||
|
|
||||||
|
|
||||||
|
def stonewall_dungeon_builder(builder, stonewall, entrance_region_names):
|
||||||
|
logger = logging.getLogger('')
|
||||||
|
logger.info('Stonewall treatment')
|
||||||
|
candidate_sectors = dict.fromkeys(builder.sectors)
|
||||||
|
dungeon_map = {}
|
||||||
|
|
||||||
|
# split stonewall sector
|
||||||
|
region = stonewall.entrance.parent_region
|
||||||
|
sector = find_sector(region.name, candidate_sectors)
|
||||||
|
del candidate_sectors[sector]
|
||||||
|
stonewall_start = Sector()
|
||||||
|
stonewall_connector = Sector()
|
||||||
|
stonewall_start.outstanding_doors.append(stonewall)
|
||||||
|
stonewall_start.regions.append(region)
|
||||||
|
stonewall_connector.outstanding_doors += [x for x in sector.outstanding_doors if x != stonewall]
|
||||||
|
stonewall_connector.regions += [x for x in sector.regions if x != region]
|
||||||
|
define_sector_features([stonewall_connector, stonewall_start])
|
||||||
|
candidate_sectors[stonewall_start] = None
|
||||||
|
candidate_sectors[stonewall_connector] = None
|
||||||
|
stone_builder = create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors)
|
||||||
|
origin_builder = create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors)
|
||||||
|
|
||||||
|
# dependent sector splits
|
||||||
|
dependency_list = []
|
||||||
|
removal = []
|
||||||
|
for sector in candidate_sectors.keys():
|
||||||
|
dependency = split_sector(sector)
|
||||||
|
if dependency is not None:
|
||||||
|
removal.append(sector)
|
||||||
|
dependency_list.append(dependency)
|
||||||
|
for sector in removal:
|
||||||
|
del candidate_sectors[sector]
|
||||||
|
retry_candidates = candidate_sectors.copy()
|
||||||
|
tries = 0
|
||||||
|
while tries < 10:
|
||||||
|
try:
|
||||||
|
# re-assign dependent regions
|
||||||
|
for parent, child in dependency_list:
|
||||||
|
candidate_sectors[parent] = None
|
||||||
|
candidate_sectors[child] = None
|
||||||
|
chosen_builder = random.choice([stone_builder, origin_builder])
|
||||||
|
assign_sector(child, chosen_builder, candidate_sectors)
|
||||||
|
if chosen_builder == stone_builder:
|
||||||
|
assign_sector(parent, chosen_builder, candidate_sectors)
|
||||||
|
return balance_split(candidate_sectors, dungeon_map)
|
||||||
|
except NeutralizingException:
|
||||||
|
tries += 1
|
||||||
|
candidate_sectors = retry_candidates
|
||||||
|
stone_builder = create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors)
|
||||||
|
origin_builder = create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors)
|
||||||
|
|
||||||
|
raise NeutralizingException('Unable to find a valid combination')
|
||||||
|
|
||||||
|
|
||||||
|
def create_origin_builder(builder, dungeon_map, entrance_region_names, stonewall_connector, candidate_sectors):
|
||||||
|
key = builder.name + ' Prewall'
|
||||||
|
dungeon_map[key] = origin_builder = DungeonBuilder(key)
|
||||||
|
origin_builder.stonewall_entrances += entrance_region_names
|
||||||
|
origin_builder.all_entrances = []
|
||||||
|
for ent in builder.all_entrances:
|
||||||
|
sector = find_sector(ent, candidate_sectors)
|
||||||
|
for door in sector.outstanding_doors:
|
||||||
|
if not door.blocked:
|
||||||
|
origin_builder.all_entrances.append(ent)
|
||||||
|
assign_sector(sector, origin_builder, candidate_sectors)
|
||||||
|
break
|
||||||
|
assign_sector(stonewall_connector, origin_builder, candidate_sectors)
|
||||||
|
return origin_builder
|
||||||
|
|
||||||
|
|
||||||
|
def create_stone_builder(builder, dungeon_map, region, stonewall_start, candidate_sectors):
|
||||||
|
key = builder.name + ' Stonewall'
|
||||||
|
dungeon_map[key] = stone_builder = DungeonBuilder(key)
|
||||||
|
stone_builder.stonewall_entrances += [region.name]
|
||||||
|
stone_builder.all_entrances = [region.name]
|
||||||
|
assign_sector(stonewall_start, stone_builder, candidate_sectors)
|
||||||
|
return stone_builder
|
||||||
|
|
||||||
|
|
||||||
|
def split_sector(sector):
|
||||||
|
entrance_set = set()
|
||||||
|
exit_map = {}
|
||||||
|
visited_regions = {}
|
||||||
|
min_cardinality = None
|
||||||
|
for door in sector.outstanding_doors:
|
||||||
|
reachable_doors = {door}
|
||||||
|
start_region = door.entrance.parent_region
|
||||||
|
visited = {start_region}
|
||||||
|
queue = deque([start_region])
|
||||||
|
while len(queue) > 0:
|
||||||
|
region = queue.popleft()
|
||||||
|
for ext in region.exits:
|
||||||
|
connect = ext.connected_region
|
||||||
|
if connect is not None and connect.type == RegionType.Dungeon and connect not in visited:
|
||||||
|
visited.add(connect)
|
||||||
|
queue.append(connect)
|
||||||
|
elif ext.door in sector.outstanding_doors:
|
||||||
|
reachable_doors.add(ext.door)
|
||||||
|
visited_regions[door] = visited
|
||||||
|
if len(reachable_doors) >= len(sector.outstanding_doors):
|
||||||
|
entrance_set.add(door)
|
||||||
|
else:
|
||||||
|
door_cardinality = len(reachable_doors)
|
||||||
|
if door_cardinality not in exit_map.keys():
|
||||||
|
exit_map[door_cardinality] = set()
|
||||||
|
exit_map[door_cardinality].add(door)
|
||||||
|
if min_cardinality is None or door_cardinality < min_cardinality:
|
||||||
|
min_cardinality = door_cardinality
|
||||||
|
exit_set = set()
|
||||||
|
if min_cardinality is not None:
|
||||||
|
for cardinality, door_set in exit_map.items():
|
||||||
|
if cardinality > min_cardinality:
|
||||||
|
entrance_set.update(door_set)
|
||||||
|
exit_set = exit_map[min_cardinality]
|
||||||
|
if len(entrance_set) > 0 and len(exit_set) > 0:
|
||||||
|
entrance_sector = Sector()
|
||||||
|
exit_sector = Sector()
|
||||||
|
entrance_sector.outstanding_doors.extend(entrance_set)
|
||||||
|
region_set = set()
|
||||||
|
for ent_door in entrance_set:
|
||||||
|
region_set.update(visited_regions[ent_door])
|
||||||
|
entrance_sector.regions.extend(region_set)
|
||||||
|
exit_sector.outstanding_doors.extend(exit_set)
|
||||||
|
region_set = set()
|
||||||
|
for ext_door in exit_set:
|
||||||
|
region_set.update(visited_regions[ext_door])
|
||||||
|
exit_sector.regions.extend(region_set)
|
||||||
|
define_sector_features([entrance_sector, exit_sector])
|
||||||
|
return entrance_sector, exit_sector
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class NeutralizingException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
# common functions - todo: move to a common place
|
# common functions - todo: move to a common place
|
||||||
def kth_combination(k, l, r):
|
def kth_combination(k, l, r):
|
||||||
if r == 0:
|
if r == 0:
|
||||||
|
|||||||
6
Main.py
6
Main.py
@@ -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.4-pre'
|
__version__ = '0.0.6-pre'
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
if args.outputpath:
|
if args.outputpath:
|
||||||
@@ -205,10 +205,10 @@ def main(args, seed=None):
|
|||||||
outfilepname += f'_P{player}'
|
outfilepname += f'_P{player}'
|
||||||
if world.players > 1 or world.teams > 1:
|
if world.players > 1 or world.teams > 1:
|
||||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
|
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
|
||||||
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player],
|
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player],
|
||||||
world.mode[player], world.goal[player],
|
world.mode[player], world.goal[player],
|
||||||
"" if world.timer in ['none', 'display'] else "-" + world.timer,
|
"" if world.timer in ['none', 'display'] else "-" + world.timer,
|
||||||
world.shuffle[player], world.algorithm, mcsb_name,
|
world.shuffle[player], world.doorShuffle[player], world.algorithm, mcsb_name,
|
||||||
"-retro" if world.retro[player] else "",
|
"-retro" if world.retro[player] else "",
|
||||||
"-prog_" + world.progressive if world.progressive in ['off', 'random'] else "",
|
"-prog_" + world.progressive if world.progressive in ['off', 'random'] else "",
|
||||||
"-nohints" if not world.hints[player] else "")) if not args.outputname else ''
|
"-nohints" if not world.hints[player] else "")) if not args.outputname else ''
|
||||||
|
|||||||
Reference in New Issue
Block a user