Add ow_layout and ow_parallel settings

This commit is contained in:
Catobat
2025-12-23 15:01:52 +01:00
parent 452981ffe2
commit 080f3b1cca
22 changed files with 164 additions and 84 deletions

View File

@@ -20,11 +20,12 @@ from source.overworld.EntranceData import door_addresses
class World(object):
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
def __init__(self, players, owLayout, owParallel, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode):
self.players = players
self.teams = 1
self.owShuffle = owShuffle.copy()
self.owLayout = owLayout.copy()
self.owParallel = owParallel.copy()
self.owTerrain = {}
self.owKeepSimilar = {}
self.owMixed = owMixed.copy()
@@ -3041,7 +3042,8 @@ class Spoiler(object):
'bow_mode': self.world.bow_mode,
'goal': self.world.goal,
'custom_goals': self.world.custom_goals,
'ow_shuffle': self.world.owShuffle,
'ow_layout': self.world.owLayout,
'ow_parallel': self.world.owParallel,
'ow_terrain': self.world.owTerrain,
'ow_crossed': self.world.owCrossed,
'ow_keepsimilar': self.world.owKeepSimilar,
@@ -3312,11 +3314,12 @@ class Spoiler(object):
outfile.write('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['dropshuffle'][player])
outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player])
outfile.write('\n')
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
if self.metadata['ow_shuffle'][player] != 'vanilla':
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_layout'][player])
if self.metadata['ow_layout'][player] != 'vanilla':
outfile.write('Parallel OW:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_parallel'][player]))
outfile.write('Free Terrain:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_terrain'][player]))
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
if self.metadata['ow_layout'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
@@ -3728,8 +3731,8 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique
# byte 10: settings_version
# byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed)
or_mode = {"vanilla": 0, "parallel": 1, "full": 2}
# byte 11: POOT WCCC (parallel, OWR layout, free terrain, whirlpools, OWR crossed)
orlayout_mode = {"vanilla": 0, "grid": 1, "wild": 2}
orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "unrestricted": 4}
# byte 12: KMBQ FF?? (keep similar, mixed/tile flip, bonk drops, follower quests, flute spots)
@@ -3795,7 +3798,7 @@ class Settings(object):
settings_version,
(or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owTerrain[p] else 0)
(0x80 if w.owParallel[p] else 0) | (orlayout_mode[w.owLayout[p]] << 5) | (0x10 if w.owTerrain[p] else 0)
| (0x08 if w.owWhirlpoolShuffle[p] else 0) | orcrossed_mode[w.owCrossed[p]],
(0x80 if w.owKeepSimilar[p] else 0) | (0x40 if w.owMixed[p] else 0)
@@ -3877,7 +3880,8 @@ class Settings(object):
args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3]
args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)]
args.ow_shuffle[p] = r(or_mode)[(settings[11] & 0xE0) >> 5]
args.ow_parallel[p] = True if settings[11] & 0x80 else False
args.ow_layout[p] = r(orlayout_mode)[(settings[11] & 0x60) >> 5]
args.ow_terrain[p] = True if settings[11] & 0x10 else False
args.ow_whirlpool[p] = True if settings[11] & 0x08 else False
args.ow_crossed[p] = r(orcrossed_mode)[(settings[11] & 0x07)]

19
CLI.py
View File

@@ -120,6 +120,16 @@ def parse_cli(argv, no_defaults=False):
ret.take_any = 'random' if ret.take_any == 'none' else ret.take_any
ret.keyshuffle = 'universal'
if ret.ow_unparallel:
ret.ow_parallel = False
if ret.ow_shuffle == 'parallel':
ret.ow_layout = 'wild'
ret.ow_parallel = True
elif ret.ow_shuffle == 'full':
ret.ow_layout = 'wild'
ret.ow_parallel = False
if player_num:
defaults = copy.deepcopy(ret)
for player in range(1, player_num + 1):
@@ -130,8 +140,8 @@ def parse_cli(argv, no_defaults=False):
for k, v in playersettings.items():
setattr(playerargs, k, v)
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle',
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', 'ow_layout',
'ow_parallel', 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory',
@@ -193,7 +203,10 @@ def parse_settings():
# Shuffle Ganon defaults to TRUE
"openpyramid": "auto",
"shuffleganon": True,
"ow_shuffle": "vanilla",
"ow_shuffle": "vanilla", # for backwards compatibility
"ow_layout": "vanilla",
"ow_parallel": True,
"ow_unparallel": False,
"ow_terrain": False,
"ow_crossed": "none",
"ow_keepsimilar": False,

View File

@@ -432,7 +432,7 @@ def init_world(args, fish):
customized.load_yaml(args.customizer)
customized.adjust_args(args, False)
world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
world = World(args.multi, args.ow_layout, args.ow_parallel, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler)
@@ -725,7 +725,7 @@ def set_starting_inventory(world, args):
def copy_world(world):
# ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams
@@ -946,7 +946,7 @@ def copy_world(world):
def copy_world_premature(world, player, create_flute_exits=True):
# ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
ret = World(world.players, world.owLayout, world.owParallel, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams

View File

@@ -144,7 +144,7 @@ def link_overworld(world, player):
parallel_links_new = {**dict(parallel_links_new), **dict({e:p[0] for e, p in parallel_links_new.inverse.items()})}
connected_edges = []
if world.owShuffle[player] != 'vanilla':
if world.owLayout[player] != 'vanilla':
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
trimmed_groups = reorganize_groups(world, trimmed_groups, player)
@@ -264,10 +264,10 @@ def link_overworld(world, player):
s[0x30], s[0x35],
s[0x41], s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('groups', text_output, ow_crossed_tiles, player)
elif limited_crossed > -1 or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'):
elif limited_crossed > -1 or (world.owLayout[player] == 'vanilla' and world.owCrossed[player] == 'unrestricted'):
crossed_candidates = list()
for group in trimmed_groups.keys():
(mode, wrld, dir, terrain, parallel, count, _) = group
(mode, wrld, _, terrain, parallel, _, _) = 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 forward_set[0] in parallel_links_new:
@@ -278,7 +278,7 @@ def link_overworld(world, player):
combine_set = forward_combine+back_combine
skip_forward = False
if world.owShuffle[player] == 'vanilla':
if world.owLayout[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):
@@ -412,7 +412,7 @@ def link_overworld(world, player):
# layout shuffle
logging.getLogger('').debug('Shuffling overworld layout')
if world.owShuffle[player] == 'vanilla':
if world.owLayout[player] == 'vanilla':
# apply outstanding flips
trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap)
@@ -425,8 +425,10 @@ def link_overworld(world, player):
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 world.owLayout[player] == 'grid':
raise NotImplementedError()
else:
if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
if world.owKeepSimilar[player] and world.owParallel[player]:
for exitname, destname in parallelsimilar_connections:
connect_two_way(world, exitname, destname, player, connected_edges)
@@ -822,7 +824,7 @@ def connect_custom(world, connected_edges, groups, forced, player):
remove_pair_from_pool(edge1.name, edge2.name, is_crossed)
connect_two_way(world, edge1.name, edge2.name, player, connected_edges)
# resolve parallel
if world.owShuffle[player] == 'parallel' and edge1.name in parallel_links_new:
if world.owParallel[player] 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):
@@ -838,7 +840,7 @@ def connect_custom(world, connected_edges, groups, forced, player):
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:
if world.owParallel[player] 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):
@@ -868,7 +870,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
x.dest = y
y.dest = x
if world.owShuffle[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none':
if world.owLayout[player] != 'vanilla' or world.owMixed[player] or world.owCrossed[player] != 'none':
world.spoiler.set_overworld(edgename2, edgename1, 'both', player)
if connected_edges is not None:
@@ -876,7 +878,7 @@ def connect_two_way(world, edgename1, edgename2, player, connected_edges=None):
connected_edges.append(edgename2)
# connecting parallel connections
if world.owShuffle[player] in ['vanilla', 'parallel']:
if world.owLayout[player] == 'vanilla' or world.owParallel[player]:
if edgename1 in parallel_links_new:
try:
parallel_forward_edge = parallel_links_new[edgename1]
@@ -965,7 +967,7 @@ def determine_forced_flips(world, tile_ow_groups, do_grouped, player):
for whirl1, whirl2 in custom_whirlpools.items():
if [whirlpool_map[whirl1], whirlpool_map[whirl2]] not in merged_owids and should_merge_group(whirlpool_map[whirl1], whirlpool_map[whirl2]):
merged_owids.append([whirlpool_map[whirl1], whirlpool_map[whirl2]])
if world.owShuffle[player] != 'vanilla':
if world.owLayout[player] != 'vanilla':
custom_edges = world.customizer.get_owedges()
if custom_edges and player in custom_edges:
custom_edges = custom_edges[player]
@@ -1071,6 +1073,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player):
exist_dw_regions.extend(dw_regions)
parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)]
if world.owLayout[player] == 'grid':
parity[1] = 0
parity[2] = 0
if not world.owKeepSimilar[player]:
parity[1] += 2*parity[2]
parity[2] = 0
@@ -1164,12 +1169,16 @@ def define_tile_groups(world, do_grouped, player):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']:
merge_groups([[0x05, 0x07]])
# all non-parallel screens
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
# special screens
if world.owLayout[player] != 'wild' and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81]])
# remaining non-parallel edges
if world.owLayout[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
# special case: non-parallel keep similar
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
if world.owLayout[player] == 'wild' and world.owParallel[player] and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x28, 0x29]])
# whirlpool screens
@@ -1225,7 +1234,7 @@ def reorganize_groups(world, groups, player):
new_group[0] = None
if world.owTerrain[player]:
new_group[3] = None
if world.owShuffle[player] != 'parallel':
if not world.owParallel[player]:
new_group[4] = None
if not world.owKeepSimilar[player]:
new_group[5] = None

View File

@@ -24,7 +24,7 @@ def main(args):
start_time = time.process_time()
# initialize the world
world = World(1, 'vanilla', 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
world = World(1, 'vanilla', True, 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
world.player_names[1].append("Player 1")
logger = logging.getLogger('')
@@ -157,9 +157,9 @@ def prefill_world(world, plando, text_patches):
elif line.startswith('!goal'):
_, goalstr = line.split(':', 1)
world.goal = {1: goalstr.strip()}
elif line.startswith('!owShuffle'):
elif line.startswith('!owLayout'):
_, modestr = line.split(':', 1)
world.owShuffle = {1: modestr.strip()}
world.owLayout = {1: modestr.strip()}
elif line.startswith('!owCrossed'):
_, modestr = line.split(':', 1)
world.owCrossed = {1: modestr.strip()}

View File

@@ -244,7 +244,7 @@ Ganons Tower - Validation Chest: Nothing
Ganon: Triforce
# set Overworld connections (lines starting with $, separate edges with =)
!owShuffle: parallel
!owLayout: wild
#!owMixed: true # Mixed OW not supported yet
!owCrossed: none
!owKeepSimilar: true

View File

@@ -132,20 +132,24 @@ Note: These changes do impact the logic. If you use `CodeTracker`, these Inverte
Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
## Overworld Layout Shuffle (--ow_shuffle)
## Overworld Layout Shuffle (--ow_layout)
OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://zelda.codemann8.com/images/shared/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip)
### Vanilla
OW Transitions are not shuffled.
### Parallel
### Grid
OW Transitions are shuffled, but both worlds will have a matching layout, similar to that of vanilla.
OW Screens are shuffled in such a way that they are still arranged on an 8x8 grid for each world like vanilla, and the OW Transitions are based on that arrangement.
### Full
### Wild
OW Transitions are shuffled within each world separately.
OW Transitions are shuffled with no respect to geometric coherence.
## Parallel (--ow_unparallel to disable)
With OW Layout Shuffle, this forces both worlds to have a matching layout.
## Free Terrain (--ow_terrain)
@@ -389,11 +393,17 @@ Districts are a concept originally conceived by Aerinon in the Door Randomizer,
Show the help message and exit.
```
--ow_shuffle <mode>
--ow_layout <mode>
```
For specifying the overworld layout shuffle you want as above. (default: vanilla)
```
--ow_unparallel
```
With OW Layout Shuffle, this no longer forces both worlds to have a matching layout.
```
--ow_terrain
```

15
Rom.py
View File

@@ -557,13 +557,10 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# patch overworld edges
inverted_buffer = [0] * 0x82
owMode = 0
if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]:
if world.owShuffle[player] == 'parallel':
owMode = 1
elif world.owShuffle[player] == 'full':
owMode = 2
if world.owKeepSimilar[player] and (world.owShuffle[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'):
if world.owLayout[player] != 'vanilla' or world.owCrossed[player] not in ['none', 'polar'] or world.owMixed[player]:
if world.owLayout[player] != 'vanilla':
owMode = 1 if world.owParallel[player] else 2
if world.owKeepSimilar[player] and (world.owLayout[player] != 'vanilla' or world.owCrossed[player] == 'unrestricted'):
owMode |= 0x0100
if world.owCrossed[player] != 'none' and (world.owCrossed[player] != 'polar' or world.owMixed[player]):
owMode |= 0x0200
@@ -2393,7 +2390,7 @@ def write_strings(rom, world, player, team):
if world.is_tile_swapped(0x18, player) or world.flute_mode[player] == 'active':
items_to_hint.remove(flute_item)
flute_item = 'Ocarina (Activated)'
if world.owShuffle[player] != 'vanilla' or world.owMixed[player]:
if world.owLayout[player] != 'vanilla' or world.owMixed[player]:
# Adding a guaranteed hint for the Flute in overworld shuffle.
this_location = world.find_items_not_key_only(flute_item, player)
if this_location and this_location not in hinted_locations:
@@ -2411,7 +2408,7 @@ def write_strings(rom, world, player, team):
random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
hint_count += 1 if world.owLayout[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
while hint_count > 0 and len(items_to_hint) > 0:
this_item = items_to_hint.pop(0)
this_location = world.find_items_not_key_only(this_item, player)

View File

@@ -234,7 +234,7 @@ You may define a list of items and a list of locations. Those items will be cons
### ow-edges
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_shuffle` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`.
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have either `ow_layout` or `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has two primary subsections: `two-way` and `groups`.
#### two-way

View File

@@ -20,7 +20,8 @@ settings:
shuffle_followers: true
shuffle: crossed
shufflelinks: true
ow_shuffle: parallel
ow_layout: wild
ow_parallel: true
ow_terrain: true
ow_crossed: grouped
ow_keepsimilar: true

View File

@@ -1,6 +1,7 @@
settings:
1:
ow_shuffle: full
ow_layout: wild
ow_parallel: false
ow_keepsimilar: false
ow-edges:
1:

View File

@@ -1,6 +1,7 @@
settings:
1:
ow_shuffle: full
ow_layout: wild
ow_parallel: false
ow_keepsimilar: false
ow-edges:
1:

View File

@@ -1,6 +1,7 @@
settings:
1:
ow_shuffle: full
ow_layout: wild
ow_parallel: false
ow_keepsimilar: false
ow-edges:
1:

View File

@@ -1,6 +1,7 @@
settings:
1:
ow_shuffle: full
ow_layout: wild
ow_parallel: false
ow_terrain: true
ow-edges:
1:

View File

@@ -173,6 +173,22 @@
"full"
]
},
"ow_layout": {
"choices": [
"vanilla",
"grid",
"wild"
]
},
"ow_parallel": {
"action": "store_true",
"help": "suppress",
"type": "bool"
},
"ow_unparallel": {
"action": "store_true",
"type": "bool"
},
"ow_terrain": {
"action": "store_true",
"type": "bool"

View File

@@ -234,14 +234,18 @@
"the entrances vanilla."
],
"ow_shuffle": [
"Deprecated, use ow_layout and ow_unparallel instead."
],
"ow_layout": [
"This shuffles the layout of the overworld.",
"Vanilla: All overworld transitions are connected the same",
" way they were in the base game.",
"Parallel: Overworld transitions are shuffled, but both worlds",
" will have the same pattern/shape.",
"Full: Overworld transitions are shuffled, but both worlds",
" will have an independent map shape."
"Grid: OW Screens are arranged on 8x8 grids and OW Transitions",
" work according to this arrangement.",
"Wild: OW Transitions are shuffled with no respect to geometric coherence."
],
"ow_unparallel": [
"With OW Layout Shuffle, this no longer forces both worlds to have a matching layout." ],
"ow_terrain": [
"With OW Layout Shuffle, this allows land and water edges to be connected." ],
"ow_crossed": [

View File

@@ -157,10 +157,12 @@
"randomizer.enemizer.enemylogic.allow_all": "Allow special enemies anywhere",
"randomizer.overworld.overworldshuffle": "Layout Shuffle",
"randomizer.overworld.overworldshuffle.vanilla": "Vanilla",
"randomizer.overworld.overworldshuffle.parallel": "Parallel",
"randomizer.overworld.overworldshuffle.full": "Full",
"randomizer.overworld.layout": "Layout Shuffle",
"randomizer.overworld.layout.vanilla": "Vanilla",
"randomizer.overworld.layout.grid": "Grid",
"randomizer.overworld.layout.wild": "Wild",
"randomizer.overworld.parallel": "Keep Worlds Parallel",
"randomizer.overworld.terrain": "Free Terrain",

View File

@@ -1,13 +1,13 @@
{
"topOverworldFrame": {},
"leftOverworldFrame": {
"overworldshuffle": {
"layout": {
"type": "selectbox",
"default": "vanilla",
"options": [
"vanilla",
"parallel",
"full"
"grid",
"wild"
]
},
"crossed": {
@@ -18,7 +18,10 @@
"grouped",
"polar",
"unrestricted"
]
],
"config": {
"pady": [16,0]
}
},
"mixed": {
"type": "checkbox",
@@ -48,19 +51,17 @@
}
},
"rightOverworldFrame": {
"parallel": {
"type": "checkbox",
"default": true
},
"terrain": {
"type": "checkbox",
"default": false,
"config": {
"pady": [3,0]
}
"default": false
},
"keepsimilar": {
"type": "checkbox",
"default": false,
"config": {
"pady": [6,0]
}
"default": false
}
}
}

View File

@@ -89,7 +89,8 @@ class CustomSettings(object):
args.mystery = True
else:
settings = defaultdict(lambda: None, player_setting)
args.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
args.ow_layout[p] = get_setting(settings['ow_layout'], args.ow_layout[p])
args.ow_parallel[p] = get_setting(settings['ow_parallel'], args.ow_parallel[p])
args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p])
args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p])
if args.ow_crossed[p] == 'chaos':
@@ -135,6 +136,14 @@ class CustomSettings(object):
args.take_any[p] = 'random' if args.take_any[p] == 'none' else args.take_any[p]
args.keyshuffle[p] = 'universal'
ow_shuffle = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
if ow_shuffle == 'parallel':
args.ow_layout = 'wild'
args.ow_parallel = True
elif ow_shuffle == 'full':
args.ow_layout = 'wild'
args.ow_parallel = False
args.mixed_travel[p] = get_setting(settings['mixed_travel'], args.mixed_travel[p])
args.standardize_palettes[p] = get_setting(settings['standardize_palettes'],
args.standardize_palettes[p])
@@ -356,7 +365,8 @@ class CustomSettings(object):
self.world_rep['start_inventory'] = start_inv = {}
for p in self.player_range:
settings_dict[p] = {}
settings_dict[p]['ow_shuffle'] = world.owShuffle[p]
settings_dict[p]['ow_layout'] = world.owLayout[p]
settings_dict[p]['ow_parallel'] = world.owParallel[p]
settings_dict[p]['ow_terrain'] = world.owTerrain[p]
settings_dict[p]['ow_crossed'] = world.owCrossed[p]
settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p]

View File

@@ -92,7 +92,8 @@ SETTINGSTOPROCESS = {
"bombbag": "bombbag"
},
"overworld": {
"overworldshuffle": "ow_shuffle",
"layout": "ow_layout",
"parallel": "ow_parallel",
"terrain": "ow_terrain",
"crossed": "ow_crossed",
"keepsimilar": "ow_keepsimilar",

View File

@@ -635,7 +635,7 @@ def do_dark_sanc(entrances, exits, avail):
forbidden.append('Links House')
else:
forbidden.append('Big Bomb Shop')
if avail.world.owShuffle[avail.player] == 'vanilla':
if avail.world.owLayout[avail.player] == 'vanilla':
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
else:
choices = [e for e in get_starting_entrances(avail) if e not in forbidden and e in entrances]
@@ -679,7 +679,7 @@ def do_links_house(entrances, exits, avail, cross_world):
forbidden.append(links_house_vanilla)
forbidden.extend(Forbidden_Swap_Entrances)
shuffle_mode = avail.world.shuffle[avail.player]
if avail.world.owShuffle[avail.player] == 'vanilla':
if avail.world.owLayout[avail.player] == 'vanilla':
# simple shuffle -
if shuffle_mode == 'simple':
avail.links_on_mountain = True # taken care of by the logic below
@@ -733,7 +733,7 @@ def do_links_house(entrances, exits, avail, cross_world):
# links on dm
dm_spots = LH_DM_Connector_List.union(LH_DM_Exit_Forbidden)
if links_house in dm_spots and avail.world.owShuffle[avail.player] == 'vanilla':
if links_house in dm_spots and avail.world.owLayout[avail.player] == 'vanilla':
if avail.links_on_mountain:
return # connector is fine
logging.getLogger('').warning(f'Links House is placed in tight area and is now unhandled. Report any errors that occur from here.')

View File

@@ -120,8 +120,16 @@ def roll_settings(weights):
ret.accessibility = get_choice('accessibility')
ret.restrict_boss_items = get_choice('restrict_boss_items')
overworld_layout = get_choice('overworld_layout')
ret.ow_layout = overworld_layout if overworld_layout != 'none' else 'vanilla'
ret.ow_parallel = get_choice_bool('overworld_parallel')
overworld_shuffle = get_choice('overworld_shuffle')
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
if overworld_shuffle == 'parallel':
ret.ow_layout = 'wild'
ret.ow_parallel = True
elif overworld_shuffle == 'full':
ret.ow_layout = 'wild'
ret.ow_parallel = False
ret.ow_terrain = get_choice_bool('overworld_terrain')
valid_options = {'none': 'none', 'polar': 'polar', 'grouped': 'polar', 'chaos': 'unrestricted', 'unrestricted': 'unrestricted'}
ret.ow_crossed = get_choice('overworld_crossed')