Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 0.7.0.2
|
||||||
|
- Fixed money error for bombbag/take-anys
|
||||||
|
- Fixed palette issue for map check/flute menu
|
||||||
|
- Fixed issue with dungeon icons showing in opposite world in Inverted
|
||||||
|
- Fixed Links position in map checks when in Special OW areas
|
||||||
|
- Fixed buffered sword issue in OW Shuffle water transitions
|
||||||
|
- Added PlasmaKappa tribute TF room text
|
||||||
|
|
||||||
# 0.7.0.1
|
# 0.7.0.1
|
||||||
- Fixed buggy sprites in post-Aga Zora's Domain
|
- Fixed buggy sprites in post-Aga Zora's Domain
|
||||||
- Fixed L/R map switch when in special OW screens
|
- Fixed L/R map switch when in special OW screens
|
||||||
|
|||||||
2
Fill.py
2
Fill.py
@@ -1106,7 +1106,7 @@ def balance_money_progression(world):
|
|||||||
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
slot = shop_to_location_table[location.parent_region.name].index(location.name)
|
||||||
shop = location.parent_region.shop
|
shop = location.parent_region.shop
|
||||||
shop_item = shop.inventory[slot]
|
shop_item = shop.inventory[slot]
|
||||||
if location.item and interesting_item(location, location.item, world, location.item.player):
|
if shop_item and location.item and interesting_item(location, location.item, world, location.item.player):
|
||||||
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
if location.item.name.startswith('Rupee') and loc_player == location.item.player:
|
||||||
if shop_item['price'] < rupee_chart[location.item.name]:
|
if shop_item['price'] < rupee_chart[location.item.name]:
|
||||||
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block
|
||||||
|
|||||||
@@ -341,9 +341,10 @@ def generate_itempool(world, player):
|
|||||||
world.escape_assist[player].append('bombs')
|
world.escape_assist[player].append('bombs')
|
||||||
|
|
||||||
for (location, item) in placed_items.items():
|
for (location, item) in placed_items.items():
|
||||||
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
loc = world.get_location(location, player)
|
||||||
world.get_location(location, player).event = True
|
world.push_item(loc, ItemFactory(item, player), False)
|
||||||
world.get_location(location, player).locked = True
|
loc.event = True
|
||||||
|
loc.locked = True
|
||||||
|
|
||||||
if world.shopsanity[player] and not skip_pool_adjustments:
|
if world.shopsanity[player] and not skip_pool_adjustments:
|
||||||
for shop in world.shops[player]:
|
for shop in world.shops[player]:
|
||||||
|
|||||||
3
Main.py
3
Main.py
@@ -17,7 +17,8 @@ from OverworldGlitchRules import create_owg_connections
|
|||||||
from PotShuffle import shuffle_pots, shuffle_pot_switches
|
from PotShuffle import shuffle_pots, shuffle_pot_switches
|
||||||
from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations
|
from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations
|
||||||
from OWEdges import create_owedges
|
from OWEdges import create_owedges
|
||||||
from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_flute_exits, create_dynamic_mirror_exits
|
from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_mirror_exits
|
||||||
|
from source.overworld.FluteShuffle import create_dynamic_flute_exits
|
||||||
from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ from DungeonGenerator import GenerationException
|
|||||||
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
|
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
|
||||||
from Regions import mark_light_dark_world_regions
|
from Regions import mark_light_dark_world_regions
|
||||||
from source.overworld.EntranceShuffle2 import connect_simple
|
from source.overworld.EntranceShuffle2 import connect_simple
|
||||||
|
from source.overworld.FluteShuffle import shuffle_flute_spots, default_flute_connections, flute_data
|
||||||
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
|
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
|
||||||
from OverworldGlitchRules import create_owg_connections
|
from OverworldGlitchRules import create_owg_connections
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
version_number = '0.7.0.1'
|
version_number = '0.7.0.2'
|
||||||
# branch indicator is intentionally different across branches
|
# branch indicator is intentionally different across branches
|
||||||
version_branch = ''
|
version_branch = ''
|
||||||
|
|
||||||
@@ -336,6 +337,7 @@ def link_overworld(world, player):
|
|||||||
world.owcrossededges[player].extend(edge_set)
|
world.owcrossededges[player].extend(edge_set)
|
||||||
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
|
assert len(world.owcrossededges[player]) == len(set(world.owcrossededges[player])), "Same edge candidate added to crossed edges"
|
||||||
|
|
||||||
|
# TODO: Someday don't force parallels also? Keeping for now to ensure full reachability
|
||||||
for edge in copy.deepcopy(world.owcrossededges[player]):
|
for edge in copy.deepcopy(world.owcrossededges[player]):
|
||||||
if edge in parallel_links_new:
|
if edge in parallel_links_new:
|
||||||
if parallel_links_new[edge] not in world.owcrossededges[player]:
|
if parallel_links_new[edge] not in world.owcrossededges[player]:
|
||||||
@@ -574,169 +576,8 @@ def link_overworld(world, player):
|
|||||||
|
|
||||||
# flute shuffle
|
# flute shuffle
|
||||||
logging.getLogger('').debug('Shuffling flute spots')
|
logging.getLogger('').debug('Shuffling flute spots')
|
||||||
def connect_flutes(flute_destinations):
|
shuffle_flute_spots(world, player)
|
||||||
for o in range(0, len(flute_destinations)):
|
|
||||||
owid = flute_destinations[o]
|
|
||||||
regions = flute_data[owid][0]
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
|
|
||||||
else:
|
|
||||||
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
|
||||||
|
|
||||||
if world.owFluteShuffle[player] == 'vanilla':
|
|
||||||
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())
|
|
||||||
new_spots = list()
|
|
||||||
ignored_regions = set()
|
|
||||||
used_flute_regions = []
|
|
||||||
forbidden_spots = []
|
|
||||||
forbidden_regions = []
|
|
||||||
|
|
||||||
def addSpot(owid, ignore_proximity, forced):
|
|
||||||
if world.owFluteShuffle[player] == 'balanced':
|
|
||||||
def getIgnored(regionname, base_owid, owid):
|
|
||||||
region = world.get_region(regionname, player)
|
|
||||||
for exit in region.exits:
|
|
||||||
if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored:
|
|
||||||
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
|
|
||||||
new_ignored.add(exit.connected_region.name)
|
|
||||||
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
|
|
||||||
if regionname in one_way_ledges:
|
|
||||||
for ledge_region in one_way_ledges[regionname]:
|
|
||||||
if ledge_region not in new_ignored:
|
|
||||||
new_ignored.add(ledge_region)
|
|
||||||
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
|
|
||||||
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
new_region = flute_data[owid][0][0]
|
|
||||||
else:
|
|
||||||
new_region = flute_data[owid][0][1]
|
|
||||||
|
|
||||||
if new_region in ignored_regions and not forced:
|
|
||||||
return False
|
|
||||||
|
|
||||||
new_ignored = {new_region}
|
|
||||||
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
|
|
||||||
if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
|
||||||
return False
|
|
||||||
ignored_regions.update(new_ignored)
|
|
||||||
if owid in flute_pool:
|
|
||||||
flute_pool.remove(owid)
|
|
||||||
if ignore_proximity and not forced:
|
|
||||||
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
|
||||||
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
|
||||||
new_spots.append(owid)
|
|
||||||
else:
|
|
||||||
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
|
||||||
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
if world.customizer:
|
|
||||||
custom_spots = world.customizer.get_owflutespots()
|
|
||||||
if custom_spots and player in custom_spots:
|
|
||||||
if 'force' in custom_spots[player]:
|
|
||||||
for id in custom_spots[player]['force']:
|
|
||||||
owid = id & 0xBF
|
|
||||||
addSpot(owid, True, True)
|
|
||||||
flute_spots -= 1
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
used_flute_regions.append(flute_data[owid][0][0])
|
|
||||||
else:
|
|
||||||
used_flute_regions.append(flute_data[owid][0][1])
|
|
||||||
if 'forbid' in custom_spots[player]:
|
|
||||||
for id in custom_spots[player]['forbid']:
|
|
||||||
owid = id & 0xBF
|
|
||||||
if owid not in new_spots:
|
|
||||||
forbidden_spots.append(owid)
|
|
||||||
if not world.is_tile_swapped(owid, player):
|
|
||||||
forbidden_regions.append(flute_data[owid][0][0])
|
|
||||||
else:
|
|
||||||
forbidden_regions.append(flute_data[owid][0][1])
|
|
||||||
|
|
||||||
# determine sectors (isolated groups of regions) to place flute spots
|
|
||||||
flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
|
|
||||||
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
|
|
||||||
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
|
|
||||||
region_total = sum([c for c,_ in flute_sectors])
|
|
||||||
sector_total = len(flute_sectors)
|
|
||||||
empty_sector_total = 0
|
|
||||||
sector_has_spot = []
|
|
||||||
|
|
||||||
# determine which sectors still need a flute spot
|
|
||||||
for sector in flute_sectors:
|
|
||||||
already_has_spot = any(region in sector for region in used_flute_regions)
|
|
||||||
sector_has_spot.append(already_has_spot)
|
|
||||||
if not already_has_spot:
|
|
||||||
empty_sector_total += 1
|
|
||||||
if flute_spots < empty_sector_total:
|
|
||||||
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
|
|
||||||
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
|
|
||||||
for i in range(len(flute_sectors)):
|
|
||||||
if not sector_has_spot[i]:
|
|
||||||
sector_has_spot[i] = True
|
|
||||||
empty_sector_total -= 1
|
|
||||||
if flute_spots == empty_sector_total:
|
|
||||||
break
|
|
||||||
|
|
||||||
# distribute flute spots for each sector
|
|
||||||
for i in range(len(flute_sectors)):
|
|
||||||
sector = flute_sectors[i]
|
|
||||||
sector_total -= 1
|
|
||||||
if not sector_has_spot[i]:
|
|
||||||
empty_sector_total -= 1
|
|
||||||
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
|
||||||
target_spots = len(new_spots) + spots_to_place
|
|
||||||
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
|
||||||
|
|
||||||
if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
|
|
||||||
addSpot(0x30, True, True) # guarantee desert/mire access
|
|
||||||
|
|
||||||
random.shuffle(sector[1])
|
|
||||||
f = 0
|
|
||||||
t = 0
|
|
||||||
while len(new_spots) < target_spots:
|
|
||||||
if f >= len(sector[1]):
|
|
||||||
f = 0
|
|
||||||
t += 1
|
|
||||||
if t > 5:
|
|
||||||
raise GenerationException('Infinite loop detected in flute shuffle')
|
|
||||||
owid = flute_regions[sector[1][f]]
|
|
||||||
if owid not in new_spots and owid not in forbidden_spots:
|
|
||||||
addSpot(owid, t > 0, False)
|
|
||||||
f += 1
|
|
||||||
|
|
||||||
region_total -= sector[0]
|
|
||||||
flute_spots -= spots_to_place
|
|
||||||
|
|
||||||
# connect new flute spots
|
|
||||||
sort_flute_spots(world, player, new_spots)
|
|
||||||
world.owflutespots[player] = new_spots
|
|
||||||
connect_flutes(new_spots)
|
|
||||||
|
|
||||||
# update spoiler
|
|
||||||
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
|
|
||||||
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
|
||||||
s[0x00], s[0x03], s[0x05],
|
|
||||||
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
|
||||||
s[0x0a], s[0x0f],
|
|
||||||
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
|
||||||
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
|
||||||
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
|
||||||
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
|
||||||
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
|
||||||
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
|
||||||
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
|
||||||
s[0x32],s[0x33],s[0x34], s[0x37],
|
|
||||||
s[0x30], s[0x35],
|
|
||||||
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
|
||||||
world.spoiler.set_map('flute', text_output, new_spots, player)
|
|
||||||
|
|
||||||
create_dynamic_flute_exits(world, player)
|
|
||||||
|
|
||||||
def connect_custom(world, connected_edges, groups, forced, player):
|
def connect_custom(world, connected_edges, groups, forced, player):
|
||||||
forced_crossed, forced_noncrossed = forced
|
forced_crossed, forced_noncrossed = forced
|
||||||
@@ -1312,28 +1153,6 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
|||||||
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
||||||
return groups
|
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:
|
|
||||||
return
|
|
||||||
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
|
||||||
if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
|
||||||
exitname = 'Flute From ' + region.name
|
|
||||||
exit = Entrance(region.player, exitname, region)
|
|
||||||
exit.spot_type = 'Flute'
|
|
||||||
exit.connect(world.get_region('Flute Sky', player))
|
|
||||||
region.exits.append(exit)
|
|
||||||
world.initialize_regions()
|
|
||||||
|
|
||||||
def get_mirror_exit_name(from_region, to_region):
|
def get_mirror_exit_name(from_region, to_region):
|
||||||
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
||||||
if len(mirror_connections[from_region]) == 1:
|
if len(mirror_connections[from_region]) == 1:
|
||||||
@@ -1900,10 +1719,6 @@ default_whirlpool_connections = [
|
|||||||
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
|
((0x55, 'Qirn Jump Whirlpool', 'Qirn Jump Water'), (0x7f, 'Bomber Corner Whirlpool', 'Bomber Corner Water'))
|
||||||
]
|
]
|
||||||
|
|
||||||
default_flute_connections = [
|
|
||||||
0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
|
|
||||||
]
|
|
||||||
|
|
||||||
ow_connections = {
|
ow_connections = {
|
||||||
0x03: ([
|
0x03: ([
|
||||||
('West Death Mountain Teleporter', 'West Dark Death Mountain (Bottom)')
|
('West Death Mountain Teleporter', 'West Dark Death Mountain (Bottom)')
|
||||||
@@ -2569,51 +2384,6 @@ isolated_regions = [
|
|||||||
'Pyramid Water'
|
'Pyramid Water'
|
||||||
]
|
]
|
||||||
|
|
||||||
flute_data = {
|
|
||||||
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
|
||||||
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
|
||||||
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x01d8, 0x0518),
|
|
||||||
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
|
||||||
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98),
|
|
||||||
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0),
|
|
||||||
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8),
|
|
||||||
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58),
|
|
||||||
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088),
|
|
||||||
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318),
|
|
||||||
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8),
|
|
||||||
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738),
|
|
||||||
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948),
|
|
||||||
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78),
|
|
||||||
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f),
|
|
||||||
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48),
|
|
||||||
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8),
|
|
||||||
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518),
|
|
||||||
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8),
|
|
||||||
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70),
|
|
||||||
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0),
|
|
||||||
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8),
|
|
||||||
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18),
|
|
||||||
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8),
|
|
||||||
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0),
|
|
||||||
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8),
|
|
||||||
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760),
|
|
||||||
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8),
|
|
||||||
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8),
|
|
||||||
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70),
|
|
||||||
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00),
|
|
||||||
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070),
|
|
||||||
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528),
|
|
||||||
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688),
|
|
||||||
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8),
|
|
||||||
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90),
|
|
||||||
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8),
|
|
||||||
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0),
|
|
||||||
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570),
|
|
||||||
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770),
|
|
||||||
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898),
|
|
||||||
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0)
|
|
||||||
}
|
|
||||||
|
|
||||||
ow_loc_prize_table = {
|
ow_loc_prize_table = {
|
||||||
'Master Sword Pedestal': (0x06d, 0x070),
|
'Master Sword Pedestal': (0x06d, 0x070),
|
||||||
'Hobo': (0xb80, 0xb90),
|
'Hobo': (0xb80, 0xb90),
|
||||||
@@ -2694,23 +2464,3 @@ H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|||||||
+-+ | s +-+-+-+ s +-+
|
+-+ | s +-+-+-+ s +-+
|
||||||
Zora: |s| H(38)| |s|s|s| |s|
|
Zora: |s| H(38)| |s|s|s| |s|
|
||||||
+-+ +---+-+-+-+---+-+"""
|
+-+ +---+-+-+-+---+-+"""
|
||||||
|
|
||||||
flute_spoiler_table = \
|
|
||||||
""" 0 1 2 3 4 5 6 7
|
|
||||||
+---+-+---+---+-+
|
|
||||||
01234567 A(00)| |s| | |s|
|
|
||||||
+--------+ | s +-+ s | s +-+
|
|
||||||
A(00)|s ss s s| B(08)| |s| | |s|
|
|
||||||
B(08)| s s| +-+-+-+-+-+-+-+-+
|
|
||||||
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
|
||||||
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
|
||||||
E(20)| s s | D(18)| |s| |s| |
|
|
||||||
F(28)|ssssssss| | s +-+ s +-+ s |
|
|
||||||
G(30)|s ssss s| E(20)| |s| |s| |
|
|
||||||
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
|
||||||
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
|
||||||
+-+-+-+-+-+-+-+-+
|
|
||||||
G(30)| |s|s|s| |s|
|
|
||||||
| s +-+-+-+ s +-+
|
|
||||||
H(38)| |s|s|s| |s|
|
|
||||||
+---+-+-+-+---+-+"""
|
|
||||||
|
|||||||
9
Rom.py
9
Rom.py
@@ -31,7 +31,7 @@ from Utils import local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
|
|||||||
from Items import ItemFactory, prize_item_table
|
from Items import ItemFactory, prize_item_table
|
||||||
from source.overworld.EntranceData import door_addresses, ow_prize_table
|
from source.overworld.EntranceData import door_addresses, ow_prize_table
|
||||||
from source.overworld.EntranceShuffle2 import exit_ids
|
from source.overworld.EntranceShuffle2 import exit_ids
|
||||||
from OverworldShuffle import default_flute_connections, flute_data
|
from source.overworld.FluteShuffle import default_flute_connections, flute_data
|
||||||
from InitialSram import InitialSram
|
from InitialSram import InitialSram
|
||||||
|
|
||||||
from source.classes.SFX import randomize_sfx, randomize_sfxinstruments, randomize_songinstruments
|
from source.classes.SFX import randomize_sfx, randomize_sfxinstruments, randomize_songinstruments
|
||||||
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '01d81a1ad16b1838b27a057b8f8936d4'
|
RANDOMIZERBASEHASH = 'da081467317d8f6902eb1cf57304dbff'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -518,7 +518,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
else:
|
else:
|
||||||
flute_spots = world.owflutespots[player]
|
flute_spots = world.owflutespots[player]
|
||||||
owFlags |= 0x0100
|
owFlags |= 0x0100
|
||||||
write_int16(rom, snes_to_pc(0x0AB7F7), 0xEAEA)
|
if world.owFluteShuffle[player] != 'vanilla':
|
||||||
|
write_int16(rom, snes_to_pc(0x0AB80B), 0xEAEA)
|
||||||
|
|
||||||
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
flute_writes = [(f, flute_data[f][1]) for f in flute_spots]
|
||||||
for o in range(0, len(flute_writes)):
|
for o in range(0, len(flute_writes)):
|
||||||
@@ -1457,7 +1458,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
map_x = x_map_position[idx]
|
map_x = x_map_position[idx]
|
||||||
map_y = y_map_position[idx]
|
map_y = y_map_position[idx]
|
||||||
if owid != 0xFF:
|
if owid != 0xFF:
|
||||||
if (owid < 0x40) == (world.is_tile_swapped(owid, player)):
|
if (owid < 0x40) == (owid in world.owswaps[player][0]):
|
||||||
coord_flags |= 0x8000 # world indicator flag
|
coord_flags |= 0x8000 # world indicator flag
|
||||||
if coord_flags & 0x4000 == 0:
|
if coord_flags & 0x4000 == 0:
|
||||||
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
map_x, map_y = adjust_ow_coordinates_to_layout(world, player, map_x, map_y, coord_flags & 0x8000 != 0)
|
||||||
|
|||||||
1
Text.py
1
Text.py
@@ -134,6 +134,7 @@ Triforce_texts = [
|
|||||||
" I promise the\n next seed will\n be better.",
|
" I promise the\n next seed will\n be better.",
|
||||||
"\n Honk.",
|
"\n Honk.",
|
||||||
" Breakfast\n is served!",
|
" Breakfast\n is served!",
|
||||||
|
"\n send help",
|
||||||
]
|
]
|
||||||
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
|
||||||
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
|
||||||
|
|||||||
Binary file not shown.
@@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
from BaseClasses import LocationType, DoorType
|
from BaseClasses import LocationType, DoorType
|
||||||
from OverworldShuffle import default_flute_connections, flute_data
|
from source.overworld.FluteShuffle import default_flute_connections, flute_data
|
||||||
from source.tools.MysteryUtils import roll_settings, get_weights
|
from source.tools.MysteryUtils import roll_settings, get_weights
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
266
source/overworld/FluteShuffle.py
Normal file
266
source/overworld/FluteShuffle.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import RaceRandom as random, logging, copy
|
||||||
|
from BaseClasses import Entrance, RegionType, Terrain
|
||||||
|
from source.overworld.EntranceShuffle2 import connect_simple
|
||||||
|
from OWEdges import OWTileRegions
|
||||||
|
from DungeonGenerator import GenerationException
|
||||||
|
|
||||||
|
def shuffle_flute_spots(world, player):
|
||||||
|
def connect_flutes(flute_destinations):
|
||||||
|
for o in range(0, len(flute_destinations)):
|
||||||
|
owid = flute_destinations[o]
|
||||||
|
regions = flute_data[owid][0]
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
|
||||||
|
else:
|
||||||
|
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
|
||||||
|
|
||||||
|
if world.owFluteShuffle[player] == 'vanilla':
|
||||||
|
flute_spots = default_flute_connections.copy()
|
||||||
|
sort_flute_spots(world, player, flute_spots)
|
||||||
|
world.owflutespots[player] = flute_spots
|
||||||
|
connect_flutes(flute_spots)
|
||||||
|
else:
|
||||||
|
from OverworldShuffle import one_way_ledges
|
||||||
|
|
||||||
|
flute_spots = 8
|
||||||
|
flute_pool = list(flute_data.keys())
|
||||||
|
new_spots = list()
|
||||||
|
ignored_regions = set()
|
||||||
|
used_flute_regions = []
|
||||||
|
forbidden_spots = []
|
||||||
|
forbidden_regions = []
|
||||||
|
|
||||||
|
def addSpot(owid, ignore_proximity, forced):
|
||||||
|
if world.owFluteShuffle[player] == 'balanced':
|
||||||
|
def getIgnored(regionname, base_owid, owid):
|
||||||
|
region = world.get_region(regionname, player)
|
||||||
|
for exit in region.exits:
|
||||||
|
if exit.connected_region is not None and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and exit.connected_region.name not in new_ignored:
|
||||||
|
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
|
||||||
|
new_ignored.add(exit.connected_region.name)
|
||||||
|
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
|
||||||
|
if regionname in one_way_ledges:
|
||||||
|
for ledge_region in one_way_ledges[regionname]:
|
||||||
|
if ledge_region not in new_ignored:
|
||||||
|
new_ignored.add(ledge_region)
|
||||||
|
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
|
||||||
|
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
new_region = flute_data[owid][0][0]
|
||||||
|
else:
|
||||||
|
new_region = flute_data[owid][0][1]
|
||||||
|
|
||||||
|
if new_region in ignored_regions and not forced:
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_ignored = {new_region}
|
||||||
|
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
|
||||||
|
if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
||||||
|
return False
|
||||||
|
ignored_regions.update(new_ignored)
|
||||||
|
if owid in flute_pool:
|
||||||
|
flute_pool.remove(owid)
|
||||||
|
if ignore_proximity and not forced:
|
||||||
|
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
||||||
|
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
||||||
|
new_spots.append(owid)
|
||||||
|
else:
|
||||||
|
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
||||||
|
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
||||||
|
return True
|
||||||
|
|
||||||
|
if world.customizer:
|
||||||
|
custom_spots = world.customizer.get_owflutespots()
|
||||||
|
if custom_spots and player in custom_spots:
|
||||||
|
if 'force' in custom_spots[player]:
|
||||||
|
for id in custom_spots[player]['force']:
|
||||||
|
owid = id & 0xBF
|
||||||
|
addSpot(owid, True, True)
|
||||||
|
flute_spots -= 1
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
used_flute_regions.append(flute_data[owid][0][0])
|
||||||
|
else:
|
||||||
|
used_flute_regions.append(flute_data[owid][0][1])
|
||||||
|
if 'forbid' in custom_spots[player]:
|
||||||
|
for id in custom_spots[player]['forbid']:
|
||||||
|
owid = id & 0xBF
|
||||||
|
if owid not in new_spots:
|
||||||
|
forbidden_spots.append(owid)
|
||||||
|
if not world.is_tile_swapped(owid, player):
|
||||||
|
forbidden_regions.append(flute_data[owid][0][0])
|
||||||
|
else:
|
||||||
|
forbidden_regions.append(flute_data[owid][0][1])
|
||||||
|
|
||||||
|
# determine sectors (isolated groups of regions) to place flute spots
|
||||||
|
flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
|
||||||
|
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
|
||||||
|
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
|
||||||
|
region_total = sum([c for c,_ in flute_sectors])
|
||||||
|
sector_total = len(flute_sectors)
|
||||||
|
empty_sector_total = 0
|
||||||
|
sector_has_spot = []
|
||||||
|
|
||||||
|
# determine which sectors still need a flute spot
|
||||||
|
for sector in flute_sectors:
|
||||||
|
already_has_spot = any(region in sector for region in used_flute_regions)
|
||||||
|
sector_has_spot.append(already_has_spot)
|
||||||
|
if not already_has_spot:
|
||||||
|
empty_sector_total += 1
|
||||||
|
if flute_spots < empty_sector_total:
|
||||||
|
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
|
||||||
|
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
|
||||||
|
for i in range(len(flute_sectors)):
|
||||||
|
if not sector_has_spot[i]:
|
||||||
|
sector_has_spot[i] = True
|
||||||
|
empty_sector_total -= 1
|
||||||
|
if flute_spots == empty_sector_total:
|
||||||
|
break
|
||||||
|
|
||||||
|
# distribute flute spots for each sector
|
||||||
|
for i in range(len(flute_sectors)):
|
||||||
|
sector = flute_sectors[i]
|
||||||
|
sector_total -= 1
|
||||||
|
if not sector_has_spot[i]:
|
||||||
|
empty_sector_total -= 1
|
||||||
|
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
||||||
|
target_spots = len(new_spots) + spots_to_place
|
||||||
|
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
||||||
|
|
||||||
|
if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
|
||||||
|
addSpot(0x30, True, True) # guarantee desert/mire access
|
||||||
|
|
||||||
|
random.shuffle(sector[1])
|
||||||
|
f = 0
|
||||||
|
t = 0
|
||||||
|
while len(new_spots) < target_spots:
|
||||||
|
if f >= len(sector[1]):
|
||||||
|
f = 0
|
||||||
|
t += 1
|
||||||
|
if t > 5:
|
||||||
|
raise GenerationException('Infinite loop detected in flute shuffle')
|
||||||
|
owid = flute_regions[sector[1][f]]
|
||||||
|
if owid not in new_spots and owid not in forbidden_spots:
|
||||||
|
addSpot(owid, t > 0, False)
|
||||||
|
f += 1
|
||||||
|
|
||||||
|
region_total -= sector[0]
|
||||||
|
flute_spots -= spots_to_place
|
||||||
|
|
||||||
|
# connect new flute spots
|
||||||
|
sort_flute_spots(world, player, new_spots)
|
||||||
|
world.owflutespots[player] = new_spots
|
||||||
|
connect_flutes(new_spots)
|
||||||
|
|
||||||
|
# update spoiler
|
||||||
|
#new_spots = list(map(lambda o: flute_data[o][1], new_spots))
|
||||||
|
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
|
||||||
|
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
|
||||||
|
s[0x00], s[0x03], s[0x05],
|
||||||
|
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
|
||||||
|
s[0x0a], s[0x0f],
|
||||||
|
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
|
||||||
|
s[0x18], s[0x1a],s[0x1b], s[0x1d],s[0x1e],
|
||||||
|
s[0x22], s[0x25], s[0x1a], s[0x1d],
|
||||||
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
|
||||||
|
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
|
||||||
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
|
||||||
|
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
|
||||||
|
s[0x32],s[0x33],s[0x34], s[0x37],
|
||||||
|
s[0x30], s[0x35],
|
||||||
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
||||||
|
world.spoiler.set_map('flute', text_output, new_spots, player)
|
||||||
|
create_dynamic_flute_exits(world, player)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
return
|
||||||
|
for region in (r for r in world.regions if r.player == player and r.terrain == Terrain.Land and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
||||||
|
if region.type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
|
||||||
|
exitname = 'Flute From ' + region.name
|
||||||
|
exit = Entrance(region.player, exitname, region)
|
||||||
|
exit.spot_type = 'Flute'
|
||||||
|
exit.connect(world.get_region('Flute Sky', player))
|
||||||
|
region.exits.append(exit)
|
||||||
|
world.initialize_regions()
|
||||||
|
|
||||||
|
|
||||||
|
default_flute_connections = [
|
||||||
|
0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
|
||||||
|
]
|
||||||
|
|
||||||
|
flute_data = {
|
||||||
|
#OWID LW Region DW Region Slot VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
|
||||||
|
0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
|
||||||
|
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x01d8, 0x0518),
|
||||||
|
0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x03bb, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
|
||||||
|
0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x03c8, 0x0d98),
|
||||||
|
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0150, 0x0ea0),
|
||||||
|
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0390, 0x04d8),
|
||||||
|
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x0360, 0x0f58),
|
||||||
|
0x10: (['Lost Woods Pass West Area', 'Skull Woods Pass West Area'], 0x10, 0x0080, 0x0400, 0x0000, 0x0448, 0x0058, 0x046f, 0x0085, 0x0000, 0x0000, 0x04f8, 0x0088),
|
||||||
|
0x11: (['Kakariko Fortune Area', 'Dark Fortune Area'], 0x11, 0x0912, 0x051e, 0x0292, 0x0588, 0x0318, 0x058d, 0x031f, 0x0000, 0xfffe, 0x05f8, 0x0318),
|
||||||
|
0x12: (['Kakariko Pond Area', 'Outcast Pond Area'], 0x12, 0x0890, 0x051a, 0x0476, 0x0578, 0x04f8, 0x0587, 0x0503, 0xfff6, 0x000a, 0x05b8, 0x04f8),
|
||||||
|
0x13: (['Sanctuary Area', 'Dark Chapel Area'], 0x13, 0x051c, 0x04aa, 0x06de, 0x0508, 0x0758, 0x0517, 0x0763, 0xfff6, 0x0002, 0x05b8, 0x0738),
|
||||||
|
0x14: (['Graveyard Area', 'Dark Graveyard Area'], 0x14, 0x089c, 0x051e, 0x08e6, 0x0580, 0x0958, 0x058b, 0x0963, 0x0000, 0xfffa, 0x05f0, 0x0918, 0x0580, 0x0948),
|
||||||
|
0x15: (['River Bend East Bank', 'Qirn Jump East Bank'], 0x15, 0x041a, 0x0486, 0x0ad2, 0x04e8, 0x0b48, 0x04f3, 0x0b4f, 0x0008, 0xfffe, 0x0548, 0x0b78),
|
||||||
|
0x16: (['Potion Shop Area', 'Dark Witch Area'], 0x16, 0x0888, 0x0516, 0x0c4e, 0x0578, 0x0cc8, 0x0583, 0x0cd3, 0xfffa, 0xfff2, 0x05e8, 0x0c9f),
|
||||||
|
0x17: (['Zora Approach Ledge', 'Catfish Approach Ledge'], 0x17, 0x039e, 0x047e, 0x0ef2, 0x04e0, 0x0f68, 0x04eb, 0x0f6f, 0x0000, 0xfffe, 0x0580, 0x0f48),
|
||||||
|
0x18: (['Kakariko Village', 'Village of Outcasts'], 0x18, 0x0b30, 0x0759, 0x017e, 0x07b7, 0x0200, 0x07c6, 0x020b, 0x0007, 0x0002, 0x0830, 0x0240, 0x07c8, 0x01f8),
|
||||||
|
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0518),
|
||||||
|
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07f8, 0x07f8),
|
||||||
|
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0750, 0x0a70),
|
||||||
|
0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09a0, 0x0cb0),
|
||||||
|
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0978, 0x04e8),
|
||||||
|
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x0918, 0x0b18),
|
||||||
|
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0ba8, 0x00b8),
|
||||||
|
0x29: (['Kakariko Suburb Area', 'Frog Area'], 0x29, 0x0408, 0x0a7c, 0x0242, 0x0ae0, 0x02c0, 0x0aeb, 0x02c7, 0x0002, 0xfffe, 0x0b30, 0x02e0),
|
||||||
|
0x2a: (['Flute Boy Area', 'Stumpy Area'], 0x2a, 0x058e, 0x0aac, 0x046e, 0x0b10, 0x04e8, 0x0b1b, 0x04f3, 0x0002, 0x0002, 0x0b60, 0x04f8),
|
||||||
|
0x2b: (['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], 0x2b, 0x0620, 0x0acc, 0x0700, 0x0b30, 0x0790, 0x0b3b, 0x0785, 0xfff2, 0x0000, 0x0b80, 0x0760),
|
||||||
|
0x2c: (['Links House Area', 'Big Bomb Shop Area'], 0x2c, 0x0588, 0x0ab9, 0x0840, 0x0b17, 0x08b8, 0x0b26, 0x08bf, 0xfff7, 0x0000, 0x0bb0, 0x08a8),
|
||||||
|
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bf0, 0x0ab8),
|
||||||
|
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0ac8, 0x0c70),
|
||||||
|
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0bc0, 0x0f00),
|
||||||
|
0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0ff0, 0x0070),
|
||||||
|
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0d00, 0x0528),
|
||||||
|
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0ce0, 0x0688),
|
||||||
|
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0dd0, 0x08e8),
|
||||||
|
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0dc8, 0x0a90),
|
||||||
|
0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0fd8, 0x0da8),
|
||||||
|
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d98, 0x0ed0),
|
||||||
|
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0ee0, 0x0570),
|
||||||
|
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0fd0, 0x0770),
|
||||||
|
0x3c: (['South Pass Area', 'Dark South Pass Area'], 0x3c, 0x0584, 0x0ed0, 0x081e, 0x0f38, 0x0898, 0x0f45, 0x08a3, 0xfffe, 0x0002, 0x0fa8, 0x0898),
|
||||||
|
0x3f: (['Octoballoon Area', 'Bomber Corner Area'], 0x3f, 0x0810, 0x0f05, 0x0e75, 0x0f67, 0x0ef3, 0x0f72, 0x0efa, 0xfffb, 0x000b, 0x0fd0, 0x0ef0)
|
||||||
|
}
|
||||||
|
|
||||||
|
flute_spoiler_table = \
|
||||||
|
""" 0 1 2 3 4 5 6 7
|
||||||
|
+---+-+---+---+-+
|
||||||
|
01234567 A(00)| |s| | |s|
|
||||||
|
+--------+ | s +-+ s | s +-+
|
||||||
|
A(00)|s ss s s| B(08)| |s| | |s|
|
||||||
|
B(08)| s s| +-+-+-+-+-+-+-+-+
|
||||||
|
C(10)|ssssssss| C(10)|s|s|s|s|s|s|s|s|
|
||||||
|
D(18)|s ss ss | +-+-+-+-+-+-+-+-+
|
||||||
|
E(20)| s s | D(18)| |s| |s| |
|
||||||
|
F(28)|ssssssss| | s +-+ s +-+ s |
|
||||||
|
G(30)|s ssss s| E(20)| |s| |s| |
|
||||||
|
H(38)| sss s| +-+-+-+-+-+-+-+-+
|
||||||
|
+--------+ F(28)|s|s|s|s|s|s|s|s|
|
||||||
|
+-+-+-+-+-+-+-+-+
|
||||||
|
G(30)| |s|s|s| |s|
|
||||||
|
| s +-+-+-+ s +-+
|
||||||
|
H(38)| |s|s|s| |s|
|
||||||
|
+---+-+-+-+---+-+"""
|
||||||
Reference in New Issue
Block a user