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

43
Fill.py
View File

@@ -27,7 +27,7 @@ def promote_dungeon_items(world):
def dungeon_tracking(world): def dungeon_tracking(world):
for dungeon in world.dungeons: for dungeon in world.dungeons:
layout = world.dungeon_layouts[dungeon.player][dungeon.name] layout = world.dungeon_layouts[dungeon.player][dungeon.name]
layout.dungeon_items = len(dungeon.all_items) layout.dungeon_items = len([i for i in dungeon.all_items if i.is_inside_dungeon_item(world)])
layout.free_items = layout.location_cnt - layout.dungeon_items layout.free_items = layout.location_cnt - layout.dungeon_items
@@ -64,13 +64,10 @@ def fill_dungeons_restrictive(world, shuffled_locations):
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False, def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
reserved_items=None): vanilla=False):
if not reserved_items:
reserved_items = []
def sweep_from_pool(): def sweep_from_pool():
new_state = base_state.copy() new_state = base_state.copy()
for item in itempool + reserved_items: for item in itempool:
new_state.collect(item, True) new_state.collect(item, True)
new_state.sweep_for_events() new_state.sweep_for_events()
return new_state return new_state
@@ -99,7 +96,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
spot_to_fill = None spot_to_fill = None
item_locations = filter_locations(item_to_place, locations, world) item_locations = filter_locations(item_to_place, locations, world, vanilla)
for location in item_locations: for location in item_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state, spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
single_player_placement, perform_access_check, itempool, single_player_placement, perform_access_check, itempool,
@@ -107,6 +104,9 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
if spot_to_fill: if spot_to_fill:
break break
if spot_to_fill is None: if spot_to_fill is None:
if vanilla:
unplaced_items.insert(0, item_to_place)
continue
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state, spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
base_state, itempool, perform_access_check, item_locations, base_state, itempool, perform_access_check, item_locations,
keys_in_itempool, single_player_placement) keys_in_itempool, single_player_placement)
@@ -166,7 +166,7 @@ def valid_key_placement(item, location, itempool, world):
cr_count = world.crystals_needed_for_gt[location.player] cr_count = world.crystals_needed_for_gt[location.player]
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count) return key_logic.check_placement(unplaced_keys, location if item.bigkey else None, prize_loc, cr_count)
else: else:
return not item.is_inside_dungeon_item(world) # todo: big deal for ambrosia to fix this return not item.is_inside_dungeon_item(world)
def valid_reserved_placement(item, location, world): def valid_reserved_placement(item, location, world):
@@ -215,10 +215,11 @@ def is_dungeon_item(item, world):
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted, def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
keys_in_itempool=None, single_player_placement=False): keys_in_itempool=None, single_player_placement=False):
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
if world.algorithm in ['balanced', 'equitable']: if world.algorithm in ['balanced', 'equitable']:
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool, return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
single_player_placement) single_player_placement)
elif world.algorithm == 'vanilla_bias': elif world.algorithm == 'vanilla_fill':
if item_to_place.type == 'Crystal': if item_to_place.type == 'Crystal':
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool, return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
@@ -354,8 +355,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
prioitempool = [item for item in world.itempool if not item.advancement and item.priority] prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
restitempool = [item for item in world.itempool if not item.advancement and not item.priority] restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_bias'] gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_only']
# dungeon bias may fill up the dungeon... and push items out into the overworld # dungeon only may fill up the dungeon... and push items out into the overworld
# fill in gtower locations with trash first # fill in gtower locations with trash first
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
@@ -384,18 +385,20 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1) progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
if world.algorithm == 'vanilla_fill':
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True)
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool) fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
random.shuffle(fill_locations) random.shuffle(fill_locations)
if world.algorithm == 'balanced': if world.algorithm == 'balanced':
fast_fill(world, prioitempool, fill_locations) fast_fill(world, prioitempool, fill_locations)
elif world.algorithm == 'vanilla_bias': elif world.algorithm == 'vanilla_fill':
fast_vanilla_fill(world, prioitempool, fill_locations) fast_vanilla_fill(world, prioitempool, fill_locations)
elif world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias', 'entangled']: elif world.algorithm in ['major_only', 'dungeon_only', 'district']:
filtered_fill(world, prioitempool, fill_locations) filtered_fill(world, prioitempool, fill_locations)
else: # just need to ensure dungeon items still get placed in dungeons else: # just need to ensure dungeon items still get placed in dungeons
fast_equitable_fill(world, prioitempool, fill_locations) fast_equitable_fill(world, prioitempool, fill_locations)
# placeholder work # placeholder work
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias': if world.algorithm == 'district':
random.shuffle(fill_locations) random.shuffle(fill_locations)
placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)'] placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
num_ph_items = len(placeholder_items) num_ph_items = len(placeholder_items)
@@ -412,7 +415,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
fill_locations.remove(l) fill_locations.remove(l)
filtered_fill(world, placeholder_items, placeholder_locations) filtered_fill(world, placeholder_items, placeholder_locations)
if world.algorithm == 'vanilla_bias': if world.algorithm == 'vanilla_fill':
fast_vanilla_fill(world, restitempool, fill_locations) fast_vanilla_fill(world, restitempool, fill_locations)
else: else:
fast_fill(world, restitempool, fill_locations) fast_fill(world, restitempool, fill_locations)
@@ -443,8 +446,18 @@ def filtered_fill(world, item_pool, fill_locations):
def fast_vanilla_fill(world, item_pool, fill_locations): def fast_vanilla_fill(world, item_pool, fill_locations):
next_item_pool = []
while item_pool and fill_locations: while item_pool and fill_locations:
item_to_place = item_pool.pop() item_to_place = item_pool.pop()
locations = filter_locations(item_to_place, fill_locations, world, vanilla_skip=True)
if len(locations):
spot_to_fill = locations.pop()
fill_locations.remove(spot_to_fill)
world.push_item(spot_to_fill, item_to_place, False)
else:
next_item_pool.append(item_to_place)
while next_item_pool and fill_locations:
item_to_place = next_item_pool.pop()
spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world))) spot_to_fill = next(iter(filter_locations(item_to_place, fill_locations, world)))
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)

View File

@@ -29,7 +29,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
from source.item.BiasedFill import create_item_pool_config, massage_item_pool from source.item.BiasedFill import create_item_pool_config, massage_item_pool, district_item_pool_config
__version__ = '1.0.0.1-u' __version__ = '1.0.0.1-u'
@@ -199,6 +199,7 @@ def main(args, seed=None, fish=None):
else: else:
lock_shop_locations(world, player) lock_shop_locations(world, player)
district_item_pool_config(world)
massage_item_pool(world) massage_item_pool(world)
logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes")) logger.info(world.fish.translate("cli", "cli", "placing.dungeon.prizes"))

View File

@@ -22,6 +22,7 @@ def _wrap(name):
# These are for intellisense purposes only, and will be overwritten below # These are for intellisense purposes only, and will be overwritten below
choice = _prng_inst.choice choice = _prng_inst.choice
choices = _prng_inst.choices
gauss = _prng_inst.gauss gauss = _prng_inst.gauss
getrandbits = _prng_inst.getrandbits getrandbits = _prng_inst.getrandbits
randint = _prng_inst.randint randint = _prng_inst.randint

40
Rom.py
View File

@@ -17,7 +17,8 @@ except ImportError:
from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorType, RegionType, PotItem from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorType, RegionType, PotItem
from DoorShuffle import compass_data, DROptions, boss_indicator from DoorShuffle import compass_data, DROptions, boss_indicator
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses, dungeon_table
from DungeonGenerator import dungeon_portals
from Regions import location_table, shop_to_location_table, retro_shops from Regions import location_table, shop_to_location_table, retro_shops
from RoomData import DoorKind from RoomData import DoorKind
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
@@ -32,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '11f4f494e999a919aafd7d2624e67679' RANDOMIZERBASEHASH = 'f2791b1fb0776849bd4a0851b75fca26'
class JsonRom(object): class JsonRom(object):
@@ -2051,6 +2052,7 @@ def write_strings(rom, world, player, team):
else: else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0
hint_count -= 2 if world.algorithm == 'district' and world.shuffle[player] not in ['simple', 'restricted'] else 0
for entrance in all_entrances: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
if hint_count > 0: if hint_count > 0:
@@ -2142,11 +2144,35 @@ def write_strings(rom, world, player, team):
else: else:
tt[hint_locations.pop(0)] = this_hint tt[hint_locations.pop(0)] = this_hint
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice. if world.shuffle[player] in ['full', 'crossed', 'insanity']:
junk_hints = junk_texts.copy() # 3 hints for dungeons - todo: replace with overworld map code
random.shuffle(junk_hints) hint_count = 3
for location in hint_locations: dungeon_candidates = list(dungeon_table.keys())
tt[location] = junk_hints.pop(0) dungeon_choices = random.choices(dungeon_candidates, k=hint_count)
for c in dungeon_choices:
portal_name = random.choice(dungeon_portals[c])
portal_region = world.get_portal(portal_name, player).door.entrance.connected_region
entrance = next(ent for ent in portal_region.entrances
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])
district =next(d for d in world.districts[player].values() if entrance.name in d.entrances)
this_hint = f'The entrance to {c} can be found in {district.name}'
tt[hint_locations.pop(0)] = this_hint
if world.algorithm == 'district':
hint_candidates = []
for name, district in world.districts[player].items():
if name not in world.item_pool_config.recorded_choices and not district.sphere_one:
hint_candidates.append(f'{name} is a foolish choice')
random.shuffle(hint_candidates)
for location in hint_locations:
tt[location] = hint_candidates.pop(0)
else:
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint
# isn't selected twice.
junk_hints = junk_texts.copy()
random.shuffle(junk_hints)
for location in hint_locations:
tt[location] = junk_hints.pop(0)
# We still need the older hints of course. Those are done here. # We still need the older hints of course. Those are done here.

View File

@@ -102,11 +102,10 @@
"choices": [ "choices": [
"balanced", "balanced",
"equitable", "equitable",
"vanilla_bias", "vanilla_fill",
"major_bias", "major_only",
"dungeon_bias", "dungeon_only",
"cluster_bias", "district"
"entangled"
] ]
}, },
"shuffle": { "shuffle": {

View File

@@ -156,27 +156,26 @@
" algorithm.", " algorithm.",
"equitable: does not place dungeon items first allowing new potential", "equitable: does not place dungeon items first allowing new potential",
" but mixed with the normal advancement pool", " but mixed with the normal advancement pool",
"biased placements: these consider all major items to be special and attempts", "restricted placements: these consider all major items to be special and attempts",
"to place items from fixed to semi-random locations. For purposes of these shuffles, all", "to place items from fixed to semi-random locations. For purposes of these shuffles, all",
"Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and", "Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and",
"1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool", "1/2 magic are considered to be part of a major items pool. Big Keys are added to the pool",
"if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),", "if shuffled. Same for small keys, compasses, maps, keydrops (if small keys are also shuffled),",
"1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and", "1 of each capacity upgrade for shopsanity, the quiver item for retro+shopsanity, and",
"triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.", "triforce pieces for Triforce Hunt. Future modes will add to these as appropriate.",
"vanilla_bias Same as above, but attempts to place items in their vanilla", "vanilla_fill As above, but attempts to place items in their vanilla",
" location first. Major items that cannot be placed that way", " location first. Major items that cannot be placed that way",
" will attempt to be placed in other failed locations first.", " will attempt to be placed in other failed locations first.",
" Also attempts to place junk items in vanilla locations", " Also attempts to place all items in vanilla locations",
"major_bias same as above, but uses the major items' location preferentially", "major_only As above, but uses the major items' location preferentially",
" major item location are defined as the group of location where", " major item location are defined as the group of location where",
" the items are found in the vanilla game. Backup locations for items", " the items are found in the vanilla game.",
" not in the vanilla game will be in the documentation", "dungeon_only As above, but major items are preferentially placed",
"dungeon_bias same as above, but major items are preferentially placed",
" in dungeons locations first", " in dungeons locations first",
"cluster_bias same as above, but groups of locations are chosen randomly", "district As above, but groups of locations are chosen randomly",
" from a pool of fixed locations designed to be interesting", " from a pool of fixed locations designed to be interesting",
" and give major clues about the location of other", " and give major clues about the location of other",
" advancement items. These fixed groups will be documented" " advancement items. These fixed groups will be documented."
], ],
"shuffle": [ "shuffle": [
"Select Entrance Shuffling Algorithm. (default: %(default)s)", "Select Entrance Shuffling Algorithm. (default: %(default)s)",

View File

@@ -281,10 +281,10 @@
"randomizer.item.sortingalgo": "Item Sorting", "randomizer.item.sortingalgo": "Item Sorting",
"randomizer.item.sortingalgo.balanced": "Balanced", "randomizer.item.sortingalgo.balanced": "Balanced",
"randomizer.item.sortingalgo.equitable": "Equitable", "randomizer.item.sortingalgo.equitable": "Equitable",
"randomizer.item.sortingalgo.vanilla_bias": "Biased: Vanilla", "randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill",
"randomizer.item.sortingalgo.major_bias": "Biased: Major Items", "randomizer.item.sortingalgo.major_only": "Major Location Restriction",
"randomizer.item.sortingalgo.dungeon_bias": "Biased: Dungeons", "randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction",
"randomizer.item.sortingalgo.cluster_bias": "Biased: Clustered", "randomizer.item.sortingalgo.district": "District Restriction",
"randomizer.item.restrict_boss_items": "Forbidden Boss Items", "randomizer.item.restrict_boss_items": "Forbidden Boss Items",
"randomizer.item.restrict_boss_items.none": "None", "randomizer.item.restrict_boss_items.none": "None",

View File

@@ -118,10 +118,10 @@
"options": [ "options": [
"balanced", "balanced",
"equitable", "equitable",
"vanilla_bias", "vanilla_fill",
"major_bias", "major_only",
"dungeon_bias", "dungeon_only",
"cluster_bias" "district"
] ]
}, },
"restrict_boss_items": { "restrict_boss_items": {

View File

@@ -3,6 +3,7 @@ import logging
from math import ceil from math import ceil
from collections import defaultdict from collections import defaultdict
from source.item.District import resolve_districts
from DoorShuffle import validate_vanilla_reservation from DoorShuffle import validate_vanilla_reservation
from Dungeons import dungeon_table from Dungeons import dungeon_table
from Items import item_table, ItemFactory from Items import item_table, ItemFactory
@@ -17,6 +18,8 @@ class ItemPoolConfig(object):
self.placeholders = None self.placeholders = None
self.reserved_locations = defaultdict(set) self.reserved_locations = defaultdict(set)
self.recorded_choices = []
class LocationGroup(object): class LocationGroup(object):
def __init__(self, name): def __init__(self, name):
@@ -57,7 +60,7 @@ def create_item_pool_config(world):
for item in dungeon.all_items: for item in dungeon.all_items:
if item.map or item.compass: if item.map or item.compass:
item.advancement = True item.advancement = True
if world.algorithm == 'vanilla_bias': if world.algorithm == 'vanilla_fill':
config.static_placement = {} config.static_placement = {}
config.location_groups = {} config.location_groups = {}
for player in range(1, world.players + 1): 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'])] LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']: for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
config.reserved_locations[player].add(loc_name) config.reserved_locations[player].add(loc_name)
elif world.algorithm == 'major_bias': elif world.algorithm == 'major_only':
config.location_groups = [ config.location_groups = [
LocationGroup('MajorItems'), LocationGroup('MajorItems'),
LocationGroup('Backup') LocationGroup('Backup')
@@ -111,7 +114,7 @@ def create_item_pool_config(world):
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops'] backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops']) + mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup) config.location_groups[1].locations = set(backup)
elif world.algorithm == 'dungeon_bias': elif world.algorithm == 'dungeon_only':
config.location_groups = [ config.location_groups = [
LocationGroup('Dungeons'), LocationGroup('Dungeons'),
LocationGroup('Backup') LocationGroup('Backup')
@@ -205,6 +208,74 @@ def create_item_pool_config(world):
config.location_groups[0].locations = chosen_locations 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): def location_prefilled(location, world, player):
if world.swords[player] == 'vanilla': if world.swords[player] == 'vanilla':
return location in vanilla_swords return location in vanilla_swords
@@ -390,6 +461,8 @@ def calc_dungeon_limits(world, player):
def determine_major_items(world, player): def determine_major_items(world, player):
major_item_set = set(major_items) major_item_set = set(major_items)
if world.progressive == 'off':
pass # now what?
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'}) major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
if world.keyshuffle[player]: if world.keyshuffle[player]:
@@ -412,16 +485,18 @@ def determine_major_items(world, player):
def classify_major_items(world): def classify_major_items(world):
if world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias'] or (world.algorithm == 'entangled' if world.algorithm in ['major_only', 'dungeon_only', 'district']:
and world.players > 1):
config = world.item_pool_config config = world.item_pool_config
for item in world.itempool: for item in world.itempool:
if item.name in config.item_pool[item.player]: 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: if item.smallkey or item.bigkey:
item.advancement = True item.advancement = True
else: else:
item.priority = True item.priority = True
else:
if item.priority:
item.priority = False
def figure_out_clustered_choices(world): def figure_out_clustered_choices(world):
@@ -488,13 +563,15 @@ def vanilla_fallback(item_to_place, locations, world):
return [] return []
def filter_locations(item_to_place, locations, world): def filter_locations(item_to_place, locations, world, vanilla_skip=False):
if world.algorithm == 'vanilla_bias': if world.algorithm == 'vanilla_fill':
config, filtered = world.item_pool_config, [] config, filtered = world.item_pool_config, []
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name 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]: if item_name in config.static_placement[item_to_place.player]:
restricted = config.static_placement[item_to_place.player][item_name] 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] 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 i = 0
while len(filtered) <= 0: while len(filtered) <= 0:
if i >= len(config.location_groups[item_to_place.player]): 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] filtered = [l for l in locations if l.player == item_to_place.player and l.name in restricted]
i += 1 i += 1
return filtered return filtered
if world.algorithm in ['major_bias', 'dungeon_bias']: if world.algorithm in ['major_only', 'dungeon_only']:
config = world.item_pool_config config = world.item_pool_config
if item_to_place.name in config.item_pool[item_to_place.player]: if item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations 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] filtered = [l for l in locations if l.name in restricted]
# bias toward certain location in overflow? (thinking about this for major_bias) # bias toward certain location in overflow? (thinking about this for major_bias)
return filtered if len(filtered) > 0 else locations 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 config = world.item_pool_config
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]: if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations 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', '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', '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)', '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', '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', 'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
'Progressive Bow', 'Progressive Bow (Alt)'} '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