Added Customizer support for OWR Layout and Whirlpool Shuffle

This commit is contained in:
codemann8
2023-08-25 11:18:21 -05:00
parent eb1888334e
commit 26727fbfca
3 changed files with 169 additions and 32 deletions

View File

@@ -2784,6 +2784,7 @@ class Spoiler(object):
self.world = world self.world = world
self.hashes = {} self.hashes = {}
self.overworlds = {} self.overworlds = {}
self.whirlpools = {}
self.maps = {} self.maps = {}
self.entrances = {} self.entrances = {}
self.doors = {} self.doors = {}
@@ -2808,6 +2809,12 @@ class Spoiler(object):
else: else:
self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
def set_whirlpool(self, entrance, exit, direction, player):
if self.world.players == 1:
self.whirlpools[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
else:
self.whirlpools[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
def set_map(self, type, text, data, player): def set_map(self, type, text, data, player):
if self.world.players == 1: if self.world.players == 1:
self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)])
@@ -3010,6 +3017,7 @@ class Spoiler(object):
self.parse_data() self.parse_data()
out = OrderedDict() out = OrderedDict()
out['Overworld'] = list(self.overworlds.values()) out['Overworld'] = list(self.overworlds.values())
out['Whirlpools'] = list(self.whirlpools.values())
out['Maps'] = list(self.maps.values()) out['Maps'] = list(self.maps.values())
out['Entrances'] = list(self.entrances.values()) out['Entrances'] = list(self.entrances.values())
out['Doors'] = list(self.doors.values()) out['Doors'] = list(self.doors.values())
@@ -3181,7 +3189,7 @@ class Spoiler(object):
for fairy, bottle in self.bottles.items(): for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n') outfile.write(f'{fairy}: {bottle}\n')
if self.overworlds or self.maps: if self.overworlds or self.whirlpools or self.maps:
outfile.write('\n\nOverworld:\n\n') outfile.write('\n\nOverworld:\n\n')
# flute shuffle # flute shuffle
@@ -3217,6 +3225,10 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('groups', player)]['text'] + '\n\n') outfile.write(self.maps[('groups', player)]['text'] + '\n\n')
if self.whirlpools:
# whirlpools
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()]))
if self.overworlds: if self.overworlds:
# overworld transitions # overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))

View File

@@ -3,6 +3,7 @@ from collections import OrderedDict, defaultdict
from DungeonGenerator import GenerationException from DungeonGenerator import GenerationException
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from Regions import mark_light_dark_world_regions from Regions import mark_light_dark_world_regions
from source.overworld.EntranceShuffle2 import connect_simple
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
from OverworldGlitchRules import create_owg_connections from OverworldGlitchRules import create_owg_connections
from Utils import bidict from Utils import bidict
@@ -278,16 +279,24 @@ def link_overworld(world, player):
connect_simple(world, from_whirlpool, to_region, player) connect_simple(world, from_whirlpool, to_region, player)
connect_simple(world, to_whirlpool, from_region, player) connect_simple(world, to_whirlpool, from_region, player)
else: else:
def connect_whirlpool(from_whirlpool, to_whirlpool):
(from_owid, from_name, from_region) = from_whirlpool
(to_owid, to_name, to_region) = to_whirlpool
connect_simple(world, from_name, to_region, player)
connect_simple(world, to_name, from_region, player)
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid
connected_whirlpools.append(tuple((from_name, to_name)))
world.spoiler.set_whirlpool(from_name, to_name, 'both', player)
whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ]
whirlpool_candidates = [[],[]] whirlpool_candidates = [[],[]]
connected_whirlpools = []
world.owwhirlpools[player] = [None] * 8 world.owwhirlpools[player] = [None] * 8
for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections: for (from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region) in default_whirlpool_connections:
if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55: if world.owCrossed[player] == 'polar' and world.owMixed[player] and from_owid == 0x55:
# connect the 2 DW whirlpools in Polar Mixed # connect the 2 DW whirlpools in Polar Mixed
connect_simple(world, from_whirlpool, to_region, player) connect_whirlpool((from_owid, from_whirlpool, from_region), (to_owid, to_whirlpool, to_region))
connect_simple(world, to_whirlpool, from_region, player)
world.owwhirlpools[player][7] = from_owid
world.owwhirlpools[player][6] = to_owid
world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player)
else: else:
if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \ if ((world.owCrossed[player] == 'none' or (world.owCrossed[player] == 'polar' and not world.owMixed[player])) and (world.get_region(from_region, player).type == RegionType.LightWorld)) \
or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \ or world.owCrossed[player] not in ['none', 'polar', 'grouped'] \
@@ -304,19 +313,27 @@ def link_overworld(world, player):
whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region))) whirlpool_candidates[1].append(tuple((to_owid, to_whirlpool, to_region)))
# shuffle happens here # shuffle happens here
whirlpool_map = [ 0x35, 0x0f, 0x15, 0x33, 0x12, 0x3f, 0x55, 0x7f ] if world.customizer:
custom_whirlpools = world.customizer.get_whirlpools()
if custom_whirlpools and player in custom_whirlpools:
custom_whirlpools = custom_whirlpools[player]
if 'two-way' in custom_whirlpools:
for whirlpools in whirlpool_candidates:
for whirlname1, whirlname2 in custom_whirlpools['two-way'].items():
whirl1 = next((w for w in whirlpools if w[1] == whirlname1), None)
whirl2 = next((w for w in whirlpools if w[1] == whirlname2), None)
if whirl1 and whirl2:
whirlpools.remove(whirl1)
whirlpools.remove(whirl2)
connect_whirlpool(whirl1, whirl2)
elif whirl1 != whirl2 or not any(w for w in connected_whirlpools if (whirlname1 in w) and (whirlname2 in w)):
raise GenerationException('Attempting to connect whirlpools not in same pool: \'%s\' <-> \'%s\'', whirl1, whirl2)
for whirlpools in whirlpool_candidates: for whirlpools in whirlpool_candidates:
random.shuffle(whirlpools) random.shuffle(whirlpools)
while len(whirlpools): while len(whirlpools):
if len(whirlpools) % 2 == 1: if len(whirlpools) % 2 == 1:
x=0 x=0
from_owid, from_whirlpool, from_region = whirlpools.pop() connect_whirlpool(whirlpools.pop(), whirlpools.pop())
to_owid, to_whirlpool, to_region = whirlpools.pop()
connect_simple(world, from_whirlpool, to_region, player)
connect_simple(world, to_whirlpool, from_region, player)
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == to_owid)] = from_owid
world.owwhirlpools[player][next(i for i, v in enumerate(whirlpool_map) if v == from_owid)] = to_owid
world.spoiler.set_overworld(from_whirlpool, to_whirlpool, 'both', player)
# layout shuffle # layout shuffle
logging.getLogger('').debug('Shuffling overworld layout') logging.getLogger('').debug('Shuffling overworld layout')
@@ -337,7 +354,7 @@ def link_overworld(world, player):
world.owsectors[player] = build_sectors(world, player) world.owsectors[player] = build_sectors(world, player)
else: else:
if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
for exitname, destname in parallelsimilar_connections: for exitname, destname in parallelsimilar_connections:
connect_two_way(world, exitname, destname, player, connected_edges) connect_two_way(world, exitname, destname, player, connected_edges)
@@ -345,11 +362,11 @@ def link_overworld(world, player):
for exitname, destname in test_connections: for exitname, destname in test_connections:
connect_two_way(world, exitname, destname, player, connected_edges) connect_two_way(world, exitname, destname, player, connected_edges)
connect_custom(world, connected_edges, player)
# layout shuffle # layout shuffle
groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
connect_custom(world, connected_edges, groups, player)
tries = 100 tries = 100
valid_layout = False valid_layout = False
connected_edge_cache = connected_edges.copy() connected_edge_cache = connected_edges.copy()
@@ -564,21 +581,100 @@ def link_overworld(world, player):
s[0x3a],s[0x3b],s[0x3c], s[0x3f]) s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('flute', text_output, new_spots, player) world.spoiler.set_map('flute', text_output, new_spots, player)
def connect_custom(world, connected_edges, player): def connect_custom(world, connected_edges, groups, player):
if hasattr(world, 'custom_overworld') and world.custom_overworld[player]: def remove_pair_from_pool(edgename1, edgename2):
for edgename1, edgename2 in world.custom_overworld[player]: def add_to_unresolved(forward_set, back_set):
if edgename1 in connected_edges or edgename2 in connected_edges: if len(forward_set) > 1:
owedge1 = world.check_for_owedge(edgename1, player) if edgename1 in forward_set:
owedge2 = world.check_for_owedge(edgename2, player) forward_set.remove(edgename1)
if owedge1.dest is not None and owedge1.dest.name == owedge2.name: back_set.remove(edgename2)
continue # if attempting to connect a pair that was already connected earlier, allow it to continue else:
raise RuntimeError('Invalid plando connection: rule violation based on current settings') back_set.remove(edgename1)
connect_two_way(world, edgename1, edgename2, player, connected_edges) forward_set.remove(edgename2)
if world.owKeepSimilar[player]: #TODO: If connecting an edge that belongs to a similar pair, the remaining edges need to get connected automatically unresolved_similars.append(tuple((forward_set, back_set)))
for forward_pool, back_pool in groups:
continue continue
if len(forward_pool[0]) == 1:
if [edgename1] in forward_pool:
if [edgename2] in back_pool:
forward_pool.remove([edgename1])
back_pool.remove([edgename2])
return
else:
break
elif [edgename1] in back_pool:
if [edgename2] in forward_pool:
back_pool.remove([edgename1])
forward_pool.remove([edgename2])
return
else:
break
else:
forward_similar = next((x for x in forward_pool if edgename1 in x), None)
if forward_similar:
back_similar = next((x for x in back_pool if edgename2 in x), None)
if back_similar:
forward_pool.remove(forward_similar)
back_pool.remove(back_similar)
add_to_unresolved(forward_similar, back_similar)
return
else:
break
else:
back_similar = next((x for x in back_pool if edgename1 in x), None)
if back_similar:
forward_similar = next((x for x in forward_pool if edgename2 in x), None)
if forward_similar:
back_pool.remove(forward_similar)
forward_pool.remove(back_similar)
add_to_unresolved(forward_similar, back_similar)
return
else:
break
for pair in unresolved_similars:
forward_set, back_set = pair
if edgename1 in forward_set:
if edgename2 in back_set:
unresolved_similars.remove(pair)
add_to_unresolved(forward_set, back_set)
return
else:
break
else:
if edgename1 in back_set:
if edgename2 in forward_set:
unresolved_similars.remove(pair)
add_to_unresolved(forward_set, back_set)
return
else:
break
raise GenerationException('Could not find both OW edges in same pool: \'%s\' <-> \'%s\'', edgename1, edgename2)
def connect_simple(world, exitname, regionname, player): if world.customizer:
world.get_entrance(exitname, player).connect(world.get_region(regionname, player)) custom_edges = world.customizer.get_owedges()
if custom_edges and player in custom_edges:
custom_edges = custom_edges[player]
if 'two-way' in custom_edges:
unresolved_similars = []
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:
# attempt connection
remove_pair_from_pool(edgename1, edgename2)
connect_two_way(world, edgename1, edgename2, player, connected_edges)
# resolve parallel
if (world.owShuffle[player] == 'parallel' and
(edgename1 in parallel_links.keys() or edgename1 in parallel_links.inverse.keys())):
parallel_forward_edge = parallel_links[edgename1] if edgename1 in parallel_links.keys() else parallel_links.inverse[edgename1][0]
parallel_back_edge = parallel_links[edgename2] if edgename2 in parallel_links.keys() else parallel_links.inverse[edgename2][0]
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:
raise GenerationException('OW Edge already connected: \'%s\' <-> \'%s\'', edgename1, edgename2)
# connect leftover similars
for forward_pool, back_pool 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)
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None): def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
edge1 = world.get_entrance(edgename1, player) edge1 = world.get_entrance(edgename1, player)

View File

@@ -196,6 +196,16 @@ class CustomSettings(object):
return self.file_source['advanced_placements'] return self.file_source['advanced_placements']
return None return None
def get_owedges(self):
if 'ow-edges' in self.file_source:
return self.file_source['ow-edges']
return None
def get_whirlpools(self):
if 'ow-whirlpools' in self.file_source:
return self.file_source['ow-whirlpools']
return None
def get_owtileflips(self): def get_owtileflips(self):
if 'ow-tileflips' in self.file_source: if 'ow-tileflips' in self.file_source:
return self.file_source['ow-tileflips'] return self.file_source['ow-tileflips']
@@ -355,21 +365,40 @@ class CustomSettings(object):
placements[location.player][location.name] = location.item.name placements[location.player][location.name] = location.item.name
def record_overworld(self, world): def record_overworld(self, world):
self.world_rep['ow-edges'] = edges = {}
self.world_rep['ow-whirlpools'] = whirlpools = {}
self.world_rep['ow-tileflips'] = flips = {} self.world_rep['ow-tileflips'] = flips = {}
self.world_rep['ow-flutespots'] = flute = {}
for p in self.player_range: for p in self.player_range:
connections = edges[p] = {}
connections['two-way'] = {}
connections['one-way'] = {}
whirlconnects = whirlpools[p] = {}
whirlconnects['two-way'] = {}
whirlconnects['one-way'] = {}
# tile flips
if p in world.owswaps and len(world.owswaps[p][0]) > 0: if p in world.owswaps and len(world.owswaps[p][0]) > 0:
flips[p] = {} flips[p] = {}
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
flips[p]['force_flip'].sort() flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0 flips[p]['undefined_chance'] = 0
self.world_rep['ow-flutespots'] = flute = {} # flute spots
for p in self.player_range:
flute[p] = {} flute[p] = {}
if p in world.owflutespots: if p in world.owflutespots:
flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p])) flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p]))
else: else:
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections)) flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
flute[p]['forbid'] = [] flute[p]['forbid'] = []
for key, data in world.spoiler.overworlds.items():
player = data['player'] if 'player' in data else 1
connections = edges[player]
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
connections[sub][data['entrance']] = data['exit']
for key, data in world.spoiler.whirlpools.items():
player = data['player'] if 'player' in data else 1
whirlconnects = whirlconnects[player]
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
whirlconnects[sub][data['entrance']] = data['exit']
def record_entrances(self, world): def record_entrances(self, world):
self.world_rep['entrances'] = entrances = {} self.world_rep['entrances'] = entrances = {}