Merged DR v1.2.0.0

This commit is contained in:
codemann8
2022-12-03 01:52:22 -06:00
68 changed files with 10253 additions and 1200 deletions

View File

@@ -0,0 +1,381 @@
import os
import urllib.request
import urllib.parse
import yaml
from yaml.representer import Representer
from collections import defaultdict
from pathlib import Path
import RaceRandom as random
from BaseClasses import LocationType, DoorType
from source.tools.MysteryUtils import roll_settings, get_weights
class CustomSettings(object):
def __init__(self):
self.file_source = None
self.relative_dir = None
self.world_rep = {}
self.player_range = None
def load_yaml(self, file):
self.file_source = load_yaml(file)
head, filename = os.path.split(file)
self.relative_dir = head
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:
return None
meta = defaultdict(lambda: None, self.file_source['meta'])
return meta['players']
def adjust_args(self, args):
def get_setting(value, default):
if 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'])
args.multi = get_setting(meta['players'], args.multi)
args.algorithm = get_setting(meta['algorithm'], args.algorithm)
args.outputname = get_setting(meta['name'], args.outputname)
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)
self.player_range = range(1, args.multi + 1)
if 'settings' in self.file_source:
for p in self.player_range:
player_setting = self.file_source['settings'][p]
if isinstance(player_setting, str):
weights = get_weights(os.path.join(self.relative_dir, player_setting))
settings = defaultdict(lambda: None, vars(roll_settings(weights)))
args.mystery = True
else:
settings = defaultdict(lambda: None, player_setting)
args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p])
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])
args.item_functionality[p] = get_setting(settings['item_functionality'], args.item_functionality[p])
args.goal[p] = get_setting(settings['goal'], args.goal[p])
args.difficulty[p] = get_setting(settings['difficulty'], args.difficulty[p])
args.accessibility[p] = get_setting(settings['accessibility'], args.accessibility[p])
args.retro[p] = get_setting(settings['retro'], args.retro[p])
args.take_any[p] = get_setting(settings['take_any'], args.take_any[p])
args.hints[p] = get_setting(settings['hints'], args.hints[p])
args.shopsanity[p] = get_setting(settings['shopsanity'], args.shopsanity[p])
args.dropshuffle[p] = get_setting(settings['dropshuffle'], args.dropshuffle[p])
args.pottery[p] = get_setting(settings['pottery'], args.pottery[p])
if get_setting(settings['keydropshuffle'], args.keydropshuffle[p]):
args.dropshuffle[p] = True
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.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])
args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
args.experimental[p] = get_setting(settings['experimental'], args.experimental[p])
args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p])
args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p])
args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p])
args.mapshuffle[p] = get_setting(settings['mapshuffle'], args.mapshuffle[p])
args.compassshuffle[p] = get_setting(settings['compassshuffle'], args.compassshuffle[p])
if get_setting(settings['keysanity'], args.keysanity):
args.bigkeyshuffle[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.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])
args.bombbag[p] = get_setting(settings['bombbag'], args.bombbag[p])
args.shufflelinks[p] = get_setting(settings['shufflelinks'], args.shufflelinks[p])
args.shuffletavern[p] = get_setting(settings['shuffletavern'], args.shuffletavern[p])
args.restrict_boss_items[p] = get_setting(settings['restrict_boss_items'], args.restrict_boss_items[p])
args.overworld_map[p] = get_setting(settings['overworld_map'], args.overworld_map[p])
args.pseudoboots[p] = get_setting(settings['pseudoboots'], args.pseudoboots[p])
args.triforce_goal[p] = get_setting(settings['triforce_goal'], args.triforce_goal[p])
args.triforce_pool[p] = get_setting(settings['triforce_pool'], args.triforce_pool[p])
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
# mystery usage
args.usestartinventory[p] = get_setting(settings['usestartinventory'], args.usestartinventory[p])
args.startinventory[p] = get_setting(settings['startinventory'], args.startinventory[p])
# rom adjust stuff
args.sprite[p] = get_setting(settings['sprite'], args.sprite[p])
args.disablemusic[p] = get_setting(settings['disablemusic'], args.disablemusic[p])
args.quickswap[p] = get_setting(settings['quickswap'], args.quickswap[p])
args.reduce_flashing[p] = get_setting(settings['reduce_flashing'], args.reduce_flashing[p])
args.fastmenu[p] = get_setting(settings['fastmenu'], args.fastmenu[p])
args.heartcolor[p] = get_setting(settings['heartcolor'], args.heartcolor[p])
args.heartbeep[p] = get_setting(settings['heartbeep'], args.heartbeep[p])
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])
def get_item_pool(self):
if 'item_pool' in self.file_source:
return self.file_source['item_pool']
return None
def get_placements(self):
if 'placements' in self.file_source:
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']
return None
def get_doors(self):
if 'doors' in self.file_source:
return self.file_source['doors']
return None
def get_bosses(self):
if 'bosses' in self.file_source:
return self.file_source['bosses']
return None
def get_start_inventory(self):
if 'start_inventory' in self.file_source:
return self.file_source['start_inventory']
return None
def get_medallions(self):
if 'medallions' in self.file_source:
return self.file_source['medallions']
return None
def get_drops(self):
if 'drops' in self.file_source:
return self.file_source['drops']
return None
def create_from_world(self, world):
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
self.world_rep['settings'] = settings_dict
for p in self.player_range:
settings_dict[p] = {}
settings_dict[p]['shuffle'] = world.shuffle[p]
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]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['logic'] = world.logic[p]
settings_dict[p]['mode'] = world.mode[p]
settings_dict[p]['swords'] = world.swords[p]
settings_dict[p]['flute_mode'] = world.flute_mode[p]
settings_dict[p]['bow_mode'] = world.bow_mode[p]
settings_dict[p]['difficulty'] = world.difficulty[p]
settings_dict[p]['goal'] = world.goal[p]
settings_dict[p]['accessibility'] = world.accessibility[p]
settings_dict[p]['item_functionality'] = world.difficulty_adjustments[p]
settings_dict[p]['take_any'] = world.take_any[p]
settings_dict[p]['hints'] = world.hints[p]
settings_dict[p]['shopsanity'] = world.shopsanity[p]
settings_dict[p]['dropshuffle'] = world.dropshuffle[p]
settings_dict[p]['pottery'] = world.pottery[p]
settings_dict[p]['mixed_travel'] = world.mixed_travel[p]
settings_dict[p]['standardize_palettes'] = world.standardize_palettes[p]
settings_dict[p]['dungeon_counters'] = world.dungeon_counters[p]
settings_dict[p]['crystals_gt'] = world.crystals_gt_orig[p]
settings_dict[p]['crystals_ganon'] = world.crystals_ganon_orig[p]
settings_dict[p]['experimental'] = world.experimental[p]
settings_dict[p]['openpyramid'] = world.open_pyramid[p]
settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p]
settings_dict[p]['keyshuffle'] = world.keyshuffle[p]
settings_dict[p]['mapshuffle'] = world.mapshuffle[p]
settings_dict[p]['compassshuffle'] = world.compassshuffle[p]
settings_dict[p]['shufflebosses'] = world.boss_shuffle[p]
settings_dict[p]['shuffleenemies'] = world.enemy_shuffle[p]
settings_dict[p]['enemy_health'] = world.enemy_health[p]
settings_dict[p]['enemy_damage'] = world.enemy_damage[p]
settings_dict[p]['shufflepots'] = world.potshuffle[p]
settings_dict[p]['bombbag'] = world.bombbag[p]
settings_dict[p]['shufflelinks'] = world.shufflelinks[p]
settings_dict[p]['shuffletavern'] = world.shuffletavern[p]
settings_dict[p]['overworld_map'] = world.overworld_map[p]
settings_dict[p]['pseudoboots'] = world.pseudoboots[p]
settings_dict[p]['triforce_goal'] = world.treasure_hunt_count[p]
settings_dict[p]['triforce_pool'] = world.treasure_hunt_total[p]
settings_dict[p]['beemizer'] = world.beemizer[p]
# rom adjust stuff
# settings_dict[p]['sprite'] = world.sprite[p]
# settings_dict[p]['disablemusic'] = world.disablemusic[p]
# settings_dict[p]['quickswap'] = world.quickswap[p]
# settings_dict[p]['reduce_flashing'] = world.reduce_flashing[p]
# settings_dict[p]['fastmenu'] = world.fastmenu[p]
# settings_dict[p]['heartcolor'] = world.heartcolor[p]
# settings_dict[p]['heartbeep'] = world.heartbeep[p]
# settings_dict[p]['ow_palettes'] = world.ow_palettes[p]
# settings_dict[p]['uw_palettes'] = world.uw_palettes[p]
# settings_dict[p]['shuffle_sfx'] = world.shuffle_sfx[p]
# more settings?
def record_info(self, world):
self.world_rep['bosses'] = bosses = {}
self.world_rep['start_inventory'] = start_inv = {}
for p in self.player_range:
bosses[p] = {}
start_inv[p] = []
for dungeon in world.dungeons:
for level, boss in dungeon.bosses.items():
location = dungeon.name if level is None else f'{dungeon.name} ({level})'
if boss and 'Agahnim' not in boss.name:
bosses[dungeon.player][location] = boss.name
for item in world.precollected_items:
start_inv[item.player].append(item.name)
def record_item_pool(self, world):
self.world_rep['item_pool'] = item_pool = {}
self.world_rep['medallions'] = medallions = {}
for p in self.player_range:
item_pool[p] = defaultdict(int)
medallions[p] = {}
for item in world.itempool:
item_pool[item.player][item.name] += 1
for p, req_medals in world.required_medallions.items():
medallions[p]['Misery Mire'] = req_medals[0]
medallions[p]['Turtle Rock'] = req_medals[1]
def record_item_placements(self, world):
self.world_rep['placements'] = placements = {}
for p in self.player_range:
placements[p] = {}
for location in world.get_locations():
if location.type != LocationType.Logical:
if location.player != location.item.player:
placements[location.player][location.name] = f'{location.item.name}#{location.item.player}'
else:
placements[location.player][location.name] = location.item.name
def record_entrances(self, world):
self.world_rep['entrances'] = entrances = {}
world.custom_entrances = {}
for p in self.player_range:
connections = entrances[p] = {}
connections['entrances'] = {}
connections['exits'] = {}
connections['two-way'] = {}
for key, data in world.spoiler.entrances.items():
player = data['player'] if 'player' in data else 1
connections = entrances[player]
sub = 'two-way' if data['direction'] == 'both' else 'exits' if data['direction'] == 'exit' else 'entrances'
connections[sub][data['entrance']] = data['exit']
def record_doors(self, world):
self.world_rep['doors'] = doors = {}
for p in self.player_range:
meta_doors = doors[p] = {}
lobbies = meta_doors['lobbies'] = {}
door_map = meta_doors['doors'] = {}
for portal in world.dungeon_portals[p]:
lobbies[portal.name] = portal.door.name
door_types = {DoorType.Normal, DoorType.SpiralStairs, DoorType.Interior}
if world.intensity[p] > 1:
door_types.update([DoorType.Open, DoorType.StraightStairs, DoorType.Ladder])
door_kinds, skip = {}, set()
for key, info in world.spoiler.doorTypes.items():
if key[1] == p:
if ' <-> ' in info['doorNames']:
dns = info['doorNames'].split(' <-> ')
for dn in dns:
door_kinds[dn] = info['type'] # Key Door, Bomb Door, Dash Door
else:
door_kinds[info['doorNames']] = info['type']
for door in world.doors:
if door.player == p and not door.entranceFlag and door.type in door_types and door not in skip:
if door.type == DoorType.Interior:
if door.name in door_kinds:
door_value = {'type': door_kinds[door.name]}
door_map[door.name] = door_value # intra-tile note
skip.add(door.dest)
elif door.dest:
if door.dest.dest == door:
door_value = door.dest.name
skip.add(door.dest)
if door.name in door_kinds:
door_value = {'dest': door_value, 'type': door_kinds[door.name]}
if door.name not in door_kinds and door.dest.name in door_kinds:
# tricky swap thing
door_value = {'dest': door.name, 'type': door_kinds[door.dest.name]}
door = door.dest # this is weird
elif door.name in door_kinds:
door_value = {'dest': door.dest.name, 'one-way': True, 'type': door_kinds[door.name]}
else:
door_value = {'dest': door.dest.name, 'one-way': True}
door_map[door.name] = door_value
def record_medallions(self):
pass
def write_to_file(self, destination):
yaml.add_representer(defaultdict, Representer.represent_dict)
with open(destination, 'w') as file:
yaml.dump(self.world_rep, file)
def load_yaml(path):
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)

View File

@@ -68,8 +68,9 @@ SETTINGSTOPROCESS = {
"weapons": "swords",
"itempool": "difficulty",
"itemfunction": "item_functionality",
"flute_mode": "flute_mode",
"bow_mode": "bow_mode",
"timer": "timer",
"progressives": "progressive",
"accessibility": "accessibility",
"sortingalgo": "algorithm",
"beemizer": "beemizer",
@@ -89,8 +90,10 @@ SETTINGSTOPROCESS = {
"openpyramid": "openpyramid",
"shuffleganon": "shuffleganon",
"shufflelinks": "shufflelinks",
"shuffletavern": "shuffletavern",
"entranceshuffle": "shuffle",
"overworld_map": "overworld_map",
"take_any": "take_any",
},
"enemizer": {
"enemyshuffle": "shuffleenemies",
@@ -105,6 +108,8 @@ SETTINGSTOPROCESS = {
"bigkeyshuffle": "bigkeyshuffle",
"dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
"door_type_mode": "door_type_mode",
"decoupledoors": "decoupledoors",
"keydropshuffle": "keydropshuffle",
"dropshuffle": "dropshuffle",
"pottery": "pottery",
@@ -133,6 +138,7 @@ SETTINGSTOPROCESS = {
"createspoiler": "create_spoiler",
"createrom": "create_rom",
"calcplaythrough": "calc_playthrough",
"print_custom_yaml": "print_custom_yaml",
"usestartinventory": "usestartinventory",
"usecustompool": "custom",
"saveonexit": "saveonexit"

View File

@@ -0,0 +1,888 @@
import RaceRandom as random
import collections
import logging
import time
from BaseClasses import CrystalBarrier, DoorType, Hook, RegionType, Sector
from BaseClasses import hook_from_door, flooded_keys
from Regions import dungeon_events, flooded_keys_reverse
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
pass
# todo: determine the part of check_valid that are necessary here
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
if builder.valid_proposal: # we made this earlier in gen, just use it
proposed_map = builder.valid_proposal
else:
proposed_map = generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon, world, player)
builder.valid_proposal = proposed_map
queue = collections.deque(proposed_map.items())
while len(queue) > 0:
a, b = queue.popleft()
if world.decoupledoors[player]:
connect_doors_one_way(a, b)
else:
connect_doors(a, b)
queue.remove((b, a))
if len(builder.sectors) == 0:
return Sector()
available_sectors = list(builder.sectors)
master_sector = available_sectors.pop()
for sub_sector in available_sectors:
master_sector.regions.extend(sub_sector.regions)
master_sector.outstanding_doors.clear()
master_sector.r_name_set = None
return master_sector
def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon, world, player):
logger = logging.getLogger('')
name = builder.name
logger.debug(f'Generating Dungeon: {name}')
entrance_regions = convert_regions(entrance_region_names, world, player)
excluded = {}
for region in entrance_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal:
if portal.destination:
excluded[region] = None
elif len(entrance_regions) > 1:
p_region = portal.door.entrance.connected_region
access_region = next(x.parent_region for x in p_region.entrances
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld])
if (access_region.name in world.inaccessible_regions[player] and
region.name not in world.enabled_entrances[player]):
excluded[region] = None
elif split_dungeon and builder.sewers_access and builder.sewers_access.entrance.parent_region == region:
continue
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()
for sector in builder.sectors:
for door in sector.outstanding_doors:
doors_to_connect[door.name] = door, idx
idx += 1
all_regions.update(sector.regions)
finished = False
# flag if standard and this is hyrule castle
paths = determine_paths_for_dungeon(world, player, all_regions, name)
proposed_map = create_random_proposal(doors_to_connect, world, player)
itr = 0
hash_code = proposal_hash(doors_to_connect, proposed_map)
hash_code_set = set()
start = time.time()
while not finished:
if itr > 1000:
elasped = time.time() - start
raise GenerationException(f'Generation taking too long. {elasped}. Ref {name}')
if hash_code in hash_code_set:
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
if hash_code not in hash_code_set:
hash_code_set.add(hash_code)
explored_state = explore_proposal(name, entrance_regions, all_regions, proposed_map, doors_to_connect,
world, player)
if check_valid(name, explored_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player):
finished = True
else:
proposed_map, hash_code = modify_proposal(proposed_map, explored_state, doors_to_connect,
hash_code_set, world, player)
itr += 1
return proposed_map
def create_random_proposal(doors_to_connect, world, player):
logger = logging.getLogger('')
hooks = [Hook.North, Hook.South, Hook.East, Hook.West, Hook.Stairs]
primary_bucket = collections.defaultdict(list)
secondary_bucket = collections.defaultdict(list)
for name, door in doors_to_connect.items():
door, idx = door
primary_bucket[hook_from_door(door)].append(door)
secondary_bucket[hook_from_door(door)].append(door)
proposal = {}
while True:
hooks_left, left = [], 0
for hook in hooks:
hook_len = len(primary_bucket[hook])
if hook_len > 0:
hooks_left.append(hook)
left += hook_len
if left == 0:
return proposal
next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook])
opp_hook, secondary_door = type_map[next_hook], None
while (secondary_door is None or secondary_door == primary_door
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_door)
secondary_bucket[opp_hook].remove(secondary_door)
if not world.decoupledoors[player]:
proposal[secondary_door] = primary_door
primary_bucket[opp_hook].remove(secondary_door)
secondary_bucket[next_hook].remove(primary_door)
logger.debug(f' Linking {primary_door.name} <-> {secondary_door.name}')
else:
logger.debug(f' Linking {primary_door.name} -> {secondary_door.name}')
def decouple_check(primary_list, secondary_list, primary_door, secondary_door, world, player):
if world.decoupledoors[player] and len(primary_list) == 2 and len(secondary_list) == 2:
primary_alone = next(d for d in primary_list if d != primary_door)
secondary_alone = next(d for d in secondary_list if d != secondary_door)
return primary_alone == secondary_alone
return False
def proposal_hash(doors_to_connect, proposed_map):
hash_code = ''
for name, door_pair in doors_to_connect.items():
door, idx = door_pair
hash_code += str(idx) + str(doors_to_connect[proposed_map[door].name][1])
return hash_code
def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_set, world, player):
logger = logging.getLogger('')
hash_code, itr = None, 0
while hash_code is None or hash_code in hash_code_set:
if itr > 10:
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
return proposed_map, hash_code
visited_bucket = collections.defaultdict(list)
unvisted_bucket = collections.defaultdict(list)
visited_choices = []
unvisted_count = 0
for door_one, door_two in proposed_map.items():
if door_one in explored_state.visited_doors:
visited_bucket[hook_from_door(door_one)].append(door_one)
visited_choices.append(door_one)
else:
unvisted_bucket[hook_from_door(door_one)].append(door_one)
unvisted_count += 1
if unvisted_count == 0:
# something is wrong beyond connectedness, crystal switch puzzle or bk layout - reshuffle
proposed_map = create_random_proposal(doors_to_connect, world, player)
hash_code = proposal_hash(doors_to_connect, proposed_map)
return proposed_map, hash_code
attempt, opp_hook = None, None
opp_hook_len, possible_swaps = 0, list(visited_choices)
while opp_hook_len == 0:
if len(possible_swaps) == 0:
break
attempt = random.choice(possible_swaps)
possible_swaps.remove(attempt)
opp_hook = type_map[hook_from_door(attempt)]
opp_hook_len = len(unvisted_bucket[opp_hook])
if opp_hook_len == 0:
itr += 1
continue
unvisted_bucket[opp_hook].sort(key=lambda d: d.name)
new_door = random.choice(unvisted_bucket[opp_hook])
old_target = proposed_map[attempt]
proposed_map[attempt] = new_door
if not world.decoupledoors[player]:
old_attempt = proposed_map[new_door]
else:
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
proposed_map[old_attempt] = old_target
if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt
proposed_map[new_door] = attempt
hash_code = proposal_hash(doors_to_connect, proposed_map)
itr += 1
if not world.decoupledoors[player]:
logger.debug(f' Re-linking {attempt.name} <-> {new_door.name}')
logger.debug(f' Re-linking {old_attempt.name} <-> {old_target.name}')
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
def explore_proposal(name, entrance_regions, all_regions, proposed_map, valid_doors, world, player):
start = ExplorationState(dungeon=name)
original_state = extend_reachable_state_lenient(entrance_regions, start, proposed_map,
all_regions, valid_doors, world, player)
return original_state
def check_valid(name, exploration_state, proposed_map, doors_to_connect, all_regions,
paths, entrance_regions, world, player):
all_visited = set()
all_visited.update(exploration_state.visited_blue)
all_visited.update(exploration_state.visited_orange)
if len(all_regions.difference(all_visited)) > 0:
return False
if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map, world, player):
return False
return True
def determine_if_bk_needed(sector, split_dungeon, bk_special, world, player):
if not split_dungeon or bk_special:
for region in sector.regions:
for ext in region.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.bigKey:
return True
return False
def check_for_special(regions):
for region in regions:
for loc in region.locations:
if loc.forced_big_key():
return True
return False
def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map, world, player):
for path in paths:
if type(path) is tuple:
target = path[1]
start_regions = []
for region in all_regions:
if path[0] == region.name:
start_regions.append(region)
break
else:
target = path
start_regions = entrance_regions
if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions, world, player):
return False
return True
def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions, world, player):
target_regions = set()
if type(target) is not list:
for region in all_regions:
if target == region.name:
target_regions.add(region)
break
else:
for region in all_regions:
if region.name in target:
target_regions.add(region)
start = ExplorationState(dungeon=name)
original_state = extend_reachable_state_lenient(starting_regions, start, proposed_map, all_regions,
valid_doors, world, player)
for exp_door in original_state.unattached_doors:
if not exp_door.door.blocked or exp_door.door.trapFlag != 0:
return True # outstanding connection possible
for target in target_regions:
if original_state.visited_at_all(target):
return True
return False # couldn't find an outstanding door or the target
boss_path_checks = ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss',
'Skull Boss', 'Ice Boss', 'Mire Boss', 'TR Boss', 'GT Agahnim 2']
# pinball is allowed to orphan you
drop_path_checks = ['Skull Pot Circle', 'Skull Left Drop', 'Skull Back Drop', 'Sewers Rat Path']
def determine_paths_for_dungeon(world, player, all_regions, name):
all_r_names = set(x.name for x in all_regions)
paths = []
non_hole_portals = []
for portal in world.dungeon_portals[player]:
if portal.door.entrance.parent_region in all_regions:
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 Dungeon':
paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names:
paths.append('Thieves Attic Window')
for boss in boss_path_checks:
if boss in all_r_names:
paths.append(boss)
if 'Thieves Boss' in all_r_names:
paths.append('Thieves Boss')
if world.get_dungeon("Thieves Town", player).boss.enemizer_name == 'Blind':
paths.append(('Thieves Blind\'s Cell', 'Thieves Boss'))
for drop_check in drop_path_checks:
if drop_check in all_r_names:
paths.append((drop_check, non_hole_portals))
return paths
def convert_regions(region_names, world, player):
region_list = []
for name in region_names:
region_list.append(world.get_region(name, player))
return region_list
type_map = {
Hook.Stairs: Hook.Stairs,
Hook.North: Hook.South,
Hook.South: Hook.North,
Hook.West: Hook.East,
Hook.East: Hook.West
}
def connect_doors(a, b):
# Return on unsupported types.
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Interior, DoorType.Logical]:
return
# Connect supported types
if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
connect_two_way(a.entrance, b.entrance)
dep_doors, target = [], None
if len(a.dependents) > 0:
dep_doors, target = a.dependents, b
elif len(b.dependents) > 0:
dep_doors, target = b.dependents, a
if target is not None:
target_region = target.entrance.parent_region
for dep in dep_doors:
connect_simple_door(dep, target_region)
return
# If we failed to account for a type, panic
raise RuntimeError('Unknown door type ' + a.type.name)
def connect_doors_one_way(a, b):
# Return on unsupported types.
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Interior, DoorType.Logical]:
return
# Connect supported types
if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
connect_one_way(a.entrance, b.entrance)
dep_doors, target = [], None
if len(a.dependents) > 0:
dep_doors, target = a.dependents, b
if target is not None:
target_region = target.entrance.parent_region
for dep in dep_doors:
connect_simple_door(dep, target_region)
return
# If we failed to account for a type, panic
raise RuntimeError('Unknown door type ' + a.type.name)
def connect_two_way(entrance, ext):
# if these were already connected somewhere, remove the backreference
if entrance.connected_region is not None:
entrance.connected_region.entrances.remove(entrance)
if ext.connected_region is not None:
ext.connected_region.entrances.remove(ext)
entrance.connect(ext.parent_region)
ext.connect(entrance.parent_region)
if entrance.parent_region.dungeon:
ext.parent_region.dungeon = entrance.parent_region.dungeon
x = entrance.door
y = ext.door
if x is not None:
x.dest = y
if y is not None:
y.dest = x
def connect_one_way(entrance, ext):
# if these were already connected somewhere, remove the backreference
if entrance.connected_region is not None:
entrance.connected_region.entrances.remove(entrance)
entrance.connect(ext.parent_region)
if entrance.parent_region.dungeon:
ext.parent_region.dungeon = entrance.parent_region.dungeon
x = entrance.door
if x is not None:
x.dest = ext.door
def connect_simple_door(exit_door, region):
exit_door.entrance.connect(region)
exit_door.dest = region
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
class ExplorationState(object):
def __init__(self, init_crystal=CrystalBarrier.Orange, dungeon=None):
self.unattached_doors = []
self.avail_doors = []
self.event_doors = []
self.visited_orange = []
self.visited_blue = []
self.visited_doors = set()
self.events = set()
self.crystal = init_crystal
# key region stuff
self.door_krs = {}
# key validation stuff
self.small_doors = []
self.big_doors = []
self.opened_doors = []
self.big_key_opened = False
self.big_key_special = False
self.found_locations = []
self.ttl_locations = 0
self.used_locations = 0
self.key_locations = 0
self.used_smalls = 0
self.bk_found = set()
self.non_door_entrances = []
self.dungeon = dungeon
self.pinball_used = False
self.prize_door_set = {}
self.prize_doors = []
self.prize_doors_opened = False
def copy(self):
ret = ExplorationState(dungeon=self.dungeon)
ret.unattached_doors = list(self.unattached_doors)
ret.avail_doors = list(self.avail_doors)
ret.event_doors = list(self.event_doors)
ret.visited_orange = list(self.visited_orange)
ret.visited_blue = list(self.visited_blue)
ret.events = set(self.events)
ret.crystal = self.crystal
ret.door_krs = self.door_krs.copy()
ret.small_doors = list(self.small_doors)
ret.big_doors = list(self.big_doors)
ret.opened_doors = list(self.opened_doors)
ret.big_key_opened = self.big_key_opened
ret.big_key_special = self.big_key_special
ret.ttl_locations = self.ttl_locations
ret.key_locations = self.key_locations
ret.used_locations = self.used_locations
ret.used_smalls = self.used_smalls
ret.found_locations = list(self.found_locations)
ret.bk_found = set(self.bk_found)
ret.non_door_entrances = list(self.non_door_entrances)
ret.dungeon = self.dungeon
ret.pinball_used = self.pinball_used
ret.prize_door_set = dict(self.prize_door_set)
ret.prize_doors = list(self.prize_doors)
ret.prize_doors_opened = self.prize_doors_opened
return ret
def next_avail_door(self):
self.avail_doors.sort(key=lambda x: 0 if x.flag else 1 if x.door.bigKey else 2)
exp_door = self.avail_doors.pop()
self.crystal = exp_door.crystal
return exp_door
def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False):
if region.type != RegionType.Dungeon:
self.crystal = CrystalBarrier.Orange
if self.crystal == CrystalBarrier.Either:
if region not in self.visited_blue:
self.visited_blue.append(region)
if region not in self.visited_orange:
self.visited_orange.append(region)
elif self.crystal == CrystalBarrier.Orange:
self.visited_orange.append(region)
elif self.crystal == CrystalBarrier.Blue:
self.visited_blue.append(region)
if region.type == RegionType.Dungeon:
for location in region.locations:
if key_checks and location not in self.found_locations:
if location.forced_item and 'Small Key' in location.item.name:
self.key_locations += 1
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
self.ttl_locations += 1
if location not in self.found_locations:
self.found_locations.append(location)
if not bk_flag:
self.bk_found.add(location)
if location.name in dungeon_events and location.name not in self.events:
if self.flooded_key_check(location):
self.perform_event(location.name, key_region)
if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if '- Prize' in location.name:
self.prize_received = True
def flooded_key_check(self, location):
if location.name not in flooded_keys.keys():
return True
return flooded_keys[location.name] in [x.name for x in self.found_locations]
def location_found(self, location_name):
for l in self.found_locations:
if l.name == location_name:
return True
return False
def perform_event(self, location_name, key_region):
self.events.add(location_name)
queue = collections.deque(self.event_doors)
while len(queue) > 0:
exp_door = queue.popleft()
if exp_door.door.req_event == location_name:
self.avail_doors.append(exp_door)
self.event_doors.remove(exp_door)
if key_region is not None:
d_name = exp_door.door.name
if d_name not in self.door_krs.keys():
self.door_krs[d_name] = key_region
def add_all_entrance_doors_check_unattached(self, region, world, player):
door_list = [x for x in get_doors(world, region, player) if x.type in [DoorType.Normal, DoorType.SpiralStairs]]
door_list.extend(get_entrance_doors(world, region, player))
for door in door_list:
if self.can_traverse(door):
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
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)
for entrance in region.entrances:
door = world.check_for_door(entrance.name, player)
if door is None:
self.non_door_entrances.append(entrance)
def add_all_doors_check_unattached(self, region, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if door.controller is not None:
door = door.controller
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
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(self, region, proposed_map, valid_doors, flag, world, player, exception):
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 door.blocked and exception(door):
self.pinball_used = True
if self.can_traverse(door, exception):
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, flag)
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, flag)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag)
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:
self.visited_doors.add(door)
if self.can_traverse_ignore_traps(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:
if door.controller is not None:
door = door.controller
if 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, False)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, False)
def add_all_doors_check_key_region(self, region, key_region, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if 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)
if door.name not in self.door_krs.keys():
self.door_krs[door.name] = key_region
else:
if door.name not in self.door_krs.keys():
self.door_krs[door.name] = key_region
def add_all_doors_check_keys(self, region, key_door_proposal, world, player):
for door in get_doors(world, region, player):
if self.can_traverse(door):
if door.controller:
door = door.controller
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:
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:
if 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 visited(self, region):
if self.crystal == CrystalBarrier.Either:
return region in self.visited_blue and region in self.visited_orange
elif self.crystal == CrystalBarrier.Orange:
return region in self.visited_orange
elif self.crystal == CrystalBarrier.Blue:
return region in self.visited_blue
return False
def visited_at_all(self, region):
return region in self.visited_blue or region in self.visited_orange
def found_forced_bk(self):
for location in self.found_locations:
if location.forced_big_key():
return True
return False
def can_traverse(self, door, exception=None):
if door.blocked:
return exception(door) if exception else False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def can_traverse_ignore_traps(self, door):
if door.blocked and door.trapFlag == 0:
return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def count_locations_exclude_specials(self, world, player):
return count_locations_exclude_big_chest(self.found_locations, world, player)
def validate(self, door, region, world, player):
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, self.dungeon,
world, player)
def in_door_list(self, door, door_list):
for d in door_list:
if d.door == door and d.crystal == self.crystal:
return True
return False
@staticmethod
def in_door_list_ic(door, door_list):
for d in door_list:
if d.door == door:
return True
return False
@staticmethod
def find_door_in_list(door, door_list):
for d in door_list:
if d.door == door:
return d
return None
def append_door_to_list(self, door, door_list, flag=False):
if door.crystal == CrystalBarrier.Null:
door_list.append(ExplorableDoor(door, self.crystal, flag))
else:
door_list.append(ExplorableDoor(door, door.crystal, flag))
def key_door_sort(self, d):
if d.door.smallKey:
if d.door in self.opened_doors:
return 1
else:
return 0
return 2
def count_locations_exclude_big_chest(locations, world, player):
cnt = 0
for loc in locations:
if ('- Big Chest' not in loc.name and not loc.forced_item and not reserved_location(loc, world, player)
and not prize_or_event(loc) and not blind_boss_unavail(loc, locations, world, player)):
cnt += 1
return cnt
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
def reserved_location(loc, world, player):
return hasattr(world, 'item_pool_config') and loc.name in world.item_pool_config.reserved_locations[player]
def blind_boss_unavail(loc, locations, world, player):
if loc.name == "Thieves' Town - Boss":
return (loc.parent_region.dungeon.boss.name == 'Blind' and
(not any(x for x in locations if x.name == 'Suspicious Maiden') or
(world.get_region('Thieves Attic Window', player).dungeon.name == 'Thieves Town' and
not any(x for x in locations if x.name == 'Attic Cracked Floor'))))
return False
class ExplorableDoor(object):
def __init__(self, door, crystal, flag):
self.door = door
self.crystal = crystal
self.flag = flag
def __str__(self):
return str(self.__unicode__())
def __unicode__(self):
return '%s (%s)' % (self.door.name, self.crystal.name)
def extend_reachable_state_improved(search_regions, state, proposed_map, all_regions, valid_doors, bk_flag, world, player, exception):
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, False, world, player, exception)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
if explorable_door.door.bigKey:
if bk_flag:
big_not_found = (not special_big_key_found(local_state) if local_state.big_key_special
else local_state.count_locations_exclude_specials(world, player) == 0)
if big_not_found:
continue # we can't open this door
if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None:
if valid_region_to_explore_in_regions(connect_region, all_regions, world, player) and not local_state.visited(
connect_region):
flag = explorable_door.flag or explorable_door.door.bigKey
local_state.visit_region(connect_region, bk_flag=flag)
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, flag, world, player, exception)
return local_state
def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regions, valid_doors, world, player):
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)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
if explorable_door.door in proposed_map:
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
else:
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
if connect_region is not None:
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)
return local_state
def special_big_key_found(state):
for location in state.found_locations:
if location.forced_item and location.forced_item.bigkey:
return True
return False
def valid_region_to_explore_in_regions(region, all_regions, world, player):
if region is None:
return False
return ((region.type == RegionType.Dungeon and region in all_regions)
or region.name in world.inaccessible_regions[player]
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
def valid_region_to_explore(region, name, world, player):
if region is None:
return False
return ((region.type == RegionType.Dungeon and region.dungeon and region.dungeon.name in name)
or region.name in world.inaccessible_regions[player]
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
def get_doors(world, region, player):
res = []
for ext in region.exits:
door = world.check_for_door(ext.name, player)
if door is not None:
res.append(door)
return res
def get_entrance_doors(world, region, player):
res = []
for ext in region.entrances:
door = world.check_for_door(ext.name, player)
if door is not None:
res.append(door)
return res
class GenerationException(Exception):
pass

View File

@@ -157,8 +157,8 @@ def adjust_page(top, parent, settings):
"quickswap": "quickswap",
"nobgm": "disablemusic",
"reduce_flashing": "reduce_flashing",
'msu_resume': 'msu_resume',
"shuffle_sfx": "shuffle_sfx",
"msu_resume": "msu_resume"
}
guiargs = Namespace()
for option in options:

View File

@@ -220,6 +220,11 @@ def create_guiargs(parent):
# Get baserom path
guiargs.rom = parent.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
# Get customizer path
customizer_value = parent.pages["randomizer"].pages["generation"].widgets["customizer"].storageVar.get()
if customizer_value and customizer_value != 'None':
guiargs.customizer = customizer_value
# Get if we're using the Custom Item Pool
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
@@ -280,4 +285,12 @@ def create_guiargs(parent):
guiargs.dropshuffle = 1
guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery
if guiargs.retro or guiargs.mode == 'retro':
if guiargs.bow_mode == 'progressive':
guiargs.bow_mode = 'retro'
elif guiargs.bow_mode == 'silvers':
guiargs.bow_mode = 'retro_silvers'
guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any
guiargs.keyshuffle = 'universal'
return guiargs

View File

@@ -27,7 +27,7 @@ def entrando_page(parent):
for key in dictWidgets:
self.widgets[key] = dictWidgets[key]
packAttrs = {"anchor":E}
if self.widgets[key].type == "checkbox" or key == "openpyramid":
if self.widgets[key].type == "checkbox" or key in ["openpyramid", "take_any"]:
packAttrs["anchor"] = W
self.widgets[key].pack(packAttrs)

View File

@@ -79,6 +79,48 @@ def generation_page(parent,settings):
# frame: pack
self.widgets[widget].pieces["frame"].pack(fill=X)
self.frames["customizer"] = Frame(self)
self.frames["customizer"].pack(anchor=W, fill=X)
## Customizer file
# This one's more-complicated, build it and stuff it
# widget ID
widget = "customizer"
# Empty object
self.widgets[widget] = Empty()
# pieces
self.widgets[widget].pieces = {}
# frame
self.widgets[widget].pieces["frame"] = Frame(self.frames["customizer"])
# frame: label
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text='Customizer File: ')
# storage var
self.widgets[widget].storageVar = StringVar()
# textbox
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"],
textvariable=self.widgets[widget].storageVar)
self.widgets[widget].storageVar.set(settings["customizer"])
# FIXME: Translate these
def FileSelect():
file = filedialog.askopenfilename(filetypes=[("Yaml Files", (".yaml", ".yml")), ("All Files", "*")],
initialdir=os.path.join("."))
self.widgets["customizer"].storageVar.set(file)
# dialog button
self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"],
text='Select File', command=FileSelect)
# frame label: pack
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
# textbox: pack
self.widgets[widget].pieces["textbox"].pack(side=LEFT, fill=X, expand=True)
# button: pack
self.widgets[widget].pieces["button"].pack(side=LEFT)
# frame: pack
self.widgets[widget].pieces["frame"].pack(fill=X)
## Run Diagnostics
# This one's more-complicated, build it and stuff it
# widget ID

View File

@@ -18,6 +18,8 @@ class ItemPoolConfig(object):
self.item_pool = None
self.placeholders = None
self.reserved_locations = defaultdict(set)
self.restricted = {}
self.preferred = {}
self.recorded_choices = []
@@ -82,10 +84,11 @@ def create_item_pool_config(world):
if world.shopsanity[player]:
for item, locs in shop_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
if world.retro[player]:
if world.take_any[player] != 'none':
for item, locs in retro_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
# universal keys
if world.keyshuffle[player] == 'universal':
universal_key_locations = []
for item, locs in vanilla_mapping.items():
if 'Small Key' in item:
@@ -98,12 +101,13 @@ def create_item_pool_config(world):
for item, locs in potkeys_vanilla_mapping.items():
universal_key_locations.extend(locs)
if world.shopsanity[player]:
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
single_arrow_placement.append('Red Shield Shop - Right')
config.static_placement[player]['Single Arrow'] = single_arrow_placement
universal_key_locations.extend(shop_vanilla_mapping['Small Heart'])
universal_key_locations.extend(shop_vanilla_mapping['Blue Shield'])
config.static_placement[player]['Small Key (Universal)'] = universal_key_locations
if world.bow_mode[player].startswith('retro') and world.shopsanity[player]:
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
single_arrow_placement.append('Red Shield Shop - Right')
config.static_placement[player]['Single Arrow'] = single_arrow_placement
config.location_groups[player] = [
LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']),
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
@@ -124,7 +128,7 @@ def create_item_pool_config(world):
groups.locations.extend(mode_grouping['Big Keys'])
if world.dropshuffle[player] != 'none':
groups.locations.extend(mode_grouping['Big Key Drops'])
if world.keyshuffle[player]:
if world.keyshuffle[player] != 'none':
groups.locations.extend(mode_grouping['Small Keys'])
if world.dropshuffle[player] != 'none':
groups.locations.extend(mode_grouping['Key Drops'])
@@ -137,7 +141,7 @@ def create_item_pool_config(world):
if world.shopsanity[player]:
groups.locations.append('Capacity Upgrade - Left')
groups.locations.append('Capacity Upgrade - Right')
if world.retro[player]:
if world.take_any[player] != 'none':
if world.shopsanity[player]:
groups.locations.extend(retro_vanilla_mapping['Heart Container'])
groups.locations.append('Old Man Sword Cave Item 1')
@@ -249,7 +253,7 @@ def previously_reserved(location, world, player):
if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player]
or not world.mapshuffle[player]
or not world.bigkeyshuffle[player]
or not (world.keyshuffle[player] or world.retro[player])):
or world.keyshuffle[player] == 'none'):
return True
return False
@@ -335,7 +339,7 @@ def determine_major_items(world, player):
pass # now what?
if world.bigkeyshuffle[player]:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
if world.keyshuffle[player]:
if world.keyshuffle[player] != 'none':
major_item_set.update({x for x, y in item_table.items() if y[2] == 'SmallKey'})
if world.compassshuffle[player]:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Compass'})
@@ -344,8 +348,9 @@ def determine_major_items(world, player):
if world.shopsanity[player]:
major_item_set.add('Bomb Upgrade (+5)')
major_item_set.add('Arrow Upgrade (+5)')
if world.retro[player]:
if world.bow_mode[player].startswith('retro'):
major_item_set.add('Single Arrow')
if world.keyshuffle[player] == 'universal':
major_item_set.add('Small Key (Universal)')
if world.goal in ['triforcehunt', 'trinity']:
major_item_set.add('Triforce Piece')
@@ -377,8 +382,10 @@ 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
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
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]
@@ -415,6 +422,12 @@ 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)
return locations
@@ -780,7 +793,7 @@ major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang', 'Progressive Glove',
'Power Glove', 'Titans Mitts', 'Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Magic Mirror',
'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)', 'Magic Upgrade (1/2)',
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Ocarina (Activated)',
'Sanctuary Heart Container', 'Boss Heart Container', 'Progressive Shield', 'Ocarina (Activated)',
'Mirror Shield', 'Progressive Armor', 'Blue Mail', 'Red Mail', 'Progressive Sword', 'Fighter Sword',
'Master Sword', 'Tempered Sword', 'Golden Sword', 'Bow', 'Silver Arrows', 'Triforce Piece', 'Moon Pearl',
'Progressive Bow', 'Progressive Bow (Alt)'}
@@ -811,3 +824,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)

File diff suppressed because it is too large Load Diff

View File

@@ -1,124 +0,0 @@
import subprocess
import sys
import multiprocessing
import concurrent.futures
import argparse
from collections import OrderedDict
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
def main(args=None):
successes = []
errors = []
task_mapping = []
tests = OrderedDict()
successes.append(f"Testing {args.dr} DR with {args.count} Tests" + (f" (intensity={args.tense})" if args.dr in ['basic', 'crossed'] else ""))
print(successes[0])
max_attempts = args.count
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
def test(testname: str, command: str):
tests[testname] = [command]
basecommand = f"python3.8 Mystery.py --suppress_rom --suppress_meta"
def gen_seed():
taskcommand = basecommand + " " + command
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
for x in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
task.success = False
task.name = testname
task.mode = "Mystery"
task.cmd = basecommand + " " + command
task_mapping.append(task)
for i in range(0, 100):
test("Mystery", "--weights mystery_testsuite.yml")
from tqdm import tqdm
with tqdm(concurrent.futures.as_completed(task_mapping),
total=len(task_mapping), unit="seed(s)",
desc=f"Success rate: 0.00%") as progressbar:
for task in progressbar:
dead_or_alive += 1
try:
result = task.result()
if result.returncode:
errors.append([task.name, task.cmd, result.stderr])
else:
alive += 1
task.success = True
except Exception as e:
raise e
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}")
def get_results(testname: str):
result = ""
for mode in ['Mystery']:
dead_or_alive = [task.success for task in task_mapping if task.name == testname and task.mode == mode]
alive = [x for x in dead_or_alive if x]
success = f"{testname} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
successes.append(success)
print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
return result.strip()
results = []
for t in tests.keys():
results.append(get_results(t))
for result in results:
print(result)
successes.append(result)
return successes, errors
if __name__ == "__main__":
successes = []
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
parser.add_argument('--help', default=False, action='store_true')
args = parser.parse_args()
if args.help:
parser.print_help()
exit(0)
cpu_threads = args.cpu_threads
for dr in [['mystery', args.count if args.count else 1, 1]]:
for tense in range(1, dr[2] + 1):
args = argparse.Namespace()
args.dr = dr[0]
args.tense = tense
args.count = dr[1]
s, errors = main(args=args)
if successes:
successes += [""] * 2
successes += s
print()
if errors:
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
for error in errors:
stream.write(error[0] + "\n")
stream.write(error[1] + "\n")
stream.write(error[2] + "\n\n")
with open("success.txt", "w") as stream:
stream.write(str.join("\n", successes))
input("Press enter to continue")

View File

@@ -0,0 +1,219 @@
import argparse
import RaceRandom as random
import os
from pathlib import Path
import urllib.request
import urllib.parse
import yaml
def get_weights(path):
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
def roll_settings(weights):
def get_choice(option, root=None):
root = weights if root is None else root
if option not in root:
return None
if type(root[option]) is not dict:
return root[option]
if not root[option]:
return None
return random.choices(list(root[option].keys()), weights=list(map(int, root[option].values())))[0]
def get_choice_default(option, root=weights, default=None):
choice = get_choice(option, root)
if choice is None and default is not None:
return default
return choice
while True:
subweights = weights.get('subweights', {})
if len(subweights) == 0:
break
chances = ({k: int(v['chance']) for (k, v) in subweights.items()})
subweight_name = random.choices(list(chances.keys()), weights=list(chances.values()))[0]
subweights = weights.get('subweights', {}).get(subweight_name, {}).get('weights', {})
subweights['subweights'] = subweights.get('subweights', {})
weights = {**weights, **subweights}
ret = argparse.Namespace()
ret.algorithm = get_choice('algorithm')
glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches',
'owg': 'owglitches', 'minorglitches': 'minorglitches'}
glitches_required = get_choice('glitches_required')
if glitches_required is not None:
if glitches_required not in glitch_map.keys():
print(f'Logic did not match one of: {", ".join(glitch_map.keys())}')
glitches_required = 'none'
ret.logic = glitch_map[glitches_required]
# item_placement = get_choice('item_placement')
# not supported in ER
dungeon_items = get_choice('dungeon_items')
dungeon_items = '' if dungeon_items == 'standard' or dungeon_items is None else dungeon_items
dungeon_items = 'mcsb' if dungeon_items == 'full' else dungeon_items
ret.mapshuffle = get_choice('map_shuffle') == 'on' if 'map_shuffle' in weights else 'm' in dungeon_items
ret.compassshuffle = get_choice('compass_shuffle') == 'on' if 'compass_shuffle' in weights else 'c' in dungeon_items
if 'smallkey_shuffle' in weights:
ret.keyshuffle = get_choice('smallkey_shuffle')
else:
if 's' in dungeon_items:
ret.keyshuffle = 'wild'
if 'u' in dungeon_items:
ret.keyshuffle = 'universal'
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else 'b' in dungeon_items
ret.accessibility = get_choice('accessibility')
ret.restrict_boss_items = get_choice('restrict_boss_items')
overworld_shuffle = get_choice('overworld_shuffle')
ret.ow_shuffle = overworld_shuffle if overworld_shuffle != 'none' else 'vanilla'
ret.ow_terrain = get_choice('overworld_terrain') == 'on'
valid_options = {'none', 'polar', 'grouped', 'limited', 'chaos'}
ret.ow_crossed = get_choice('overworld_crossed')
ret.ow_crossed = ret.ow_crossed if ret.ow_crossed in valid_options else 'none'
ret.ow_keepsimilar = get_choice('overworld_keepsimilar') == 'on'
ret.ow_mixed = get_choice('overworld_swap') == 'on'
ret.ow_whirlpool = get_choice('whirlpool_shuffle') == 'on'
overworld_flute = get_choice('flute_shuffle')
ret.ow_fluteshuffle = overworld_flute if overworld_flute != 'none' else 'vanilla'
ret.bonk_drops = get_choice('bonk_drops') == 'on'
entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
overworld_map = get_choice('overworld_map')
ret.overworld_map = overworld_map if overworld_map != 'default' else 'default'
door_shuffle = get_choice('door_shuffle')
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.decoupledoors = get_choice('decoupledoors') == 'on'
ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on'
ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
ret.pseudoboots = get_choice('pseudoboots') == 'on'
ret.shopsanity = get_choice('shopsanity') == 'on'
keydropshuffle = get_choice('keydropshuffle') == 'on'
ret.dropshuffle = get_choice('dropshuffle') == 'on' or keydropshuffle
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.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
else 'standardize')
goal = get_choice('goals')
if goal is not None:
ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals',
'dungeons': 'dungeons',
'pedestal': 'pedestal',
'triforce-hunt': 'triforcehunt',
'trinity': 'trinity'
}[goal]
ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
ret.shuffleganon = get_choice('shuffleganon') == 'on'
ret.shufflelinks = get_choice('shufflelinks') == 'on'
ret.shuffletavern = get_choice('shuffletavern') == 'on'
ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open')
from ItemList import set_default_triforce
default_tf_goal, default_tf_pool = set_default_triforce(ret.goal, 0, 0)
goal_min = get_choice_default('triforce_goal_min', default=default_tf_goal)
goal_max = get_choice_default('triforce_goal_max', default=default_tf_goal)
pool_min = get_choice_default('triforce_pool_min', default=default_tf_pool)
pool_max = get_choice_default('triforce_pool_max', default=default_tf_pool)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
min_diff = get_choice_default('triforce_min_difference', default=(default_tf_pool-default_tf_goal))
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state')
if ret.mode == 'retro':
ret.mode = 'open'
ret.retro = True
ret.retro = get_choice('retro') == 'on' # this overrides world_state if used
ret.take_any = get_choice_default('take_any', default='none')
ret.bombbag = get_choice('bombbag') == 'on'
ret.hints = get_choice('hints') == 'on'
swords = get_choice('weapons')
if swords is not None:
ret.swords = {'randomized': 'random',
'assured': 'assured',
'vanilla': 'vanilla',
'swordless': 'swordless'
}[swords]
ret.difficulty = get_choice('item_pool')
ret.flute_mode = get_choice_default('flute_mode', default='normal')
ret.bow_mode = get_choice_default('bow_mode', default='progressive')
ret.item_functionality = get_choice('item_functionality')
old_style_bosses = {'basic': 'simple',
'normal': 'full',
'chaos': 'random'}
boss_choice = get_choice('boss_shuffle')
if boss_choice in old_style_bosses.keys():
boss_choice = old_style_bosses[boss_choice]
ret.shufflebosses = boss_choice
enemy_choice = get_choice('enemy_shuffle')
if enemy_choice == 'chaos':
enemy_choice = 'random'
ret.shuffleenemies = enemy_choice
old_style_damage = {'none': 'default',
'chaos': 'random'}
damage_choice = get_choice('enemy_damage')
if damage_choice in old_style_damage:
damage_choice = old_style_damage[damage_choice]
ret.enemy_damage = damage_choice
ret.enemy_health = get_choice('enemy_health')
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
inventoryweights = weights.get('startinventory', {})
startitems = []
for item in inventoryweights.keys():
if get_choice(item, inventoryweights) == 'on':
startitems.append(item)
ret.startinventory = ','.join(startitems)
if len(startitems) > 0:
ret.usestartinventory = True
if 'rom' in weights:
romweights = weights['rom']
ret.sprite = get_choice('sprite', romweights)
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
ret.quickswap = get_choice('quickswap', romweights) == 'on'
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
ret.fastmenu = get_choice('menuspeed', romweights)
ret.heartcolor = get_choice('heartcolor', romweights)
ret.heartbeep = get_choice('heartbeep', romweights)
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'
return ret