Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ weights/
|
||||
/QUsb2Snes/
|
||||
/output/
|
||||
/enemizer/
|
||||
visualizations/
|
||||
|
||||
base2current.json
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -32,6 +33,7 @@ class World(object):
|
||||
self.owCrossed = self.owCrossed if self.owCrossed != 'polar' or self.owMixed else 'none'
|
||||
self.owWhirlpoolShuffle = {}
|
||||
self.owFluteShuffle = {}
|
||||
self.owFog = {}
|
||||
self.shuffle = shuffle.copy()
|
||||
self.doorShuffle = doorShuffle.copy()
|
||||
self.intensity = {}
|
||||
@@ -86,6 +88,9 @@ class World(object):
|
||||
self.owswaps = {}
|
||||
self.owcrossededges = {}
|
||||
self.owwhirlpools = {}
|
||||
self.owgrid = {}
|
||||
self.owlayoutmap_lw = {}
|
||||
self.owlayoutmap_dw = {}
|
||||
self.owflutespots = {}
|
||||
self.owsectors = {}
|
||||
self.allow_flip_sanc = {}
|
||||
@@ -118,6 +123,7 @@ class World(object):
|
||||
set_player_attr('owswaps', [[],[],[]])
|
||||
set_player_attr('owcrossededges', [])
|
||||
set_player_attr('owwhirlpools', [])
|
||||
set_player_attr('owgrid', None)
|
||||
set_player_attr('owsectors', None)
|
||||
set_player_attr('allow_flip_sanc', False)
|
||||
set_player_attr('remote_items', False)
|
||||
@@ -2344,12 +2350,11 @@ class OWEdge(object):
|
||||
def __init__(self, player, name, owIndex, direction, terrain, edge_id, owSlotIndex=0xff):
|
||||
self.player = player
|
||||
self.name = name
|
||||
self.type = DoorType.Open
|
||||
self.direction = direction
|
||||
self.terrain = terrain
|
||||
self.parallel = None
|
||||
self.specialEntrance = False
|
||||
self.specialExit = False
|
||||
self.deadEnd = False
|
||||
|
||||
# rom properties
|
||||
self.owIndex = owIndex
|
||||
@@ -2380,7 +2385,6 @@ class OWEdge(object):
|
||||
self.worldType = WorldType.Dark
|
||||
|
||||
# logical properties
|
||||
# self.connected = False # combine with Dest?
|
||||
self.dest = None
|
||||
self.dependents = []
|
||||
self.dead = False
|
||||
@@ -2397,9 +2401,6 @@ class OWEdge(object):
|
||||
def getTarget(self):
|
||||
return self.dest.specialID if self.dest.specialExit else self.dest.edge_id
|
||||
|
||||
def dead_end(self):
|
||||
self.deadEnd = True
|
||||
|
||||
def coordInfo(self, midpoint, vram_loc):
|
||||
self.midpoint = midpoint
|
||||
self.vramLoc = vram_loc
|
||||
@@ -3041,13 +3042,15 @@ 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,
|
||||
'ow_mixed': self.world.owMixed,
|
||||
'ow_whirlpool': self.world.owWhirlpoolShuffle,
|
||||
'ow_fluteshuffle': self.world.owFluteShuffle,
|
||||
'ow_fog': self.world.owFog,
|
||||
'bonk_drops': self.world.shuffle_bonk_drops,
|
||||
'shuffle_followers': self.world.shuffle_followers,
|
||||
'shuffle': self.world.shuffle,
|
||||
@@ -3312,15 +3315,18 @@ 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]))
|
||||
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
|
||||
if self.metadata['ow_layout'][player] == 'grid' or self.metadata['ow_mixed'][player]:
|
||||
outfile.write('Overworld Fog:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_fog'][player]))
|
||||
outfile.write('\n')
|
||||
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != 'vanilla':
|
||||
@@ -3431,41 +3437,32 @@ class Spoiler(object):
|
||||
outfile.write(f'{fairy}: {bottle}\n')
|
||||
|
||||
if self.maps:
|
||||
def write_map(type, title):
|
||||
for player in range(1, self.world.players + 1):
|
||||
if (type, player) in self.maps:
|
||||
outfile.write('\n\n' + title + '\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if (type, player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[(type, player)]['text'])
|
||||
|
||||
if 'all' in self.settings or 'flute' in self.settings:
|
||||
# flute shuffle
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('flute', player) in self.maps:
|
||||
outfile.write('\n\nFlute Spots:\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('flute', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('flute', player)]['text'])
|
||||
|
||||
write_map('flute', 'Flute Spots:')
|
||||
|
||||
if 'all' in self.settings or 'overworld' in self.settings:
|
||||
# overworld tile flips
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
outfile.write('\n\nOW Tile Flips:\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('swaps', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('swaps', player)]['text'])
|
||||
write_map('swaps', 'OW Tile Flips:')
|
||||
|
||||
# crossed groups
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
outfile.write('\n\nOW Crossed Groups:\n\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('groups', player)]['text'])
|
||||
|
||||
write_map('groups', 'OW Crossed Groups:')
|
||||
|
||||
# grid layout
|
||||
write_map('layout_grid_lw', 'Light World Layout:')
|
||||
write_map('layout_grid_dw', 'Dark World Layout:')
|
||||
|
||||
if self.overworlds and ('all' in self.settings or 'overworld' in self.settings):
|
||||
outfile.write('\n\nOverworld Edges:\n\n')
|
||||
# overworld transitions
|
||||
@@ -3728,11 +3725,11 @@ 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)
|
||||
# byte 12: KMBQ FFO? (keep similar, mixed/tile flip, bonk drops, follower quests, flute spots, fog)
|
||||
flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
||||
|
||||
# byte 13: FBBB TTPP (flute_mode, bow_mode, take_any, prize shuffle)
|
||||
@@ -3795,12 +3792,12 @@ 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)
|
||||
| (0x20 if w.shuffle_bonk_drops[p] else 0) | (0x10 if w.shuffle_followers[p] else 0)
|
||||
| (flutespot_mode[w.owFluteShuffle[p]] << 4),
|
||||
| (flutespot_mode[w.owFluteShuffle[p]] << 4) | (0x02 if w.owFog[p] else 0),
|
||||
|
||||
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
|
||||
| take_any_mode[w.take_any[p]] << 2 | prizeshuffle_mode[w.prizeshuffle[p]]),
|
||||
@@ -3877,7 +3874,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)]
|
||||
@@ -3887,6 +3885,7 @@ class Settings(object):
|
||||
args.bonk_drops[p] = True if settings[12] & 0x20 else False
|
||||
args.shuffle_followers[p] = True if settings[12] & 0x10 else False
|
||||
args.ow_fluteshuffle[p] = r(flutespot_mode)[(settings[12] & 0x0C) >> 2]
|
||||
args.ow_fog[p] = True if settings[12] & 0x02 else False
|
||||
|
||||
if len(settings) > 13:
|
||||
args.flute_mode[p] = r(flute_mode)[(settings[13] & 0x80) >> 7]
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
# 0.7.0.0
|
||||
- New OW Layout Shuffle Mode: Grid
|
||||
- Implemented Fog of War for Tile Flip
|
||||
|
||||
# 0.6.1.11
|
||||
- Fixed bonk drops duplicate counting and potentially overwriting arbitrary values
|
||||
- Fixed boss icons on dungeon map check
|
||||
|
||||
26
CLI.py
26
CLI.py
@@ -120,6 +120,19 @@ 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 ret.ow_no_fog:
|
||||
ret.ow_fog = False
|
||||
|
||||
if player_num:
|
||||
defaults = copy.deepcopy(ret)
|
||||
for player in range(1, player_num + 1):
|
||||
@@ -130,9 +143,9 @@ 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',
|
||||
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
|
||||
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',
|
||||
'ow_fog', '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',
|
||||
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||
@@ -193,13 +206,18 @@ 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,
|
||||
"ow_mixed": False,
|
||||
"ow_whirlpool": False,
|
||||
"ow_fluteshuffle": "vanilla",
|
||||
"ow_fog": True,
|
||||
"ow_no_fog": False,
|
||||
"shuffle_followers": False,
|
||||
"bonk_drops": False,
|
||||
"shuffle": "vanilla",
|
||||
|
||||
9
Main.py
9
Main.py
@@ -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)
|
||||
|
||||
@@ -453,6 +453,7 @@ def init_world(args, fish):
|
||||
world.owKeepSimilar = args.ow_keepsimilar.copy()
|
||||
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
|
||||
world.owFluteShuffle = args.ow_fluteshuffle.copy()
|
||||
world.owFog = args.ow_fog.copy()
|
||||
world.shuffle_followers = args.shuffle_followers.copy()
|
||||
world.shuffle_bonk_drops = args.bonk_drops.copy()
|
||||
world.open_pyramid = args.openpyramid.copy()
|
||||
@@ -725,7 +726,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
|
||||
@@ -765,6 +766,7 @@ def copy_world(world):
|
||||
ret.owKeepSimilar = world.owKeepSimilar.copy()
|
||||
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
|
||||
ret.owFluteShuffle = world.owFluteShuffle.copy()
|
||||
ret.owFog = world.owFog.copy()
|
||||
ret.shuffle_followers = world.shuffle_followers.copy()
|
||||
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
|
||||
ret.open_pyramid = world.open_pyramid.copy()
|
||||
@@ -946,7 +948,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
|
||||
@@ -986,6 +988,7 @@ def copy_world_premature(world, player, create_flute_exits=True):
|
||||
ret.owKeepSimilar = world.owKeepSimilar.copy()
|
||||
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
|
||||
ret.owFluteShuffle = world.owFluteShuffle.copy()
|
||||
ret.owFog = world.owFog.copy()
|
||||
ret.shuffle_followers = world.shuffle_followers.copy()
|
||||
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
|
||||
ret.open_pyramid = world.open_pyramid.copy()
|
||||
|
||||
10
OWEdges.py
10
OWEdges.py
@@ -321,15 +321,23 @@ def create_owedges(world, player):
|
||||
create_owedge(player, 'Hobo EC', 0x80, Ea, Wr, 0x4a) .coordInfo(0x008c, 0x0020).special_exit(0x81),
|
||||
create_owedge(player, 'Zoras Domain SW', 0x81, So, Ld, 0x41, 0x89).coordInfo(0x02a4, 0x1782).special_exit(0x82)
|
||||
]
|
||||
|
||||
|
||||
world.owedges += edges
|
||||
world.initialize_owedges(edges)
|
||||
set_parallel_owedge_links(world, player, edges)
|
||||
|
||||
def create_owedge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex=0xff):
|
||||
if name not in OWExitTypes['OWEdge']:
|
||||
OWExitTypes['OWEdge'].append(name)
|
||||
return OWEdge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex)
|
||||
|
||||
def set_parallel_owedge_links(world, player, edges):
|
||||
for edge in edges:
|
||||
if edge.name in parallel_links:
|
||||
dw_edge = world.get_owedge(parallel_links[edge.name], player)
|
||||
edge.parallel = dw_edge
|
||||
dw_edge.parallel = edge
|
||||
|
||||
|
||||
OWEdgeGroups = {
|
||||
#(IsStandard, World, EdgeAxis, Terrain, HasParallel, NumberInGroup, CustomizerGroup)
|
||||
|
||||
@@ -8,7 +8,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
|
||||
from OverworldGlitchRules import create_owg_connections
|
||||
from Utils import bidict
|
||||
|
||||
version_number = '0.6.1.11'
|
||||
version_number = '0.7.0.0'
|
||||
# branch indicator is intentionally different across branches
|
||||
version_branch = ''
|
||||
|
||||
@@ -114,37 +114,38 @@ def link_overworld(world, player):
|
||||
|
||||
# restructure Maze Race/Suburb/Frog/Dig Game manually due to NP/P relationship
|
||||
parallel_links_new = bidict(parallel_links) # shallow copy is enough (deep copy is broken)
|
||||
if world.owKeepSimilar[player]:
|
||||
del parallel_links_new['Maze Race ES']
|
||||
del parallel_links_new['Kakariko Suburb WS']
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, parallel, _, custom) = group
|
||||
if parallel == IsParallel.Yes:
|
||||
if world.owLayout[player] != 'grid':
|
||||
if world.owKeepSimilar[player]:
|
||||
del parallel_links_new['Maze Race ES']
|
||||
del parallel_links_new['Kakariko Suburb WS']
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, parallel, _, custom) = group
|
||||
if parallel == IsParallel.Yes:
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Maze Race ES'] in forward_edges:
|
||||
forward_edges.remove(['Maze Race ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES'])
|
||||
if ['Kakariko Suburb WS'] in back_edges:
|
||||
back_edges.remove(['Kakariko Suburb WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
else:
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, _, _, custom) = group
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Maze Race ES'] in forward_edges:
|
||||
forward_edges.remove(['Maze Race ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Maze Race ES'])
|
||||
if ['Kakariko Suburb WS'] in back_edges:
|
||||
back_edges.remove(['Kakariko Suburb WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Kakariko Suburb WS'])
|
||||
if ['Dig Game EC', 'Dig Game ES'] in forward_edges:
|
||||
forward_edges.remove(['Dig Game EC', 'Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC'])
|
||||
if ['Frog WC', 'Frog WS'] in back_edges:
|
||||
back_edges.remove(['Frog WC', 'Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
else:
|
||||
for group in trimmed_groups.keys():
|
||||
(std, region, axis, terrain, _, _, custom) = group
|
||||
(forward_edges, back_edges) = trimmed_groups[group]
|
||||
if ['Dig Game EC', 'Dig Game ES'] in forward_edges:
|
||||
forward_edges.remove(['Dig Game EC', 'Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][0].append(['Dig Game ES'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][0].append(['Dig Game EC'])
|
||||
if ['Frog WC', 'Frog WS'] in back_edges:
|
||||
back_edges.remove(['Frog WC', 'Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.Yes, 1, custom)][1].append(['Frog WS'])
|
||||
trimmed_groups[(std, region, axis, terrain, IsParallel.No, 1, custom)][1].append(['Frog WC'])
|
||||
trimmed_groups[group] = (forward_edges, back_edges)
|
||||
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)
|
||||
|
||||
@@ -232,7 +233,7 @@ def link_overworld(world, player):
|
||||
if 'undefined_chance' in custom_crossed:
|
||||
undefined_chance = custom_crossed['undefined_chance']
|
||||
|
||||
if limited_crossed > -1:
|
||||
if limited_crossed > -1 and world.owLayout[player] != 'grid':
|
||||
# connect forced crossed non-parallel edges based on previously determined tile flips
|
||||
for edge in swapped_edges:
|
||||
if edge not in parallel_links_new:
|
||||
@@ -264,10 +265,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 and world.owLayout[player] != 'grid') 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 +279,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 +413,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 +426,15 @@ 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':
|
||||
from source.overworld.LayoutGenerator import generate_random_grid_layout
|
||||
|
||||
for exitname, destname in special_screen_connections:
|
||||
connect_two_way(world, exitname, destname, player, connected_edges)
|
||||
|
||||
generate_random_grid_layout(world, player, connected_edges, ow_crossed_tiles if world.owCrossed[player] == 'grouped' else [], force_noncrossed, force_crossed, limited_crossed, undefined_chance / 100)
|
||||
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)
|
||||
|
||||
@@ -576,7 +584,10 @@ def link_overworld(world, player):
|
||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
||||
|
||||
if world.owFluteShuffle[player] == 'vanilla':
|
||||
connect_flutes(default_flute_connections)
|
||||
flute_spots = default_flute_connections.copy()
|
||||
sort_flute_spots(world, player, flute_spots)
|
||||
world.owflutespots[player] = flute_spots
|
||||
connect_flutes(flute_spots)
|
||||
else:
|
||||
flute_spots = 8
|
||||
flute_pool = list(flute_data.keys())
|
||||
@@ -701,9 +712,9 @@ def link_overworld(world, player):
|
||||
|
||||
region_total -= sector[0]
|
||||
flute_spots -= spots_to_place
|
||||
|
||||
|
||||
# connect new flute spots
|
||||
new_spots.sort()
|
||||
sort_flute_spots(world, player, new_spots)
|
||||
world.owflutespots[player] = new_spots
|
||||
connect_flutes(new_spots)
|
||||
|
||||
@@ -822,7 +833,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,13 +849,13 @@ 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):
|
||||
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):
|
||||
def connect_two_way(world, edgename1, edgename2, player, connected_edges=None, set_spoiler=True):
|
||||
edge1 = world.get_entrance(edgename1, player)
|
||||
edge2 = world.get_entrance(edgename2, player)
|
||||
x = world.get_owedge(edgename1, player)
|
||||
@@ -868,7 +879,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 set_spoiler and (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 +887,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 +976,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 +1082,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 +1178,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 +1243,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
|
||||
@@ -1294,6 +1312,15 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
||||
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
||||
return groups
|
||||
|
||||
def sort_flute_spots(world, player, flute_spots):
|
||||
if world.owLayout[player] != 'grid':
|
||||
flute_spots.sort(key=lambda id: flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04)
|
||||
else:
|
||||
world_layout = world.owgrid[player][0] if world.mode[player] != 'inverted' else world.owgrid[player][1]
|
||||
layout_list = sum(world_layout, [])
|
||||
layout_map = {id & 0xBF: i for i, id in enumerate(layout_list)}
|
||||
flute_spots.sort(key=lambda id: layout_map[flute_data[id][1] if id != 0x03 or not world.is_tile_swapped(0x03, player) else 0x04])
|
||||
|
||||
def create_dynamic_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)
|
||||
if not flute_in_pool:
|
||||
@@ -2327,6 +2354,11 @@ parallelsimilar_connections = [('Maze Race ES', 'Kakariko Suburb WS'),
|
||||
('Dig Game ES', 'Frog WS')
|
||||
]
|
||||
|
||||
special_screen_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
|
||||
('Stone Bridge WC', 'Hobo EC'),
|
||||
('Zora Waterfall NE', 'Zoras Domain SW')
|
||||
]
|
||||
|
||||
# non shuffled overworld
|
||||
default_connections = [('Lost Woods NW', 'Master Sword Meadow SC'),
|
||||
('Lost Woods SW', 'Lost Woods Pass NW'),
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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
|
||||
|
||||
28
README.md
28
README.md
@@ -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
|
||||
```
|
||||
@@ -424,6 +434,12 @@ This gives each OW tile a random chance to be flipped to the opposite world
|
||||
|
||||
For randomizing the flute spots around the overworld
|
||||
|
||||
```
|
||||
--ow_no_fog
|
||||
```
|
||||
|
||||
With OW Grid Layout Shuffle or Mixed, this disables the fog that prevents you from seeing unvisited screens on the overworld map.
|
||||
|
||||
```
|
||||
--shuffle_followers
|
||||
```
|
||||
|
||||
82
Rom.py
82
Rom.py
@@ -512,14 +512,15 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
|
||||
# patch flute spots
|
||||
owFlags = 0
|
||||
if world.owFluteShuffle[player] == 'vanilla':
|
||||
owFog = 0
|
||||
if world.owFluteShuffle[player] == 'vanilla' and world.owLayout[player] != 'grid':
|
||||
flute_spots = default_flute_connections
|
||||
else:
|
||||
flute_spots = world.owflutespots[player]
|
||||
owFlags |= 0x0100
|
||||
write_int16(rom, snes_to_pc(0x0AB7F7), 0xEAEA)
|
||||
|
||||
flute_writes = sorted([(f, flute_data[f][1]) for f in flute_spots], key = lambda f: f[1])
|
||||
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
||||
for o in range(0, len(flute_writes)):
|
||||
owid = flute_writes[o][0]
|
||||
offset = 0
|
||||
@@ -544,26 +545,52 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
write_int16(rom, snes_to_pc(0x02E937 + (o * 2)), data[base_index + 8]) # cam X
|
||||
write_int16(rom, snes_to_pc(0x02E959 + (o * 2)), data[base_index + 9]) # unknown 1
|
||||
write_int16(rom, snes_to_pc(0x02E97B + (o * 2)), data[base_index + 10]) # unknown 2
|
||||
rom.write_byte(snes_to_pc(0x0AB783 + o), data[base_index + 12] & 0xff) # flute menu blip - X low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB78B + o), data[base_index + 12] // 0x100) # flute menu blip - X high byte
|
||||
rom.write_byte(snes_to_pc(0x0AB793 + o), data[base_index + 11] & 0xff) # flute menu blip - Y low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB79B + o), data[base_index + 11] // 0x100) # flute menu blip - Y high byte
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, data[base_index + 12], data[base_index + 11], world.mode[player] == 'inverted')
|
||||
rom.write_byte(snes_to_pc(0x0AB783 + o), map_x & 0xff) # flute menu blip - X low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB78B + o), map_x // 0x100) # flute menu blip - X high byte
|
||||
rom.write_byte(snes_to_pc(0x0AB793 + o), map_y & 0xff) # flute menu blip - Y low byte
|
||||
rom.write_byte(snes_to_pc(0x0AB79B + o), map_y // 0x100) # flute menu blip - Y high byte
|
||||
|
||||
# patch whirlpools
|
||||
if world.owWhirlpoolShuffle[player]:
|
||||
owFlags |= 0x01
|
||||
write_int16s(rom, snes_to_pc(0x02EA5C), world.owwhirlpools[player])
|
||||
|
||||
|
||||
# set custom overworld map layout and fog
|
||||
if world.owLayout[player] == 'grid':
|
||||
owFlags |= 0x06
|
||||
owFog = 1 if world.owParallel[player] else 2
|
||||
grid = world.owgrid[player]
|
||||
all_rows = grid[0] + grid[1]
|
||||
all_cells = sum(all_rows, [])
|
||||
rom.write_bytes(0x153C80, all_cells)
|
||||
for pos, cell_id in enumerate(sum(grid[0], [])):
|
||||
rom.write_byte(0x153D00 + cell_id % 0x40, pos)
|
||||
for pos, cell_id in enumerate(sum(grid[1], [])):
|
||||
rom.write_byte(0x153D40 + cell_id % 0x40, pos)
|
||||
elif world.owMixed[player]:
|
||||
owFlags |= 0x02
|
||||
owFog = 1
|
||||
large_screen_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35, 0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
for cell_id in range(0x80):
|
||||
if cell_id - 0x01 in large_screen_ids:
|
||||
screen_id = cell_id - 0x01
|
||||
elif cell_id - 0x08 in large_screen_ids:
|
||||
screen_id = cell_id - 0x08
|
||||
elif cell_id - 0x09 in large_screen_ids:
|
||||
screen_id = cell_id - 0x09
|
||||
else:
|
||||
screen_id = cell_id
|
||||
world_flag = 0x40 if screen_id in world.owswaps[player][0] else 0x00
|
||||
rom.write_byte(0x153C80 + cell_id, cell_id ^ world_flag)
|
||||
|
||||
# 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
|
||||
@@ -598,10 +625,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
rom.write_byte(0x1539B0 + b + 9, world_flag)
|
||||
|
||||
for edge in world.owedges:
|
||||
if edge.dest is not None and isinstance(edge.dest, OWEdge) and edge.player == player:
|
||||
if edge.player == player:
|
||||
write_int16(rom, edge.getAddress() + 0x0a, edge.vramLoc)
|
||||
if not edge.specialExit:
|
||||
rom.write_byte(0x1539A0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, edge.getTarget())
|
||||
destination = edge.getTarget() if edge.dest is not None and isinstance(edge.dest, OWEdge) else 0xFF
|
||||
rom.write_byte(0x1539A0 + (edge.specialID - 0x80) * 2 if edge.specialEntrance else edge.getAddress() + 0x0e, destination)
|
||||
|
||||
# patch bonk prizes
|
||||
if world.shuffle_bonk_drops[player]:
|
||||
@@ -630,6 +658,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
|
||||
write_int16(rom, 0x150002, owMode)
|
||||
write_int16(rom, 0x150004, owFlags)
|
||||
write_int16(rom, 0x150008, owFog if world.owFog[player] else 0x00)
|
||||
|
||||
# patch entrance/exits/holes
|
||||
for region in world.regions:
|
||||
@@ -1425,10 +1454,14 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
y_map_position = [0x06E0, 0x0E50, 0xFF00, 0x0FD0, 0x06E0, 0x0D80, 0x0160, 0x0E80, 0x0130, 0x0840, 0x01B0]
|
||||
idx = ent
|
||||
owid = owid_map[idx]
|
||||
map_x = x_map_position[idx]
|
||||
map_y = y_map_position[idx]
|
||||
if owid != 0xFF:
|
||||
if (owid < 0x40) == (world.is_tile_swapped(owid, player)):
|
||||
coord_flags |= 0x8000 # world indicator flag
|
||||
return (coord_flags | x_map_position[idx], y_map_position[idx])
|
||||
if coord_flags & 0x4000 == 0:
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
||||
return (coord_flags | map_x, map_y)
|
||||
elif type(ent) is Location:
|
||||
from OverworldShuffle import OWTileRegions, ow_loc_prize_table
|
||||
if ent.name in ow_loc_prize_table:
|
||||
@@ -1449,8 +1482,10 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
||||
coords = (door_addresses[ent.name][1][6], door_addresses[ent.name][1][5])
|
||||
else:
|
||||
raise Exception(f"No overworld map coordinates for entrance {ent.name}")
|
||||
coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | coords[0], coords[1])
|
||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, coords[0], coords[1], ent.parent_region.type == RegionType.DarkWorld)
|
||||
coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | map_x, map_y)
|
||||
return coords
|
||||
|
||||
if world.overworld_map[player] == 'default':
|
||||
# disable HC/AT/GT icons
|
||||
if not world.owMixed[player]:
|
||||
@@ -2399,7 +2434,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:
|
||||
@@ -2417,7 +2452,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)
|
||||
@@ -3001,6 +3036,13 @@ def update_compasses(rom, dungeon_locations, world, player):
|
||||
if not provided_dungeon:
|
||||
rom.write_byte(0x186FFF, 0xff)
|
||||
|
||||
def adjust_ow_coordinates_to_layout(world, player, x, y, dw_flag):
|
||||
if world.owLayout[player] != 'grid':
|
||||
return (x, y)
|
||||
layout_map = world.owlayoutmap_dw[player] if dw_flag else world.owlayoutmap_lw[player]
|
||||
original_slot_id = ((y // 0x0200) % 0x08) * 0x08 + ((x // 0x0200) % 0x08)
|
||||
new_slot_id = layout_map[original_slot_id]
|
||||
return ((new_slot_id % 0x08) * 0x0200 + x % 0x0200, ((new_slot_id // 0x08) % 0x08) * 0x0200 + y % 0x0200)
|
||||
|
||||
|
||||
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',
|
||||
|
||||
BIN
data/overworld/darkworld.png
Normal file
BIN
data/overworld/darkworld.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
BIN
data/overworld/lightworld.png
Normal file
BIN
data/overworld/lightworld.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
@@ -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
|
||||
|
||||
@@ -253,6 +253,14 @@ someDescription:
|
||||
- Stone Bridge WS*
|
||||
```
|
||||
|
||||
### ow-grid
|
||||
|
||||
`grid` contains additional options that only have an effect when `ow_layout` is set to `grid`.
|
||||
|
||||
#### wrap_horizontal / wrap_vertical
|
||||
|
||||
Set these to `true` to allow for overworld edge transitions to wrap from one side of a world to the opposite side. With `wrap_horizontal`, there can be east transitions on the eastern edge of the world map that send the player to the western edge of the world. With `wrap_vertical`, there can be south transitions on the southern edge of the world map that send the player to the northern edge of the world.
|
||||
|
||||
### ow-crossed
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_crossed` enabled in the `settings` section in order for any values here to take effect. This section has four primary subsections: `force_crossed`, `force_noncrossed`, `limit_crossed`, and `undefined_chance`. There are also
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_keepsimilar: false
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
settings:
|
||||
1:
|
||||
ow_shuffle: full
|
||||
ow_layout: wild
|
||||
ow_parallel: false
|
||||
ow_terrain: true
|
||||
ow-edges:
|
||||
1:
|
||||
|
||||
@@ -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"
|
||||
@@ -208,6 +224,15 @@
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"ow_fog": {
|
||||
"action": "store_true",
|
||||
"help": "suppress",
|
||||
"type": "bool"
|
||||
},
|
||||
"ow_no_fog": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"shuffle": {
|
||||
"choices": [
|
||||
"vanilla",
|
||||
|
||||
@@ -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": [
|
||||
@@ -276,6 +280,9 @@
|
||||
" spots from being on any adjacent screen.",
|
||||
"Random: New flute spots will be generated with minimal bias."
|
||||
],
|
||||
"ow_no_fog": [
|
||||
"With OW Grid Layout Shuffle or Mixed, this disables the fog that prevents",
|
||||
"you from seeing unvisited screens on the overworld map." ],
|
||||
"door_shuffle": [
|
||||
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
||||
"Basic: Doors are mixed within a single dungeon.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -181,6 +183,8 @@
|
||||
"randomizer.overworld.overworldflute.balanced": "Balanced",
|
||||
"randomizer.overworld.overworldflute.random": "Random",
|
||||
|
||||
"randomizer.overworld.fog": "Overworld Map Fog",
|
||||
|
||||
|
||||
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
||||
"randomizer.entrance.openpyramid.auto": "Auto",
|
||||
|
||||
@@ -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",
|
||||
@@ -45,22 +48,27 @@
|
||||
"config": {
|
||||
"pady": [20,0]
|
||||
}
|
||||
},
|
||||
"fog": {
|
||||
"type": "checkbox",
|
||||
"default": true,
|
||||
"config": {
|
||||
"pady": [20,0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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':
|
||||
@@ -100,6 +101,7 @@ class CustomSettings(object):
|
||||
args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p])
|
||||
args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p])
|
||||
args.ow_fluteshuffle[p] = get_setting(settings['ow_fluteshuffle'], args.ow_fluteshuffle[p])
|
||||
args.ow_fog[p] = get_setting(settings['ow_fog'], args.ow_fog[p])
|
||||
args.shuffle_followers[p] = get_setting(settings['shuffle_followers'], args.shuffle_followers[p])
|
||||
args.bonk_drops[p] = get_setting(settings['bonk_drops'], args.bonk_drops[p])
|
||||
args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p])
|
||||
@@ -135,6 +137,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])
|
||||
@@ -253,6 +263,11 @@ class CustomSettings(object):
|
||||
return self.file_source['ow-edges']
|
||||
return None
|
||||
|
||||
def get_owgrid(self):
|
||||
if 'ow-grid' in self.file_source:
|
||||
return self.file_source['ow-grid']
|
||||
return None
|
||||
|
||||
def get_owcrossed(self):
|
||||
if 'ow-crossed' in self.file_source:
|
||||
return self.file_source['ow-crossed']
|
||||
@@ -356,13 +371,15 @@ 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]
|
||||
settings_dict[p]['ow_mixed'] = world.owMixed[p]
|
||||
settings_dict[p]['ow_whirlpool'] = world.owWhirlpoolShuffle[p]
|
||||
settings_dict[p]['ow_fluteshuffle'] = world.owFluteShuffle[p]
|
||||
settings_dict[p]['ow_fog'] = world.owFog[p]
|
||||
settings_dict[p]['shuffle_followers'] = world.shuffle_followers[p]
|
||||
settings_dict[p]['bonk_drops'] = world.shuffle_bonk_drops[p]
|
||||
settings_dict[p]['shuffle'] = world.shuffle[p]
|
||||
|
||||
@@ -92,13 +92,15 @@ SETTINGSTOPROCESS = {
|
||||
"bombbag": "bombbag"
|
||||
},
|
||||
"overworld": {
|
||||
"overworldshuffle": "ow_shuffle",
|
||||
"layout": "ow_layout",
|
||||
"parallel": "ow_parallel",
|
||||
"terrain": "ow_terrain",
|
||||
"crossed": "ow_crossed",
|
||||
"keepsimilar": "ow_keepsimilar",
|
||||
"mixed": "ow_mixed",
|
||||
"whirlpool": "ow_whirlpool",
|
||||
"overworldflute": "ow_fluteshuffle"
|
||||
"overworldflute": "ow_fluteshuffle",
|
||||
"fog": "ow_fog"
|
||||
},
|
||||
"entrance": {
|
||||
"entranceshuffle": "shuffle",
|
||||
|
||||
@@ -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.')
|
||||
|
||||
1423
source/overworld/LayoutGenerator.py
Normal file
1423
source/overworld/LayoutGenerator.py
Normal file
File diff suppressed because it is too large
Load Diff
473
source/overworld/LayoutVisualizer.py
Normal file
473
source/overworld/LayoutVisualizer.py
Normal file
@@ -0,0 +1,473 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
from PIL import Image, ImageDraw
|
||||
from BaseClasses import Direction, OWEdge
|
||||
from source.overworld.LayoutGenerator import Screen
|
||||
|
||||
def get_edge_lists(grid: List[List[List[int]]],
|
||||
overworld_screens: Dict[int, Screen],
|
||||
large_screen_quadrant_info: Dict[int, Dict]) -> Dict:
|
||||
"""
|
||||
Get list of edges for each cell and direction.
|
||||
|
||||
Args:
|
||||
grid: 3D list [world][row][col] containing screen IDs
|
||||
overworld_screens: Dict of screen_id -> Screen objects
|
||||
large_screen_quadrant_info: Dict of screen_id -> quadrant info for large screens
|
||||
|
||||
Returns:
|
||||
Dict mapping (world, row, col, direction) -> list of edges
|
||||
Each edge has a .dest property (None if unconnected)
|
||||
"""
|
||||
GRID_SIZE = 8
|
||||
edge_lists = {}
|
||||
|
||||
# Large screen base IDs
|
||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
|
||||
for world_idx in range(2):
|
||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
# Empty cell - no edges
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
continue
|
||||
|
||||
screen = overworld_screens.get(screen_id)
|
||||
if not screen:
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell is
|
||||
# Find all positions of this large screen and determine quadrant
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
|
||||
# Determine quadrant by finding relative position
|
||||
# The quadrant is determined by which cells are adjacent
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Get edges for this quadrant
|
||||
if screen_id in large_screen_quadrant_info:
|
||||
quad_info = large_screen_quadrant_info[screen_id]
|
||||
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edges = quad_info.get(quadrant, {}).get(direction, [])
|
||||
edge_lists[(world_idx, row, col, direction)] = edges
|
||||
else:
|
||||
# No quadrant info - no edges
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edge_lists[(world_idx, row, col, direction)] = []
|
||||
else:
|
||||
# Small screen - get edges directly
|
||||
for direction in [Direction.North, Direction.South, Direction.East, Direction.West]:
|
||||
edges_in_dir = [e for e in screen.edges.values() if e.direction == direction]
|
||||
edge_lists[(world_idx, row, col, direction)] = edges_in_dir
|
||||
|
||||
return edge_lists
|
||||
|
||||
def determine_large_screen_quadrant(row: int, col: int, positions: List[tuple], grid_size: int) -> str:
|
||||
"""
|
||||
Determine which quadrant (NW, NE, SW, SE) a cell is in for a large screen.
|
||||
Handles wrapping correctly by checking adjacency patterns.
|
||||
|
||||
Args:
|
||||
row: Current cell row
|
||||
col: Current cell column
|
||||
positions: List of all (row, col) positions for this large screen
|
||||
grid_size: Size of the grid (8)
|
||||
|
||||
Returns:
|
||||
Quadrant string: "NW", "NE", "SW", or "SE"
|
||||
"""
|
||||
positions_set = set(positions)
|
||||
|
||||
# Check which adjacent cells also belong to this large screen
|
||||
has_right = ((row, (col + 1) % grid_size) in positions_set)
|
||||
has_below = (((row + 1) % grid_size, col) in positions_set)
|
||||
has_left = ((row, (col - 1) % grid_size) in positions_set)
|
||||
has_above = (((row - 1) % grid_size, col) in positions_set)
|
||||
|
||||
# Determine quadrant based on adjacency
|
||||
# NW: has right and below neighbors
|
||||
# NE: has left and below neighbors
|
||||
# SW: has right and above neighbors
|
||||
# SE: has left and above neighbors
|
||||
|
||||
if has_right and has_below:
|
||||
return "NW"
|
||||
elif has_left and has_below:
|
||||
return "NE"
|
||||
elif has_right and has_above:
|
||||
return "SW"
|
||||
elif has_left and has_above:
|
||||
return "SE"
|
||||
else:
|
||||
raise Exception("?")
|
||||
|
||||
def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool:
|
||||
if edge.dest is None:
|
||||
return False
|
||||
|
||||
source_screen = overworld_screens.get(edge.owIndex)
|
||||
dest_screen = overworld_screens.get(edge.dest.owIndex)
|
||||
return source_screen.dark_world != dest_screen.dark_world
|
||||
|
||||
def visualize_layout(grid: List[List[List[int]]], output_dir: str,
|
||||
overworld_screens: Dict[int, Screen],
|
||||
large_screen_quadrant_info: Dict[int, Dict]) -> None:
|
||||
# Constants
|
||||
GRID_SIZE = 8
|
||||
BORDER_WIDTH = 1
|
||||
OUTPUT_CELL_SIZE = 64 # Each cell in output is always 64x64 pixels
|
||||
|
||||
# Load the world images
|
||||
try:
|
||||
lightworld_img = Image.open("data/overworld/lightworld.png")
|
||||
darkworld_img = Image.open("data/overworld/darkworld.png")
|
||||
except FileNotFoundError as e:
|
||||
raise FileNotFoundError(f"World image not found: {e}. Ensure lightworld.png and darkworld.png are in the data/overworld directory.")
|
||||
|
||||
# Calculate source cell size from the base images
|
||||
# Each world image is 8x8 screens, so divide by 8 to get source cell size
|
||||
img_width, _ = lightworld_img.size
|
||||
SOURCE_CELL_SIZE = img_width // GRID_SIZE # Size of each cell in the source image
|
||||
|
||||
# Calculate dimensions for the output (always based on 64x64 cells)
|
||||
world_width = GRID_SIZE * OUTPUT_CELL_SIZE
|
||||
world_height = GRID_SIZE * OUTPUT_CELL_SIZE
|
||||
|
||||
# Create output image (two worlds side by side with a small gap)
|
||||
gap = 32
|
||||
output_width = world_width * 2 + gap
|
||||
output_height = world_height
|
||||
output_img = Image.new('RGB', (output_width, output_height), color='black')
|
||||
|
||||
# Large screen base IDs (defined once for reuse)
|
||||
large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35,
|
||||
0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75]
|
||||
|
||||
# Process both worlds
|
||||
for world_idx in range(2):
|
||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||
|
||||
# Build a map of screen_id -> list of (row, col) positions for large screens
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
# Process each cell in the grid individually
|
||||
# This handles wrapped large screens correctly by drawing each quadrant separately
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
# Empty cell - fill with black (already black from initialization)
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
# Calculate source position in the world image
|
||||
source_row = (screen_id % 0x40) >> 3
|
||||
source_col = screen_id % 0x08
|
||||
world_img = lightworld_img if screen_id < 0x40 else darkworld_img
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell represents
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Map quadrant to source offset within the 2x2 large screen
|
||||
quadrant_offsets = {
|
||||
"NW": (0, 0),
|
||||
"NE": (1, 0),
|
||||
"SW": (0, 1),
|
||||
"SE": (1, 1)
|
||||
}
|
||||
q_col_offset, q_row_offset = quadrant_offsets[quadrant]
|
||||
|
||||
# Calculate source position for this quadrant
|
||||
source_x = (source_col + q_col_offset) * SOURCE_CELL_SIZE
|
||||
source_y = (source_row + q_row_offset) * SOURCE_CELL_SIZE
|
||||
|
||||
# Crop single cell from source (the specific quadrant)
|
||||
cropped = world_img.crop((
|
||||
source_x,
|
||||
source_y,
|
||||
source_x + SOURCE_CELL_SIZE,
|
||||
source_y + SOURCE_CELL_SIZE
|
||||
))
|
||||
else:
|
||||
# Small screen (1x1)
|
||||
source_x = source_col * SOURCE_CELL_SIZE
|
||||
source_y = source_row * SOURCE_CELL_SIZE
|
||||
|
||||
# Crop single cell from source
|
||||
cropped = world_img.crop((
|
||||
source_x,
|
||||
source_y,
|
||||
source_x + SOURCE_CELL_SIZE,
|
||||
source_y + SOURCE_CELL_SIZE
|
||||
))
|
||||
|
||||
# Resize to output size (64x64 pixels)
|
||||
resized = cropped.resize(
|
||||
(OUTPUT_CELL_SIZE, OUTPUT_CELL_SIZE),
|
||||
Image.LANCZOS
|
||||
)
|
||||
|
||||
# Paste into output at grid position
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
output_img.paste(resized, (dest_x, dest_y))
|
||||
|
||||
edge_lists = get_edge_lists(grid, overworld_screens, large_screen_quadrant_info)
|
||||
|
||||
# Draw borders and edge connection indicators after all screens are placed
|
||||
draw = ImageDraw.Draw(output_img)
|
||||
|
||||
# Size of the indicator squares
|
||||
INDICATOR_SIZE = 12
|
||||
|
||||
for world_idx in range(2):
|
||||
x_offset = 0 if world_idx == 0 else (world_width + gap)
|
||||
|
||||
# Build large screen positions map for this world
|
||||
large_screen_positions = {}
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id != -1 and screen_id in large_screen_base_ids:
|
||||
if screen_id not in large_screen_positions:
|
||||
large_screen_positions[screen_id] = []
|
||||
large_screen_positions[screen_id].append((row, col))
|
||||
|
||||
# Draw borders for each cell
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
|
||||
if screen_id == -1:
|
||||
continue
|
||||
|
||||
is_large = screen_id in large_screen_base_ids
|
||||
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
|
||||
if is_large:
|
||||
# For large screens, determine which quadrant this cell is
|
||||
positions = large_screen_positions.get(screen_id, [(row, col)])
|
||||
quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE)
|
||||
|
||||
# Draw border only on the outer edges of the large screen
|
||||
# (not on internal edges between quadrants)
|
||||
# NW: draw top and left borders
|
||||
# NE: draw top and right borders
|
||||
# SW: draw bottom and left borders
|
||||
# SE: draw bottom and right borders
|
||||
|
||||
if quadrant in ["NW", "NE"]:
|
||||
# Draw top border
|
||||
draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["SW", "SE"]:
|
||||
# Draw bottom border
|
||||
draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["NW", "SW"]:
|
||||
# Draw left border
|
||||
draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
if quadrant in ["NE", "SE"]:
|
||||
# Draw right border
|
||||
draw.line([(dest_x + OUTPUT_CELL_SIZE - 1, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH)
|
||||
else:
|
||||
# Small screen - draw border around single cell
|
||||
draw.rectangle(
|
||||
[dest_x, dest_y, dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1],
|
||||
outline='black',
|
||||
width=BORDER_WIDTH
|
||||
)
|
||||
|
||||
# Draw edge connection indicators for each cell
|
||||
for row in range(GRID_SIZE):
|
||||
for col in range(GRID_SIZE):
|
||||
screen_id = grid[world_idx][row][col]
|
||||
if screen_id == -1:
|
||||
continue
|
||||
|
||||
dest_x = x_offset + col * OUTPUT_CELL_SIZE
|
||||
dest_y = row * OUTPUT_CELL_SIZE
|
||||
|
||||
# Draw indicator for each direction (only if edges exist)
|
||||
# Use bright colors for visibility
|
||||
GREEN = (0, 255, 0) # Bright green
|
||||
YELLOW = (255, 255, 0) # Bright yellow
|
||||
RED = (255, 0, 0) # Bright red
|
||||
|
||||
# North indicators - positioned based on edge midpoint
|
||||
north_edges = edge_lists.get((world_idx, row, col, Direction.North), [])
|
||||
if north_edges:
|
||||
north_y = dest_y # Touch the top border
|
||||
|
||||
for edge in north_edges:
|
||||
# For north/south edges, midpoint gives the X coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_x_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_x = dest_x + edge_x_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in north_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[edge_x, north_y, edge_x + INDICATOR_SIZE - 1, north_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[edge_x, north_y, edge_x + INDICATOR_SIZE - 1, north_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[edge_x + INDICATOR_SIZE - 1, north_y, edge_x, north_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# South indicators - positioned based on edge midpoint
|
||||
south_edges = edge_lists.get((world_idx, row, col, Direction.South), [])
|
||||
if south_edges:
|
||||
south_y = dest_y + OUTPUT_CELL_SIZE - INDICATOR_SIZE # Touch the bottom border
|
||||
|
||||
for edge in south_edges:
|
||||
# For north/south edges, midpoint gives the X coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_x_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_x = dest_x + edge_x_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in south_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[edge_x, south_y, edge_x + INDICATOR_SIZE - 1, south_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[edge_x, south_y, edge_x + INDICATOR_SIZE - 1, south_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[edge_x + INDICATOR_SIZE - 1, south_y, edge_x, south_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# West indicators - positioned based on edge midpoint
|
||||
west_edges = edge_lists.get((world_idx, row, col, Direction.West), [])
|
||||
if west_edges:
|
||||
west_x = dest_x # Touch the left border
|
||||
|
||||
for edge in west_edges:
|
||||
# For west/east edges, midpoint gives the Y coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_y_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_y = dest_y + edge_y_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in west_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[west_x, edge_y, west_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[west_x, edge_y, west_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[west_x + INDICATOR_SIZE - 1, edge_y, west_x, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# East indicators - positioned based on edge midpoint
|
||||
east_edges = edge_lists.get((world_idx, row, col, Direction.East), [])
|
||||
if east_edges:
|
||||
east_x = dest_x + OUTPUT_CELL_SIZE - INDICATOR_SIZE # Touch the right border
|
||||
|
||||
for edge in east_edges:
|
||||
# For west/east edges, midpoint gives the Y coordinate
|
||||
# Take midpoint modulo 0x0200, range 0-0x01FF maps to full side
|
||||
midpoint = edge.midpoint % 0x0200
|
||||
# Map from game coordinate range to pixel position
|
||||
edge_y_offset = (midpoint * OUTPUT_CELL_SIZE) // 0x0200
|
||||
edge_y = dest_y + edge_y_offset - INDICATOR_SIZE // 2
|
||||
|
||||
edge_color = GREEN if edge.dest is not None else YELLOW if any(e for e in east_edges if e.dest) else RED
|
||||
draw.rectangle(
|
||||
[east_x, edge_y, east_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill=edge_color,
|
||||
outline='black'
|
||||
)
|
||||
|
||||
# Draw diagonal cross if edge crosses between worlds
|
||||
if edge.dest is not None and is_crossed_edge(edge, overworld_screens):
|
||||
draw.line(
|
||||
[east_x, edge_y, east_x + INDICATOR_SIZE - 1, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
draw.line(
|
||||
[east_x + INDICATOR_SIZE - 1, edge_y, east_x, edge_y + INDICATOR_SIZE - 1],
|
||||
fill='black',
|
||||
width=1
|
||||
)
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"layout_{timestamp}.png"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
# Save the image
|
||||
output_img.save(filepath, "PNG")
|
||||
logging.getLogger('').info(f"Layout visualization saved to {filepath}")
|
||||
@@ -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')
|
||||
@@ -131,6 +139,7 @@ def roll_settings(weights):
|
||||
ret.ow_whirlpool = get_choice_bool('whirlpool_shuffle')
|
||||
overworld_flute = get_choice('flute_shuffle')
|
||||
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
|
||||
ret.ow_fog = get_choice_bool('overworld_fog')
|
||||
ret.shuffle_followers = get_choice_bool('shuffle_followers')
|
||||
ret.bonk_drops = get_choice_bool('bonk_drops')
|
||||
entrance_shuffle = get_choice('entrance_shuffle')
|
||||
|
||||
Reference in New Issue
Block a user