Files
alttpr-python/source/classes/CustomSettings.py
2026-01-25 13:59:45 -06:00

600 lines
32 KiB
Python

import os
import urllib.parse
import urllib.request
from collections import defaultdict
from pathlib import Path
from typing import Any
import yaml
from yaml.representer import Representer
import RaceRandom as random
from BaseClasses import DoorType, LocationType
from OverworldShuffle import default_flute_connections, flute_data
from source.tools.MysteryUtils import get_weights, roll_settings
from Utils import HexInt, hex_representer
class CustomSettings(object):
def __init__(self):
self.file_source = None
self.relative_dir = None
self.world_rep = {}
self.player_range = None
self.player_map = {} # player number to name
def load_yaml(self, file):
self.file_source = load_yaml(file)
head, filename = os.path.split(file)
self.relative_dir = head
if 'version' in self.file_source and self.file_source['version'].startswith('2'):
player_number = 1
for key in self.file_source.keys():
if key in ['meta', 'version']:
continue
else:
self.player_map[player_number] = key
player_number += 1
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, resolve_weighted=True):
def get_setting(value: Any, default):
if value or value == 0:
if isinstance(value, dict):
if resolve_weighted:
return random.choices(list(value.keys()), list(value.values()), k=1)[0]
return None
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.skip_playthrough = get_setting(meta['skip_playthrough'], args.skip_playthrough)
args.spoiler = get_setting(meta['spoiler'], args.spoiler)
args.names = get_setting(meta['names'], args.names)
args.race = get_setting(meta['race'], args.race)
args.notes = get_setting(meta['user_notes'], args.notes)
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.ow_shuffle[p] = get_setting(settings['ow_shuffle'], args.ow_shuffle[p])
args.ow_terrain[p] = get_setting(settings['ow_terrain'], args.ow_terrain[p])
args.ow_crossed[p] = get_setting(settings['ow_crossed'], args.ow_crossed[p])
if args.ow_crossed[p] == 'chaos':
import logging
logging.getLogger('').info("Crossed OWR option 'chaos' is deprecated. Use 'unrestricted' instead.")
args.ow_crossed[p] = 'unrestricted'
args.ow_keepsimilar[p] = get_setting(settings['ow_keepsimilar'], args.ow_keepsimilar[p])
args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p])
args.ow_whirlpool[p] = get_setting(settings['ow_whirlpool'], args.ow_whirlpool[p])
args.ow_fluteshuffle[p] = get_setting(settings['ow_fluteshuffle'], args.ow_fluteshuffle[p])
args.shuffle_followers[p] = get_setting(settings['shuffle_followers'], args.shuffle_followers[p])
args.bonk_drops[p] = get_setting(settings['bonk_drops'], args.bonk_drops[p])
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]):
if args.dropshuffle[p] == 'none':
args.dropshuffle[p] = 'keys'
if args.pottery[p] == 'none':
args.pottery[p] = 'keys'
if args.retro[p] or args.mode[p] == 'retro':
if args.bow_mode[p] == 'progressive':
args.bow_mode[p] = 'retro'
elif args.bow_mode[p] == 'silvers':
args.bow_mode[p] = 'retro_silvers'
args.take_any[p] = 'random' if args.take_any[p] == 'none' else args.take_any[p]
args.keyshuffle[p] = 'universal'
args.mixed_travel[p] = get_setting(settings['mixed_travel'], args.mixed_travel[p])
args.standardize_palettes[p] = get_setting(settings['standardize_palettes'],
args.standardize_palettes[p])
args.intensity[p] = get_setting(settings['intensity'], args.intensity[p])
args.door_type_mode[p] = get_setting(settings['door_type_mode'], args.door_type_mode[p])
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[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.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p])
args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p])
args.prizeshuffle[p] = get_setting(settings['prizeshuffle'], args.prizeshuffle[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])
dungeon_item_map = { 0: 'none',
1: 'wild' }
if args.mapshuffle[p] in dungeon_item_map:
args.mapshuffle[p] = dungeon_item_map[args.mapshuffle[p]]
if args.compassshuffle[p] in dungeon_item_map:
args.compassshuffle[p] = dungeon_item_map[args.compassshuffle[p]]
if args.bigkeyshuffle[p] in dungeon_item_map:
args.bigkeyshuffle[p] = dungeon_item_map[args.bigkeyshuffle[p]]
if get_setting(settings['keysanity'], args.keysanity):
if args.bigkeyshuffle[p] in ['none', 0]:
args.bigkeyshuffle[p] = 'wild'
if args.keyshuffle[p] == 'none':
args.keyshuffle[p] = 'wild'
if args.mapshuffle[p] in ['none', 0]:
args.mapshuffle[p] = 'wild'
if args.compassshuffle[p] in ['none', 0]:
args.compassshuffle[p] = 'wild'
args.shufflebosses[p] = get_setting(settings['boss_shuffle'], get_setting(settings['shufflebosses'], args.shufflebosses[p]))
args.shuffleenemies[p] = get_setting(settings['enemy_shuffle'], 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.any_enemy_logic[p] = get_setting(settings['any_enemy_logic'], args.any_enemy_logic[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.skullwoods[p] = get_setting(settings['skullwoods'], args.skullwoods[p])
args.linked_drops[p] = get_setting(settings['linked_drops'], args.linked_drops[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.mirrorscroll[p] = get_setting(settings['mirrorscroll'], args.mirrorscroll[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.triforce_goal_min[p] = get_setting(settings['triforce_goal_min'], args.triforce_goal_min[p])
args.triforce_goal_max[p] = get_setting(settings['triforce_goal_max'], args.triforce_goal_max[p])
args.triforce_pool_min[p] = get_setting(settings['triforce_pool_min'], args.triforce_pool_min[p])
args.triforce_pool_max[p] = get_setting(settings['triforce_pool_max'], args.triforce_pool_max[p])
args.triforce_min_difference[p] = get_setting(settings['triforce_min_difference'], args.triforce_min_difference[p])
args.triforce_max_difference[p] = get_setting(settings['triforce_max_difference'], args.triforce_max_difference[p])
args.beemizer[p] = get_setting(settings['beemizer'], args.beemizer[p])
args.aga_randomness[p] = get_setting(settings['aga_randomness'], args.aga_randomness[p])
args.money_balance[p] = get_setting(settings['money_balance'], args.money_balance[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.triforce_gfx[p] = get_setting(settings['triforce_gfx'], args.triforce_gfx[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])
args.shuffle_sfxinstruments[p] = get_setting(settings['shuffle_sfxinstruments'], args.shuffle_sfxinstruments[p])
args.shuffle_songinstruments[p] = get_setting(settings['shuffle_songinstruments'], args.shuffle_songinstruments[p])
args.msu_resume[p] = get_setting(settings['msu_resume'], args.msu_resume[p])
def has_setting(self, player, setting):
if 'settings' in self.file_source and player in self.file_source['settings']:
return setting in self.file_source['settings'][player]
return False
def get_setting(self, player, setting):
return self.file_source['settings'][player][setting]
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_prices(self, player):
return self.get_attribute_by_player_composite('prices', player)
def get_advanced_placements(self):
if 'advanced_placements' in self.file_source:
return self.file_source['advanced_placements']
return None
def get_owedges(self):
if 'ow-edges' in self.file_source:
return self.file_source['ow-edges']
return None
def get_owcrossed(self):
if 'ow-crossed' in self.file_source:
return self.file_source['ow-crossed']
return None
def get_whirlpools(self):
if 'ow-whirlpools' in self.file_source:
return self.file_source['ow-whirlpools']
return None
def get_owtileflips(self):
if 'ow-tileflips' in self.file_source:
return self.file_source['ow-tileflips']
return None
def get_owflutespots(self):
if 'ow-flutespots' in self.file_source:
return self.file_source['ow-flutespots']
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 get_enemies(self):
if 'enemies' in self.file_source:
return self.file_source['enemies']
return None
def get_goals(self):
if 'goals' in self.file_source:
return self.file_source['goals']
return None
def get_attribute_by_player_composite(self, attribute, player):
attempt = self.get_attribute_by_player_new(attribute, player)
if attempt is not None:
return attempt
attempt = self.get_attribute_by_player(attribute, player)
return attempt
def get_attribute_by_player(self, attribute, player):
if attribute in self.file_source:
if player in self.file_source[attribute]:
return self.file_source[attribute][player]
return None
def get_attribute_by_player_new(self, attribute, player):
player_id = self.get_player_id(player)
if player_id is not None:
if attribute in self.file_source[player_id]:
return self.file_source[player_id][attribute]
return None
def get_player_id(self, player):
if player in self.file_source:
return player
if player in self.player_map and self.player_map[player] in self.file_source:
return self.player_map[player]
return None
def create_from_world(self, world, settings):
self.player_range = range(1, world.players + 1)
settings_dict, meta_dict = {}, {}
self.world_rep['meta'] = meta_dict
if world.seed:
meta_dict['seed'] = world.seed
meta_dict['players'] = world.players
meta_dict['algorithm'] = world.algorithm
meta_dict['race'] = settings.race
meta_dict['user_notes'] = settings.notes
self.world_rep['settings'] = settings_dict
if world.precollected_items:
self.world_rep['start_inventory'] = start_inv = {}
for p in self.player_range:
settings_dict[p] = {}
settings_dict[p]['ow_shuffle'] = world.owShuffle[p]
settings_dict[p]['ow_terrain'] = world.owTerrain[p]
settings_dict[p]['ow_crossed'] = world.owCrossed[p]
settings_dict[p]['ow_keepsimilar'] = world.owKeepSimilar[p]
settings_dict[p]['ow_mixed'] = world.owMixed[p]
settings_dict[p]['ow_whirlpool'] = world.owWhirlpoolShuffle[p]
settings_dict[p]['ow_fluteshuffle'] = world.owFluteShuffle[p]
settings_dict[p]['shuffle_followers'] = world.shuffle_followers[p]
settings_dict[p]['bonk_drops'] = world.shuffle_bonk_drops[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]['trap_door_mode'] = world.trap_door_mode[p]
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['door_self_loops'] = world.door_self_loops[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]['collection_rate'] = world.collection_rate[p]
settings_dict[p]['openpyramid'] = world.open_pyramid[p]
settings_dict[p]['prizeshuffle'] = world.prizeshuffle[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]['boss_shuffle'] = world.boss_shuffle[p]
settings_dict[p]['enemy_shuffle'] = 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]['any_enemy_logic'] = world.any_enemy_logic[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]['skullwoods'] = world.skullwoods[p]
settings_dict[p]['linked_drops'] = world.linked_drops[p]
settings_dict[p]['overworld_map'] = world.overworld_map[p]
settings_dict[p]['pseudoboots'] = world.pseudoboots[p]
settings_dict[p]['mirrorscroll'] = world.mirrorscroll[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]
settings_dict[p]['aga_randomness'] = world.aga_randomness[p]
settings_dict[p]['money_balance'] = world.money_balance[p]
if world.precollected_items:
start_inv[p] = []
for item in world.precollected_items:
start_inv[item.player].append(item.name)
# 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]
# settings_dict[p]['shuffle_songinstruments'] = world.shuffle_songinstruments[p]
# more settings?
def record_info(self, world):
self.world_rep['meta']['seed'] = world.seed
self.world_rep['bosses'] = bosses = {}
self.world_rep['medallions'] = medallions = {}
for p in self.player_range:
bosses[p] = {}
medallions[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 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_pool(self, world, use_custom_pool=False):
if not use_custom_pool or world.custom:
self.world_rep['item_pool'] = item_pool = {}
for p in self.player_range:
if not use_custom_pool or p in world.customitemarray:
item_pool[p] = defaultdict(int)
if use_custom_pool and world.custom:
import source.classes.constants as CONST
for p in world.customitemarray:
for i, c in world.customitemarray[p].items():
if c > 0:
item = CONST.CUSTOMITEMLABELS[CONST.CUSTOMITEMS.index(i)]
item_pool[p][item] += c
else:
for item in world.itempool:
item_pool[item.player][item.name] += 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_overworld(self, world):
self.world_rep['ow-edges'] = edges = {}
self.world_rep['ow-whirlpools'] = whirlpools = {}
self.world_rep['ow-tileflips'] = flips = {}
self.world_rep['ow-flutespots'] = flute = {}
for p in self.player_range:
connections = edges[p] = {}
connections['two-way'] = {}
connections['one-way'] = {}
whirlconnects = whirlpools[p] = {}
whirlconnects['two-way'] = {}
whirlconnects['one-way'] = {}
# tile flips
if p in world.owswaps and len(world.owswaps[p][0]) > 0:
flips[p] = {}
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f & 0x40 == 0)
flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0
# flute spots
flute[p] = {}
if p in world.owflutespots:
flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p]))
else:
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
flute[p]['forbid'] = []
for key, data in world.spoiler.overworlds.items():
player = data['player'] if 'player' in data else 1
connections = edges[player]
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
connections[sub][data['entrance']] = data['exit']
for key, data in world.spoiler.whirlpools.items():
player = data['player'] if 'player' in data else 1
whirlconnects = whirlconnects[player]
sub = 'two-way' if data['direction'] == 'both' else 'one-way'
whirlconnects[sub][data['entrance']] = data['exit']
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)
yaml.add_representer(HexInt, hex_representer)
with open(destination, 'w') as file:
yaml.dump(self.world_rep, file)
def load_yaml(path):
try:
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)
except yaml.YAMLError as e:
error_msg = f"Error parsing YAML file '{path}':\n{str(e)}"
raise ValueError(error_msg) from e