Adding Customizer support for Crossed OWR

Also made Layout OWR compatible with Mixed OWR with asterisk notation
This commit is contained in:
codemann8
2023-09-05 00:30:53 -05:00
parent c1f98dfb51
commit 927588a3d8
4 changed files with 296 additions and 75 deletions

View File

@@ -205,20 +205,40 @@ def link_overworld(world, player):
# crossed shuffle
logging.getLogger('').debug('Crossing overworld edges')
crossed_edges = list()
#TODO: Revisit with changes to Limited/Allowed
if world.owCrossed[player] not in ['none', 'grouped', 'polar', 'chaos']:
# customizer setup
force_crossed = set()
force_noncrossed = set()
count_crossed = 0
limited_crossed = 9 if world.owCrossed[player] == 'limited' else -1
if world.customizer:
custom_crossed = world.customizer.get_owcrossed()
if custom_crossed and player in custom_crossed:
custom_crossed = custom_crossed[player]
if 'force_crossed' in custom_crossed and len(custom_crossed['force_crossed']) > 0:
for edgename in custom_crossed['force_crossed']:
edge = world.check_for_owedge(edgename, player)
force_crossed.add(edge.name)
if 'force_noncrossed' in custom_crossed and len(custom_crossed['force_noncrossed']) > 0:
for edgename in custom_crossed['force_noncrossed']:
edge = world.check_for_owedge(edgename, player)
force_noncrossed.add(edge.name)
if 'limit_crossed' in custom_crossed:
limited_crossed = custom_crossed['limit_crossed']
if limited_crossed > -1:
# connect forced crossed non-parallel edges based on previously determined tile flips
for edge in swapped_edges:
crossed_edges.append(edge)
if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'):
if edge not in parallel_links_new:
world.owcrossededges[player].append(edge)
count_crossed = count_crossed + 1
if world.owCrossed[player] == 'grouped' or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos') or limited_crossed > -1:
if world.owCrossed[player] == 'grouped':
# the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work
# Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask
ow_crossed_tiles_mask = [[],[],[]]
crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player)
world.owcrossededges[player] = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player)
ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])]
# update spoiler
@@ -244,32 +264,77 @@ def link_overworld(world, player):
(mode, wrld, dir, terrain, parallel, count) = group
if wrld == WorldType.Light and mode != OpenStd.Standard:
for (forward_set, back_set) in zip(trimmed_groups[group][0], trimmed_groups[group][1]):
if world.owKeepSimilar[player]:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
for edge in forward_set:
crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited':
crossed_candidates.append(forward_set)
if forward_set[0] in parallel_links_new:
forward_parallel = [parallel_links_new[e] for e in forward_set]
back_parallel = [parallel_links_new[e] for e in back_set]
forward_combine = forward_set+forward_parallel
back_combine = back_set+back_parallel
combine_set = forward_combine+back_combine
skip_forward = False
if world.owShuffle[player] == 'vanilla':
if any(edge in force_crossed for edge in combine_set):
if not any(edge in force_noncrossed for edge in combine_set):
if any(edge in force_crossed for edge in forward_combine):
world.owcrossededges[player].extend(forward_set)
count_crossed = count_crossed + 1
continue
else:
world.owcrossededges[player].extend(back_set)
count_crossed = count_crossed + 1
continue
else:
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in combine_set):
continue
else:
for edge in forward_set:
if world.owCrossed[player] == 'chaos' and random.randint(0, 1):
crossed_edges.append(edge)
elif world.owCrossed[player] == 'limited':
crossed_candidates.append([edge])
if world.owCrossed[player] == 'limited':
skip_back = False
if any(edge in force_crossed for edge in forward_combine):
if not any(edge in force_noncrossed for edge in forward_combine):
world.owcrossededges[player].extend(forward_set)
count_crossed = count_crossed + 1
skip_forward = True
else:
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
if any(edge in force_crossed for edge in back_combine):
if not any(edge in force_noncrossed for edge in back_combine):
world.owcrossededges[player].extend(back_set)
count_crossed = count_crossed + 1
skip_back = True
else:
raise GenerationException('Conflict detected in force_crossed and force_noncrossed')
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in forward_combine):
skip_forward = True
if any(edge in list(force_noncrossed)+world.owcrossededges[player] for edge in back_combine):
skip_back = True
if not skip_back:
if limited_crossed > -1:
crossed_candidates.append(back_set)
elif random.randint(0, 1):
world.owcrossededges[player].extend(back_set)
count_crossed = count_crossed + 1
if not skip_forward:
if limited_crossed > -1:
crossed_candidates.append(forward_set)
elif random.randint(0, 1):
world.owcrossededges[player].extend(forward_set)
count_crossed = count_crossed + 1
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge added to crossed edges"
if limited_crossed > -1:
limit = limited_crossed - count_crossed
random.shuffle(crossed_candidates)
for edge_set in crossed_candidates[:9]:
for edge in edge_set:
crossed_edges.append(edge)
for edge in copy.deepcopy(crossed_edges):
for edge_set in crossed_candidates[:limit]:
world.owcrossededges[player].extend(edge_set)
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
for edge in copy.deepcopy(world.owcrossededges[player]):
if edge in parallel_links_new:
crossed_edges.append(parallel_links_new[edge])
elif edge in parallel_links_new.inverse:
crossed_edges.append(parallel_links_new.inverse[edge][0])
if parallel_links_new[edge] not in world.owcrossededges[player]:
world.owcrossededges[player].append(parallel_links_new[edge])
# after tile flip and crossed, determine edges that need to flip
edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)]
edges_to_swap = [e for e in swapped_edges+world.owcrossededges[player] if (e not in swapped_edges) or (e not in world.owcrossededges[player])]
# whirlpool shuffle
logging.getLogger('').debug('Shuffling whirlpools')
@@ -367,53 +432,142 @@ def link_overworld(world, player):
# layout shuffle
groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
connect_custom(world, connected_edges, groups, player)
connect_custom(world, connected_edges, groups, (force_crossed, force_noncrossed), player)
tries = 100
valid_layout = False
connected_edge_cache = connected_edges.copy()
groups_cache = copy.deepcopy(groups)
while not valid_layout and tries > 0:
def connect_set(forward_set, back_set, connected_edges):
if forward_set is not None and back_set is not None:
assert len(forward_set) == len(back_set)
for (forward_edge, back_edge) in zip(forward_set, back_set):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
elif forward_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0])
elif back_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0])
connected_edges = connected_edge_cache.copy()
groups = copy.deepcopy(groups_cache)
groupKeys = list(groups.keys())
if world.mode[player] == 'standard':
random.shuffle(groups[2:]) # keep first 2 groups (Standard) first
random.shuffle(groupKeys[2:]) # keep first 2 groups (Standard) first
else:
random.shuffle(groups)
random.shuffle(groupKeys)
for (forward_edge_sets, back_edge_sets) in groups:
assert len(forward_edge_sets) == len(back_edge_sets)
for key in groupKeys:
(mode, wrld, dir, terrain, parallel, count) = key
(forward_edge_sets, back_edge_sets) = groups[key]
def remove_connected():
s = 0
while s < len(forward_edge_sets):
forward_set = forward_edge_sets[s]
if forward_set[0] in connected_edges:
del forward_edge_sets[s]
continue
s += 1
s = 0
while s < len(back_edge_sets):
back_set = back_edge_sets[s]
if back_set[0] in connected_edges:
del back_edge_sets[s]
continue
s += 1
assert len(forward_edge_sets) == len(back_edge_sets)
remove_connected()
random.shuffle(forward_edge_sets)
random.shuffle(back_edge_sets)
if len(forward_edge_sets) > 0:
f = 0
b = 0
while f < len(forward_edge_sets) and b < len(back_edge_sets):
forward_set = forward_edge_sets[f]
back_set = back_edge_sets[b]
while forward_set[0] in connected_edges:
f += 1
if f < len(forward_edge_sets):
forward_set = forward_edge_sets[f]
if wrld is None and len(force_crossed) + len(force_noncrossed) > 0:
# divide forward/back sets into LW/DW
forward_lw_sets, forward_dw_sets = [], []
back_lw_sets, back_dw_sets = [], []
forward_parallel_lw_sets, forward_parallel_dw_sets = [], []
back_parallel_lw_sets, back_parallel_dw_sets = [], []
for edge_set in forward_edge_sets:
if world.check_for_owedge(edge_set[0], player).is_lw(world):
forward_lw_sets.append(edge_set)
if parallel == IsParallel.Yes:
forward_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set])
else:
forward_dw_sets.append(edge_set)
if parallel == IsParallel.Yes:
forward_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set])
for edge_set in back_edge_sets:
if world.check_for_owedge(edge_set[0], player).is_lw(world):
back_lw_sets.append(edge_set)
if parallel == IsParallel.Yes:
back_parallel_lw_sets.append([parallel_links_new[e] for e in edge_set])
else:
back_dw_sets.append(edge_set)
if parallel == IsParallel.Yes:
back_parallel_dw_sets.append([parallel_links_new[e] for e in edge_set])
crossed_sets = []
noncrossed_sets = []
def add_to_crossed_sets(sets, parallel_sets):
for i in range(0, len(sets)):
affected_edges = set(sets[i]+(parallel_sets[i] if parallel == IsParallel.Yes else []))
if sets[i] not in crossed_sets and len(set.intersection(set(force_crossed), affected_edges)) > 0:
crossed_sets.append(sets[i])
if sets not in noncrossed_sets and len(set.intersection(set(force_noncrossed), affected_edges)) > 0:
noncrossed_sets.append(sets[i])
if sets[i] in crossed_sets and sets[i] in noncrossed_sets:
raise GenerationException('Conflict in force crossed/non-crossed definition')
add_to_crossed_sets(forward_lw_sets, forward_parallel_lw_sets)
add_to_crossed_sets(forward_dw_sets, forward_parallel_dw_sets)
add_to_crossed_sets(back_lw_sets, back_parallel_lw_sets)
add_to_crossed_sets(back_dw_sets, back_parallel_dw_sets)
# random connect forced crossed/noncrossed
c = 0
while c < len(noncrossed_sets):
if noncrossed_sets[c] in forward_edge_sets:
forward_set = noncrossed_sets[c]
if forward_set in forward_lw_sets:
back_set = next(s for s in back_lw_sets if s in back_edge_sets and s not in crossed_sets)
else:
forward_set = None
break
f += 1
while back_set[0] in connected_edges:
b += 1
if b < len(back_edge_sets):
back_set = back_edge_sets[b]
back_set = next(s for s in back_dw_sets if s in back_edge_sets and s not in crossed_sets)
elif noncrossed_sets[c] in back_edge_sets:
back_set = noncrossed_sets[c]
if back_set in back_lw_sets:
forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets and s not in crossed_sets)
else:
back_set = None
break
b += 1
if forward_set is not None and back_set is not None:
assert len(forward_set) == len(back_set)
for (forward_edge, back_edge) in zip(forward_set, back_set):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
elif forward_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0])
elif back_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0])
forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets and s not in crossed_sets)
else:
c = c + 1
continue
connect_set(forward_set, back_set, connected_edges)
remove_connected()
c = c + 1
c = 0
while c < len(crossed_sets):
if crossed_sets[c] in forward_edge_sets:
forward_set = crossed_sets[c]
if forward_set in forward_lw_sets:
back_set = next(s for s in back_dw_sets if s in back_edge_sets)
else:
back_set = next(s for s in back_lw_sets if s in back_edge_sets)
elif crossed_sets[c] in back_edge_sets:
back_set = crossed_sets[c]
if back_set in back_lw_sets:
forward_set = next(s for s in forward_dw_sets if s in forward_edge_sets)
else:
forward_set = next(s for s in forward_lw_sets if s in forward_edge_sets)
else:
c = c + 1
continue
connect_set(forward_set, back_set, connected_edges)
remove_connected()
c = c + 1
while len(forward_edge_sets) > 0 and len(back_edge_sets) > 0:
connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges)
remove_connected()
assert len(connected_edges) == len(default_connections) * 2, connected_edges
world.owsectors[player] = build_sectors(world, player)
@@ -583,8 +737,9 @@ def link_overworld(world, player):
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('flute', text_output, new_spots, player)
def connect_custom(world, connected_edges, groups, player):
def remove_pair_from_pool(edgename1, edgename2):
def connect_custom(world, connected_edges, groups, forced, player):
forced_crossed, forced_noncrossed = forced
def remove_pair_from_pool(edgename1, edgename2, is_crossed):
def add_to_unresolved(forward_set, back_set):
if len(forward_set) > 1:
if edgename1 in forward_set:
@@ -593,8 +748,8 @@ def connect_custom(world, connected_edges, groups, player):
else:
back_set.remove(edgename1)
forward_set.remove(edgename2)
unresolved_similars.append(tuple((forward_set, back_set)))
for forward_pool, back_pool in groups:
unresolved_similars.append(tuple((forward_set, back_set, is_crossed)))
for forward_pool, back_pool in groups.values():
if not len(forward_pool):
continue
if len(forward_pool[0]) == 1:
@@ -635,7 +790,7 @@ def connect_custom(world, connected_edges, groups, player):
else:
break
for pair in unresolved_similars:
forward_set, back_set = pair
forward_set, back_set, _ = pair
if edgename1 in forward_set:
if edgename2 in back_set:
unresolved_similars.remove(pair)
@@ -659,24 +814,45 @@ def connect_custom(world, connected_edges, groups, player):
custom_edges = custom_edges[player]
if 'two-way' in custom_edges:
unresolved_similars = []
def validate_crossed_allowed(edge1, edge2, is_crossed):
return not ((not is_crossed and (edge1 in forced_crossed or edge2 in forced_crossed))
or (is_crossed and (edge1 in forced_noncrossed or edge2 in forced_noncrossed)))
for edgename1, edgename2 in custom_edges['two-way'].items():
edge1 = world.check_for_owedge(edgename1, player)
edge2 = world.check_for_owedge(edgename2, player)
if edgename1 not in connected_edges and edgename2 not in connected_edges:
is_crossed = edge1.is_lw(world) != edge2.is_lw(world)
if not validate_crossed_allowed(edge1.name, edge2.name, is_crossed):
if edgename2[-1] == '*':
edge2 = world.check_for_owedge(edge2.name + '*', player)
is_crossed = not is_crossed
else:
raise GenerationException('Violation of force crossed rules: \'%s\' <-> \'%s\'', edgename1, edgename2)
if edge1.name not in connected_edges and edge2.name not in connected_edges:
# attempt connection
remove_pair_from_pool(edgename1, edgename2)
connect_two_way(world, edgename1, edgename2, player, connected_edges)
remove_pair_from_pool(edge1.name, edge2.name, is_crossed)
connect_two_way(world, edge1.name, edge2.name, player, connected_edges)
# resolve parallel
remove_pair_from_pool(parallel_forward_edge, parallel_back_edge)
elif not edge1.dest or not edge2.dest or edge1.dest.name != edgename2 or edge2.dest.name != edgename1:
if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new:
parallel_forward_edge = parallel_links_new[edge1.name]
parallel_back_edge = parallel_links_new[edge2.name]
if validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
remove_pair_from_pool(parallel_forward_edge, parallel_back_edge, is_crossed)
else:
raise GenerationException('Violation of force crossed rules on parallel connection: \'%s\' <-> \'%s\'', edgename1, edgename2)
elif not edge1.dest or not edge2.dest or edge1.dest.name != edge2.name or edge2.dest.name != edge1.name:
raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2)
# connect leftover similars
for forward_pool, back_pool in unresolved_similars:
for forward_pool, back_pool, is_crossed in unresolved_similars:
for (forward_edge, back_edge) in zip(forward_pool, back_pool):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
if validate_crossed_allowed(forward_edge, back_edge, is_crossed):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
else:
raise GenerationException('Violation of force crossed rules on unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
if world.owShuffle[player] == 'parallel' and forward_edge in parallel_links_new:
parallel_forward_edge = parallel_links_new[forward_edge]
parallel_back_edge = parallel_links_new[back_edge]
if not validate_crossed_allowed(parallel_forward_edge, parallel_back_edge, is_crossed):
raise GenerationException('Violation of force crossed rules on parallel unresolved similars: \'%s\' <-> \'%s\'', forward_edge, back_edge)
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
edge1 = world.get_entrance(edgename1, player)
@@ -1054,12 +1230,16 @@ def reorganize_groups(world, groups, player):
def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
groups = defaultdict(lambda: ([],[]))
limited_crossed = False
if world.customizer:
custom_crossed = world.customizer.get_owcrossed()
limited_crossed = custom_crossed and (player in custom_crossed) and ('limit_crossed' in custom_crossed[player])
for (key, group) in trimmed_groups.items():
(mode, wrld, dir, terrain, parallel, count) = key
if mode == OpenStd.Standard:
groups[key] = group
else:
if world.owCrossed[player] == 'chaos':
if world.owCrossed[player] == 'chaos' and not limited_crossed:
groups[(mode, None, dir, terrain, parallel, count)][0].extend(group[0])
groups[(mode, None, dir, terrain, parallel, count)][1].extend(group[1])
else:
@@ -1069,7 +1249,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
if edge_set[0] in edges_to_swap:
new_world += 1
groups[(mode, WorldType(new_world % 2), dir, terrain, parallel, count)][i].append(edge_set)
return list(groups.values())
return groups
def create_flute_exits(world, player):
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)