Skip to content

Commit

Permalink
Ocarina of Time: options and general cleanup (#3767)
Browse files Browse the repository at this point in the history
* working?

* missed one

* fix old start inventory usage

* missed global random usage

---------

Co-authored-by: NewSoupVi <[email protected]>
  • Loading branch information
Silvris and NewSoupVi authored Sep 18, 2024
1 parent fced905 commit 025c550
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 226 deletions.
41 changes: 20 additions & 21 deletions worlds/oot/Cosmetics.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .Utils import data_path, __version__
from .Colors import *
import logging
import worlds.oot.Music as music
import worlds.oot.Sounds as sfx
import worlds.oot.IconManip as icon
from . import Music as music
from . import Sounds as sfx
from . import IconManip as icon
from .JSONDump import dump_obj, CollapseList, CollapseDict, AlignedDict, SortedDict
import json

Expand Down Expand Up @@ -105,7 +105,7 @@ def patch_tunic_colors(rom, ootworld, symbols):

# handle random
if tunic_option == 'Random Choice':
tunic_option = random.choice(tunic_color_list)
tunic_option = ootworld.random.choice(tunic_color_list)
# handle completely random
if tunic_option == 'Completely Random':
color = generate_random_color()
Expand Down Expand Up @@ -156,9 +156,9 @@ def patch_navi_colors(rom, ootworld, symbols):

# choose a random choice for the whole group
if navi_option_inner == 'Random Choice':
navi_option_inner = random.choice(navi_color_list)
navi_option_inner = ootworld.random.choice(navi_color_list)
if navi_option_outer == 'Random Choice':
navi_option_outer = random.choice(navi_color_list)
navi_option_outer = ootworld.random.choice(navi_color_list)

if navi_option_outer == 'Match Inner':
navi_option_outer = navi_option_inner
Expand Down Expand Up @@ -233,9 +233,9 @@ def patch_sword_trails(rom, ootworld, symbols):

# handle random choice
if option_inner == 'Random Choice':
option_inner = random.choice(sword_trail_color_list)
option_inner = ootworld.random.choice(sword_trail_color_list)
if option_outer == 'Random Choice':
option_outer = random.choice(sword_trail_color_list)
option_outer = ootworld.random.choice(sword_trail_color_list)

if option_outer == 'Match Inner':
option_outer = option_inner
Expand Down Expand Up @@ -326,9 +326,9 @@ def patch_trails(rom, ootworld, trails):

# handle random choice
if option_inner == 'Random Choice':
option_inner = random.choice(trail_color_list)
option_inner = ootworld.random.choice(trail_color_list)
if option_outer == 'Random Choice':
option_outer = random.choice(trail_color_list)
option_outer = ootworld.random.choice(trail_color_list)

if option_outer == 'Match Inner':
option_outer = option_inner
Expand Down Expand Up @@ -393,7 +393,7 @@ def patch_gauntlet_colors(rom, ootworld, symbols):

# handle random
if gauntlet_option == 'Random Choice':
gauntlet_option = random.choice(gauntlet_color_list)
gauntlet_option = ootworld.random.choice(gauntlet_color_list)
# handle completely random
if gauntlet_option == 'Completely Random':
color = generate_random_color()
Expand Down Expand Up @@ -424,10 +424,10 @@ def patch_shield_frame_colors(rom, ootworld, symbols):

# handle random
if shield_frame_option == 'Random Choice':
shield_frame_option = random.choice(shield_frame_color_list)
shield_frame_option = ootworld.random.choice(shield_frame_color_list)
# handle completely random
if shield_frame_option == 'Completely Random':
color = [random.getrandbits(8), random.getrandbits(8), random.getrandbits(8)]
color = [ootworld.random.getrandbits(8), ootworld.random.getrandbits(8), ootworld.random.getrandbits(8)]
# grab the color from the list
elif shield_frame_option in shield_frame_colors:
color = list(shield_frame_colors[shield_frame_option])
Expand Down Expand Up @@ -458,7 +458,7 @@ def patch_heart_colors(rom, ootworld, symbols):

# handle random
if heart_option == 'Random Choice':
heart_option = random.choice(heart_color_list)
heart_option = ootworld.random.choice(heart_color_list)
# handle completely random
if heart_option == 'Completely Random':
color = generate_random_color()
Expand Down Expand Up @@ -495,7 +495,7 @@ def patch_magic_colors(rom, ootworld, symbols):
magic_option = format_cosmetic_option_result(ootworld.__dict__[magic_setting])

if magic_option == 'Random Choice':
magic_option = random.choice(magic_color_list)
magic_option = ootworld.random.choice(magic_color_list)

if magic_option == 'Completely Random':
color = generate_random_color()
Expand Down Expand Up @@ -559,7 +559,7 @@ def patch_button_colors(rom, ootworld, symbols):

# handle random
if button_option == 'Random Choice':
button_option = random.choice(list(button_colors.keys()))
button_option = ootworld.random.choice(list(button_colors.keys()))
# handle completely random
if button_option == 'Completely Random':
fixed_font_color = [10, 10, 10]
Expand Down Expand Up @@ -618,11 +618,11 @@ def patch_sfx(rom, ootworld, symbols):
rom.write_int16(loc, sound_id)
else:
if selection == 'random-choice':
selection = random.choice(sfx.get_hook_pool(hook)).value.keyword
selection = ootworld.random.choice(sfx.get_hook_pool(hook)).value.keyword
elif selection == 'random-ear-safe':
selection = random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword
selection = ootworld.random.choice(sfx.get_hook_pool(hook, "TRUE")).value.keyword
elif selection == 'completely-random':
selection = random.choice(sfx.standard).value.keyword
selection = ootworld.random.choice(sfx.standard).value.keyword
sound_id = sound_dict[selection]
for loc in hook.value.locations:
rom.write_int16(loc, sound_id)
Expand All @@ -644,7 +644,7 @@ def patch_instrument(rom, ootworld, symbols):

choice = ootworld.sfx_ocarina
if choice == 'random-choice':
choice = random.choice(list(instruments.keys()))
choice = ootworld.random.choice(list(instruments.keys()))

rom.write_byte(0x00B53C7B, instruments[choice])
rom.write_byte(0x00B4BF6F, instruments[choice]) # For Lost Woods Skull Kids' minigame in Lost Woods
Expand Down Expand Up @@ -769,7 +769,6 @@ def patch_instrument(rom, ootworld, symbols):

def patch_cosmetics(ootworld, rom):
# Use the world's slot seed for cosmetics
random.seed(ootworld.multiworld.per_slot_randoms[ootworld.player].random())

# try to detect the cosmetic patch data format
versioned_patch_set = None
Expand Down
4 changes: 2 additions & 2 deletions worlds/oot/Entrance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
class OOTEntrance(Entrance):
game: str = 'Ocarina of Time'

def __init__(self, player, world, name='', parent=None):
def __init__(self, player, multiworld, name='', parent=None):
super(OOTEntrance, self).__init__(player, name, parent)
self.multiworld = world
self.multiworld = multiworld
self.access_rules = []
self.reverse = None
self.replaces = None
Expand Down
40 changes: 20 additions & 20 deletions worlds/oot/EntranceShuffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,16 +440,16 @@ class EntranceShuffleError(Exception):


def shuffle_random_entrances(ootworld):
world = ootworld.multiworld
multiworld = ootworld.multiworld
player = ootworld.player

# Gather locations to keep reachable for validation
all_state = ootworld.get_state_with_complete_itempool()
all_state.sweep_for_advancements(locations=ootworld.get_locations())
locations_to_ensure_reachable = {loc for loc in world.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}
locations_to_ensure_reachable = {loc for loc in multiworld.get_reachable_locations(all_state, player) if not (loc.type == 'Drop' or (loc.type == 'Event' and 'Subrule' in loc.name))}

# Set entrance data for all entrances
set_all_entrances_data(world, player)
set_all_entrances_data(multiworld, player)

# Determine entrance pools based on settings
one_way_entrance_pools = {}
Expand Down Expand Up @@ -547,10 +547,10 @@ def shuffle_random_entrances(ootworld):
none_state = CollectionState(ootworld.multiworld)

# Plando entrances
if world.plando_connections[player]:
if ootworld.options.plando_connections:
rollbacks = []
all_targets = {**one_way_target_entrance_pools, **target_entrance_pools}
for conn in world.plando_connections[player]:
for conn in ootworld.options.plando_connections:
try:
entrance = ootworld.get_entrance(conn.entrance)
exit = ootworld.get_entrance(conn.exit)
Expand Down Expand Up @@ -628,7 +628,7 @@ def shuffle_random_entrances(ootworld):
logging.getLogger('').error(f'Root has too many entrances left after shuffling entrances')
# Game is beatable
new_all_state = ootworld.get_state_with_complete_itempool()
if not world.has_beaten_game(new_all_state, player):
if not multiworld.has_beaten_game(new_all_state, player):
raise EntranceShuffleError('Cannot beat game')
# Validate world
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state)
Expand Down Expand Up @@ -675,7 +675,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools):

avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools))
ootworld.multiworld.random.shuffle(avail_pool)
ootworld.random.shuffle(avail_pool)

for entrance in avail_pool:
if entrance.replaces:
Expand Down Expand Up @@ -725,11 +725,11 @@ def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances,
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')

def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
ootworld.multiworld.random.shuffle(entrances)
ootworld.random.shuffle(entrances)
for entrance in entrances:
if entrance.connected_region != None:
continue
ootworld.multiworld.random.shuffle(target_entrances)
ootworld.random.shuffle(target_entrances)
# Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
# success rate over randomization
if pool_type in {'InteriorSoft', 'MixedSoft'}:
Expand Down Expand Up @@ -785,7 +785,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran
# TODO: improve this function
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig):

world = ootworld.multiworld
multiworld = ootworld.multiworld
player = ootworld.player

all_state = all_state_orig.copy()
Expand Down Expand Up @@ -828,8 +828,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
# Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints
potion_front = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
potion_back = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
potion_front = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
potion_back = get_entrance_replacing(multiworld.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
if potion_front is not None and potion_back is not None and not same_hint_area(potion_front, potion_back):
raise EntranceShuffleError('Kak Potion Shop entrances are not in the same hint area')
elif (potion_front and not potion_back) or (not potion_front and potion_back):
Expand All @@ -840,8 +840,8 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all

# When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides
if ootworld.shuffle_cows:
impas_front = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
impas_back = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
impas_front = get_entrance_replacing(multiworld.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
impas_back = get_entrance_replacing(multiworld.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
if impas_front is not None and impas_back is not None and not same_hint_area(impas_front, impas_back):
raise EntranceShuffleError('Kak Impas House entrances are not in the same hint area')
elif (impas_front and not impas_back) or (not impas_front and impas_back):
Expand All @@ -861,25 +861,25 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
any(region for region in time_travel_state.adult_reachable_regions[player] if region.time_passes)):
raise EntranceShuffleError('Time passing is not guaranteed as both ages')

if ootworld.starting_age == 'child' and (world.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]):
if ootworld.starting_age == 'child' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.adult_reachable_regions[player]):
raise EntranceShuffleError('Path to ToT as adult not guaranteed')
if ootworld.starting_age == 'adult' and (world.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]):
if ootworld.starting_age == 'adult' and (multiworld.get_region('Temple of Time', player) not in time_travel_state.child_reachable_regions[player]):
raise EntranceShuffleError('Path to ToT as child not guaranteed')

if (ootworld.shuffle_interior_entrances or ootworld.shuffle_overworld_entrances) and \
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior', 'Overworld', 'Spawn', 'WarpSong', 'OwlDrop']):
# Ensure big poe shop is always reachable as adult
if world.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]:
if multiworld.get_region('Market Guard House', player) not in time_travel_state.adult_reachable_regions[player]:
raise EntranceShuffleError('Big Poe Shop access not guaranteed as adult')
if ootworld.shopsanity == 'off':
# Ensure that Goron and Zora shops are accessible as adult
if world.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]:
if multiworld.get_region('GC Shop', player) not in all_state.adult_reachable_regions[player]:
raise EntranceShuffleError('Goron City Shop not accessible as adult')
if world.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]:
if multiworld.get_region('ZD Shop', player) not in all_state.adult_reachable_regions[player]:
raise EntranceShuffleError('Zora\'s Domain Shop not accessible as adult')
if ootworld.open_forest == 'closed':
# Ensure that Kokiri Shop is reachable as child with no items
if world.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]:
if multiworld.get_region('KF Kokiri Shop', player) not in none_state.child_reachable_regions[player]:
raise EntranceShuffleError('Kokiri Forest Shop not accessible as child in closed forest')


Expand Down
24 changes: 11 additions & 13 deletions worlds/oot/HintList.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import random

from BaseClasses import LocationProgressType
from .Items import OOTItem

Expand Down Expand Up @@ -28,39 +26,39 @@ class Hint(object):
text = ""
type = []

def __init__(self, name, text, type, choice=None):
def __init__(self, name, text, type, rand, choice=None):
self.name = name
self.type = [type] if not isinstance(type, list) else type

if isinstance(text, str):
self.text = text
else:
if choice == None:
self.text = random.choice(text)
self.text = rand.choice(text)
else:
self.text = text[choice]


def getHint(item, clearer_hint=False):
def getHint(item, rand, clearer_hint=False):
if item in hintTable:
textOptions, clearText, hintType = hintTable[item]
if clearer_hint:
if clearText == None:
return Hint(item, textOptions, hintType, 0)
return Hint(item, clearText, hintType)
return Hint(item, textOptions, hintType, rand, 0)
return Hint(item, clearText, hintType, rand)
else:
return Hint(item, textOptions, hintType)
return Hint(item, textOptions, hintType, rand)
elif isinstance(item, str):
return Hint(item, item, 'generic')
return Hint(item, item, 'generic', rand)
else: # is an Item
return Hint(item.name, item.hint_text, 'item')
return Hint(item.name, item.hint_text, 'item', rand)


def getHintGroup(group, world):
ret = []
for name in hintTable:

hint = getHint(name, world.clearer_hints)
hint = getHint(name, world.random, world.clearer_hints)

if hint.name in world.always_hints and group == 'always':
hint.type = 'always'
Expand Down Expand Up @@ -95,7 +93,7 @@ def getHintGroup(group, world):
def getRequiredHints(world):
ret = []
for name in hintTable:
hint = getHint(name)
hint = getHint(name, world.random)
if 'always' in hint.type or hint.name in conditional_always and conditional_always[hint.name](world):
ret.append(hint)
return ret
Expand Down Expand Up @@ -1689,7 +1687,7 @@ def hintExclusions(world, clear_cache=False):

location_hints = []
for name in hintTable:
hint = getHint(name, world.clearer_hints)
hint = getHint(name, world.random, world.clearer_hints)
if any(item in hint.type for item in
['always',
'dual_always',
Expand Down
Loading

0 comments on commit 025c550

Please sign in to comment.