Merge branch 'DoorDevUnstable' into Sandbox
# Conflicts: # ItemList.py # Items.py # Main.py # Rom.py # data/base2current.bps
This commit is contained in:
@@ -24,15 +24,20 @@ class CustomSettings(object):
|
||||
head, filename = os.path.split(file)
|
||||
self.relative_dir = head
|
||||
|
||||
def determine_seed(self):
|
||||
if 'meta' not in self.file_source:
|
||||
return None
|
||||
meta = defaultdict(lambda: None, self.file_source['meta'])
|
||||
seed = meta['seed']
|
||||
if seed:
|
||||
random.seed(seed)
|
||||
return seed
|
||||
return None
|
||||
def determine_seed(self, default_seed):
|
||||
if 'meta' in self.file_source:
|
||||
meta = defaultdict(lambda: None, self.file_source['meta'])
|
||||
seed = meta['seed']
|
||||
if seed:
|
||||
random.seed(seed)
|
||||
return seed
|
||||
if default_seed is None:
|
||||
random.seed(None)
|
||||
seed = random.randint(0, 999999999)
|
||||
else:
|
||||
seed = default_seed
|
||||
random.seed(seed)
|
||||
return seed
|
||||
|
||||
def determine_players(self):
|
||||
if 'meta' not in self.file_source:
|
||||
@@ -43,7 +48,10 @@ class CustomSettings(object):
|
||||
def adjust_args(self, args):
|
||||
def get_setting(value, default):
|
||||
if value:
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return random.choices(list(value.keys()), list(value.values()), k=1)[0]
|
||||
else:
|
||||
return value
|
||||
return default
|
||||
if 'meta' in self.file_source:
|
||||
meta = defaultdict(lambda: None, self.file_source['meta'])
|
||||
@@ -53,6 +61,7 @@ class CustomSettings(object):
|
||||
args.bps = get_setting(meta['bps'], args.bps)
|
||||
args.suppress_rom = get_setting(meta['suppress_rom'], args.suppress_rom)
|
||||
args.names = get_setting(meta['names'], args.names)
|
||||
args.race = get_setting(meta['race'], args.race)
|
||||
self.player_range = range(1, args.multi + 1)
|
||||
if 'settings' in self.file_source:
|
||||
for p in self.player_range:
|
||||
@@ -67,6 +76,7 @@ class CustomSettings(object):
|
||||
args.door_shuffle[p] = get_setting(settings['door_shuffle'], args.door_shuffle[p])
|
||||
args.logic[p] = get_setting(settings['logic'], args.logic[p])
|
||||
args.mode[p] = get_setting(settings['mode'], args.mode[p])
|
||||
args.boots_hint[p] = get_setting(settings['boots_hint'], args.boots_hint[p])
|
||||
args.swords[p] = get_setting(settings['swords'], args.swords[p])
|
||||
args.flute_mode[p] = get_setting(settings['flute_mode'], args.flute_mode[p])
|
||||
args.bow_mode[p] = get_setting(settings['bow_mode'], args.bow_mode[p])
|
||||
@@ -87,11 +97,21 @@ class CustomSettings(object):
|
||||
if args.pottery[p] == 'none':
|
||||
args.pottery[p] = 'keys'
|
||||
|
||||
if args.retro[p] or args.mode[p] == 'retro':
|
||||
if args.bow_mode[p] == 'progressive':
|
||||
args.bow_mode[p] = 'retro'
|
||||
elif args.bow_mode[p] == 'silvers':
|
||||
args.bow_mode[p] = 'retro_silvers'
|
||||
args.take_any[p] = 'random' if args.take_any[p] == 'none' else args.take_any[p]
|
||||
args.keyshuffle[p] = 'universal'
|
||||
|
||||
args.mixed_travel[p] = get_setting(settings['mixed_travel'], args.mixed_travel[p])
|
||||
args.standardize_palettes[p] = get_setting(settings['standardize_palettes'],
|
||||
args.standardize_palettes[p])
|
||||
args.intensity[p] = get_setting(settings['intensity'], args.intensity[p])
|
||||
args.door_type_mode[p] = get_setting(settings['door_type_mode'], args.door_type_mode[p])
|
||||
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
|
||||
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
|
||||
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
|
||||
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
|
||||
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
|
||||
@@ -105,12 +125,13 @@ class CustomSettings(object):
|
||||
|
||||
if get_setting(settings['keysanity'], args.keysanity):
|
||||
args.bigkeyshuffle[p] = True
|
||||
args.keyshuffle[p] = True
|
||||
if args.keyshuffle[p] == 'none':
|
||||
args.keyshuffle[p] = 'wild'
|
||||
args.mapshuffle[p] = True
|
||||
args.compassshuffle[p] = True
|
||||
|
||||
args.shufflebosses[p] = get_setting(settings['shufflebosses'], args.shufflebosses[p])
|
||||
args.shuffleenemies[p] = get_setting(settings['shuffleenemies'], args.shuffleenemies[p])
|
||||
args.shufflebosses[p] = get_setting(settings['boss_shuffle'], args.shufflebosses[p])
|
||||
args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], args.shuffleenemies[p])
|
||||
args.enemy_health[p] = get_setting(settings['enemy_health'], args.enemy_health[p])
|
||||
args.enemy_damage[p] = get_setting(settings['enemy_damage'], args.enemy_damage[p])
|
||||
args.shufflepots[p] = get_setting(settings['shufflepots'], args.shufflepots[p])
|
||||
@@ -139,6 +160,7 @@ class CustomSettings(object):
|
||||
args.ow_palettes[p] = get_setting(settings['ow_palettes'], args.ow_palettes[p])
|
||||
args.uw_palettes[p] = get_setting(settings['uw_palettes'], args.uw_palettes[p])
|
||||
args.shuffle_sfx[p] = get_setting(settings['shuffle_sfx'], args.shuffle_sfx[p])
|
||||
args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p])
|
||||
|
||||
def get_item_pool(self):
|
||||
if 'item_pool' in self.file_source:
|
||||
@@ -150,6 +172,11 @@ class CustomSettings(object):
|
||||
return self.file_source['placements']
|
||||
return None
|
||||
|
||||
def get_advanced_placements(self):
|
||||
if 'advanced_placements' in self.file_source:
|
||||
return self.file_source['advanced_placements']
|
||||
return None
|
||||
|
||||
def get_entrances(self):
|
||||
if 'entrances' in self.file_source:
|
||||
return self.file_source['entrances']
|
||||
@@ -175,13 +202,19 @@ class CustomSettings(object):
|
||||
return self.file_source['medallions']
|
||||
return None
|
||||
|
||||
def create_from_world(self, world):
|
||||
def get_drops(self):
|
||||
if 'drops' in self.file_source:
|
||||
return self.file_source['drops']
|
||||
return None
|
||||
|
||||
def create_from_world(self, world, race):
|
||||
self.player_range = range(1, world.players + 1)
|
||||
settings_dict, meta_dict = {}, {}
|
||||
self.world_rep['meta'] = meta_dict
|
||||
meta_dict['players'] = world.players
|
||||
meta_dict['algorithm'] = world.algorithm
|
||||
meta_dict['seed'] = world.seed
|
||||
meta_dict['race'] = race
|
||||
self.world_rep['settings'] = settings_dict
|
||||
for p in self.player_range:
|
||||
settings_dict[p] = {}
|
||||
@@ -189,6 +222,8 @@ class CustomSettings(object):
|
||||
settings_dict[p]['door_shuffle'] = world.doorShuffle[p]
|
||||
settings_dict[p]['intensity'] = world.intensity[p]
|
||||
settings_dict[p]['door_type_mode'] = world.door_type_mode[p]
|
||||
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
|
||||
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
|
||||
settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
|
||||
settings_dict[p]['logic'] = world.logic[p]
|
||||
settings_dict[p]['mode'] = world.mode[p]
|
||||
|
||||
@@ -98,6 +98,8 @@ SETTINGSTOPROCESS = {
|
||||
"dungeondoorshuffle": "door_shuffle",
|
||||
"dungeonintensity": "intensity",
|
||||
"door_type_mode": "door_type_mode",
|
||||
"trap_door_mode": "trap_door_mode",
|
||||
"key_logic_algorithm": "key_logic_algorithm",
|
||||
"decoupledoors": "decoupledoors",
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dropshuffle": "dropshuffle",
|
||||
@@ -130,6 +132,7 @@ SETTINGSTOPROCESS = {
|
||||
"print_custom_yaml": "print_custom_yaml",
|
||||
"usestartinventory": "usestartinventory",
|
||||
"usecustompool": "custom",
|
||||
"race": "race",
|
||||
"saveonexit": "saveonexit"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -58,15 +58,17 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
|
||||
excluded[region] = None
|
||||
elif split_dungeon and builder.sewers_access and builder.sewers_access.entrance.parent_region == region:
|
||||
continue
|
||||
elif len(region.entrances) == 1: # for holes
|
||||
access_region = next(x.parent_region for x in region.entrances
|
||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
|
||||
or x.parent_region.name == 'Sewer Drop')
|
||||
if access_region.name == 'Sewer Drop':
|
||||
access_region = next(x.parent_region for x in access_region.entrances)
|
||||
if (access_region.name in world.inaccessible_regions[player] and
|
||||
region.name not in world.enabled_entrances[player]):
|
||||
drop_region = next((x.parent_region for x in region.entrances
|
||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]
|
||||
or x.parent_region.name == 'Sewer Drop'), None)
|
||||
if drop_region: # for holes
|
||||
if drop_region.name == 'Sewer Drop':
|
||||
drop_region = next(x.parent_region for x in drop_region.entrances)
|
||||
if (drop_region.name in world.inaccessible_regions[player] and
|
||||
region.name not in world.enabled_entrances[player]):
|
||||
excluded[region] = None
|
||||
elif region in excluded:
|
||||
del excluded[region]
|
||||
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
|
||||
doors_to_connect, idx = {}, 0
|
||||
all_regions = set()
|
||||
@@ -216,7 +218,6 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
|
||||
else:
|
||||
logger.debug(f' Re-Linking {attempt.name} -> {new_door.name}')
|
||||
logger.debug(f' Re-Linking {old_attempt.name} -> {old_target.name}')
|
||||
hash_code_set.add(hash_code)
|
||||
return proposed_map, hash_code
|
||||
|
||||
|
||||
@@ -315,9 +316,14 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
|
||||
non_hole_portals.append(portal.door.entrance.parent_region.name)
|
||||
if portal.destination:
|
||||
paths.append(portal.door.entrance.parent_region.name)
|
||||
if world.mode[player] == 'standard' and name == 'Hyrule Castle':
|
||||
if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon':
|
||||
paths.append('Hyrule Dungeon Cellblock')
|
||||
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
||||
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
|
||||
entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South')
|
||||
# todo: in non-er, we can use the other portals too
|
||||
paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name))
|
||||
paths.append(('Hyrule Castle Throne Room', [entrance.door.entrance.parent_region.name,
|
||||
'Hyrule Dungeon Cellblock']))
|
||||
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
|
||||
paths.append('Thieves Attic Window')
|
||||
elif 'Thieves Attic Window' in all_r_names:
|
||||
@@ -431,6 +437,13 @@ def connect_simple_door(exit_door, region):
|
||||
|
||||
|
||||
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
|
||||
std_special_big_key_doors = ['Hyrule Castle Throne Room Tapestry'] + special_big_key_doors
|
||||
|
||||
|
||||
def get_special_big_key_doors(world, player):
|
||||
if world.mode[player] == 'standard':
|
||||
return std_special_big_key_doors
|
||||
return special_big_key_doors
|
||||
|
||||
|
||||
class ExplorationState(object):
|
||||
@@ -618,6 +631,7 @@ class ExplorationState(object):
|
||||
elif not self.in_door_list(door, self.avail_doors):
|
||||
self.append_door_to_list(door, self.avail_doors, flag)
|
||||
|
||||
# same as above but traps are ignored, and flag is not used
|
||||
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player):
|
||||
for door in get_doors(world, region, player):
|
||||
if door in proposed_map and door.name in valid_doors:
|
||||
@@ -638,6 +652,27 @@ class ExplorationState(object):
|
||||
elif not self.in_door_list(door, self.avail_doors):
|
||||
self.append_door_to_list(door, self.avail_doors)
|
||||
|
||||
# same as above but traps are checked for
|
||||
def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player):
|
||||
for door in get_doors(world, region, player):
|
||||
if door in proposed_map and door.name in valid_doors:
|
||||
self.visited_doors.add(door)
|
||||
if self.can_traverse(door):
|
||||
if door.controller is not None:
|
||||
door = door.controller
|
||||
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
|
||||
if not self.in_door_list_ic(door, self.unattached_doors):
|
||||
self.append_door_to_list(door, self.unattached_doors)
|
||||
else:
|
||||
other = self.find_door_in_list(door, self.unattached_doors)
|
||||
if self.crystal != other.crystal:
|
||||
other.crystal = CrystalBarrier.Either
|
||||
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
|
||||
self.event_doors):
|
||||
self.append_door_to_list(door, self.event_doors)
|
||||
elif not self.in_door_list(door, self.avail_doors):
|
||||
self.append_door_to_list(door, self.avail_doors)
|
||||
|
||||
def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player):
|
||||
for door in get_doors(world, region, player):
|
||||
if self.can_traverse_ignore_traps(door) and door not in proposed_traps:
|
||||
@@ -671,7 +706,7 @@ class ExplorationState(object):
|
||||
if door in key_door_proposal and door not in self.opened_doors:
|
||||
if not self.in_door_list(door, self.small_doors):
|
||||
self.append_door_to_list(door, self.small_doors)
|
||||
elif (door.bigKey or door.name in special_big_key_doors) and not self.big_key_opened:
|
||||
elif (door.bigKey or door.name in get_special_big_key_doors(world, player)) and not self.big_key_opened:
|
||||
if not self.in_door_list(door, self.big_doors):
|
||||
self.append_door_to_list(door, self.big_doors)
|
||||
elif door.req_event is not None and door.req_event not in self.events:
|
||||
@@ -824,7 +859,10 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
|
||||
local_state = state.copy()
|
||||
for region in search_regions:
|
||||
local_state.visit_region(region)
|
||||
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
|
||||
if world.trap_door_mode[player] == 'vanilla':
|
||||
local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player)
|
||||
else:
|
||||
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
|
||||
while len(local_state.avail_doors) > 0:
|
||||
explorable_door = local_state.next_avail_door()
|
||||
if explorable_door.door in proposed_map:
|
||||
@@ -835,7 +873,10 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
|
||||
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
|
||||
and not local_state.visited(connect_region)):
|
||||
local_state.visit_region(connect_region)
|
||||
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
|
||||
if world.trap_door_mode[player] == 'vanilla':
|
||||
local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player)
|
||||
else:
|
||||
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
|
||||
return local_state
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from source.item.District import resolve_districts
|
||||
from BaseClasses import PotItem, PotFlags
|
||||
from BaseClasses import PotItem, PotFlags, LocationType
|
||||
from DoorShuffle import validate_vanilla_reservation
|
||||
from Dungeons import dungeon_table
|
||||
from Items import item_table, ItemFactory
|
||||
@@ -18,6 +18,11 @@ class ItemPoolConfig(object):
|
||||
self.item_pool = None
|
||||
self.placeholders = None
|
||||
self.reserved_locations = defaultdict(set)
|
||||
self.restricted = {}
|
||||
self.preferred = {}
|
||||
self.verify = {}
|
||||
self.verify_count = 0
|
||||
self.verify_target = 0
|
||||
|
||||
self.recorded_choices = []
|
||||
|
||||
@@ -77,8 +82,8 @@ def create_item_pool_config(world):
|
||||
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||
item = pot_items[pot.item]
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
location = f'{pot.room} {descriptor}'
|
||||
config.static_placement[player][item].append(location)
|
||||
loc = f'{pot.room} {descriptor}'
|
||||
config.static_placement[player][item].append(loc)
|
||||
if world.shopsanity[player]:
|
||||
for item, locs in shop_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
@@ -158,7 +163,11 @@ def create_item_pool_config(world):
|
||||
dungeon_set = (mode_grouping['Big Chests'] + mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
|
||||
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||
mode_grouping['Big Key Drops'])
|
||||
mode_grouping['Pot Keys'] + mode_grouping['Big Key Drops'])
|
||||
dungeon_set = set(dungeon_set)
|
||||
for loc in world.get_locations():
|
||||
if loc.parent_region.dungeon and loc.type in [LocationType.Pot, LocationType.Drop]:
|
||||
dungeon_set.add(loc.name)
|
||||
for player in range(1, world.players + 1):
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
config.location_groups[0].locations = set(dungeon_set)
|
||||
@@ -187,26 +196,27 @@ def district_item_pool_config(world):
|
||||
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
|
||||
dist_adj = adjustment
|
||||
if name not in district_choices:
|
||||
district_choices[name] = (district.sphere_one, dist_len)
|
||||
district_choices[name] = (district.sphere_one, dist_adj)
|
||||
else:
|
||||
so, amt = district_choices[name]
|
||||
district_choices[name] = (so or district.sphere_one, amt + dist_len)
|
||||
district_choices[name] = (so or district.sphere_one, amt + dist_adj)
|
||||
|
||||
chosen_locations = defaultdict(set)
|
||||
location_cnt = 0
|
||||
adjustment_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
|
||||
so, adj = district_choices[sphere_one]
|
||||
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)
|
||||
adjustment_cnt += adj
|
||||
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||
|
||||
scale_factors = defaultdict(int)
|
||||
scale_total = 0
|
||||
@@ -215,8 +225,9 @@ def district_item_pool_config(world):
|
||||
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
|
||||
if scale > 0:
|
||||
scale_total += scale
|
||||
scale_factors[dungeon.name] += scale
|
||||
scale_total = max(1, scale_total)
|
||||
scale_divisors = defaultdict(lambda: 1)
|
||||
scale_divisors.update(scale_factors)
|
||||
@@ -224,13 +235,15 @@ def district_item_pool_config(world):
|
||||
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
|
||||
so, adj = district_choices[choice]
|
||||
|
||||
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)
|
||||
adjustment_cnt += adj
|
||||
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||
config.placeholders = location_cnt - item_cnt
|
||||
config.location_groups[0].locations = chosen_locations
|
||||
|
||||
@@ -380,8 +393,13 @@ def vanilla_fallback(item_to_place, locations, world):
|
||||
|
||||
|
||||
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
||||
config = world.item_pool_config
|
||||
if not isinstance(item_to_place, str):
|
||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||
else:
|
||||
item_name = item_to_place
|
||||
if world.algorithm == 'vanilla_fill':
|
||||
config, filtered = world.item_pool_config, []
|
||||
filtered = []
|
||||
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]
|
||||
@@ -408,7 +426,8 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
if 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]:
|
||||
if ((isinstance(item_to_place,str) and item_to_place == 'Placeholder')
|
||||
or item_to_place.name in config.item_pool[item_to_place.player]):
|
||||
restricted = config.location_groups[0].locations
|
||||
filtered = [l for l in locations if l.name in restricted and l.player in restricted[l.name]]
|
||||
return filtered if len(filtered) > 0 else locations
|
||||
@@ -418,6 +437,15 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
|
||||
if len(filtered) == 0:
|
||||
raise RuntimeError('Can\'t sell potion of a certain type due to district restriction')
|
||||
return filtered
|
||||
if (item_name, item_to_place.player) in config.restricted:
|
||||
locs = config.restricted[(item_name, item_to_place.player)]
|
||||
return sorted(locations, key=lambda l: 1 if l.name in locs else 0)
|
||||
if (item_name, item_to_place.player) in config.preferred:
|
||||
locs = config.preferred[(item_name, item_to_place.player)]
|
||||
return sorted(locations, key=lambda l: 0 if l.name in locs else 1)
|
||||
if (item_name, item_to_place.player) in config.verify:
|
||||
locs = config.verify[(item_name, item_to_place.player)].keys()
|
||||
return sorted(locations, key=lambda l: 0 if l.name in locs else 1)
|
||||
return locations
|
||||
|
||||
|
||||
@@ -815,3 +843,26 @@ pot_items = {
|
||||
}
|
||||
|
||||
valid_pot_items = {y: x for x, y in pot_items.items()}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import yaml
|
||||
from yaml.representer import Representer
|
||||
advanced_placements = {'advanced_placements': {}}
|
||||
player_map = advanced_placements['advanced_placements']
|
||||
placement_list = []
|
||||
player_map[1] = placement_list
|
||||
for item, location_list in vanilla_mapping.items():
|
||||
for location in location_list:
|
||||
placement = {}
|
||||
placement['type'] = 'LocationGroup'
|
||||
placement['item'] = item
|
||||
locations = placement['locations'] = []
|
||||
locations.append(location)
|
||||
locations.append('Random')
|
||||
placement_list.append(placement)
|
||||
yaml.add_representer(defaultdict, Representer.represent_dict)
|
||||
with open('fillgen.yaml', 'w') as file:
|
||||
yaml.dump(advanced_placements, file)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import importlib.util
|
||||
import webbrowser
|
||||
from tkinter import Tk, Label, Button, Frame
|
||||
|
||||
|
||||
def check_requirements(console=False):
|
||||
@@ -26,6 +24,9 @@ def check_requirements(console=False):
|
||||
logger.error('See the step about "Installing Platform-specific dependencies":')
|
||||
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
|
||||
else:
|
||||
import webbrowser
|
||||
from tkinter import Tk, Label, Button, Frame
|
||||
|
||||
master = Tk()
|
||||
master.title('Error')
|
||||
frame = Frame(master)
|
||||
|
||||
@@ -66,6 +66,8 @@ def link_entrances_new(world, player):
|
||||
default_map['Old Man Cave (East)'] = 'Death Mountain Return Cave Exit (West)'
|
||||
one_way_map['Bumper Cave (Top)'] = 'Dark Death Mountain Healer Fairy'
|
||||
del default_map['Bumper Cave (Top)']
|
||||
del one_way_map['Big Bomb Shop']
|
||||
one_way_map['Inverted Big Bomb Shop'] = 'Inverted Big Bomb Shop'
|
||||
avail_pool.default_map = default_map
|
||||
avail_pool.one_way_map = one_way_map
|
||||
|
||||
@@ -273,7 +275,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
||||
# OM Cave entrance in lw/dw if cross_world off
|
||||
if 'Old Man Cave Exit (West)' in rem_exits:
|
||||
world_limiter = DW_Entrances if avail.inverted else LW_Entrances
|
||||
om_cave_options = [x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)]
|
||||
om_cave_options = sorted([x for x in rem_entrances if x in world_limiter and bonk_fairy_exception(x)])
|
||||
om_cave_choice = random.choice(om_cave_options)
|
||||
if not avail.coupled:
|
||||
connect_exit('Old Man Cave Exit (West)', om_cave_choice, avail)
|
||||
@@ -302,14 +304,15 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
|
||||
unused_entrances = set()
|
||||
if not cross_world:
|
||||
lw_entrances, dw_entrances = [], []
|
||||
for x in rem_entrances:
|
||||
left = sorted(rem_entrances)
|
||||
for x in left:
|
||||
if bonk_fairy_exception(x):
|
||||
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
|
||||
do_same_world_connectors(lw_entrances, dw_entrances, multi_exit_caves, avail)
|
||||
unused_entrances.update(lw_entrances)
|
||||
unused_entrances.update(dw_entrances)
|
||||
else:
|
||||
entrance_list = [x for x in rem_entrances if bonk_fairy_exception(x)]
|
||||
entrance_list = sorted([x for x in rem_entrances if bonk_fairy_exception(x)])
|
||||
do_cross_world_connectors(entrance_list, multi_exit_caves, avail)
|
||||
unused_entrances.update(entrance_list)
|
||||
|
||||
@@ -630,7 +633,7 @@ def do_fixed_shuffle(avail, entrance_list):
|
||||
rules = Restrictions()
|
||||
rules.size = size
|
||||
if ('Hyrule Castle Entrance (South)' in entrances and
|
||||
avail.world.doorShuffle[avail.player] in ['basic', 'crossed']):
|
||||
avail.world.doorShuffle[avail.player] != 'vanilla'):
|
||||
rules.must_exit_to_lw = True
|
||||
if 'Inverted Ganons Tower' in entrances and not avail.world.shuffle_ganon:
|
||||
rules.fixed = True
|
||||
@@ -774,12 +777,19 @@ def do_vanilla_connect(pool_def, avail):
|
||||
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
|
||||
return
|
||||
defaults = inverted_default_connections if avail.inverted else default_connections
|
||||
if avail.inverted:
|
||||
if 'Dark Death Mountain Fairy' in pool_def['entrances']:
|
||||
pool_def['entrances'].remove('Dark Death Mountain Fairy')
|
||||
pool_def['entrances'].append('Bumper Cave (top)')
|
||||
for entrance in pool_def['entrances']:
|
||||
if entrance in avail.entrances:
|
||||
target = defaults[entrance]
|
||||
connect_simple(avail.world, entrance, target, avail.player)
|
||||
avail.entrances.remove(entrance)
|
||||
avail.exits.remove(target)
|
||||
if entrance in avail.default_map:
|
||||
connect_vanilla_two_way(entrance, avail.default_map[entrance], avail)
|
||||
else:
|
||||
connect_simple(avail.world, entrance, target, avail.player)
|
||||
avail.entrances.remove(entrance)
|
||||
avail.exits.remove(target)
|
||||
|
||||
|
||||
def do_mandatory_connections(avail, entrances, cave_options, must_exit):
|
||||
@@ -1013,12 +1023,15 @@ def connect_custom(avail_pool, world, player):
|
||||
if world.customizer and world.customizer.get_entrances():
|
||||
custom_entrances = world.customizer.get_entrances()
|
||||
player_key = player
|
||||
for ent_name, exit_name in custom_entrances[player_key]['two-way'].items():
|
||||
connect_two_way(ent_name, exit_name, avail_pool)
|
||||
for ent_name, exit_name in custom_entrances[player_key]['entrances'].items():
|
||||
connect_entrance(ent_name, exit_name, avail_pool)
|
||||
for ent_name, exit_name in custom_entrances[player_key]['exits'].items():
|
||||
connect_exit(exit_name, ent_name, avail_pool)
|
||||
if 'two-way' in custom_entrances[player_key]:
|
||||
for ent_name, exit_name in custom_entrances[player_key]['two-way'].items():
|
||||
connect_two_way(ent_name, exit_name, avail_pool)
|
||||
if 'entrances' in custom_entrances[player_key]:
|
||||
for ent_name, exit_name in custom_entrances[player_key]['entrances'].items():
|
||||
connect_entrance(ent_name, exit_name, avail_pool)
|
||||
if 'exits' in custom_entrances[player_key]:
|
||||
for ent_name, exit_name in custom_entrances[player_key]['exits'].items():
|
||||
connect_exit(exit_name, ent_name, avail_pool)
|
||||
|
||||
|
||||
def connect_simple(world, exit_name, region_name, player):
|
||||
@@ -1228,7 +1241,7 @@ modes = {
|
||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||
'Dark Desert Hint',
|
||||
'Links House', 'Inverted Links House', 'Tavern North']
|
||||
'Links House', 'Tavern North']
|
||||
},
|
||||
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
|
||||
'special': 'old_man_cave_east',
|
||||
@@ -1304,7 +1317,7 @@ modes = {
|
||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||
'Dark Desert Hint',
|
||||
'Links House', 'Inverted Links House', 'Tavern North']
|
||||
'Links House', 'Tavern North'] # inverted links house gets substituted
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1572,6 +1585,7 @@ entrance_map = {
|
||||
'Paradox Cave (Bottom)': 'Paradox Cave Exit (Bottom)',
|
||||
'Paradox Cave (Middle)': 'Paradox Cave Exit (Middle)',
|
||||
'Paradox Cave (Top)': 'Paradox Cave Exit (Top)',
|
||||
'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary Exit',
|
||||
}
|
||||
|
||||
|
||||
@@ -1607,7 +1621,7 @@ single_entrance_map = {
|
||||
'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy',
|
||||
'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam',
|
||||
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing',
|
||||
'Inverted Bomb Shop': 'Inverted Bomb Shop', 'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary',
|
||||
'Inverted Bomb Shop': 'Inverted Bomb Shop'
|
||||
}
|
||||
|
||||
default_dw = {
|
||||
@@ -1616,7 +1630,7 @@ default_dw = {
|
||||
'Palace of Darkness Exit', 'Swamp Palace Exit', 'Turtle Rock Exit (Front)', 'Turtle Rock Ledge Exit (West)',
|
||||
'Turtle Rock Ledge Exit (East)', 'Turtle Rock Isolated Ledge Exit', 'Bumper Cave Exit (Top)',
|
||||
'Bumper Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)',
|
||||
'Hookshot Cave Front Exit', 'Hookshot Cave Back Exit', 'Ganons Tower Exit', 'Pyramid Exit',
|
||||
'Hookshot Cave Front Exit', 'Hookshot Cave Back Exit', 'Ganons Tower Exit', 'Pyramid Exit', 'Bonk Fairy (Dark)',
|
||||
'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Dark Desert Healer Fairy',
|
||||
'Dark Death Mountain Healer Fairy', 'Cave Shop (Dark Death Mountain)', 'Pyramid Fairy', 'East Dark World Hint',
|
||||
'Palace of Darkness Hint', 'Big Bomb Shop', 'Village of Outcasts Shop', 'Dark Lake Hylia Shop',
|
||||
@@ -1639,7 +1653,7 @@ default_lw = {
|
||||
'Spectacle Rock Cave Exit (Top)', 'Spectacle Rock Cave Exit (Peak)', 'Paradox Cave Exit (Bottom)',
|
||||
'Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Fairy Ascension Cave Exit (Bottom)',
|
||||
'Fairy Ascension Cave Exit (Top)', 'Spiral Cave Exit', 'Spiral Cave Exit (Top)', 'Waterfall of Wishing', 'Dam',
|
||||
'Blinds Hideout', 'Lumberjack House', 'Bonk Fairy (Light)', 'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy',
|
||||
'Blinds Hideout', 'Lumberjack House', 'Bonk Fairy (Light)', 'Lake Hylia Healer Fairy',
|
||||
'Swamp Healer Fairy', 'Desert Healer Fairy', 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Kings Grave', 'Tavern',
|
||||
'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Cave Shop (Lake Hylia)', 'Capacity Upgrade', 'Blacksmiths Hut',
|
||||
'Sick Kids House', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House',
|
||||
@@ -1684,7 +1698,7 @@ DW_Entrances = ['Bumper Cave (Bottom)', 'Superbunny Cave (Top)', 'Superbunny Ca
|
||||
'Dark Lake Hylia Ledge Hint', 'Chest Game', 'Dark Desert Fairy', 'Dark Lake Hylia Ledge Fairy',
|
||||
'Fortune Teller (Dark)', 'Dark World Hammer Peg Cave', 'Pyramid Entrance',
|
||||
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
|
||||
'Skull Woods Second Section Door (West)',
|
||||
'Skull Woods Second Section Door (West)', 'Ganons Tower',
|
||||
'Inverted Dark Sanctuary', 'Inverted Links House', 'Inverted Agahnims Tower']
|
||||
|
||||
LW_Must_Exit = ['Desert Palace Entrance (East)']
|
||||
@@ -1938,6 +1952,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Bumper Cave Entrance Mirror Spot', 'Death Mountain Entrance'),
|
||||
('Bumper Cave Ledge Drop', 'West Dark World'),
|
||||
('Bumper Cave Ledge Mirror Spot', 'Death Mountain Return Ledge'),
|
||||
('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'),
|
||||
('Bumper Cave Bottom to Top', 'Bumper Cave (top)'),
|
||||
('Skull Woods Forest', 'Skull Woods Forest'),
|
||||
('Desert Ledge Mirror Spot', 'Desert Ledge'),
|
||||
('Desert Ledge (Northeast) Mirror Spot', 'Desert Ledge (Northeast)'),
|
||||
@@ -2066,6 +2082,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
|
||||
('Bumper Cave Entrance Rock', 'Bumper Cave Entrance'),
|
||||
('Bumper Cave Entrance Drop', 'West Dark World'),
|
||||
('Bumper Cave Ledge Drop', 'West Dark World'),
|
||||
('Bumper Cave Top To Bottom', 'Bumper Cave (bottom)'),
|
||||
('Bumper Cave Bottom to Top', 'Bumper Cave (top)'),
|
||||
('Skull Woods Forest', 'Skull Woods Forest'),
|
||||
('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'),
|
||||
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
||||
@@ -2290,8 +2308,8 @@ default_connections = {'Links House': 'Links House',
|
||||
'C-Shaped House': 'C-Shaped House',
|
||||
'Chest Game': 'Chest Game',
|
||||
'Dark World Hammer Peg Cave': 'Dark World Hammer Peg Cave',
|
||||
'Bumper Cave (Bottom)': 'Bumper Cave',
|
||||
'Bumper Cave (Top)': 'Bumper Cave',
|
||||
'Bumper Cave (Bottom)': 'Bumper Cave (bottom)',
|
||||
'Bumper Cave (Top)': 'Bumper Cave (top)',
|
||||
'Red Shield Shop': 'Red Shield Shop',
|
||||
'Dark Sanctuary Hint': 'Dark Sanctuary Hint',
|
||||
'Fortune Teller (Dark)': 'Fortune Teller (Dark)',
|
||||
@@ -2452,7 +2470,7 @@ inverted_default_connections = {'Waterfall of Wishing': 'Waterfall of Wishing',
|
||||
'Inverted Big Bomb Shop': 'Inverted Big Bomb Shop',
|
||||
'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary',
|
||||
'Inverted Dark Sanctuary Exit': 'West Dark World',
|
||||
'Old Man Cave (West)': 'Bumper Cave',
|
||||
'Old Man Cave (West)': 'Bumper Cave (bottom)',
|
||||
'Old Man Cave (East)': 'Death Mountain Return Cave (left)',
|
||||
'Old Man Cave Exit (West)': 'West Dark World',
|
||||
'Old Man Cave Exit (East)': 'Dark Death Mountain',
|
||||
@@ -2461,7 +2479,7 @@ inverted_default_connections = {'Waterfall of Wishing': 'Waterfall of Wishing',
|
||||
'Bumper Cave (Top)': 'Dark Death Mountain Healer Fairy',
|
||||
'Bumper Cave Exit (Top)': 'Death Mountain Return Ledge',
|
||||
'Bumper Cave Exit (Bottom)': 'Light World',
|
||||
'Death Mountain Return Cave (West)': 'Bumper Cave',
|
||||
'Death Mountain Return Cave (West)': 'Bumper Cave (top)',
|
||||
'Death Mountain Return Cave (East)': 'Death Mountain Return Cave (right)',
|
||||
'Death Mountain Return Cave Exit (West)': 'Death Mountain',
|
||||
'Death Mountain Return Cave Exit (East)': 'Death Mountain',
|
||||
|
||||
@@ -84,6 +84,8 @@ def roll_settings(weights):
|
||||
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||
ret.intensity = get_choice('intensity')
|
||||
ret.door_type_mode = get_choice('door_type_mode')
|
||||
ret.trap_door_mode = get_choice('trap_door_mode')
|
||||
ret.key_logic_algorithm = get_choice('key_logic_algorithm')
|
||||
ret.decoupledoors = get_choice('decoupledoors') == 'on'
|
||||
ret.experimental = get_choice('experimental') == 'on'
|
||||
ret.collection_rate = get_choice('collection_rate') == 'on'
|
||||
@@ -101,7 +103,7 @@ def roll_settings(weights):
|
||||
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' and keydropshuffle else ret.dropshuffle
|
||||
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
|
||||
ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery
|
||||
ret.colorizepots = get_choice('colorizepots') == 'on'
|
||||
ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on'
|
||||
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
||||
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
|
||||
ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights
|
||||
@@ -114,7 +116,9 @@ def roll_settings(weights):
|
||||
'dungeons': 'dungeons',
|
||||
'pedestal': 'pedestal',
|
||||
'triforce-hunt': 'triforcehunt',
|
||||
'trinity': 'trinity'
|
||||
'trinity': 'trinity',
|
||||
'ganonhunt': 'ganonhunt',
|
||||
'completionist': 'completionist'
|
||||
}[goal]
|
||||
ret.openpyramid = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False
|
||||
|
||||
@@ -202,5 +206,6 @@ def roll_settings(weights):
|
||||
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
||||
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
||||
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
|
||||
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
|
||||
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user