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:
parent
fced9050a4
commit
025c550991
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()])
|
||||||
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue