diff --git a/worlds/oot/Cosmetics.py b/worlds/oot/Cosmetics.py index f40f8a1e..4a748c60 100644 --- a/worlds/oot/Cosmetics.py +++ b/worlds/oot/Cosmetics.py @@ -1,9 +1,9 @@ from .Utils import data_path, __version__ from .Colors import * import logging -import worlds.oot.Music as music -import worlds.oot.Sounds as sfx -import worlds.oot.IconManip as icon +from . import Music as music +from . import Sounds as sfx +from . import IconManip as icon from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict import json @@ -105,7 +105,7 @@ def patch_tunic_colors(rom, ootworld, symbols): # handle random if tunic_option == 'Random Choice': - tunic_option = random.choice(tunic_color_list) + tunic_option = ootworld.random.choice(tunic_color_list) # handle completely random if tunic_option == 'Completely Random': color = generate_random_color() @@ -156,9 +156,9 @@ def patch_navi_colors(rom, ootworld, symbols): # choose a random choice for the whole group if navi_option_inner == 'Random Choice': - navi_option_inner = random.choice(navi_color_list) + navi_option_inner = ootworld.random.choice(navi_color_list) if navi_option_outer == 'Random Choice': - navi_option_outer = random.choice(navi_color_list) + navi_option_outer = ootworld.random.choice(navi_color_list) if navi_option_outer == 'Match Inner': navi_option_outer = navi_option_inner @@ -233,9 +233,9 @@ def patch_sword_trails(rom, ootworld, symbols): # handle random choice if option_inner == 'Random Choice': - option_inner = random.choice(sword_trail_color_list) + option_inner = ootworld.random.choice(sword_trail_color_list) if option_outer == 'Random Choice': - option_outer = random.choice(sword_trail_color_list) + option_outer = ootworld.random.choice(sword_trail_color_list) if option_outer == 'Match Inner': option_outer = option_inner @@ -326,9 +326,9 @@ def patch_trails(rom, ootworld, trails): # handle random choice if option_inner == 'Random Choice': - option_inner = random.choice(trail_color_list) + option_inner = ootworld.random.choice(trail_color_list) if option_outer == 'Random Choice': - option_outer = random.choice(trail_color_list) + option_outer = ootworld.random.choice(trail_color_list) if option_outer == 'Match Inner': option_outer = option_inner @@ -393,7 +393,7 @@ def patch_gauntlet_colors(rom, ootworld, symbols): # handle random if gauntlet_option == 'Random Choice': - gauntlet_option = random.choice(gauntlet_color_list) + gauntlet_option = ootworld.random.choice(gauntlet_color_list) # handle completely random if gauntlet_option == 'Completely Random': color = generate_random_color() @@ -424,10 +424,10 @@ def patch_shield_frame_colors(rom, ootworld, symbols): # handle random if shield_frame_option == 'Random Choice': - shield_frame_option = random.choice(shield_frame_color_list) + shield_frame_option = ootworld.random.choice(shield_frame_color_list) # handle completely random if shield_frame_option == 'Completely Random': - color = [random.getrandbits(8), random.getrandbits(8), random.getrandbits(8)] + color = [ootworld.random.getrandbits(8), ootworld.random.getrandbits(8), ootworld.random.getrandbits(8)] # grab the color from the list elif shield_frame_option in shield_frame_colors: color = list(shield_frame_colors[shield_frame_option]) @@ -458,7 +458,7 @@ def patch_heart_colors(rom, ootworld, symbols): # handle random if heart_option == 'Random Choice': - heart_option = random.choice(heart_color_list) + heart_option = ootworld.random.choice(heart_color_list) # handle completely random if heart_option == 'Completely Random': color = generate_random_color() @@ -495,7 +495,7 @@ def patch_magic_colors(rom, ootworld, symbols): magic_option = format_cosmetic_option_result(ootworld.__dict__[magic_setting]) if magic_option == 'Random Choice': - magic_option = random.choice(magic_color_list) + magic_option = ootworld.random.choice(magic_color_list) if magic_option == 'Completely Random': color = generate_random_color() @@ -559,7 +559,7 @@ def patch_button_colors(rom, ootworld, symbols): # handle random if button_option == 'Random Choice': - button_option = random.choice(list(button_colors.keys())) + button_option = ootworld.random.choice(list(button_colors.keys())) # handle completely random if button_option == 'Completely Random': fixed_font_color = [10, 10, 10] @@ -618,11 +618,11 @@ def patch_sfx(rom, ootworld, symbols): rom.write_int16(loc, sound_id) else: if selection == 'random-choice': - selection = random.choice(sfx.get_hook_pool(hook)).value.keyword + selection = ootworld.random.choice(sfx.get_hook_pool(hook)).value.keyword elif selection == 'random-ear-safe': - selection = random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword + selection = ootworld.random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword elif selection == 'completely-random': - selection = random.choice(sfx.standard).value.keyword + selection = ootworld.random.choice(sfx.standard).value.keyword sound_id = sound_dict[selection] for loc in hook.value.locations: rom.write_int16(loc, sound_id) @@ -644,7 +644,7 @@ def patch_instrument(rom, ootworld, symbols): choice = ootworld.sfx_ocarina if choice == 'random-choice': - choice = random.choice(list(instruments.keys())) + choice = ootworld.random.choice(list(instruments.keys())) rom.write_byte(0x00B53C7B, instruments[choice]) rom.write_byte(0x00B4BF6F, instruments[choice]) # For Lost Woods Skull Kids' minigame in Lost Woods @@ -769,7 +769,6 @@ patch_sets[0x1F073FD9] = { def patch_cosmetics(ootworld, rom): # Use the world's slot seed for cosmetics - random.seed(ootworld.multiworld.per_slot_randoms[ootworld.player].random()) # try to detect the cosmetic patch data format versioned_patch_set = None diff --git a/worlds/oot/Entrance.py b/worlds/oot/Entrance.py index 6c4b6428..8b041f04 100644 --- a/worlds/oot/Entrance.py +++ b/worlds/oot/Entrance.py @@ -3,9 +3,9 @@ from BaseClasses import Entrance class OOTEntrance(Entrance): game: str = 'Ocarina of Time' - def __init__(self, player, world, name='', parent=None): + def __init__(self, player, multiworld, name='', parent=None): super(OOTEntrance, self).__init__(player, name, parent) - self.multiworld = world + self.multiworld = multiworld self.access_rules = [] self.reverse = None self.replaces = None diff --git a/worlds/oot/EntranceShuffle.py b/worlds/oot/EntranceShuffle.py index cda442ff..66c5df80 100644 --- a/worlds/oot/EntranceShuffle.py +++ b/worlds/oot/EntranceShuffle.py @@ -440,16 +440,16 @@ class EntranceShuffleError(Exception): def shuffle_random_entrances(ootworld): - world = ootworld.multiworld + multiworld = ootworld.multiworld player = ootworld.player # Gather locations to keep reachable for validation all_state = ootworld.get_state_with_complete_itempool() all_state.sweep_for_advancements(locations=ootworld.get_locations()) - locations_to_ensure_reachable = {loc for loc in world.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))} + locations_to_ensure_reachable = {loc for loc in multiworld.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))} # Set entrance data for all entrances - set_all_entrances_data(world, player) + set_all_entrances_data(multiworld, player) # Determine entrance pools based on settings one_way_entrance_pools = {} @@ -547,10 +547,10 @@ def shuffle_random_entrances(ootworld): none_state = CollectionState(ootworld.multiworld) # Plando entrances - if world.plando_connections[player]: + if ootworld.options.plando_connections: rollbacks = [] all_targets = {**one_way_target_entrance_pools, **target_entrance_pools} - for conn in world.plando_connections[player]: + for conn in ootworld.options.plando_connections: try: entrance = ootworld.get_entrance(conn.entrance) exit = ootworld.get_entrance(conn.exit) @@ -628,7 +628,7 @@ def shuffle_random_entrances(ootworld): logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances') # Game is beatable new_all_state = ootworld.get_state_with_complete_itempool() - if not world.has_beaten_game(new_all_state, player): + if not multiworld.has_beaten_game(new_all_state, player): raise EntranceShuffleError('Cannot beat game') # Validate world validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state) @@ -675,7 +675,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools): avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools)) - ootworld.multiworld.random.shuffle(avail_pool) + ootworld.random.shuffle(avail_pool) for entrance in avail_pool: if entrance.replaces: @@ -725,11 +725,11 @@ def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances, raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}') def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state): - ootworld.multiworld.random.shuffle(entrances) + ootworld.random.shuffle(entrances) for entrance in entrances: if entrance.connected_region != None: continue - ootworld.multiworld.random.shuffle(target_entrances) + ootworld.random.shuffle(target_entrances) # Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems. # success rate over randomization if pool_type in {'InteriorSoft', 'MixedSoft'}: @@ -785,7 +785,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran # TODO: improve this function def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig): - world = ootworld.multiworld + multiworld = ootworld.multiworld player = ootworld.player all_state = all_state_orig.copy() @@ -828,8 +828,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \ (entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']): # Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints - potion_front = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player) - potion_back = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player) + potion_front = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player) + potion_back = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player) if potion_front is not None and potion_back is not None and not same_hint_area(potion_front, potion_back): raise EntranceShuffleError('Kak Potion Shop entrances are not in the same hint area') elif (potion_front and not potion_back) or (not potion_front and potion_back): @@ -840,8 +840,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all # When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides if ootworld.shuffle_cows: - impas_front = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player) - impas_back = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player) + impas_front = get_entrance_replacing(multiworld.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player) + impas_back = get_entrance_replacing(multiworld.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player) if impas_front is not None and impas_back is not None and not same_hint_area(impas_front, impas_back): raise EntranceShuffleError('Kak Impas House entrances are not in the same hint area') elif (impas_front and not impas_back) or (not impas_front and impas_back): @@ -861,25 +861,25 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all any(region for region in time_travel_state.adult_reachable_regions[player] if region.time_passes)): raise EntranceShuffleError('Time passing is not guaranteed as both ages') - if ootworld.starting_age == 'child' and (world.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]): + if ootworld.starting_age == 'child' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]): raise EntranceShuffleError('Path to ToT as adult not guaranteed') - if ootworld.starting_age == 'adult' and (world.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]): + if ootworld.starting_age == 'adult' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]): raise EntranceShuffleError('Path to ToT as child not guaranteed') if (ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances) and \ (entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']): # Ensure big poe shop is always reachable as adult - if world.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]: + if multiworld.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]: raise EntranceShuffleError('Big Poe Shop access not guaranteed as adult') if ootworld.shopsanity == 'off': # Ensure that Goron and Zora shops are accessible as adult - if world.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]: + if multiworld.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]: raise EntranceShuffleError('Goron City Shop not accessible as adult') - if world.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]: + if multiworld.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]: raise EntranceShuffleError('Zora\'s Domain Shop not accessible as adult') if ootworld.open_forest == 'closed': # Ensure that Kokiri Shop is reachable as child with no items - if world.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]: + if multiworld.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]: raise EntranceShuffleError('Kokiri Forest Shop not accessible as child in closed forest') diff --git a/worlds/oot/HintList.py b/worlds/oot/HintList.py index b0f20858..28a5d37a 100644 --- a/worlds/oot/HintList.py +++ b/worlds/oot/HintList.py @@ -1,5 +1,3 @@ -import random - from BaseClasses import LocationProgressType from .Items import OOTItem @@ -28,7 +26,7 @@ class Hint(object): text = "" type = [] - def __init__(self, name, text, type, choice=None): + def __init__(self, name, text, type, rand, choice=None): self.name = name self.type = [type] if not isinstance(type, list) else type @@ -36,31 +34,31 @@ class Hint(object): self.text = text else: if choice == None: - self.text = random.choice(text) + self.text = rand.choice(text) else: self.text = text[choice] -def getHint(item, clearer_hint=False): +def getHint(item, rand, clearer_hint=False): if item in hintTable: textOptions, clearText, hintType = hintTable[item] if clearer_hint: if clearText == None: - return Hint(item, textOptions, hintType, 0) - return Hint(item, clearText, hintType) + return Hint(item, textOptions, hintType, rand, 0) + return Hint(item, clearText, hintType, rand) else: - return Hint(item, textOptions, hintType) + return Hint(item, textOptions, hintType, rand) elif isinstance(item, str): - return Hint(item, item, 'generic') + return Hint(item, item, 'generic', rand) else: # is an Item - return Hint(item.name, item.hint_text, 'item') + return Hint(item.name, item.hint_text, 'item', rand) def getHintGroup(group, world): ret = [] for name in hintTable: - hint = getHint(name, world.clearer_hints) + hint = getHint(name, world.random, world.clearer_hints) if hint.name in world.always_hints and group == 'always': hint.type = 'always' @@ -95,7 +93,7 @@ def getHintGroup(group, world): def getRequiredHints(world): ret = [] for name in hintTable: - hint = getHint(name) + hint = getHint(name, world.random) if 'always' in hint.type or hint.name in conditional_always and conditional_always[hint.name](world): ret.append(hint) return ret @@ -1689,7 +1687,7 @@ def hintExclusions(world, clear_cache=False): location_hints = [] for name in hintTable: - hint = getHint(name, world.clearer_hints) + hint = getHint(name, world.random, world.clearer_hints) if any(item in hint.type for item in ['always', 'dual_always', diff --git a/worlds/oot/Hints.py b/worlds/oot/Hints.py index e63e135e..c01241d0 100644 --- a/worlds/oot/Hints.py +++ b/worlds/oot/Hints.py @@ -136,13 +136,13 @@ def getItemGenericName(item): def isRestrictedDungeonItem(dungeon, item): if not isinstance(item, OOTItem): return False - if (item.map or item.compass) and dungeon.multiworld.shuffle_mapcompass == 'dungeon': + if (item.map or item.compass) and dungeon.world.options.shuffle_mapcompass == 'dungeon': return item in dungeon.dungeon_items - if item.type == 'SmallKey' and dungeon.multiworld.shuffle_smallkeys == 'dungeon': + if item.type == 'SmallKey' and dungeon.world.options.shuffle_smallkeys == 'dungeon': return item in dungeon.small_keys - if item.type == 'BossKey' and dungeon.multiworld.shuffle_bosskeys == 'dungeon': + if item.type == 'BossKey' and dungeon.world.options.shuffle_bosskeys == 'dungeon': return item in dungeon.boss_key - if item.type == 'GanonBossKey' and dungeon.multiworld.shuffle_ganon_bosskey == 'dungeon': + if item.type == 'GanonBossKey' and dungeon.world.options.shuffle_ganon_bosskey == 'dungeon': return item in dungeon.boss_key return False @@ -261,8 +261,8 @@ hintPrefixes = [ '', ] -def getSimpleHintNoPrefix(item): - hint = getHint(item.name, True).text +def getSimpleHintNoPrefix(item, rand): + hint = getHint(item.name, rand, True).text for prefix in hintPrefixes: if hint.startswith(prefix): @@ -417,9 +417,9 @@ class HintArea(Enum): # Formats the hint text for this area with proper grammar. # Dungeons are hinted differently depending on the clearer_hints setting. - def text(self, clearer_hints, preposition=False, world=None): + def text(self, rand, clearer_hints, preposition=False, world=None): if self.is_dungeon: - text = getHint(self.dungeon_name, clearer_hints).text + text = getHint(self.dungeon_name, rand, clearer_hints).text else: text = str(self) prefix, suffix = text.replace('#', '').split(' ', 1) @@ -489,7 +489,7 @@ def get_woth_hint(world, checked): if getattr(location.parent_region, "dungeon", None): world.woth_dungeon += 1 - location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text + location_text = getHint(location.parent_region.dungeon.name, world.random, world.clearer_hints).text else: location_text = get_hint_area(location) @@ -570,9 +570,9 @@ def get_good_item_hint(world, checked): location = world.hint_rng.choice(locations) checked[location.player].add(location.name) - item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text + item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text if getattr(location.parent_region, "dungeon", None): - location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text + location_text = getHint(location.parent_region.dungeon.name, world.hint_rng, world.clearer_hints).text return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)), ['Green', 'Red']), location) else: @@ -613,10 +613,10 @@ def get_specific_item_hint(world, checked): location = world.hint_rng.choice(locations) checked[location.player].add(location.name) - item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text + item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text if getattr(location.parent_region, "dungeon", None): - location_text = getHint(location.parent_region.dungeon.name, world.clearer_hints).text + location_text = getHint(location.parent_region.dungeon.name, world.hint_rng, world.clearer_hints).text if world.hint_dist_user.get('vague_named_items', False): return (GossipText('#%s# may be on the hero\'s path.' % (location_text), ['Green']), location) else: @@ -648,9 +648,9 @@ def get_random_location_hint(world, checked): checked[location.player].add(location.name) dungeon = location.parent_region.dungeon - item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text + item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text if dungeon: - location_text = getHint(dungeon.name, world.clearer_hints).text + location_text = getHint(dungeon.name, world.hint_rng, world.clearer_hints).text return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)), ['Green', 'Red']), location) else: @@ -675,7 +675,7 @@ def get_specific_hint(world, checked, type): location_text = hint.text if '#' not in location_text: location_text = '#%s#' % location_text - item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text + item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text return (GossipText('%s #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)), ['Green', 'Red']), location) @@ -724,9 +724,9 @@ def get_entrance_hint(world, checked): connected_region = entrance.connected_region if connected_region.dungeon: - region_text = getHint(connected_region.dungeon.name, world.clearer_hints).text + region_text = getHint(connected_region.dungeon.name, world.hint_rng, world.clearer_hints).text else: - region_text = getHint(connected_region.name, world.clearer_hints).text + region_text = getHint(connected_region.name, world.hint_rng, world.clearer_hints).text if '#' not in region_text: region_text = '#%s#' % region_text @@ -882,10 +882,10 @@ def buildWorldGossipHints(world, checkedLocations=None): if location.name in world.hint_text_overrides: location_text = world.hint_text_overrides[location.name] else: - location_text = getHint(location.name, world.clearer_hints).text + location_text = getHint(location.name, world.hint_rng, world.clearer_hints).text if '#' not in location_text: location_text = '#%s#' % location_text - item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text + item_text = getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text add_hint(world, stoneGroups, GossipText('%s #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True) logging.getLogger('').debug('Placed always hint for %s.', location.name) @@ -1003,16 +1003,16 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True) ('Goron Ruby', 'Red'), ('Zora Sapphire', 'Blue'), ] - child_text += getHint('Spiritual Stone Text Start', world.clearer_hints).text + '\x04' + child_text += getHint('Spiritual Stone Text Start', world.hint_rng, world.clearer_hints).text + '\x04' for (reward, color) in bossRewardsSpiritualStones: child_text += buildBossString(reward, color, world) - child_text += getHint('Child Altar Text End', world.clearer_hints).text + child_text += getHint('Child Altar Text End', world.hint_rng, world.clearer_hints).text child_text += '\x0B' update_message_by_id(messages, 0x707A, get_raw_text(child_text), 0x20) # text that appears at altar as an adult. adult_text = '\x08' - adult_text += getHint('Adult Altar Text Start', world.clearer_hints).text + '\x04' + adult_text += getHint('Adult Altar Text Start', world.hint_rng, world.clearer_hints).text + '\x04' if include_rewards: bossRewardsMedallions = [ ('Light Medallion', 'Light Blue'), @@ -1029,7 +1029,7 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True) adult_text += '\x04' adult_text += buildGanonBossKeyString(world) else: - adult_text += getHint('Adult Altar Text End', world.clearer_hints).text + adult_text += getHint('Adult Altar Text End', world.hint_rng, world.clearer_hints).text adult_text += '\x0B' update_message_by_id(messages, 0x7057, get_raw_text(adult_text), 0x20) @@ -1044,7 +1044,7 @@ def buildBossString(reward, color, world): text = GossipText(f"\x08\x13{item_icon}One in #@'s pocket#...", [color], prefix='') else: location = world.hinted_dungeon_reward_locations[reward] - location_text = HintArea.at(location).text(world.clearer_hints, preposition=True) + location_text = HintArea.at(location).text(world.hint_rng, world.clearer_hints, preposition=True) text = GossipText(f"\x08\x13{item_icon}One {location_text}...", [color], prefix='') return str(text) + '\x04' @@ -1054,7 +1054,7 @@ def buildBridgeReqsString(world): if world.bridge == 'open': string += "The awakened ones will have #already created a bridge# to the castle where the evil dwells." else: - item_req_string = getHint('bridge_' + world.bridge, world.clearer_hints).text + item_req_string = getHint('bridge_' + world.bridge, world.hint_rng, world.clearer_hints).text if world.bridge == 'medallions': item_req_string = str(world.bridge_medallions) + ' ' + item_req_string elif world.bridge == 'stones': @@ -1077,7 +1077,7 @@ def buildGanonBossKeyString(world): string += "And the door to the \x05\x41evil one\x05\x40's chamber will be left #unlocked#." else: if world.shuffle_ganon_bosskey == 'on_lacs': - item_req_string = getHint('lacs_' + world.lacs_condition, world.clearer_hints).text + item_req_string = getHint('lacs_' + world.lacs_condition, world.hint_rng, world.clearer_hints).text if world.lacs_condition == 'medallions': item_req_string = str(world.lacs_medallions) + ' ' + item_req_string elif world.lacs_condition == 'stones': @@ -1092,7 +1092,7 @@ def buildGanonBossKeyString(world): item_req_string = '#%s#' % item_req_string bk_location_string = "provided by Zelda once %s are retrieved" % item_req_string elif world.shuffle_ganon_bosskey in ['stones', 'medallions', 'dungeons', 'tokens', 'hearts']: - item_req_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.clearer_hints).text + item_req_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.hint_rng, world.clearer_hints).text if world.shuffle_ganon_bosskey == 'medallions': item_req_string = str(world.ganon_bosskey_medallions) + ' ' + item_req_string elif world.shuffle_ganon_bosskey == 'stones': @@ -1107,7 +1107,7 @@ def buildGanonBossKeyString(world): item_req_string = '#%s#' % item_req_string bk_location_string = "automatically granted once %s are retrieved" % item_req_string else: - bk_location_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.clearer_hints).text + bk_location_string = getHint('ganonBK_' + world.shuffle_ganon_bosskey, world.hint_rng, world.clearer_hints).text string += "And the \x05\x41evil one\x05\x40's key will be %s." % bk_location_string return str(GossipText(string, ['Yellow'], prefix='')) @@ -1142,16 +1142,16 @@ def buildMiscItemHints(world, messages): if location.player != world.player: player_text = world.multiworld.get_player_name(location.player) + "'s " if location.game == 'Ocarina of Time': - area = HintArea.at(location, use_alt_hint=data['use_alt_hint']).text(world.clearer_hints, world=None) + area = HintArea.at(location, use_alt_hint=data['use_alt_hint']).text(world.hint_rng, world.clearer_hints, world=None) else: area = location.name text = data['default_item_text'].format(area=rom_safe_text(player_text + area)) elif 'default_item_fallback' in data: text = data['default_item_fallback'] else: - text = getHint('Validation Line', world.clearer_hints).text + text = getHint('Validation Line', world.hint_rng, world.clearer_hints).text location = world.get_location('Ganons Tower Boss Key Chest') - text += f"#{getHint(getItemGenericName(location.item), world.clearer_hints).text}#" + text += f"#{getHint(getItemGenericName(location.item), world.hint_rng, world.clearer_hints).text}#" for find, replace in data.get('replace', {}).items(): text = text.replace(find, replace) @@ -1165,7 +1165,7 @@ def buildMiscLocationHints(world, messages): if hint_type in world.misc_hints: location = world.get_location(data['item_location']) item = location.item - item_text = getHint(getItemGenericName(item), world.clearer_hints).text + item_text = getHint(getItemGenericName(item), world.hint_rng, world.clearer_hints).text if item.player != world.player: item_text += f' for {world.multiworld.get_player_name(item.player)}' text = data['location_text'].format(item=rom_safe_text(item_text)) diff --git a/worlds/oot/ItemPool.py b/worlds/oot/ItemPool.py index 6ca6bc92..805d1fc7 100644 --- a/worlds/oot/ItemPool.py +++ b/worlds/oot/ItemPool.py @@ -295,16 +295,14 @@ random = None def get_junk_pool(ootworld): junk_pool[:] = list(junk_pool_base) - if ootworld.junk_ice_traps == 'on': + if ootworld.options.junk_ice_traps == 'on': junk_pool.append(('Ice Trap', 10)) - elif ootworld.junk_ice_traps in ['mayhem', 'onslaught']: + elif ootworld.options.junk_ice_traps in ['mayhem', 'onslaught']: junk_pool[:] = [('Ice Trap', 1)] return junk_pool -def get_junk_item(count=1, pool=None, plando_pool=None): - global random - +def get_junk_item(rand, count=1, pool=None, plando_pool=None): if count < 1: raise ValueError("get_junk_item argument 'count' must be greater than 0.") @@ -323,17 +321,17 @@ def get_junk_item(count=1, pool=None, plando_pool=None): raise RuntimeError("Not enough junk is available in the item pool to replace removed items.") else: junk_items, junk_weights = zip(*junk_pool) - return_pool.extend(random.choices(junk_items, weights=junk_weights, k=count)) + return_pool.extend(rand.choices(junk_items, weights=junk_weights, k=count)) return return_pool -def replace_max_item(items, item, max): +def replace_max_item(items, item, max, rand): count = 0 for i,val in enumerate(items): if val == item: if count >= max: - items[i] = get_junk_item()[0] + items[i] = get_junk_item(rand)[0] count += 1 @@ -375,7 +373,7 @@ def get_pool_core(world): pending_junk_pool.append('Kokiri Sword') if world.shuffle_ocarinas: pending_junk_pool.append('Ocarina') - if world.shuffle_beans and world.multiworld.start_inventory[world.player].value.get('Magic Bean Pack', 0): + if world.shuffle_beans and world.options.start_inventory.value.get('Magic Bean Pack', 0): pending_junk_pool.append('Magic Bean Pack') if (world.gerudo_fortress != "open" and world.shuffle_hideoutkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']): @@ -450,7 +448,7 @@ def get_pool_core(world): else: item = deku_scrubs_items[location.vanilla_item] if isinstance(item, list): - item = random.choices([i[0] for i in item], weights=[i[1] for i in item], k=1)[0] + item = world.random.choices([i[0] for i in item], weights=[i[1] for i in item], k=1)[0] shuffle_item = True # Kokiri Sword @@ -489,7 +487,7 @@ def get_pool_core(world): # Cows elif location.vanilla_item == 'Milk': if world.shuffle_cows: - item = get_junk_item()[0] + item = get_junk_item(world.random)[0] shuffle_item = world.shuffle_cows if not shuffle_item: location.show_in_spoiler = False @@ -508,13 +506,13 @@ def get_pool_core(world): item = 'Rutos Letter' ruto_bottles -= 1 else: - item = random.choice(normal_bottles) + item = world.random.choice(normal_bottles) shuffle_item = True # Magic Beans elif location.vanilla_item == 'Buy Magic Bean': if world.shuffle_beans: - item = 'Magic Bean Pack' if not world.multiworld.start_inventory[world.player].value.get('Magic Bean Pack', 0) else get_junk_item()[0] + item = 'Magic Bean Pack' if not world.options.start_inventory.value.get('Magic Bean Pack', 0) else get_junk_item(world.random)[0] shuffle_item = world.shuffle_beans if not shuffle_item: location.show_in_spoiler = False @@ -528,7 +526,7 @@ def get_pool_core(world): # Adult Trade Item elif location.vanilla_item == 'Pocket Egg': potential_trade_items = world.adult_trade_start if world.adult_trade_start else trade_items - item = random.choice(sorted(potential_trade_items)) + item = world.random.choice(sorted(potential_trade_items)) world.selected_adult_trade_item = item shuffle_item = True @@ -541,7 +539,7 @@ def get_pool_core(world): shuffle_item = False location.show_in_spoiler = False if shuffle_item and world.gerudo_fortress == 'normal' and 'Thieves Hideout' in world.key_rings: - item = get_junk_item()[0] if location.name != 'Hideout 1 Torch Jail Gerudo Key' else 'Small Key Ring (Thieves Hideout)' + item = get_junk_item(world.random)[0] if location.name != 'Hideout 1 Torch Jail Gerudo Key' else 'Small Key Ring (Thieves Hideout)' # Freestanding Rupees and Hearts elif location.type in ['ActorOverride', 'Freestanding', 'RupeeTower']: @@ -618,7 +616,7 @@ def get_pool_core(world): elif dungeon.name in world.key_rings and not dungeon.small_keys: item = dungeon.item_name("Small Key Ring") elif dungeon.name in world.key_rings: - item = get_junk_item()[0] + item = get_junk_item(world.random)[0] shuffle_item = True # Any other item in a dungeon. elif location.type in ["Chest", "NPC", "Song", "Collectable", "Cutscene", "BossHeart"]: @@ -630,7 +628,7 @@ def get_pool_core(world): if shuffle_setting in ['remove', 'startwith']: world.multiworld.push_precollected(dungeon_collection[-1]) world.remove_from_start_inventory.append(dungeon_collection[-1].name) - item = get_junk_item()[0] + item = get_junk_item(world.random)[0] shuffle_item = True elif shuffle_setting in ['any_dungeon', 'overworld', 'regional']: dungeon_collection[-1].priority = True @@ -658,9 +656,9 @@ def get_pool_core(world): shop_non_item_count = len(world.shop_prices) shop_item_count = shop_slots_count - shop_non_item_count - pool.extend(random.sample(remain_shop_items, shop_item_count)) + pool.extend(world.random.sample(remain_shop_items, shop_item_count)) if shop_non_item_count: - pool.extend(get_junk_item(shop_non_item_count)) + pool.extend(get_junk_item(world.random, shop_non_item_count)) # Extra rupees for shopsanity. if world.shopsanity not in ['off', '0']: @@ -706,19 +704,19 @@ def get_pool_core(world): if world.shuffle_ganon_bosskey in ['stones', 'medallions', 'dungeons', 'tokens', 'hearts', 'triforce']: placed_items['Gift from Sages'] = 'Boss Key (Ganons Castle)' - pool.extend(get_junk_item()) + pool.extend(get_junk_item(world.random)) else: placed_items['Gift from Sages'] = IGNORE_LOCATION world.get_location('Gift from Sages').show_in_spoiler = False if world.junk_ice_traps == 'off': - replace_max_item(pool, 'Ice Trap', 0) + replace_max_item(pool, 'Ice Trap', 0, world.random) elif world.junk_ice_traps == 'onslaught': for item in [item for item, weight in junk_pool_base] + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)']: - replace_max_item(pool, item, 0) + replace_max_item(pool, item, 0, world.random) for item, maximum in item_difficulty_max[world.item_pool_value].items(): - replace_max_item(pool, item, maximum) + replace_max_item(pool, item, maximum, world.random) # world.distribution.alter_pool(world, pool) @@ -748,7 +746,7 @@ def get_pool_core(world): pending_item = pending_junk_pool.pop() if not junk_candidates: raise RuntimeError("Not enough junk exists in item pool for %s (+%d others) to be added." % (pending_item, len(pending_junk_pool) - 1)) - junk_item = random.choice(junk_candidates) + junk_item = world.random.choice(junk_candidates) junk_candidates.remove(junk_item) pool.remove(junk_item) pool.append(pending_item) diff --git a/worlds/oot/Messages.py b/worlds/oot/Messages.py index 25c2a993..5059c01f 100644 --- a/worlds/oot/Messages.py +++ b/worlds/oot/Messages.py @@ -1,6 +1,5 @@ # text details: https://wiki.cloudmodding.com/oot/Text_Format -import random from .HintList import misc_item_hint_table, misc_location_hint_table from .TextBox import line_wrap from .Utils import find_last @@ -969,7 +968,7 @@ def repack_messages(rom, messages, permutation=None, always_allow_skip=True, spe rom.write_bytes(entry_offset, [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) # shuffles the messages in the game, making sure to keep various message types in their own group -def shuffle_messages(messages, except_hints=True, always_allow_skip=True): +def shuffle_messages(messages, rand, except_hints=True, always_allow_skip=True): permutation = [i for i, _ in enumerate(messages)] @@ -1002,7 +1001,7 @@ def shuffle_messages(messages, except_hints=True, always_allow_skip=True): def shuffle_group(group): group_permutation = [i for i, _ in enumerate(group)] - random.shuffle(group_permutation) + rand.shuffle(group_permutation) for index_from, index_to in enumerate(group_permutation): permutation[group[index_to].index] = group[index_from].index diff --git a/worlds/oot/Music.py b/worlds/oot/Music.py index 6ed1ab54..1bb3b65a 100644 --- a/worlds/oot/Music.py +++ b/worlds/oot/Music.py @@ -1,6 +1,5 @@ #Much of this is heavily inspired from and/or based on az64's / Deathbasket's MM randomizer -import random import os from .Utils import compare_version, data_path @@ -175,7 +174,7 @@ def process_sequences(rom, sequences, target_sequences, disabled_source_sequence return sequences, target_sequences -def shuffle_music(sequences, target_sequences, music_mapping, log): +def shuffle_music(sequences, target_sequences, music_mapping, log, rand): sequence_dict = {} sequence_ids = [] @@ -191,7 +190,7 @@ def shuffle_music(sequences, target_sequences, music_mapping, log): # Shuffle the sequences if len(sequences) < len(target_sequences): raise Exception(f"Not enough custom music/fanfares ({len(sequences)}) to omit base Ocarina of Time sequences ({len(target_sequences)}).") - random.shuffle(sequence_ids) + rand.shuffle(sequence_ids) sequences = [] for target_sequence in target_sequences: @@ -328,7 +327,7 @@ def rebuild_sequences(rom, sequences): rom.write_byte(base, j.instrument_set) -def shuffle_pointers_table(rom, ids, music_mapping, log): +def shuffle_pointers_table(rom, ids, music_mapping, log, rand): # Read in all the Music data bgm_data = {} bgm_ids = [] @@ -341,7 +340,7 @@ def shuffle_pointers_table(rom, ids, music_mapping, log): bgm_ids.append(bgm[0]) # shuffle data - random.shuffle(bgm_ids) + rand.shuffle(bgm_ids) # Write Music data back in random ordering for bgm in ids: @@ -424,13 +423,13 @@ def randomize_music(rom, ootworld, music_mapping): # process_sequences(rom, sequences, target_sequences, disabled_source_sequences, disabled_target_sequences, bgm_ids) # if ootworld.background_music == 'random_custom_only': # sequences = [seq for seq in sequences if seq.cosmetic_name not in [x[0] for x in bgm_ids] or seq.cosmetic_name in music_mapping.values()] - # sequences, log = shuffle_music(sequences, target_sequences, music_mapping, log) + # sequences, log = shuffle_music(sequences, target_sequences, music_mapping, log, ootworld.random) # if ootworld.fanfares in ['random', 'random_custom_only'] or ff_mapped or ocarina_mapped: # process_sequences(rom, fanfare_sequences, fanfare_target_sequences, disabled_source_sequences, disabled_target_sequences, ff_ids, 'fanfare') # if ootworld.fanfares == 'random_custom_only': # fanfare_sequences = [seq for seq in fanfare_sequences if seq.cosmetic_name not in [x[0] for x in fanfare_sequence_ids] or seq.cosmetic_name in music_mapping.values()] - # fanfare_sequences, log = shuffle_music(fanfare_sequences, fanfare_target_sequences, music_mapping, log) + # fanfare_sequences, log = shuffle_music(fanfare_sequences, fanfare_target_sequences, music_mapping, log, ootworld.random) # if disabled_source_sequences: # log = disable_music(rom, disabled_source_sequences.values(), log) @@ -438,10 +437,10 @@ def randomize_music(rom, ootworld, music_mapping): # rebuild_sequences(rom, sequences + fanfare_sequences) # else: if ootworld.background_music == 'randomized' or bgm_mapped: - log = shuffle_pointers_table(rom, bgm_ids, music_mapping, log) + log = shuffle_pointers_table(rom, bgm_ids, music_mapping, log, ootworld.random) if ootworld.fanfares == 'randomized' or ff_mapped or ocarina_mapped: - log = shuffle_pointers_table(rom, ff_ids, music_mapping, log) + log = shuffle_pointers_table(rom, ff_ids, music_mapping, log, ootworld.random) # end_else if disabled_target_sequences: log = disable_music(rom, disabled_target_sequences.values(), log) diff --git a/worlds/oot/N64Patch.py b/worlds/oot/N64Patch.py index 5af3279e..3013a94a 100644 --- a/worlds/oot/N64Patch.py +++ b/worlds/oot/N64Patch.py @@ -1,5 +1,4 @@ import struct -import random import io import array import zlib @@ -88,7 +87,7 @@ def write_block_section(start, key_skip, in_data, patch_data, is_continue): # xor_range is the range the XOR key will read from. This range is not # too important, but I tried to choose from a section that didn't really # have big gaps of 0s which we want to avoid. -def create_patch_file(rom, xor_range=(0x00B8AD30, 0x00F029A0)): +def create_patch_file(rom, rand, xor_range=(0x00B8AD30, 0x00F029A0)): dma_start, dma_end = rom.get_dma_table_range() # add header @@ -100,7 +99,7 @@ def create_patch_file(rom, xor_range=(0x00B8AD30, 0x00F029A0)): # get random xor key. This range is chosen because it generally # doesn't have many sections of 0s - xor_address = random.Random().randint(*xor_range) + xor_address = rand.randint(*xor_range) patch_data.append_int32(xor_address) new_buffer = copy.copy(rom.original.buffer) diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py index daf072ad..613c5d01 100644 --- a/worlds/oot/Options.py +++ b/worlds/oot/Options.py @@ -1,6 +1,8 @@ import typing import random -from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections +from dataclasses import dataclass +from Options import Option, DefaultOnToggle, Toggle, Range, OptionList, OptionSet, DeathLink, PlandoConnections, \ + PerGameCommonOptions, OptionGroup from .EntranceShuffle import entrance_shuffle_table from .LogicTricks import normalized_name_tricks from .ColorSFXOptions import * @@ -1281,21 +1283,166 @@ class LogicTricks(OptionList): valid_keys_casefold = True -# All options assembled into a single dict -oot_options: typing.Dict[str, type(Option)] = { - "plando_connections": OoTPlandoConnections, - "logic_rules": Logic, - "logic_no_night_tokens_without_suns_song": NightTokens, - **open_options, - **world_options, - **bridge_options, - **dungeon_items_options, - **shuffle_options, - **timesavers_options, - **misc_options, - **itempool_options, - **cosmetic_options, - **sfx_options, - "logic_tricks": LogicTricks, - "death_link": DeathLink, -} +@dataclass +class OoTOptions(PerGameCommonOptions): + plando_connections: OoTPlandoConnections + death_link: DeathLink + logic_rules: Logic + logic_no_night_tokens_without_suns_song: NightTokens + logic_tricks: LogicTricks + open_forest: Forest + open_kakariko: Gate + open_door_of_time: DoorOfTime + zora_fountain: Fountain + gerudo_fortress: Fortress + bridge: Bridge + trials: Trials + starting_age: StartingAge + shuffle_interior_entrances: InteriorEntrances + shuffle_grotto_entrances: GrottoEntrances + shuffle_dungeon_entrances: DungeonEntrances + shuffle_overworld_entrances: OverworldEntrances + owl_drops: OwlDrops + warp_songs: WarpSongs + spawn_positions: SpawnPositions + shuffle_bosses: BossEntrances + # mix_entrance_pools: MixEntrancePools + # decouple_entrances: DecoupleEntrances + triforce_hunt: TriforceHunt + triforce_goal: TriforceGoal + extra_triforce_percentage: ExtraTriforces + bombchus_in_logic: LogicalChus + dungeon_shortcuts: DungeonShortcuts + dungeon_shortcuts_list: DungeonShortcutsList + mq_dungeons_mode: MQDungeons + mq_dungeons_list: MQDungeonList + mq_dungeons_count: MQDungeonCount + # empty_dungeons_mode: EmptyDungeons + # empty_dungeons_list: EmptyDungeonList + # empty_dungeon_count: EmptyDungeonCount + bridge_stones: BridgeStones + bridge_medallions: BridgeMedallions + bridge_rewards: BridgeRewards + bridge_tokens: BridgeTokens + bridge_hearts: BridgeHearts + shuffle_mapcompass: ShuffleMapCompass + shuffle_smallkeys: ShuffleKeys + shuffle_hideoutkeys: ShuffleGerudoKeys + shuffle_bosskeys: ShuffleBossKeys + enhance_map_compass: EnhanceMC + shuffle_ganon_bosskey: ShuffleGanonBK + ganon_bosskey_medallions: GanonBKMedallions + ganon_bosskey_stones: GanonBKStones + ganon_bosskey_rewards: GanonBKRewards + ganon_bosskey_tokens: GanonBKTokens + ganon_bosskey_hearts: GanonBKHearts + key_rings: KeyRings + key_rings_list: KeyRingList + shuffle_song_items: SongShuffle + shopsanity: ShopShuffle + shop_slots: ShopSlots + shopsanity_prices: ShopPrices + tokensanity: TokenShuffle + shuffle_scrubs: ScrubShuffle + shuffle_child_trade: ShuffleChildTrade + shuffle_freestanding_items: ShuffleFreestanding + shuffle_pots: ShufflePots + shuffle_crates: ShuffleCrates + shuffle_cows: ShuffleCows + shuffle_beehives: ShuffleBeehives + shuffle_kokiri_sword: ShuffleSword + shuffle_ocarinas: ShuffleOcarinas + shuffle_gerudo_card: ShuffleCard + shuffle_beans: ShuffleBeans + shuffle_medigoron_carpet_salesman: ShuffleMedigoronCarpet + shuffle_frog_song_rupees: ShuffleFrogRupees + no_escape_sequence: SkipEscape + no_guard_stealth: SkipStealth + no_epona_race: SkipEponaRace + skip_some_minigame_phases: SkipMinigamePhases + complete_mask_quest: CompleteMaskQuest + useful_cutscenes: UsefulCutscenes + fast_chests: FastChests + free_scarecrow: FreeScarecrow + fast_bunny_hood: FastBunny + plant_beans: PlantBeans + chicken_count: ChickenCount + big_poe_count: BigPoeCount + fae_torch_count: FAETorchCount + correct_chest_appearances: CorrectChestAppearance + minor_items_as_major_chest: MinorInMajor + invisible_chests: InvisibleChests + correct_potcrate_appearances: CorrectPotCrateAppearance + hints: Hints + misc_hints: MiscHints + hint_dist: HintDistribution + text_shuffle: TextShuffle + damage_multiplier: DamageMultiplier + deadly_bonks: DeadlyBonks + no_collectible_hearts: HeroMode + starting_tod: StartingToD + blue_fire_arrows: BlueFireArrows + fix_broken_drops: FixBrokenDrops + start_with_consumables: ConsumableStart + start_with_rupees: RupeeStart + item_pool_value: ItemPoolValue + junk_ice_traps: IceTraps + ice_trap_appearance: IceTrapVisual + adult_trade_start: AdultTradeStart + default_targeting: Targeting + display_dpad: DisplayDpad + dpad_dungeon_menu: DpadDungeonMenu + correct_model_colors: CorrectColors + background_music: BackgroundMusic + fanfares: Fanfares + ocarina_fanfares: OcarinaFanfares + kokiri_color: kokiri_color + goron_color: goron_color + zora_color: zora_color + silver_gauntlets_color: silver_gauntlets_color + golden_gauntlets_color: golden_gauntlets_color + mirror_shield_frame_color: mirror_shield_frame_color + navi_color_default_inner: navi_color_default_inner + navi_color_default_outer: navi_color_default_outer + navi_color_enemy_inner: navi_color_enemy_inner + navi_color_enemy_outer: navi_color_enemy_outer + navi_color_npc_inner: navi_color_npc_inner + navi_color_npc_outer: navi_color_npc_outer + navi_color_prop_inner: navi_color_prop_inner + navi_color_prop_outer: navi_color_prop_outer + sword_trail_duration: SwordTrailDuration + sword_trail_color_inner: sword_trail_color_inner + sword_trail_color_outer: sword_trail_color_outer + bombchu_trail_color_inner: bombchu_trail_color_inner + bombchu_trail_color_outer: bombchu_trail_color_outer + boomerang_trail_color_inner: boomerang_trail_color_inner + boomerang_trail_color_outer: boomerang_trail_color_outer + heart_color: heart_color + magic_color: magic_color + a_button_color: a_button_color + b_button_color: b_button_color + c_button_color: c_button_color + start_button_color: start_button_color + sfx_navi_overworld: sfx_navi_overworld + sfx_navi_enemy: sfx_navi_enemy + sfx_low_hp: sfx_low_hp + sfx_menu_cursor: sfx_menu_cursor + sfx_menu_select: sfx_menu_select + sfx_nightfall: sfx_nightfall + sfx_horse_neigh: sfx_horse_neigh + sfx_hover_boots: sfx_hover_boots + sfx_ocarina: SfxOcarina + + +oot_option_groups: typing.List[OptionGroup] = [ + OptionGroup("Open", [option for option in open_options.values()]), + OptionGroup("World", [*[option for option in world_options.values()], + *[option for option in bridge_options.values()]]), + OptionGroup("Shuffle", [option for option in shuffle_options.values()]), + OptionGroup("Dungeon Items", [option for option in dungeon_items_options.values()]), + OptionGroup("Timesavers", [option for option in timesavers_options.values()]), + OptionGroup("Misc", [option for option in misc_options.values()]), + OptionGroup("Item Pool", [option for option in itempool_options.values()]), + OptionGroup("Cosmetics", [option for option in cosmetic_options.values()]), + OptionGroup("SFX", [option for option in sfx_options.values()]) +] diff --git a/worlds/oot/Patches.py b/worlds/oot/Patches.py index 2219d7bb..561d7c3f 100644 --- a/worlds/oot/Patches.py +++ b/worlds/oot/Patches.py @@ -208,8 +208,8 @@ def patch_rom(world, rom): # Fix Ice Cavern Alcove Camera if not world.dungeon_mq['Ice Cavern']: - rom.write_byte(0x2BECA25,0x01); - rom.write_byte(0x2BECA2D,0x01); + rom.write_byte(0x2BECA25,0x01) + rom.write_byte(0x2BECA2D,0x01) # Fix GS rewards to be static rom.write_int32(0xEA3934, 0) @@ -944,7 +944,7 @@ def patch_rom(world, rom): scene_table = 0x00B71440 for scene in range(0x00, 0x65): - scene_start = rom.read_int32(scene_table + (scene * 0x14)); + scene_start = rom.read_int32(scene_table + (scene * 0x14)) add_scene_exits(scene_start) return exit_table @@ -1632,10 +1632,10 @@ def patch_rom(world, rom): reward_text = None elif getattr(location.item, 'looks_like_item', None) is not None: jabu_item = location.item.looks_like_item - reward_text = create_fake_name(getHint(getItemGenericName(location.item.looks_like_item), True).text) + reward_text = create_fake_name(getHint(getItemGenericName(location.item.looks_like_item), world.hint_rng, True).text) else: jabu_item = location.item - reward_text = getHint(getItemGenericName(location.item), True).text + reward_text = getHint(getItemGenericName(location.item), world.hint_rng, True).text # Update "Princess Ruto got the Spiritual Stone!" text before the midboss in Jabu if reward_text is None: @@ -1687,7 +1687,7 @@ def patch_rom(world, rom): # Sets hooks for gossip stone changes - symbol = rom.sym("GOSSIP_HINT_CONDITION"); + symbol = rom.sym("GOSSIP_HINT_CONDITION") if world.hints == 'none': rom.write_int32(symbol, 0) @@ -2264,9 +2264,9 @@ def patch_rom(world, rom): # text shuffle if world.text_shuffle == 'except_hints': - permutation = shuffle_messages(messages, except_hints=True) + permutation = shuffle_messages(messages, world.random, except_hints=True) elif world.text_shuffle == 'complete': - permutation = shuffle_messages(messages, except_hints=False) + permutation = shuffle_messages(messages, world.random, except_hints=False) # update warp song preview text boxes update_warp_song_text(messages, world) @@ -2358,7 +2358,7 @@ def patch_rom(world, rom): # Write numeric seed truncated to 32 bits for rng seeding # Overwritten with new seed every time a new rng value is generated - rng_seed = world.multiworld.per_slot_randoms[world.player].getrandbits(32) + rng_seed = world.random.getrandbits(32) rom.write_int32(rom.sym('RNG_SEED_INT'), rng_seed) # Static initial seed value for one-time random actions like the Hylian Shield discount rom.write_int32(rom.sym('RANDOMIZER_RNG_SEED'), rng_seed) @@ -2560,7 +2560,7 @@ def scene_get_actors(rom, actor_func, scene_data, scene, alternate=None, process room_count = rom.read_byte(scene_data + 1) room_list = scene_start + (rom.read_int32(scene_data + 4) & 0x00FFFFFF) for _ in range(0, room_count): - room_data = rom.read_int32(room_list); + room_data = rom.read_int32(room_list) if not room_data in processed_rooms: actors.update(room_get_actors(rom, actor_func, room_data, scene)) @@ -2591,7 +2591,7 @@ def get_actor_list(rom, actor_func): actors = {} scene_table = 0x00B71440 for scene in range(0x00, 0x65): - scene_data = rom.read_int32(scene_table + (scene * 0x14)); + scene_data = rom.read_int32(scene_table + (scene * 0x14)) actors.update(scene_get_actors(rom, actor_func, scene_data, scene)) return actors @@ -2605,7 +2605,7 @@ def get_override_itemid(override_table, scene, type, flags): def remove_entrance_blockers(rom): def remove_entrance_blockers_do(rom, actor_id, actor, scene): if actor_id == 0x014E and scene == 97: - actor_var = rom.read_int16(actor + 14); + actor_var = rom.read_int16(actor + 14) if actor_var == 0xFF01: rom.write_int16(actor + 14, 0x0700) get_actor_list(rom, remove_entrance_blockers_do) @@ -2789,7 +2789,7 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F purchase_text = '\x08%s %d Rupees\x09\x01%s\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (split_item_name[0], location.price, split_item_name[1]) else: if item_display.game == "Ocarina of Time": - shop_item_name = getSimpleHintNoPrefix(item_display) + shop_item_name = getSimpleHintNoPrefix(item_display, world.random) else: shop_item_name = item_display.name diff --git a/worlds/oot/RuleParser.py b/worlds/oot/RuleParser.py index 0791ad5d..e5390474 100644 --- a/worlds/oot/RuleParser.py +++ b/worlds/oot/RuleParser.py @@ -53,7 +53,7 @@ def isliteral(expr): class Rule_AST_Transformer(ast.NodeTransformer): def __init__(self, world, player): - self.multiworld = world + self.world = world self.player = player self.events = set() # map Region -> rule ast string -> item name @@ -86,9 +86,9 @@ class Rule_AST_Transformer(ast.NodeTransformer): ctx=ast.Load()), args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)], keywords=[]) - elif node.id in self.multiworld.__dict__: + elif node.id in self.world.__dict__: # Settings are constant - return ast.parse('%r' % self.multiworld.__dict__[node.id], mode='eval').body + return ast.parse('%r' % self.world.__dict__[node.id], mode='eval').body elif node.id in State.__dict__: return self.make_call(node, node.id, [], []) elif node.id in self.kwarg_defaults or node.id in allowed_globals: @@ -137,7 +137,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): if isinstance(count, ast.Name): # Must be a settings constant - count = ast.parse('%r' % self.multiworld.__dict__[count.id], mode='eval').body + count = ast.parse('%r' % self.world.__dict__[count.id], mode='eval').body if iname in escaped_items: iname = escaped_items[iname] @@ -182,7 +182,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): new_args = [] for child in node.args: if isinstance(child, ast.Name): - if child.id in self.multiworld.__dict__: + if child.id in self.world.__dict__: # child = ast.Attribute( # value=ast.Attribute( # value=ast.Name(id='state', ctx=ast.Load()), @@ -190,7 +190,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): # ctx=ast.Load()), # attr=child.id, # ctx=ast.Load()) - child = ast.Constant(getattr(self.multiworld, child.id)) + child = ast.Constant(getattr(self.world, child.id)) elif child.id in rule_aliases: child = self.visit(child) elif child.id in escaped_items: @@ -242,7 +242,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): # Fast check for json can_use if (len(node.ops) == 1 and isinstance(node.ops[0], ast.Eq) and isinstance(node.left, ast.Name) and isinstance(node.comparators[0], ast.Name) - and node.left.id not in self.multiworld.__dict__ and node.comparators[0].id not in self.multiworld.__dict__): + and node.left.id not in self.world.__dict__ and node.comparators[0].id not in self.world.__dict__): return ast.NameConstant(node.left.id == node.comparators[0].id) node.left = escape_or_string(node.left) @@ -378,7 +378,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): # Requires the target regions have been defined in the world. def create_delayed_rules(self): for region_name, node, subrule_name in self.delayed_rules: - region = self.multiworld.multiworld.get_region(region_name, self.player) + region = self.world.multiworld.get_region(region_name, self.player) event = OOTLocation(self.player, subrule_name, type='Event', parent=region, internal=True) event.show_in_spoiler = False @@ -395,7 +395,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): set_rule(event, access_rule) region.locations.append(event) - self.multiworld.make_event_item(subrule_name, event) + self.world.make_event_item(subrule_name, event) # Safeguard in case this is called multiple times per world self.delayed_rules.clear() @@ -448,7 +448,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): ## Handlers for compile-time optimizations (former State functions) def at_day(self, node): - if self.multiworld.ensure_tod_access: + if self.world.ensure_tod_access: # tod has DAY or (tod == NONE and (ss or find a path from a provider)) # parsing is better than constructing this expression by hand r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region @@ -456,7 +456,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): return ast.NameConstant(True) def at_dampe_time(self, node): - if self.multiworld.ensure_tod_access: + if self.world.ensure_tod_access: # tod has DAMPE or (tod == NONE and (find a path from a provider)) # parsing is better than constructing this expression by hand r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region @@ -464,10 +464,10 @@ class Rule_AST_Transformer(ast.NodeTransformer): return ast.NameConstant(True) def at_night(self, node): - if self.current_spot.type == 'GS Token' and self.multiworld.logic_no_night_tokens_without_suns_song: + if self.current_spot.type == 'GS Token' and self.world.logic_no_night_tokens_without_suns_song: # Using visit here to resolve 'can_play' rule return self.visit(ast.parse('can_play(Suns_Song)', mode='eval').body) - if self.multiworld.ensure_tod_access: + if self.world.ensure_tod_access: # tod has DAMPE or (tod == NONE and (ss or find a path from a provider)) # parsing is better than constructing this expression by hand r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region @@ -501,7 +501,7 @@ class Rule_AST_Transformer(ast.NodeTransformer): return ast.parse(f"state._oot_reach_as_age('{r.name}', 'adult', {self.player})", mode='eval').body def current_spot_starting_age_access(self, node): - return self.current_spot_child_access(node) if self.multiworld.starting_age == 'child' else self.current_spot_adult_access(node) + return self.current_spot_child_access(node) if self.world.starting_age == 'child' else self.current_spot_adult_access(node) def has_bottle(self, node): return ast.parse(f"state._oot_has_bottle({self.player})", mode='eval').body diff --git a/worlds/oot/Rules.py b/worlds/oot/Rules.py index 36563a3f..00f4aeb4 100644 --- a/worlds/oot/Rules.py +++ b/worlds/oot/Rules.py @@ -10,7 +10,7 @@ from .LocationList import dungeon_song_locations from BaseClasses import CollectionState, MultiWorld from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item -from ..AutoWorld import LogicMixin +from worlds.AutoWorld import LogicMixin class OOTLogic(LogicMixin): @@ -132,17 +132,17 @@ class OOTLogic(LogicMixin): def set_rules(ootworld): logger = logging.getLogger('') - world = ootworld.multiworld + multiworld = ootworld.multiworld player = ootworld.player if ootworld.logic_rules != 'no_logic': if ootworld.triforce_hunt: - world.completion_condition[player] = lambda state: state.has('Triforce Piece', player, ootworld.triforce_goal) + multiworld.completion_condition[player] = lambda state: state.has('Triforce Piece', player, ootworld.triforce_goal) else: - world.completion_condition[player] = lambda state: state.has('Triforce', player) + multiworld.completion_condition[player] = lambda state: state.has('Triforce', player) # ganon can only carry triforce - world.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce' + multiworld.get_location('Ganon', player).item_rule = lambda item: item.name == 'Triforce' # is_child = ootworld.parser.parse_rule('is_child') guarantee_hint = ootworld.parser.parse_rule('guarantee_hint') @@ -156,22 +156,22 @@ def set_rules(ootworld): if (ootworld.dungeon_mq['Forest Temple'] and ootworld.shuffle_bosskeys == 'dungeon' and ootworld.shuffle_smallkeys == 'dungeon' and ootworld.tokensanity == 'off'): # First room chest needs to be a small key. Make sure the boss key isn't placed here. - location = world.get_location('Forest Temple MQ First Room Chest', player) + location = multiworld.get_location('Forest Temple MQ First Room Chest', player) forbid_item(location, 'Boss Key (Forest Temple)', ootworld.player) if ootworld.shuffle_song_items in {'song', 'dungeon'} and not ootworld.songs_as_items: # Sheik in Ice Cavern is the only song location in a dungeon; need to ensure that it cannot be anything else. # This is required if map/compass included, or any_dungeon shuffle. - location = world.get_location('Sheik in Ice Cavern', player) + location = multiworld.get_location('Sheik in Ice Cavern', player) add_item_rule(location, lambda item: oot_is_item_of_type(item, 'Song')) if ootworld.shuffle_child_trade == 'skip_child_zelda': # Song from Impa must be local - location = world.get_location('Song from Impa', player) + location = multiworld.get_location('Song from Impa', player) add_item_rule(location, lambda item: item.player == player) for name in ootworld.always_hints: - add_rule(world.get_location(name, player), guarantee_hint) + add_rule(multiworld.get_location(name, player), guarantee_hint) # TODO: re-add hints once they are working # if location.type == 'HintStone' and ootworld.hints == 'mask': diff --git a/worlds/oot/TextBox.py b/worlds/oot/TextBox.py index a9db4799..e502d739 100644 --- a/worlds/oot/TextBox.py +++ b/worlds/oot/TextBox.py @@ -1,4 +1,4 @@ -import worlds.oot.Messages as Messages +from . import Messages # Least common multiple of all possible character widths. A line wrap must occur when the combined widths of all of the # characters on a line reach this value. diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index 94587a41..b93f60b2 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -20,7 +20,7 @@ from .ItemPool import generate_itempool, get_junk_item, get_junk_pool from .Regions import OOTRegion, TimeOfDay from .Rules import set_rules, set_shop_rules, set_entrances_based_rules from .RuleParser import Rule_AST_Transformer -from .Options import oot_options +from .Options import OoTOptions, oot_option_groups from .Utils import data_path, read_json from .LocationList import business_scrubs, set_drop_location_names, dungeon_song_locations from .DungeonList import dungeon_table, create_dungeons @@ -30,12 +30,12 @@ from .Patches import OoTContainer, patch_rom from .N64Patch import create_patch_file from .Cosmetics import patch_cosmetics -from Utils import get_options +from settings import get_settings from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections from Fill import fill_restrictive, fast_fill, FillError from worlds.generic.Rules import exclusion_rules, add_item_rule -from ..AutoWorld import World, AutoLogicRegister, WebWorld +from worlds.AutoWorld import World, AutoLogicRegister, WebWorld # OoT's generate_output doesn't benefit from more than 2 threads, instead it uses a lot of memory. i_o_limiter = threading.Semaphore(2) @@ -128,6 +128,7 @@ class OOTWeb(WebWorld): ) tutorials = [setup, setup_es, setup_fr, setup_de] + option_groups = oot_option_groups class OOTWorld(World): @@ -137,7 +138,8 @@ class OOTWorld(World): to rescue the Seven Sages, and then confront Ganondorf to save Hyrule! """ game: str = "Ocarina of Time" - option_definitions: dict = oot_options + options_dataclass = OoTOptions + options: OoTOptions settings: typing.ClassVar[OOTSettings] topology_present: bool = True item_name_to_id = {item_name: oot_data_to_ap_id(data, False) for item_name, data in item_table.items() if @@ -195,15 +197,15 @@ class OOTWorld(World): @classmethod def stage_assert_generate(cls, multiworld: MultiWorld): - rom = Rom(file=get_options()['oot_options']['rom_file']) + rom = Rom(file=get_settings()['oot_options']['rom_file']) # Option parsing, handling incompatible options, building useful-item table def generate_early(self): self.parser = Rule_AST_Transformer(self, self.player) - for (option_name, option) in oot_options.items(): - result = getattr(self.multiworld, option_name)[self.player] + for option_name in self.options_dataclass.type_hints: + result = getattr(self.options, option_name) if isinstance(result, Range): option_value = int(result) elif isinstance(result, Toggle): @@ -223,8 +225,8 @@ class OOTWorld(World): self.remove_from_start_inventory = [] # some items will be precollected but not in the inventory self.starting_items = Counter() self.songs_as_items = False - self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)] - self.connect_name = ''.join(self.multiworld.random.choices(printable, k=16)) + self.file_hash = [self.random.randint(0, 31) for i in range(5)] + self.connect_name = ''.join(self.random.choices(printable, k=16)) self.collectible_flag_addresses = {} # Incompatible option handling @@ -283,7 +285,7 @@ class OOTWorld(World): local_types.append('BossKey') if self.shuffle_ganon_bosskey != 'keysanity': local_types.append('GanonBossKey') - self.multiworld.local_items[self.player].value |= set(name for name, data in item_table.items() if data[0] in local_types) + self.options.local_items.value |= set(name for name, data in item_table.items() if data[0] in local_types) # If any songs are itemlinked, set songs_as_items for group in self.multiworld.groups.values(): @@ -297,7 +299,7 @@ class OOTWorld(World): # Determine skipped trials in GT # This needs to be done before the logic rules in GT are parsed trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light'] - chosen_trials = self.multiworld.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip + chosen_trials = self.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list} # Determine tricks in logic @@ -311,8 +313,8 @@ class OOTWorld(World): # No Logic forces all tricks on, prog balancing off and beatable-only elif self.logic_rules == 'no_logic': - self.multiworld.progression_balancing[self.player].value = False - self.multiworld.accessibility[self.player].value = Accessibility.option_minimal + self.options.progression_balancing.value = False + self.options.accessibility.value = Accessibility.option_minimal for trick in normalized_name_tricks.values(): setattr(self, trick['name'], True) @@ -333,8 +335,8 @@ class OOTWorld(World): # Set internal names used by the OoT generator self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld'] - self.trials_random = self.multiworld.trials[self.player].randomized - self.mq_dungeons_random = self.multiworld.mq_dungeons_count[self.player].randomized + self.trials_random = self.options.trials.randomized + self.mq_dungeons_random = self.options.mq_dungeons_count.randomized self.easier_fire_arrow_entry = self.fae_torch_count < 24 if self.misc_hints: @@ -393,8 +395,8 @@ class OOTWorld(World): elif self.key_rings == 'choose': self.key_rings = self.key_rings_list elif self.key_rings == 'random_dungeons': - self.key_rings = self.multiworld.random.sample(keyring_dungeons, - self.multiworld.random.randint(0, len(keyring_dungeons))) + self.key_rings = self.random.sample(keyring_dungeons, + self.random.randint(0, len(keyring_dungeons))) # Determine which dungeons are MQ. Not compatible with glitched logic. mq_dungeons = set() @@ -405,7 +407,7 @@ class OOTWorld(World): elif self.mq_dungeons_mode == 'specific': mq_dungeons = self.mq_dungeons_specific elif self.mq_dungeons_mode == 'count': - mq_dungeons = self.multiworld.random.sample(all_dungeons, self.mq_dungeons_count) + mq_dungeons = self.random.sample(all_dungeons, self.mq_dungeons_count) else: self.mq_dungeons_mode = 'count' self.mq_dungeons_count = 0 @@ -425,8 +427,8 @@ class OOTWorld(World): elif self.dungeon_shortcuts_choice == 'all': self.dungeon_shortcuts = set(shortcut_dungeons) elif self.dungeon_shortcuts_choice == 'random': - self.dungeon_shortcuts = self.multiworld.random.sample(shortcut_dungeons, - self.multiworld.random.randint(0, len(shortcut_dungeons))) + self.dungeon_shortcuts = self.random.sample(shortcut_dungeons, + self.random.randint(0, len(shortcut_dungeons))) # == 'choice', leave as previous else: self.dungeon_shortcuts = set() @@ -576,7 +578,7 @@ class OOTWorld(World): new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region) new_exit.vanilla_connected_region = exit new_exit.rule_string = rule - if self.multiworld.logic_rules != 'none': + if self.options.logic_rules != 'no_logic': self.parser.parse_spot_rule(new_exit) if new_exit.never: logger.debug('Dropping unreachable exit: %s', new_exit.name) @@ -607,7 +609,7 @@ class OOTWorld(World): elif self.shuffle_scrubs == 'random': # this is a random value between 0-99 # average value is ~33 rupees - price = int(self.multiworld.random.betavariate(1, 2) * 99) + price = int(self.random.betavariate(1, 2) * 99) # Set price in the dictionary as well as the location. self.scrub_prices[scrub_item] = price @@ -624,7 +626,7 @@ class OOTWorld(World): self.shop_prices = {} for region in self.regions: if self.shopsanity == 'random': - shop_item_count = self.multiworld.random.randint(0, 4) + shop_item_count = self.random.randint(0, 4) else: shop_item_count = int(self.shopsanity) @@ -632,17 +634,17 @@ class OOTWorld(World): if location.type == 'Shop': if location.name[-1:] in shop_item_indexes[:shop_item_count]: if self.shopsanity_prices == 'normal': - self.shop_prices[location.name] = int(self.multiworld.random.betavariate(1.5, 2) * 60) * 5 + self.shop_prices[location.name] = int(self.random.betavariate(1.5, 2) * 60) * 5 elif self.shopsanity_prices == 'affordable': self.shop_prices[location.name] = 10 elif self.shopsanity_prices == 'starting_wallet': - self.shop_prices[location.name] = self.multiworld.random.randrange(0,100,5) + self.shop_prices[location.name] = self.random.randrange(0,100,5) elif self.shopsanity_prices == 'adults_wallet': - self.shop_prices[location.name] = self.multiworld.random.randrange(0,201,5) + self.shop_prices[location.name] = self.random.randrange(0,201,5) elif self.shopsanity_prices == 'giants_wallet': - self.shop_prices[location.name] = self.multiworld.random.randrange(0,501,5) + self.shop_prices[location.name] = self.random.randrange(0,501,5) elif self.shopsanity_prices == 'tycoons_wallet': - self.shop_prices[location.name] = self.multiworld.random.randrange(0,1000,5) + self.shop_prices[location.name] = self.random.randrange(0,1000,5) # Fill boss prizes @@ -667,8 +669,8 @@ class OOTWorld(World): while bossCount: bossCount -= 1 - self.multiworld.random.shuffle(prizepool) - self.multiworld.random.shuffle(prize_locs) + self.random.shuffle(prizepool) + self.random.shuffle(prize_locs) item = prizepool.pop() loc = prize_locs.pop() loc.place_locked_item(item) @@ -778,7 +780,7 @@ class OOTWorld(World): # Call the junk fill and get a replacement if item in self.itempool: self.itempool.remove(item) - self.itempool.append(self.create_item(*get_junk_item(pool=junk_pool))) + self.itempool.append(self.create_item(*get_junk_item(self.random, pool=junk_pool))) if self.start_with_consumables: self.starting_items['Deku Sticks'] = 30 self.starting_items['Deku Nuts'] = 40 @@ -881,7 +883,7 @@ class OOTWorld(World): # Prefill shops, songs, and dungeon items items = self.get_pre_fill_items() locations = list(self.multiworld.get_unfilled_locations(self.player)) - self.multiworld.random.shuffle(locations) + self.random.shuffle(locations) # Set up initial state state = CollectionState(self.multiworld) @@ -910,7 +912,7 @@ class OOTWorld(World): if isinstance(locations, list): for item in stage_items: self.pre_fill_items.remove(item) - self.multiworld.random.shuffle(locations) + self.random.shuffle(locations) fill_restrictive(self.multiworld, prefill_state(state), locations, stage_items, single_player_placement=True, lock=True, allow_excluded=True) else: @@ -923,7 +925,7 @@ class OOTWorld(World): if isinstance(locations, list): for item in dungeon_items: self.pre_fill_items.remove(item) - self.multiworld.random.shuffle(locations) + self.random.shuffle(locations) fill_restrictive(self.multiworld, prefill_state(state), locations, dungeon_items, single_player_placement=True, lock=True, allow_excluded=True) @@ -964,7 +966,7 @@ class OOTWorld(World): while tries: try: - self.multiworld.random.shuffle(song_locations) + self.random.shuffle(song_locations) fill_restrictive(self.multiworld, prefill_state(state), song_locations[:], songs[:], single_player_placement=True, lock=True, allow_excluded=True) logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)") @@ -996,7 +998,7 @@ class OOTWorld(World): 'Buy Goron Tunic': 1, 'Buy Zora Tunic': 1, }.get(item.name, 0)) # place Deku Shields if needed, then tunics, then other advancement - self.multiworld.random.shuffle(shop_locations) + self.random.shuffle(shop_locations) self.pre_fill_items = [] # all prefill should be done fill_restrictive(self.multiworld, prefill_state(state), shop_locations, shop_prog, single_player_placement=True, lock=True, allow_excluded=True) @@ -1028,7 +1030,7 @@ class OOTWorld(World): ganon_junk_fill = min(1, ganon_junk_fill) gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons)) locations = [loc.name for region in gc.regions for loc in region.locations if loc.item is None] - junk_fill_locations = self.multiworld.random.sample(locations, round(len(locations) * ganon_junk_fill)) + junk_fill_locations = self.random.sample(locations, round(len(locations) * ganon_junk_fill)) exclusion_rules(self.multiworld, self.player, junk_fill_locations) # Locations which are not sendable must be converted to events @@ -1074,13 +1076,13 @@ class OOTWorld(World): trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap] self.trap_appearances = {} for loc_id in trap_location_ids: - self.trap_appearances[loc_id] = self.create_item(self.multiworld.per_slot_randoms[self.player].choice(self.fake_items).name) + self.trap_appearances[loc_id] = self.create_item(self.random.choice(self.fake_items).name) # Seed hint RNG, used for ganon text lines also - self.hint_rng = self.multiworld.per_slot_randoms[self.player] + self.hint_rng = self.random outfile_name = self.multiworld.get_out_file_name_base(self.player) - rom = Rom(file=get_options()['oot_options']['rom_file']) + rom = Rom(file=get_settings()['oot_options']['rom_file']) try: if self.hints != 'none': buildWorldGossipHints(self) @@ -1092,7 +1094,7 @@ class OOTWorld(World): finally: self.collectible_flags_available.set() rom.update_header() - patch_data = create_patch_file(rom) + patch_data = create_patch_file(rom, self.random) rom.restore() apz5 = OoTContainer(patch_data, outfile_name, output_directory, @@ -1399,7 +1401,7 @@ class OOTWorld(World): return all_state def get_filler_item_name(self) -> str: - return get_junk_item(count=1, pool=get_junk_pool(self))[0] + return get_junk_item(self.random, count=1, pool=get_junk_pool(self))[0] def valid_dungeon_item_location(world: OOTWorld, option: str, dungeon: str, loc: OOTLocation) -> bool: