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 .Utils import data_path, __version__
from .Colors import * from .Colors import *
import logging import logging
import worlds.oot.Music as music from . import Music as music
import worlds.oot.Sounds as sfx from . import Sounds as sfx
import worlds.oot.IconManip as icon from . import IconManip as icon
from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict
import json import json
@ -105,7 +105,7 @@ def patch_tunic_colors(rom, ootworld, symbols):
# handle random # handle random
if tunic_option == 'Random Choice': if tunic_option == 'Random Choice':
tunic_option = random.choice(tunic_color_list) tunic_option = ootworld.random.choice(tunic_color_list)
# handle completely random # handle completely random
if tunic_option == 'Completely Random': if tunic_option == 'Completely Random':
color = generate_random_color() color = generate_random_color()
@ -156,9 +156,9 @@ def patch_navi_colors(rom, ootworld, symbols):
# choose a random choice for the whole group # choose a random choice for the whole group
if navi_option_inner == 'Random Choice': 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': 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': if navi_option_outer == 'Match Inner':
navi_option_outer = navi_option_inner navi_option_outer = navi_option_inner
@ -233,9 +233,9 @@ def patch_sword_trails(rom, ootworld, symbols):
# handle random choice # handle random choice
if option_inner == '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': 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': if option_outer == 'Match Inner':
option_outer = option_inner option_outer = option_inner
@ -326,9 +326,9 @@ def patch_trails(rom, ootworld, trails):
# handle random choice # handle random choice
if option_inner == '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': 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': if option_outer == 'Match Inner':
option_outer = option_inner option_outer = option_inner
@ -393,7 +393,7 @@ def patch_gauntlet_colors(rom, ootworld, symbols):
# handle random # handle random
if gauntlet_option == 'Random Choice': if gauntlet_option == 'Random Choice':
gauntlet_option = random.choice(gauntlet_color_list) gauntlet_option = ootworld.random.choice(gauntlet_color_list)
# handle completely random # handle completely random
if gauntlet_option == 'Completely Random': if gauntlet_option == 'Completely Random':
color = generate_random_color() color = generate_random_color()
@ -424,10 +424,10 @@ def patch_shield_frame_colors(rom, ootworld, symbols):
# handle random # handle random
if shield_frame_option == 'Random Choice': 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 # handle completely random
if shield_frame_option == '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 # grab the color from the list
elif shield_frame_option in shield_frame_colors: elif shield_frame_option in shield_frame_colors:
color = list(shield_frame_colors[shield_frame_option]) color = list(shield_frame_colors[shield_frame_option])
@ -458,7 +458,7 @@ def patch_heart_colors(rom, ootworld, symbols):
# handle random # handle random
if heart_option == 'Random Choice': if heart_option == 'Random Choice':
heart_option = random.choice(heart_color_list) heart_option = ootworld.random.choice(heart_color_list)
# handle completely random # handle completely random
if heart_option == 'Completely Random': if heart_option == 'Completely Random':
color = generate_random_color() 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]) magic_option = format_cosmetic_option_result(ootworld.__dict__[magic_setting])
if magic_option == 'Random Choice': 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': if magic_option == 'Completely Random':
color = generate_random_color() color = generate_random_color()
@ -559,7 +559,7 @@ def patch_button_colors(rom, ootworld, symbols):
# handle random # handle random
if button_option == 'Random Choice': 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 # handle completely random
if button_option == 'Completely Random': if button_option == 'Completely Random':
fixed_font_color = [10, 10, 10] fixed_font_color = [10, 10, 10]
@ -618,11 +618,11 @@ def patch_sfx(rom, ootworld, symbols):
rom.write_int16(loc, sound_id) rom.write_int16(loc, sound_id)
else: else:
if selection == 'random-choice': 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': 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': elif selection == 'completely-random':
selection = random.choice(sfx.standard).value.keyword selection = ootworld.random.choice(sfx.standard).value.keyword
sound_id = sound_dict[selection] sound_id = sound_dict[selection]
for loc in hook.value.locations: for loc in hook.value.locations:
rom.write_int16(loc, sound_id) rom.write_int16(loc, sound_id)
@ -644,7 +644,7 @@ def patch_instrument(rom, ootworld, symbols):
choice = ootworld.sfx_ocarina choice = ootworld.sfx_ocarina
if choice == 'random-choice': 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(0x00B53C7B, instruments[choice])
rom.write_byte(0x00B4BF6F, instruments[choice]) # For Lost Woods Skull Kids' minigame in Lost Woods 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): def patch_cosmetics(ootworld, rom):
# Use the world's slot seed for cosmetics # 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 # try to detect the cosmetic patch data format
versioned_patch_set = None versioned_patch_set = None

View File

@ -3,9 +3,9 @@ from BaseClasses import Entrance
class OOTEntrance(Entrance): class OOTEntrance(Entrance):
game: str = 'Ocarina of Time' 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) super(OOTEntrance, self).__init__(player, name, parent)
self.multiworld = world self.multiworld = multiworld
self.access_rules = [] self.access_rules = []
self.reverse = None self.reverse = None
self.replaces = None self.replaces = None

View File

@ -440,16 +440,16 @@ class EntranceShuffleError(Exception):
def shuffle_random_entrances(ootworld): def shuffle_random_entrances(ootworld):
world = ootworld.multiworld multiworld = ootworld.multiworld
player = ootworld.player player = ootworld.player
# Gather locations to keep reachable for validation # Gather locations to keep reachable for validation
all_state = ootworld.get_state_with_complete_itempool() all_state = ootworld.get_state_with_complete_itempool()
all_state.sweep_for_advancements(locations=ootworld.get_locations()) 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 entrance data for all entrances
set_all_entrances_data(world, player) set_all_entrances_data(multiworld, player)
# Determine entrance pools based on settings # Determine entrance pools based on settings
one_way_entrance_pools = {} one_way_entrance_pools = {}
@ -547,10 +547,10 @@ def shuffle_random_entrances(ootworld):
none_state = CollectionState(ootworld.multiworld) none_state = CollectionState(ootworld.multiworld)
# Plando entrances # Plando entrances
if world.plando_connections[player]: if ootworld.options.plando_connections:
rollbacks = [] rollbacks = []
all_targets = {**one_way_target_entrance_pools, **target_entrance_pools} 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: try:
entrance = ootworld.get_entrance(conn.entrance) entrance = ootworld.get_entrance(conn.entrance)
exit = ootworld.get_entrance(conn.exit) 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') logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances')
# Game is beatable # Game is beatable
new_all_state = ootworld.get_state_with_complete_itempool() 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') raise EntranceShuffleError('Cannot beat game')
# Validate world # Validate world
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state) 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): 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)) 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: for entrance in avail_pool:
if entrance.replaces: 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}') 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): 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: for entrance in entrances:
if entrance.connected_region != None: if entrance.connected_region != None:
continue 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. # Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
# success rate over randomization # success rate over randomization
if pool_type in {'InteriorSoft', 'MixedSoft'}: 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 # TODO: improve this function
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig): 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 player = ootworld.player
all_state = all_state_orig.copy() 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 \ if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']): (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 # 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_front = get_entrance_replacing(multiworld.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_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): 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') 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): 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 # When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides
if ootworld.shuffle_cows: if ootworld.shuffle_cows:
impas_front = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player) impas_front = get_entrance_replacing(multiworld.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_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): 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') 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): 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)): 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') 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') 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') raise EntranceShuffleError('Path to ToT as child not guaranteed')
if (ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances) and \ 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']): (entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']):
# Ensure big poe shop is always reachable as adult # 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') raise EntranceShuffleError('Big Poe Shop access not guaranteed as adult')
if ootworld.shopsanity == 'off': if ootworld.shopsanity == 'off':
# Ensure that Goron and Zora shops are accessible as adult # 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') 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') raise EntranceShuffleError('Zora\'s Domain Shop not accessible as adult')
if ootworld.open_forest == 'closed': if ootworld.open_forest == 'closed':
# Ensure that Kokiri Shop is reachable as child with no items # 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') 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 BaseClasses import LocationProgressType
from .Items import OOTItem from .Items import OOTItem
@ -28,7 +26,7 @@ class Hint(object):
text = "" text = ""
type = [] type = []
def __init__(self, name, text, type, choice=None): def __init__(self, name, text, type, rand, choice=None):
self.name = name self.name = name
self.type = [type] if not isinstance(type, list) else type self.type = [type] if not isinstance(type, list) else type
@ -36,31 +34,31 @@ class Hint(object):
self.text = text self.text = text
else: else:
if choice == None: if choice == None:
self.text = random.choice(text) self.text = rand.choice(text)
else: else:
self.text = text[choice] self.text = text[choice]
def getHint(item, clearer_hint=False): def getHint(item, rand, clearer_hint=False):
if item in hintTable: if item in hintTable:
textOptions, clearText, hintType = hintTable[item] textOptions, clearText, hintType = hintTable[item]
if clearer_hint: if clearer_hint:
if clearText == None: if clearText == None:
return Hint(item, textOptions, hintType, 0) return Hint(item, textOptions, hintType, rand, 0)
return Hint(item, clearText, hintType) return Hint(item, clearText, hintType, rand)
else: else:
return Hint(item, textOptions, hintType) return Hint(item, textOptions, hintType, rand)
elif isinstance(item, str): elif isinstance(item, str):
return Hint(item, item, 'generic') return Hint(item, item, 'generic', rand)
else: # is an Item 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): def getHintGroup(group, world):
ret = [] ret = []
for name in hintTable: 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': if hint.name in world.always_hints and group == 'always':
hint.type = 'always' hint.type = 'always'
@ -95,7 +93,7 @@ def getHintGroup(group, world):
def getRequiredHints(world): def getRequiredHints(world):
ret = [] ret = []
for name in hintTable: 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): if 'always' in hint.type or hint.name in conditional_always and conditional_always[hint.name](world):
ret.append(hint) ret.append(hint)
return ret return ret
@ -1689,7 +1687,7 @@ def hintExclusions(world, clear_cache=False):
location_hints = [] location_hints = []
for name in hintTable: 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 if any(item in hint.type for item in
['always', ['always',
'dual_always', 'dual_always',

View File

@ -136,13 +136,13 @@ def getItemGenericName(item):
def isRestrictedDungeonItem(dungeon, item): def isRestrictedDungeonItem(dungeon, item):
if not isinstance(item, OOTItem): if not isinstance(item, OOTItem):
return False 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 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 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 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 item in dungeon.boss_key
return False return False
@ -261,8 +261,8 @@ hintPrefixes = [
'', '',
] ]
def getSimpleHintNoPrefix(item): def getSimpleHintNoPrefix(item, rand):
hint = getHint(item.name, True).text hint = getHint(item.name, rand, True).text
for prefix in hintPrefixes: for prefix in hintPrefixes:
if hint.startswith(prefix): if hint.startswith(prefix):
@ -417,9 +417,9 @@ class HintArea(Enum):
# Formats the hint text for this area with proper grammar. # Formats the hint text for this area with proper grammar.
# Dungeons are hinted differently depending on the clearer_hints setting. # 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: if self.is_dungeon:
text = getHint(self.dungeon_name, clearer_hints).text text = getHint(self.dungeon_name, rand, clearer_hints).text
else: else:
text = str(self) text = str(self)
prefix, suffix = text.replace('#', '').split(' ', 1) prefix, suffix = text.replace('#', '').split(' ', 1)
@ -489,7 +489,7 @@ def get_woth_hint(world, checked):
if getattr(location.parent_region, "dungeon", None): if getattr(location.parent_region, "dungeon", None):
world.woth_dungeon += 1 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: else:
location_text = get_hint_area(location) location_text = get_hint_area(location)
@ -570,9 +570,9 @@ def get_good_item_hint(world, checked):
location = world.hint_rng.choice(locations) location = world.hint_rng.choice(locations)
checked[location.player].add(location.name) 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): 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)), return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
['Green', 'Red']), location) ['Green', 'Red']), location)
else: else:
@ -613,10 +613,10 @@ def get_specific_item_hint(world, checked):
location = world.hint_rng.choice(locations) location = world.hint_rng.choice(locations)
checked[location.player].add(location.name) 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): 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): if world.hint_dist_user.get('vague_named_items', False):
return (GossipText('#%s# may be on the hero\'s path.' % (location_text), ['Green']), location) return (GossipText('#%s# may be on the hero\'s path.' % (location_text), ['Green']), location)
else: else:
@ -648,9 +648,9 @@ def get_random_location_hint(world, checked):
checked[location.player].add(location.name) checked[location.player].add(location.name)
dungeon = location.parent_region.dungeon 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: 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)), return (GossipText('#%s# hoards #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
['Green', 'Red']), location) ['Green', 'Red']), location)
else: else:
@ -675,7 +675,7 @@ def get_specific_hint(world, checked, type):
location_text = hint.text location_text = hint.text
if '#' not in location_text: if '#' not in location_text:
location_text = '#%s#' % 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)), return (GossipText('%s #%s#.' % (attach_name(location_text, location, world), attach_name(item_text, location.item, world)),
['Green', 'Red']), location) ['Green', 'Red']), location)
@ -724,9 +724,9 @@ def get_entrance_hint(world, checked):
connected_region = entrance.connected_region connected_region = entrance.connected_region
if connected_region.dungeon: 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: 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: if '#' not in region_text:
region_text = '#%s#' % region_text region_text = '#%s#' % region_text
@ -882,10 +882,10 @@ def buildWorldGossipHints(world, checkedLocations=None):
if location.name in world.hint_text_overrides: if location.name in world.hint_text_overrides:
location_text = world.hint_text_overrides[location.name] location_text = world.hint_text_overrides[location.name]
else: 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: if '#' not in location_text:
location_text = '#%s#' % 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)), 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) ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)
logging.getLogger('').debug('Placed always hint for %s.', location.name) 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'), ('Goron Ruby', 'Red'),
('Zora Sapphire', 'Blue'), ('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: for (reward, color) in bossRewardsSpiritualStones:
child_text += buildBossString(reward, color, world) 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' child_text += '\x0B'
update_message_by_id(messages, 0x707A, get_raw_text(child_text), 0x20) update_message_by_id(messages, 0x707A, get_raw_text(child_text), 0x20)
# text that appears at altar as an adult. # text that appears at altar as an adult.
adult_text = '\x08' 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: if include_rewards:
bossRewardsMedallions = [ bossRewardsMedallions = [
('Light Medallion', 'Light Blue'), ('Light Medallion', 'Light Blue'),
@ -1029,7 +1029,7 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True)
adult_text += '\x04' adult_text += '\x04'
adult_text += buildGanonBossKeyString(world) adult_text += buildGanonBossKeyString(world)
else: 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' adult_text += '\x0B'
update_message_by_id(messages, 0x7057, get_raw_text(adult_text), 0x20) 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='') text = GossipText(f"\x08\x13{item_icon}One in #@'s pocket#...", [color], prefix='')
else: else:
location = world.hinted_dungeon_reward_locations[reward] 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='') text = GossipText(f"\x08\x13{item_icon}One {location_text}...", [color], prefix='')
return str(text) + '\x04' return str(text) + '\x04'
@ -1054,7 +1054,7 @@ def buildBridgeReqsString(world):
if world.bridge == 'open': if world.bridge == 'open':
string += "The awakened ones will have #already created a bridge# to the castle where the evil dwells." string += "The awakened ones will have #already created a bridge# to the castle where the evil dwells."
else: 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': if world.bridge == 'medallions':
item_req_string = str(world.bridge_medallions) + ' ' + item_req_string item_req_string = str(world.bridge_medallions) + ' ' + item_req_string
elif world.bridge == 'stones': 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#." string += "And the door to the \x05\x41evil one\x05\x40's chamber will be left #unlocked#."
else: else:
if world.shuffle_ganon_bosskey == 'on_lacs': 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': if world.lacs_condition == 'medallions':
item_req_string = str(world.lacs_medallions) + ' ' + item_req_string item_req_string = str(world.lacs_medallions) + ' ' + item_req_string
elif world.lacs_condition == 'stones': elif world.lacs_condition == 'stones':
@ -1092,7 +1092,7 @@ def buildGanonBossKeyString(world):
item_req_string = '#%s#' % item_req_string item_req_string = '#%s#' % item_req_string
bk_location_string = "provided by Zelda once %s are retrieved" % 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']: 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': if world.shuffle_ganon_bosskey == 'medallions':
item_req_string = str(world.ganon_bosskey_medallions) + ' ' + item_req_string item_req_string = str(world.ganon_bosskey_medallions) + ' ' + item_req_string
elif world.shuffle_ganon_bosskey == 'stones': elif world.shuffle_ganon_bosskey == 'stones':
@ -1107,7 +1107,7 @@ def buildGanonBossKeyString(world):
item_req_string = '#%s#' % item_req_string item_req_string = '#%s#' % item_req_string
bk_location_string = "automatically granted once %s are retrieved" % item_req_string bk_location_string = "automatically granted once %s are retrieved" % item_req_string
else: 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 string += "And the \x05\x41evil one\x05\x40's key will be %s." % bk_location_string
return str(GossipText(string, ['Yellow'], prefix='')) return str(GossipText(string, ['Yellow'], prefix=''))
@ -1142,16 +1142,16 @@ def buildMiscItemHints(world, messages):
if location.player != world.player: if location.player != world.player:
player_text = world.multiworld.get_player_name(location.player) + "'s " player_text = world.multiworld.get_player_name(location.player) + "'s "
if location.game == 'Ocarina of Time': 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: else:
area = location.name area = location.name
text = data['default_item_text'].format(area=rom_safe_text(player_text + area)) text = data['default_item_text'].format(area=rom_safe_text(player_text + area))
elif 'default_item_fallback' in data: elif 'default_item_fallback' in data:
text = data['default_item_fallback'] text = data['default_item_fallback']
else: 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') 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(): for find, replace in data.get('replace', {}).items():
text = text.replace(find, replace) text = text.replace(find, replace)
@ -1165,7 +1165,7 @@ def buildMiscLocationHints(world, messages):
if hint_type in world.misc_hints: if hint_type in world.misc_hints:
location = world.get_location(data['item_location']) location = world.get_location(data['item_location'])
item = location.item 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: if item.player != world.player:
item_text += f' for {world.multiworld.get_player_name(item.player)}' item_text += f' for {world.multiworld.get_player_name(item.player)}'
text = data['location_text'].format(item=rom_safe_text(item_text)) text = data['location_text'].format(item=rom_safe_text(item_text))

View File

@ -295,16 +295,14 @@ random = None
def get_junk_pool(ootworld): def get_junk_pool(ootworld):
junk_pool[:] = list(junk_pool_base) 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)) 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)] junk_pool[:] = [('Ice Trap', 1)]
return junk_pool return junk_pool
def get_junk_item(count=1, pool=None, plando_pool=None): def get_junk_item(rand, count=1, pool=None, plando_pool=None):
global random
if count < 1: if count < 1:
raise ValueError("get_junk_item argument 'count' must be greater than 0.") 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.") raise RuntimeError("Not enough junk is available in the item pool to replace removed items.")
else: else:
junk_items, junk_weights = zip(*junk_pool) 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 return return_pool
def replace_max_item(items, item, max): def replace_max_item(items, item, max, rand):
count = 0 count = 0
for i,val in enumerate(items): for i,val in enumerate(items):
if val == item: if val == item:
if count >= max: if count >= max:
items[i] = get_junk_item()[0] items[i] = get_junk_item(rand)[0]
count += 1 count += 1
@ -375,7 +373,7 @@ def get_pool_core(world):
pending_junk_pool.append('Kokiri Sword') pending_junk_pool.append('Kokiri Sword')
if world.shuffle_ocarinas: if world.shuffle_ocarinas:
pending_junk_pool.append('Ocarina') 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') pending_junk_pool.append('Magic Bean Pack')
if (world.gerudo_fortress != "open" if (world.gerudo_fortress != "open"
and world.shuffle_hideoutkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']): and world.shuffle_hideoutkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']):
@ -450,7 +448,7 @@ def get_pool_core(world):
else: else:
item = deku_scrubs_items[location.vanilla_item] item = deku_scrubs_items[location.vanilla_item]
if isinstance(item, list): 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 shuffle_item = True
# Kokiri Sword # Kokiri Sword
@ -489,7 +487,7 @@ def get_pool_core(world):
# Cows # Cows
elif location.vanilla_item == 'Milk': elif location.vanilla_item == 'Milk':
if world.shuffle_cows: if world.shuffle_cows:
item = get_junk_item()[0] item = get_junk_item(world.random)[0]
shuffle_item = world.shuffle_cows shuffle_item = world.shuffle_cows
if not shuffle_item: if not shuffle_item:
location.show_in_spoiler = False location.show_in_spoiler = False
@ -508,13 +506,13 @@ def get_pool_core(world):
item = 'Rutos Letter' item = 'Rutos Letter'
ruto_bottles -= 1 ruto_bottles -= 1
else: else:
item = random.choice(normal_bottles) item = world.random.choice(normal_bottles)
shuffle_item = True shuffle_item = True
# Magic Beans # Magic Beans
elif location.vanilla_item == 'Buy Magic Bean': elif location.vanilla_item == 'Buy Magic Bean':
if world.shuffle_beans: 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 shuffle_item = world.shuffle_beans
if not shuffle_item: if not shuffle_item:
location.show_in_spoiler = False location.show_in_spoiler = False
@ -528,7 +526,7 @@ def get_pool_core(world):
# Adult Trade Item # Adult Trade Item
elif location.vanilla_item == 'Pocket Egg': elif location.vanilla_item == 'Pocket Egg':
potential_trade_items = world.adult_trade_start if world.adult_trade_start else trade_items 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 world.selected_adult_trade_item = item
shuffle_item = True shuffle_item = True
@ -541,7 +539,7 @@ def get_pool_core(world):
shuffle_item = False shuffle_item = False
location.show_in_spoiler = False location.show_in_spoiler = False
if shuffle_item and world.gerudo_fortress == 'normal' and 'Thieves Hideout' in world.key_rings: 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 # Freestanding Rupees and Hearts
elif location.type in ['ActorOverride', 'Freestanding', 'RupeeTower']: 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: elif dungeon.name in world.key_rings and not dungeon.small_keys:
item = dungeon.item_name("Small Key Ring") item = dungeon.item_name("Small Key Ring")
elif dungeon.name in world.key_rings: elif dungeon.name in world.key_rings:
item = get_junk_item()[0] item = get_junk_item(world.random)[0]
shuffle_item = True shuffle_item = True
# Any other item in a dungeon. # Any other item in a dungeon.
elif location.type in ["Chest", "NPC", "Song", "Collectable", "Cutscene", "BossHeart"]: 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']: if shuffle_setting in ['remove', 'startwith']:
world.multiworld.push_precollected(dungeon_collection[-1]) world.multiworld.push_precollected(dungeon_collection[-1])
world.remove_from_start_inventory.append(dungeon_collection[-1].name) 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 shuffle_item = True
elif shuffle_setting in ['any_dungeon', 'overworld', 'regional']: elif shuffle_setting in ['any_dungeon', 'overworld', 'regional']:
dungeon_collection[-1].priority = True dungeon_collection[-1].priority = True
@ -658,9 +656,9 @@ def get_pool_core(world):
shop_non_item_count = len(world.shop_prices) shop_non_item_count = len(world.shop_prices)
shop_item_count = shop_slots_count - shop_non_item_count 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: 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. # Extra rupees for shopsanity.
if world.shopsanity not in ['off', '0']: 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']: if world.shuffle_ganon_bosskey in ['stones', 'medallions', 'dungeons', 'tokens', 'hearts', 'triforce']:
placed_items['Gift from Sages'] = 'Boss Key (Ganons Castle)' placed_items['Gift from Sages'] = 'Boss Key (Ganons Castle)'
pool.extend(get_junk_item()) pool.extend(get_junk_item(world.random))
else: else:
placed_items['Gift from Sages'] = IGNORE_LOCATION placed_items['Gift from Sages'] = IGNORE_LOCATION
world.get_location('Gift from Sages').show_in_spoiler = False world.get_location('Gift from Sages').show_in_spoiler = False
if world.junk_ice_traps == 'off': 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': elif world.junk_ice_traps == 'onslaught':
for item in [item for item, weight in junk_pool_base] + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)']: 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(): 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) # world.distribution.alter_pool(world, pool)
@ -748,7 +746,7 @@ def get_pool_core(world):
pending_item = pending_junk_pool.pop() pending_item = pending_junk_pool.pop()
if not junk_candidates: 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)) 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) junk_candidates.remove(junk_item)
pool.remove(junk_item) pool.remove(junk_item)
pool.append(pending_item) pool.append(pending_item)

View File

@ -1,6 +1,5 @@
# text details: https://wiki.cloudmodding.com/oot/Text_Format # text details: https://wiki.cloudmodding.com/oot/Text_Format
import random
from .HintList import misc_item_hint_table, misc_location_hint_table from .HintList import misc_item_hint_table, misc_location_hint_table
from .TextBox import line_wrap from .TextBox import line_wrap
from .Utils import find_last 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]) 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 # 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)] 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): def shuffle_group(group):
group_permutation = [i for i, _ in enumerate(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): for index_from, index_to in enumerate(group_permutation):
permutation[group[index_to].index] = group[index_from].index 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 #Much of this is heavily inspired from and/or based on az64's / Deathbasket's MM randomizer
import random
import os import os
from .Utils import compare_version, data_path 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 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_dict = {}
sequence_ids = [] sequence_ids = []
@ -191,7 +190,7 @@ def shuffle_music(sequences, target_sequences, music_mapping, log):
# Shuffle the sequences # Shuffle the sequences
if len(sequences) < len(target_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)}).") 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 = [] sequences = []
for target_sequence in target_sequences: for target_sequence in target_sequences:
@ -328,7 +327,7 @@ def rebuild_sequences(rom, sequences):
rom.write_byte(base, j.instrument_set) 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 # Read in all the Music data
bgm_data = {} bgm_data = {}
bgm_ids = [] bgm_ids = []
@ -341,7 +340,7 @@ def shuffle_pointers_table(rom, ids, music_mapping, log):
bgm_ids.append(bgm[0]) bgm_ids.append(bgm[0])
# shuffle data # shuffle data
random.shuffle(bgm_ids) rand.shuffle(bgm_ids)
# Write Music data back in random ordering # Write Music data back in random ordering
for bgm in ids: 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) # process_sequences(rom, sequences, target_sequences, disabled_source_sequences, disabled_target_sequences, bgm_ids)
# if ootworld.background_music == 'random_custom_only': # 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 = [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: # 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') # process_sequences(rom, fanfare_sequences, fanfare_target_sequences, disabled_source_sequences, disabled_target_sequences, ff_ids, 'fanfare')
# if ootworld.fanfares == 'random_custom_only': # 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 = [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: # if disabled_source_sequences:
# log = disable_music(rom, disabled_source_sequences.values(), log) # 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) # rebuild_sequences(rom, sequences + fanfare_sequences)
# else: # else:
if ootworld.background_music == 'randomized' or bgm_mapped: 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: 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 # end_else
if disabled_target_sequences: if disabled_target_sequences:
log = disable_music(rom, disabled_target_sequences.values(), log) log = disable_music(rom, disabled_target_sequences.values(), log)

View File

@ -1,5 +1,4 @@
import struct import struct
import random
import io import io
import array import array
import zlib 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 # 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 # too important, but I tried to choose from a section that didn't really
# have big gaps of 0s which we want to avoid. # 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() dma_start, dma_end = rom.get_dma_table_range()
# add header # 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 # get random xor key. This range is chosen because it generally
# doesn't have many sections of 0s # 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) patch_data.append_int32(xor_address)
new_buffer = copy.copy(rom.original.buffer) new_buffer = copy.copy(rom.original.buffer)

View File

@ -1,6 +1,8 @@
import typing import typing
import random 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 .EntranceShuffle import entrance_shuffle_table
from .LogicTricks import normalized_name_tricks from .LogicTricks import normalized_name_tricks
from .ColorSFXOptions import * from .ColorSFXOptions import *
@ -1281,21 +1283,166 @@ class LogicTricks(OptionList):
valid_keys_casefold = True valid_keys_casefold = True
# All options assembled into a single dict @dataclass
oot_options: typing.Dict[str, type(Option)] = { class OoTOptions(PerGameCommonOptions):
"plando_connections": OoTPlandoConnections, plando_connections: OoTPlandoConnections
"logic_rules": Logic, death_link: DeathLink
"logic_no_night_tokens_without_suns_song": NightTokens, logic_rules: Logic
**open_options, logic_no_night_tokens_without_suns_song: NightTokens
**world_options, logic_tricks: LogicTricks
**bridge_options, open_forest: Forest
**dungeon_items_options, open_kakariko: Gate
**shuffle_options, open_door_of_time: DoorOfTime
**timesavers_options, zora_fountain: Fountain
**misc_options, gerudo_fortress: Fortress
**itempool_options, bridge: Bridge
**cosmetic_options, trials: Trials
**sfx_options, starting_age: StartingAge
"logic_tricks": LogicTricks, shuffle_interior_entrances: InteriorEntrances
"death_link": DeathLink, 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 # Fix Ice Cavern Alcove Camera
if not world.dungeon_mq['Ice Cavern']: if not world.dungeon_mq['Ice Cavern']:
rom.write_byte(0x2BECA25,0x01); rom.write_byte(0x2BECA25,0x01)
rom.write_byte(0x2BECA2D,0x01); rom.write_byte(0x2BECA2D,0x01)
# Fix GS rewards to be static # Fix GS rewards to be static
rom.write_int32(0xEA3934, 0) rom.write_int32(0xEA3934, 0)
@ -944,7 +944,7 @@ def patch_rom(world, rom):
scene_table = 0x00B71440 scene_table = 0x00B71440
for scene in range(0x00, 0x65): 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) add_scene_exits(scene_start)
return exit_table return exit_table
@ -1632,10 +1632,10 @@ def patch_rom(world, rom):
reward_text = None reward_text = None
elif getattr(location.item, 'looks_like_item', None) is not None: elif getattr(location.item, 'looks_like_item', None) is not None:
jabu_item = location.item.looks_like_item 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: else:
jabu_item = location.item 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 # Update "Princess Ruto got the Spiritual Stone!" text before the midboss in Jabu
if reward_text is None: if reward_text is None:
@ -1687,7 +1687,7 @@ def patch_rom(world, rom):
# Sets hooks for gossip stone changes # Sets hooks for gossip stone changes
symbol = rom.sym("GOSSIP_HINT_CONDITION"); symbol = rom.sym("GOSSIP_HINT_CONDITION")
if world.hints == 'none': if world.hints == 'none':
rom.write_int32(symbol, 0) rom.write_int32(symbol, 0)
@ -2264,9 +2264,9 @@ def patch_rom(world, rom):
# text shuffle # text shuffle
if world.text_shuffle == 'except_hints': 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': 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 preview text boxes
update_warp_song_text(messages, world) 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 # Write numeric seed truncated to 32 bits for rng seeding
# Overwritten with new seed every time a new rng value is generated # 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) rom.write_int32(rom.sym('RNG_SEED_INT'), rng_seed)
# Static initial seed value for one-time random actions like the Hylian Shield discount # Static initial seed value for one-time random actions like the Hylian Shield discount
rom.write_int32(rom.sym('RANDOMIZER_RNG_SEED'), rng_seed) 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_count = rom.read_byte(scene_data + 1)
room_list = scene_start + (rom.read_int32(scene_data + 4) & 0x00FFFFFF) room_list = scene_start + (rom.read_int32(scene_data + 4) & 0x00FFFFFF)
for _ in range(0, room_count): 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: if not room_data in processed_rooms:
actors.update(room_get_actors(rom, actor_func, room_data, scene)) actors.update(room_get_actors(rom, actor_func, room_data, scene))
@ -2591,7 +2591,7 @@ def get_actor_list(rom, actor_func):
actors = {} actors = {}
scene_table = 0x00B71440 scene_table = 0x00B71440
for scene in range(0x00, 0x65): 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)) actors.update(scene_get_actors(rom, actor_func, scene_data, scene))
return actors return actors
@ -2605,7 +2605,7 @@ def get_override_itemid(override_table, scene, type, flags):
def remove_entrance_blockers(rom): def remove_entrance_blockers(rom):
def remove_entrance_blockers_do(rom, actor_id, actor, scene): def remove_entrance_blockers_do(rom, actor_id, actor, scene):
if actor_id == 0x014E and scene == 97: 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: if actor_var == 0xFF01:
rom.write_int16(actor + 14, 0x0700) rom.write_int16(actor + 14, 0x0700)
get_actor_list(rom, remove_entrance_blockers_do) 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]) 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: else:
if item_display.game == "Ocarina of Time": if item_display.game == "Ocarina of Time":
shop_item_name = getSimpleHintNoPrefix(item_display) shop_item_name = getSimpleHintNoPrefix(item_display, world.random)
else: else:
shop_item_name = item_display.name shop_item_name = item_display.name

View File

@ -53,7 +53,7 @@ def isliteral(expr):
class Rule_AST_Transformer(ast.NodeTransformer): class Rule_AST_Transformer(ast.NodeTransformer):
def __init__(self, world, player): def __init__(self, world, player):
self.multiworld = world self.world = world
self.player = player self.player = player
self.events = set() self.events = set()
# map Region -> rule ast string -> item name # map Region -> rule ast string -> item name
@ -86,9 +86,9 @@ class Rule_AST_Transformer(ast.NodeTransformer):
ctx=ast.Load()), ctx=ast.Load()),
args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)], args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)],
keywords=[]) keywords=[])
elif node.id in self.multiworld.__dict__: elif node.id in self.world.__dict__:
# Settings are constant # 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__: elif node.id in State.__dict__:
return self.make_call(node, node.id, [], []) return self.make_call(node, node.id, [], [])
elif node.id in self.kwarg_defaults or node.id in allowed_globals: 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): if isinstance(count, ast.Name):
# Must be a settings constant # 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: if iname in escaped_items:
iname = escaped_items[iname] iname = escaped_items[iname]
@ -182,7 +182,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
new_args = [] new_args = []
for child in node.args: for child in node.args:
if isinstance(child, ast.Name): if isinstance(child, ast.Name):
if child.id in self.multiworld.__dict__: if child.id in self.world.__dict__:
# child = ast.Attribute( # child = ast.Attribute(
# value=ast.Attribute( # value=ast.Attribute(
# value=ast.Name(id='state', ctx=ast.Load()), # value=ast.Name(id='state', ctx=ast.Load()),
@ -190,7 +190,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
# ctx=ast.Load()), # ctx=ast.Load()),
# attr=child.id, # attr=child.id,
# ctx=ast.Load()) # 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: elif child.id in rule_aliases:
child = self.visit(child) child = self.visit(child)
elif child.id in escaped_items: elif child.id in escaped_items:
@ -242,7 +242,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
# Fast check for json can_use # Fast check for json can_use
if (len(node.ops) == 1 and isinstance(node.ops[0], ast.Eq) 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 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) return ast.NameConstant(node.left.id == node.comparators[0].id)
node.left = escape_or_string(node.left) 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. # Requires the target regions have been defined in the world.
def create_delayed_rules(self): def create_delayed_rules(self):
for region_name, node, subrule_name in self.delayed_rules: 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 = OOTLocation(self.player, subrule_name, type='Event', parent=region, internal=True)
event.show_in_spoiler = False event.show_in_spoiler = False
@ -395,7 +395,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
set_rule(event, access_rule) set_rule(event, access_rule)
region.locations.append(event) 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 # Safeguard in case this is called multiple times per world
self.delayed_rules.clear() self.delayed_rules.clear()
@ -448,7 +448,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
## Handlers for compile-time optimizations (former State functions) ## Handlers for compile-time optimizations (former State functions)
def at_day(self, node): 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)) # tod has DAY or (tod == NONE and (ss or find a path from a provider))
# parsing is better than constructing this expression by hand # 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 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) return ast.NameConstant(True)
def at_dampe_time(self, node): 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)) # tod has DAMPE or (tod == NONE and (find a path from a provider))
# parsing is better than constructing this expression by hand # 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 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) return ast.NameConstant(True)
def at_night(self, node): 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 # Using visit here to resolve 'can_play' rule
return self.visit(ast.parse('can_play(Suns_Song)', mode='eval').body) 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)) # tod has DAMPE or (tod == NONE and (ss or find a path from a provider))
# parsing is better than constructing this expression by hand # 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 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 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): 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): def has_bottle(self, node):
return ast.parse(f"state._oot_has_bottle({self.player})", mode='eval').body 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 BaseClasses import CollectionState, MultiWorld
from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item 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): class OOTLogic(LogicMixin):
@ -132,17 +132,17 @@ class OOTLogic(LogicMixin):
def set_rules(ootworld): def set_rules(ootworld):
logger = logging.getLogger('') logger = logging.getLogger('')
world = ootworld.multiworld multiworld = ootworld.multiworld
player = ootworld.player player = ootworld.player
if ootworld.logic_rules != 'no_logic': if ootworld.logic_rules != 'no_logic':
if ootworld.triforce_hunt: 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: 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 # 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') # is_child = ootworld.parser.parse_rule('is_child')
guarantee_hint = ootworld.parser.parse_rule('guarantee_hint') 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' if (ootworld.dungeon_mq['Forest Temple'] and ootworld.shuffle_bosskeys == 'dungeon'
and ootworld.shuffle_smallkeys == 'dungeon' and ootworld.tokensanity == 'off'): 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. # 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) forbid_item(location, 'Boss Key (Forest Temple)', ootworld.player)
if ootworld.shuffle_song_items in {'song', 'dungeon'} and not ootworld.songs_as_items: 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. # 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. # 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')) add_item_rule(location, lambda item: oot_is_item_of_type(item, 'Song'))
if ootworld.shuffle_child_trade == 'skip_child_zelda': if ootworld.shuffle_child_trade == 'skip_child_zelda':
# Song from Impa must be local # 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) add_item_rule(location, lambda item: item.player == player)
for name in ootworld.always_hints: 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 # TODO: re-add hints once they are working
# if location.type == 'HintStone' and ootworld.hints == 'mask': # 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 # 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. # 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 .Regions import OOTRegion, TimeOfDay
from .Rules import set_rules, set_shop_rules, set_entrances_based_rules from .Rules import set_rules, set_shop_rules, set_entrances_based_rules
from .RuleParser import Rule_AST_Transformer 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 .Utils import data_path, read_json
from .LocationList import business_scrubs, set_drop_location_names, dungeon_song_locations from .LocationList import business_scrubs, set_drop_location_names, dungeon_song_locations
from .DungeonList import dungeon_table, create_dungeons from .DungeonList import dungeon_table, create_dungeons
@ -30,12 +30,12 @@ from .Patches import OoTContainer, patch_rom
from .N64Patch import create_patch_file from .N64Patch import create_patch_file
from .Cosmetics import patch_cosmetics from .Cosmetics import patch_cosmetics
from Utils import get_options from settings import get_settings
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections
from Fill import fill_restrictive, fast_fill, FillError from Fill import fill_restrictive, fast_fill, FillError
from worlds.generic.Rules import exclusion_rules, add_item_rule 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. # 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) i_o_limiter = threading.Semaphore(2)
@ -128,6 +128,7 @@ class OOTWeb(WebWorld):
) )
tutorials = [setup, setup_es, setup_fr, setup_de] tutorials = [setup, setup_es, setup_fr, setup_de]
option_groups = oot_option_groups
class OOTWorld(World): class OOTWorld(World):
@ -137,7 +138,8 @@ class OOTWorld(World):
to rescue the Seven Sages, and then confront Ganondorf to save Hyrule! to rescue the Seven Sages, and then confront Ganondorf to save Hyrule!
""" """
game: str = "Ocarina of Time" game: str = "Ocarina of Time"
option_definitions: dict = oot_options options_dataclass = OoTOptions
options: OoTOptions
settings: typing.ClassVar[OOTSettings] settings: typing.ClassVar[OOTSettings]
topology_present: bool = True 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 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 @classmethod
def stage_assert_generate(cls, multiworld: MultiWorld): 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 # Option parsing, handling incompatible options, building useful-item table
def generate_early(self): def generate_early(self):
self.parser = Rule_AST_Transformer(self, self.player) self.parser = Rule_AST_Transformer(self, self.player)
for (option_name, option) in oot_options.items(): for option_name in self.options_dataclass.type_hints:
result = getattr(self.multiworld, option_name)[self.player] result = getattr(self.options, option_name)
if isinstance(result, Range): if isinstance(result, Range):
option_value = int(result) option_value = int(result)
elif isinstance(result, Toggle): 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.remove_from_start_inventory = [] # some items will be precollected but not in the inventory
self.starting_items = Counter() self.starting_items = Counter()
self.songs_as_items = False self.songs_as_items = False
self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)] self.file_hash = [self.random.randint(0, 31) for i in range(5)]
self.connect_name = ''.join(self.multiworld.random.choices(printable, k=16)) self.connect_name = ''.join(self.random.choices(printable, k=16))
self.collectible_flag_addresses = {} self.collectible_flag_addresses = {}
# Incompatible option handling # Incompatible option handling
@ -283,7 +285,7 @@ class OOTWorld(World):
local_types.append('BossKey') local_types.append('BossKey')
if self.shuffle_ganon_bosskey != 'keysanity': if self.shuffle_ganon_bosskey != 'keysanity':
local_types.append('GanonBossKey') 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 # If any songs are itemlinked, set songs_as_items
for group in self.multiworld.groups.values(): for group in self.multiworld.groups.values():
@ -297,7 +299,7 @@ class OOTWorld(World):
# Determine skipped trials in GT # Determine skipped trials in GT
# This needs to be done before the logic rules in GT are parsed # This needs to be done before the logic rules in GT are parsed
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light'] 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} self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list}
# Determine tricks in logic # Determine tricks in logic
@ -311,8 +313,8 @@ class OOTWorld(World):
# No Logic forces all tricks on, prog balancing off and beatable-only # No Logic forces all tricks on, prog balancing off and beatable-only
elif self.logic_rules == 'no_logic': elif self.logic_rules == 'no_logic':
self.multiworld.progression_balancing[self.player].value = False self.options.progression_balancing.value = False
self.multiworld.accessibility[self.player].value = Accessibility.option_minimal self.options.accessibility.value = Accessibility.option_minimal
for trick in normalized_name_tricks.values(): for trick in normalized_name_tricks.values():
setattr(self, trick['name'], True) setattr(self, trick['name'], True)
@ -333,8 +335,8 @@ class OOTWorld(World):
# Set internal names used by the OoT generator # Set internal names used by the OoT generator
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld'] self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
self.trials_random = self.multiworld.trials[self.player].randomized self.trials_random = self.options.trials.randomized
self.mq_dungeons_random = self.multiworld.mq_dungeons_count[self.player].randomized self.mq_dungeons_random = self.options.mq_dungeons_count.randomized
self.easier_fire_arrow_entry = self.fae_torch_count < 24 self.easier_fire_arrow_entry = self.fae_torch_count < 24
if self.misc_hints: if self.misc_hints:
@ -393,8 +395,8 @@ class OOTWorld(World):
elif self.key_rings == 'choose': elif self.key_rings == 'choose':
self.key_rings = self.key_rings_list self.key_rings = self.key_rings_list
elif self.key_rings == 'random_dungeons': elif self.key_rings == 'random_dungeons':
self.key_rings = self.multiworld.random.sample(keyring_dungeons, self.key_rings = self.random.sample(keyring_dungeons,
self.multiworld.random.randint(0, len(keyring_dungeons))) self.random.randint(0, len(keyring_dungeons)))
# Determine which dungeons are MQ. Not compatible with glitched logic. # Determine which dungeons are MQ. Not compatible with glitched logic.
mq_dungeons = set() mq_dungeons = set()
@ -405,7 +407,7 @@ class OOTWorld(World):
elif self.mq_dungeons_mode == 'specific': elif self.mq_dungeons_mode == 'specific':
mq_dungeons = self.mq_dungeons_specific mq_dungeons = self.mq_dungeons_specific
elif self.mq_dungeons_mode == 'count': 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: else:
self.mq_dungeons_mode = 'count' self.mq_dungeons_mode = 'count'
self.mq_dungeons_count = 0 self.mq_dungeons_count = 0
@ -425,8 +427,8 @@ class OOTWorld(World):
elif self.dungeon_shortcuts_choice == 'all': elif self.dungeon_shortcuts_choice == 'all':
self.dungeon_shortcuts = set(shortcut_dungeons) self.dungeon_shortcuts = set(shortcut_dungeons)
elif self.dungeon_shortcuts_choice == 'random': elif self.dungeon_shortcuts_choice == 'random':
self.dungeon_shortcuts = self.multiworld.random.sample(shortcut_dungeons, self.dungeon_shortcuts = self.random.sample(shortcut_dungeons,
self.multiworld.random.randint(0, len(shortcut_dungeons))) self.random.randint(0, len(shortcut_dungeons)))
# == 'choice', leave as previous # == 'choice', leave as previous
else: else:
self.dungeon_shortcuts = set() 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 = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
new_exit.vanilla_connected_region = exit new_exit.vanilla_connected_region = exit
new_exit.rule_string = rule 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) self.parser.parse_spot_rule(new_exit)
if new_exit.never: if new_exit.never:
logger.debug('Dropping unreachable exit: %s', new_exit.name) logger.debug('Dropping unreachable exit: %s', new_exit.name)
@ -607,7 +609,7 @@ class OOTWorld(World):
elif self.shuffle_scrubs == 'random': elif self.shuffle_scrubs == 'random':
# this is a random value between 0-99 # this is a random value between 0-99
# average value is ~33 rupees # 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. # Set price in the dictionary as well as the location.
self.scrub_prices[scrub_item] = price self.scrub_prices[scrub_item] = price
@ -624,7 +626,7 @@ class OOTWorld(World):
self.shop_prices = {} self.shop_prices = {}
for region in self.regions: for region in self.regions:
if self.shopsanity == 'random': if self.shopsanity == 'random':
shop_item_count = self.multiworld.random.randint(0, 4) shop_item_count = self.random.randint(0, 4)
else: else:
shop_item_count = int(self.shopsanity) shop_item_count = int(self.shopsanity)
@ -632,17 +634,17 @@ class OOTWorld(World):
if location.type == 'Shop': if location.type == 'Shop':
if location.name[-1:] in shop_item_indexes[:shop_item_count]: if location.name[-1:] in shop_item_indexes[:shop_item_count]:
if self.shopsanity_prices == 'normal': 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': elif self.shopsanity_prices == 'affordable':
self.shop_prices[location.name] = 10 self.shop_prices[location.name] = 10
elif self.shopsanity_prices == 'starting_wallet': 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': 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': 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': 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 # Fill boss prizes
@ -667,8 +669,8 @@ class OOTWorld(World):
while bossCount: while bossCount:
bossCount -= 1 bossCount -= 1
self.multiworld.random.shuffle(prizepool) self.random.shuffle(prizepool)
self.multiworld.random.shuffle(prize_locs) self.random.shuffle(prize_locs)
item = prizepool.pop() item = prizepool.pop()
loc = prize_locs.pop() loc = prize_locs.pop()
loc.place_locked_item(item) loc.place_locked_item(item)
@ -778,7 +780,7 @@ class OOTWorld(World):
# Call the junk fill and get a replacement # Call the junk fill and get a replacement
if item in self.itempool: if item in self.itempool:
self.itempool.remove(item) 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: if self.start_with_consumables:
self.starting_items['Deku Sticks'] = 30 self.starting_items['Deku Sticks'] = 30
self.starting_items['Deku Nuts'] = 40 self.starting_items['Deku Nuts'] = 40
@ -881,7 +883,7 @@ class OOTWorld(World):
# Prefill shops, songs, and dungeon items # Prefill shops, songs, and dungeon items
items = self.get_pre_fill_items() items = self.get_pre_fill_items()
locations = list(self.multiworld.get_unfilled_locations(self.player)) locations = list(self.multiworld.get_unfilled_locations(self.player))
self.multiworld.random.shuffle(locations) self.random.shuffle(locations)
# Set up initial state # Set up initial state
state = CollectionState(self.multiworld) state = CollectionState(self.multiworld)
@ -910,7 +912,7 @@ class OOTWorld(World):
if isinstance(locations, list): if isinstance(locations, list):
for item in stage_items: for item in stage_items:
self.pre_fill_items.remove(item) 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, fill_restrictive(self.multiworld, prefill_state(state), locations, stage_items,
single_player_placement=True, lock=True, allow_excluded=True) single_player_placement=True, lock=True, allow_excluded=True)
else: else:
@ -923,7 +925,7 @@ class OOTWorld(World):
if isinstance(locations, list): if isinstance(locations, list):
for item in dungeon_items: for item in dungeon_items:
self.pre_fill_items.remove(item) 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, fill_restrictive(self.multiworld, prefill_state(state), locations, dungeon_items,
single_player_placement=True, lock=True, allow_excluded=True) single_player_placement=True, lock=True, allow_excluded=True)
@ -964,7 +966,7 @@ class OOTWorld(World):
while tries: while tries:
try: try:
self.multiworld.random.shuffle(song_locations) self.random.shuffle(song_locations)
fill_restrictive(self.multiworld, prefill_state(state), song_locations[:], songs[:], fill_restrictive(self.multiworld, prefill_state(state), song_locations[:], songs[:],
single_player_placement=True, lock=True, allow_excluded=True) single_player_placement=True, lock=True, allow_excluded=True)
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)") 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 Goron Tunic': 1,
'Buy Zora Tunic': 1, 'Buy Zora Tunic': 1,
}.get(item.name, 0)) # place Deku Shields if needed, then tunics, then other advancement }.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 self.pre_fill_items = [] # all prefill should be done
fill_restrictive(self.multiworld, prefill_state(state), shop_locations, shop_prog, fill_restrictive(self.multiworld, prefill_state(state), shop_locations, shop_prog,
single_player_placement=True, lock=True, allow_excluded=True) single_player_placement=True, lock=True, allow_excluded=True)
@ -1028,7 +1030,7 @@ class OOTWorld(World):
ganon_junk_fill = min(1, ganon_junk_fill) ganon_junk_fill = min(1, ganon_junk_fill)
gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons)) 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] 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) exclusion_rules(self.multiworld, self.player, junk_fill_locations)
# Locations which are not sendable must be converted to events # 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] trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
self.trap_appearances = {} self.trap_appearances = {}
for loc_id in trap_location_ids: 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 # 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) 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: try:
if self.hints != 'none': if self.hints != 'none':
buildWorldGossipHints(self) buildWorldGossipHints(self)
@ -1092,7 +1094,7 @@ class OOTWorld(World):
finally: finally:
self.collectible_flags_available.set() self.collectible_flags_available.set()
rom.update_header() rom.update_header()
patch_data = create_patch_file(rom) patch_data = create_patch_file(rom, self.random)
rom.restore() rom.restore()
apz5 = OoTContainer(patch_data, outfile_name, output_directory, apz5 = OoTContainer(patch_data, outfile_name, output_directory,
@ -1399,7 +1401,7 @@ class OOTWorld(World):
return all_state return all_state
def get_filler_item_name(self) -> str: 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: def valid_dungeon_item_location(world: OOTWorld, option: str, dungeon: str, loc: OOTLocation) -> bool: