Adding Customizer support for flute shuffle

This commit is contained in:
codemann8
2023-08-19 12:55:55 -05:00
parent bbd3187cb1
commit 8b077cb379
4 changed files with 100 additions and 13 deletions

View File

@@ -417,11 +417,15 @@ def link_overworld(world, player):
if world.owFluteShuffle[player] == 'vanilla': if world.owFluteShuffle[player] == 'vanilla':
connect_flutes(default_flute_connections) connect_flutes(default_flute_connections)
else: else:
flute_spots = 8
flute_pool = list(flute_data.keys()) flute_pool = list(flute_data.keys())
new_spots = list() new_spots = list()
ignored_regions = set() ignored_regions = set()
used_flute_regions = []
forbidden_spots = []
forbidden_regions = []
def addSpot(owid, ignore_proximity): def addSpot(owid, ignore_proximity, forced):
if world.owFluteShuffle[player] == 'balanced': if world.owFluteShuffle[player] == 'balanced':
def getIgnored(regionname, base_owid, owid): def getIgnored(regionname, base_owid, owid):
region = world.get_region(regionname, player) region = world.get_region(regionname, player)
@@ -430,23 +434,28 @@ def link_overworld(world, player):
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid): 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) new_ignored.add(exit.connected_region.name)
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[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): if not world.is_tile_swapped(owid, player):
new_region = flute_data[owid][0][0] new_region = flute_data[owid][0][0]
else: else:
new_region = flute_data[owid][0][1] new_region = flute_data[owid][0][1]
if new_region in ignored_regions: if new_region in ignored_regions and not forced:
return False return False
new_ignored = {new_region} new_ignored = {new_region}
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region]) getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
return False return False
ignored_regions.update(new_ignored) ignored_regions.update(new_ignored)
if owid in flute_pool: if owid in flute_pool:
flute_pool.remove(owid) flute_pool.remove(owid)
if ignore_proximity: if ignore_proximity and not forced:
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
new_spots.append(owid) new_spots.append(owid)
@@ -455,23 +464,65 @@ def link_overworld(world, player):
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
return True 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 # 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()} 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 = [(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] flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
region_total = sum([c for c,_ in flute_sectors]) region_total = sum([c for c,_ in flute_sectors])
sector_total = len(flute_sectors) sector_total = len(flute_sectors)
empty_sector_total = 0
sector_has_spot = []
# reserve a number of flute spots for each sector # determine which sectors still need a flute spot
flute_spots = 8
for sector in flute_sectors: 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 sector_total -= 1
spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) 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 target_spots = len(new_spots) + spots_to_place
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
if 'Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]: 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, False) # guarantee desert/mire access addSpot(0x30, True, True) # guarantee desert/mire access
random.shuffle(sector[1]) random.shuffle(sector[1])
f = 0 f = 0
@@ -482,8 +533,9 @@ def link_overworld(world, player):
t += 1 t += 1
if t > 5: if t > 5:
raise GenerationException('Infinite loop detected in flute shuffle') raise GenerationException('Infinite loop detected in flute shuffle')
if sector[1][f] not in new_spots: owid = flute_regions[sector[1][f]]
addSpot(flute_regions[sector[1][f]], t > 0) if owid not in new_spots and owid not in forbidden_spots:
addSpot(owid, t > 0, False)
f += 1 f += 1
region_total -= sector[0] region_total -= sector[0]

View File

@@ -89,7 +89,7 @@ You may define an item and a list of locations that an item should not be placed
### ow-tileflips ### ow-tileflips
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined`. This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`.
#### force_flip / force_no_flip #### force_flip / force_no_flip
@@ -110,6 +110,14 @@ force_no_flip:
`undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped. `undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped.
### ow-flutespots
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have some form of Flute Shuffle in order for any values here to take effect. This section has two subsections: `force` and `forbid`. Both are lists of OW Screen IDs, please refer to ow-tileflips above for more information.
Everything listed in `force` means that this screen must contain a flute spot.
Everything listed in `forbid` means that this screen must not contain a flute spot.
### entrances ### entrances
This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`. This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`.

View File

@@ -77,6 +77,19 @@ ow-tileflips:
- 0x2c - 0x2c
- 0x18 - 0x18
undefined_chance: 50 undefined_chance: 50
ow-flutespots:
1:
force:
- 0x00
- 0x12
- 0x18
- 0x1a
- 0x2f
- 0x30
- 0x35
forbid:
- 0x03
- 0x05
entrances: entrances:
1: 1:
entrances: entrances:

View File

@@ -10,6 +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.tools.MysteryUtils import roll_settings, get_weights from source.tools.MysteryUtils import roll_settings, get_weights
@@ -200,6 +201,11 @@ class CustomSettings(object):
return self.file_source['ow-tileflips'] return self.file_source['ow-tileflips']
return None return None
def get_owflutespots(self):
if 'ow-flutespots' in self.file_source:
return self.file_source['ow-flutespots']
return None
def get_entrances(self): def get_entrances(self):
if 'entrances' in self.file_source: if 'entrances' in self.file_source:
return self.file_source['entrances'] return self.file_source['entrances']
@@ -356,6 +362,14 @@ class CustomSettings(object):
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
flips[p]['force_flip'].sort() flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0 flips[p]['undefined_chance'] = 0
self.world_rep['ow-flutespots'] = flute = {}
for p in self.player_range:
flute[p] = {}
if p in world.owflutespots:
flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p]))
else:
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
flute[p]['forbid'] = []
def record_entrances(self, world): def record_entrances(self, world):
self.world_rep['entrances'] = entrances = {} self.world_rep['entrances'] = entrances = {}