Merged DR v1.2.0.0
This commit is contained in:
381
source/classes/CustomSettings.py
Normal file
381
source/classes/CustomSettings.py
Normal 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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
888
source/dungeon/DungeonStitcher.py
Normal file
888
source/dungeon/DungeonStitcher.py
Normal 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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
3040
source/overworld/EntranceShuffle2.py
Normal file
3040
source/overworld/EntranceShuffle2.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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")
|
||||
219
source/tools/MysteryUtils.py
Normal file
219
source/tools/MysteryUtils.py
Normal 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
|
||||
Reference in New Issue
Block a user