Changed bias named. Added districting
This commit is contained in:
43
Fill.py
43
Fill.py
@@ -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)
|
||||||
|
|||||||
3
Main.py
3
Main.py
@@ -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"))
|
||||||
|
|
||||||
|
|||||||
@@ -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
40
Rom.py
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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)",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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
169
source/item/District.py
Normal 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
|
||||||
Reference in New Issue
Block a user