Changed bias named. Added districting

This commit is contained in:
aerinon
2021-10-05 13:58:30 -06:00
parent 058b78cff9
commit 28b87428cc
10 changed files with 341 additions and 56 deletions

View File

@@ -3,6 +3,7 @@ import logging
from math import ceil
from collections import defaultdict
from source.item.District import resolve_districts
from DoorShuffle import validate_vanilla_reservation
from Dungeons import dungeon_table
from Items import item_table, ItemFactory
@@ -17,6 +18,8 @@ class ItemPoolConfig(object):
self.placeholders = None
self.reserved_locations = defaultdict(set)
self.recorded_choices = []
class LocationGroup(object):
def __init__(self, name):
@@ -57,7 +60,7 @@ def create_item_pool_config(world):
for item in dungeon.all_items:
if item.map or item.compass:
item.advancement = True
if world.algorithm == 'vanilla_bias':
if world.algorithm == 'vanilla_fill':
config.static_placement = {}
config.location_groups = {}
for player in range(1, world.players + 1):
@@ -78,7 +81,7 @@ def create_item_pool_config(world):
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
config.reserved_locations[player].add(loc_name)
elif world.algorithm == 'major_bias':
elif world.algorithm == 'major_only':
config.location_groups = [
LocationGroup('MajorItems'),
LocationGroup('Backup')
@@ -111,7 +114,7 @@ def create_item_pool_config(world):
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup)
elif world.algorithm == 'dungeon_bias':
elif world.algorithm == 'dungeon_only':
config.location_groups = [
LocationGroup('Dungeons'),
LocationGroup('Backup')
@@ -205,6 +208,74 @@ def create_item_pool_config(world):
config.location_groups[0].locations = chosen_locations
def district_item_pool_config(world):
resolve_districts(world)
if world.algorithm == 'district':
config = world.item_pool_config
config.location_groups = [
LocationGroup('Districts'),
]
item_cnt = 0
config.item_pool = {}
for player in range(1, world.players + 1):
config.item_pool[player] = determine_major_items(world, player)
item_cnt += count_major_items(world, player)
# set district choices
district_choices = {}
for p in range(1, world.players + 1):
for name, district in world.districts[p].items():
adjustment = 0
if district.dungeon:
adjustment = len([i for i in world.get_dungeon(name, p).all_items
if i.is_inside_dungeon_item(world)])
dist_len = len(district.locations) - adjustment
if name not in district_choices:
district_choices[name] = (district.sphere_one, dist_len)
else:
so, amt = district_choices[name]
district_choices[name] = (so or district.sphere_one, amt + dist_len)
chosen_locations = defaultdict(set)
location_cnt = 0
# choose a sphere one district
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
sphere_one = random.choice(sphere_one_choices)
so, amt = district_choices[sphere_one]
location_cnt += amt
for player in range(1, world.players + 1):
for location in world.districts[player][sphere_one].locations:
chosen_locations[location].add(player)
del district_choices[sphere_one]
config.recorded_choices.append(sphere_one)
scale_factors = defaultdict(int)
scale_total = 0
for p in range(1, world.players + 1):
ent = 'Inverted Ganons Tower' if world.mode[p] == 'inverted' else 'Ganons Tower'
dungeon = world.get_entrance(ent, p).connected_region.dungeon
if dungeon:
scale = world.crystals_needed_for_gt[p]
scale_total += scale
scale_factors[dungeon.name] += scale
scale_total = max(1, scale_total)
scale_divisors = defaultdict(lambda: 1)
scale_divisors.update(scale_factors)
while location_cnt < item_cnt:
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
so, amt = district_choices[choice]
location_cnt += amt
for player in range(1, world.players + 1):
for location in world.districts[player][choice].locations:
chosen_locations[location].add(player)
del district_choices[choice]
config.recorded_choices.append(choice)
config.placeholders = location_cnt - item_cnt
config.location_groups[0].locations = chosen_locations
def location_prefilled(location, world, player):
if world.swords[player] == 'vanilla':
return location in vanilla_swords
@@ -390,6 +461,8 @@ def calc_dungeon_limits(world, player):
def determine_major_items(world, player):
major_item_set = set(major_items)
if world.progressive == 'off':
pass # now what?
if world.bigkeyshuffle[player]:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
if world.keyshuffle[player]:
@@ -412,16 +485,18 @@ def determine_major_items(world, player):
def classify_major_items(world):
if world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias'] or (world.algorithm == 'entangled'
and world.players > 1):
if world.algorithm in ['major_only', 'dungeon_only', 'district']:
config = world.item_pool_config
for item in world.itempool:
if item.name in config.item_pool[item.player]:
if not item.advancement or not item.priority:
if not item.advancement and not item.priority:
if item.smallkey or item.bigkey:
item.advancement = True
else:
item.priority = True
else:
if item.priority:
item.priority = False
def figure_out_clustered_choices(world):
@@ -488,13 +563,15 @@ def vanilla_fallback(item_to_place, locations, world):
return []
def filter_locations(item_to_place, locations, world):
if world.algorithm == 'vanilla_bias':
def filter_locations(item_to_place, locations, world, vanilla_skip=False):
if world.algorithm == 'vanilla_fill':
config, filtered = world.item_pool_config, []
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
if item_name in config.static_placement[item_to_place.player]:
restricted = config.static_placement[item_to_place.player][item_name]
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
if vanilla_skip and len(filtered) == 0:
return filtered
i = 0
while len(filtered) <= 0:
if i >= len(config.location_groups[item_to_place.player]):
@@ -503,7 +580,7 @@ def filter_locations(item_to_place, locations, world):
filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
i += 1
return filtered
if world.algorithm in ['major_bias', 'dungeon_bias']:
if world.algorithm in ['major_only', 'dungeon_only']:
config = world.item_pool_config
if item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations
@@ -513,7 +590,7 @@ def filter_locations(item_to_place, locations, world):
filtered = [l for l in locations if l.name in restricted]
# bias toward certain location in overflow? (thinking about this for major_bias)
return filtered if len(filtered) > 0 else locations
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias':
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'district':
config = world.item_pool_config
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations
@@ -835,7 +912,7 @@ major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove',
'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror',
'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)',
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Blue Shield', 'Red Shield',
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield',
'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword',
'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
'Progressive Bow', 'Progressive Bow (Alt)'}

169
source/item/District.py Normal file
View File

@@ -0,0 +1,169 @@
from collections import deque
from BaseClasses import CollectionState, RegionType
from Dungeons import dungeon_table
class District(object):
def __init__(self, name, locations, entrances=None, dungeon=None):
self.name = name
self.dungeon = dungeon
self.locations = locations
self.entrances = entrances if entrances else []
self.sphere_one = False
def create_districts(world):
world.districts = {}
for p in range(1, world.players + 1):
create_district_helper(world, p)
def create_district_helper(world, player):
inverted = world.mode[player] == 'inverted'
districts = {}
kak_locations = {'Bottle Merchant', 'Kakariko Tavern', 'Maze Race'}
nw_lw_locations = {'Mushroom', 'Master Sword Pedestal'}
central_lw_locations = {'Sunken Treasure', 'Flute Spot'}
desert_locations = {'Purple Chest', 'Desert Ledge'}
lake_locations = {'Hobo'}
east_lw_locations = {"Zora's Ledge", 'King Zora'}
lw_dm_locations = {'Old Man', 'Spectacle Rock', 'Ether Tablet'}
east_dw_locations = {'Pyramid', 'Catfish'}
south_dw_locations = {'Stumpy', 'Digging Game', 'Bombos Tablet', 'Lake Hylia Island'}
voo_north_locations = {'Bumper Cave Ledge'}
ddm_locations = {'Floating Island'}
kak_entrances = ['Kakariko Well Cave', 'Bat Cave Cave', 'Elder House (East)', 'Elder House (West)',
'Two Brothers House (East)', 'Two Brothers House (West)', 'Blinds Hideout', 'Chicken House',
'Blacksmiths Hut', 'Sick Kids House', 'Snitch Lady (East)', 'Snitch Lady (West)',
'Bush Covered House', 'Tavern (Front)', 'Light World Bomb Hut', 'Kakariko Shop', 'Library',
'Kakariko Gamble Game', 'Kakariko Well Drop', 'Bat Cave Drop']
nw_lw_entrances = ['North Fairy Cave', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary',
'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Kings Grave', 'Lost Woods Gamble',
'Fortune Teller (Light)', 'Bonk Rock Cave', 'Lumberjack House', 'North Fairy Cave Drop',
'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave']
central_lw_entrances = ['Links House', 'Hyrule Castle Entrance (South)', 'Hyrule Castle Entrance (West)',
'Hyrule Castle Entrance (East)', 'Agahnims Tower', 'Hyrule Castle Secret Entrance Stairs',
'Dam', 'Bonk Fairy (Light)', 'Light Hype Fairy', 'Cave Shop (Lake Hylia)',
'Lake Hylia Fortune Teller', 'Hyrule Castle Secret Entrance Drop']
desert_entrances = ['Desert Palace Entrance (South)', 'Desert Palace Entrance (West)',
'Desert Palace Entrance (North)', 'Desert Palace Entrance (East)', 'Desert Fairy',
'Aginahs Cave', '50 Rupee Cave']
lake_entrances = ['Capacity Upgrade', 'Mini Moldorm Cave', 'Good Bee Cave', '20 Rupee Cave', 'Ice Rod Cave']
east_lw_entrances = ['Eastern Palace', 'Waterfall of Wishing', 'Lake Hylia Fairy', 'Sahasrahlas Hut',
'Long Fairy Cave', 'Potion Shop']
lw_dm_entrances = ['Tower of Hera', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
'Death Mountain Return Cave (East)', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave',
'Spectacle Rock Cave (Bottom)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)',
'Paradox Cave (Top)', 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)',
'Spiral Cave', 'Spiral Cave (Bottom)', 'Hookshot Fairy']
east_dw_entrances = ['Palace of Darkness', 'Pyramid Entrance', 'Pyramid Fairy', 'East Dark World Hint',
'Palace of Darkness Hint', 'Dark Lake Hylia Fairy', 'Dark World Potion Shop', 'Pyramid Hole']
south_dw_entrances = ['Ice Palace', 'Swamp Palace', 'Dark Lake Hylia Ledge Fairy',
'Dark Lake Hylia Ledge Spike Cave', 'Dark Lake Hylia Ledge Hint', 'Hype Cave',
'Bonk Fairy (Dark)', 'Archery Game', 'Big Bomb Shop', 'Dark Lake Hylia Shop', 'Cave 45']
voo_north_entrances = ['Thieves Town', 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
'Skull Woods Second Section Door (West)', 'Skull Woods Final Section',
'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Brewery', 'C-Shaped House', 'Chest Game',
'Dark World Hammer Peg Cave', 'Red Shield Shop', 'Dark Sanctuary Hint',
'Fortune Teller (Dark)', 'Dark World Shop', 'Dark World Lumberjack Shop', 'Graveyard Cave',
'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)',
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
mire_entrances = ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy', 'Checkerboard Cave']
ddm_entrances = ['Turtle Rock', 'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)',
'Turtle Rock Isolated Ledge Entrance', 'Superbunny Cave (Top)', 'Superbunny Cave (Bottom)',
'Hookshot Cave', 'Hookshot Cave Back Entrance', 'Ganons Tower', 'Spike Cave',
'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Fairy', 'Mimic Cave']
if inverted:
south_dw_locations.remove('Bombos Tablet')
south_dw_locations.remove('Lake Hylia Island')
voo_north_locations.remove('Bumper Cave Ledge')
ddm_locations.remove('Floating Island')
desert_locations.add('Bombos Tablet')
lake_locations.add('Lake Hylia Island')
nw_lw_locations.add('Bumper Cave Ledge')
lw_dm_locations.add('Floating Island')
south_dw_entrances.remove('Cave 45')
central_lw_entrances.append('Cave 45')
voo_north_entrances.remove('Graveyard Cave')
nw_lw_entrances.append('Graveyard Cave')
mire_entrances.remove('Checkerboard Cave')
desert_entrances.append('Checkerboard Cave')
ddm_entrances.remove('Mimic Cave')
lw_dm_entrances.append('Mimic Cave')
south_dw_entrances.remove('Big Bomb Shop')
central_lw_entrances.append('Inverted Big Bomb Shop')
central_lw_entrances.remove('Links House')
south_dw_entrances.append('Inverted Links House')
voo_north_entrances.remove('Dark Sanctuary')
voo_north_entrances.append('Inverted Dark Sanctuary')
ddm_entrances.remove('Ganons Tower')
central_lw_entrances.append('Inverted Ganons Tower')
central_lw_entrances.remove('Agahnims Tower')
ddm_entrances.append('Inverted Agahnims Tower')
east_dw_entrances.remove('Pyramid Entrance')
central_lw_entrances.append('Inverted Pyramid Entrance')
east_dw_entrances.remove('Pyramid Hole')
central_lw_entrances.append('Inverted Pyramid Hole')
districts['Kakariko'] = District('Kakariko', kak_locations, entrances=kak_entrances)
districts['Northwest Hyrule'] = District('Northwest Hyrule', nw_lw_locations, entrances=nw_lw_entrances)
districts['Central Hyrule'] = District('Central Hyrule', central_lw_locations, entrances=central_lw_entrances)
districts['Desert'] = District('Desert', desert_locations, entrances=desert_entrances)
districts['Lake Hylia'] = District('Lake Hylia', lake_locations, entrances=lake_entrances)
districts['Eastern Hyrule'] = District('Eastern Hyrule', east_lw_locations, entrances=east_lw_entrances)
districts['Death Mountain'] = District('Death Mountain', lw_dm_locations, entrances=lw_dm_entrances)
districts['East Dark World'] = District('East Dark World', east_dw_locations, entrances=east_dw_entrances)
districts['South Dark World'] = District('South Dark World', south_dw_locations, entrances=south_dw_entrances)
districts['Northwest Dark World'] = District('Northwest Dark World', voo_north_locations,
entrances=voo_north_entrances)
districts['The Mire'] = District('The Mire', set(), entrances=mire_entrances)
districts['Dark Death Mountain'] = District('Dark Death Mountain', ddm_locations, entrances=ddm_entrances)
districts.update({x: District(x, set(), dungeon=x) for x in dungeon_table.keys()})
world.districts[player] = districts
def resolve_districts(world):
create_districts(world)
state = CollectionState(world)
state.sweep_for_events()
for player in range(1, world.players + 1):
check_set = find_reachable_locations(state, player)
used_locations = {l for d in world.districts[player].values() for l in d.locations}
for name, district in world.districts[player].items():
if district.dungeon:
layout = world.dungeon_layouts[player][district.dungeon]
district.locations.update([l.name for r in layout.master_sector.regions
for l in r.locations if not l.item and l.real])
else:
for entrance in district.entrances:
ent = world.get_entrance(entrance, player)
queue = deque([ent.connected_region])
visited = set()
while len(queue) > 0:
region = queue.pop()
visited.add(region)
if region.type == RegionType.Cave:
for location in region.locations:
if location.name not in used_locations and not location.item and location.real:
district.locations.add(location.name)
used_locations.add(location.name)
for ext in region.exits:
if ext.connected_region not in visited:
queue.appendleft(ext.connected_region)
district.sphere_one = len(check_set.intersection(district.locations)) > 0
def find_reachable_locations(state, player):
check_set = set()
for region in state.reachable_regions[player]:
for location in region.locations:
if location.can_reach(state) and not location.forced_item and location.real:
check_set.add(location.name)
return check_set