Ocarina of Time: options and general cleanup (#3767)

* working?

* missed one

* fix old start inventory usage

* missed global random usage

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
Silvris 2024-09-18 14:26:59 -05:00 committed by GitHub
parent fced9050a4
commit 025c550991
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 367 additions and 226 deletions

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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',

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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()])
]

View File

@ -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

View File

@ -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

View File

@ -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':

View File

@ -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.

View File

@ -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: