mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
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:
@@ -20,7 +20,7 @@ from .ItemPool import generate_itempool, get_junk_item, get_junk_pool
|
||||
from .Regions import OOTRegion, TimeOfDay
|
||||
from .Rules import set_rules, set_shop_rules, set_entrances_based_rules
|
||||
from .RuleParser import Rule_AST_Transformer
|
||||
from .Options import oot_options
|
||||
from .Options import OoTOptions, oot_option_groups
|
||||
from .Utils import data_path, read_json
|
||||
from .LocationList import business_scrubs, set_drop_location_names, dungeon_song_locations
|
||||
from .DungeonList import dungeon_table, create_dungeons
|
||||
@@ -30,12 +30,12 @@ from .Patches import OoTContainer, patch_rom
|
||||
from .N64Patch import create_patch_file
|
||||
from .Cosmetics import patch_cosmetics
|
||||
|
||||
from Utils import get_options
|
||||
from settings import get_settings
|
||||
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
|
||||
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections
|
||||
from Fill import fill_restrictive, fast_fill, FillError
|
||||
from worlds.generic.Rules import exclusion_rules, add_item_rule
|
||||
from ..AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
|
||||
|
||||
# OoT's generate_output doesn't benefit from more than 2 threads, instead it uses a lot of memory.
|
||||
i_o_limiter = threading.Semaphore(2)
|
||||
@@ -128,6 +128,7 @@ class OOTWeb(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_es, setup_fr, setup_de]
|
||||
option_groups = oot_option_groups
|
||||
|
||||
|
||||
class OOTWorld(World):
|
||||
@@ -137,7 +138,8 @@ class OOTWorld(World):
|
||||
to rescue the Seven Sages, and then confront Ganondorf to save Hyrule!
|
||||
"""
|
||||
game: str = "Ocarina of Time"
|
||||
option_definitions: dict = oot_options
|
||||
options_dataclass = OoTOptions
|
||||
options: OoTOptions
|
||||
settings: typing.ClassVar[OOTSettings]
|
||||
topology_present: bool = True
|
||||
item_name_to_id = {item_name: oot_data_to_ap_id(data, False) for item_name, data in item_table.items() if
|
||||
@@ -195,15 +197,15 @@ class OOTWorld(World):
|
||||
|
||||
@classmethod
|
||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
rom = Rom(file=get_settings()['oot_options']['rom_file'])
|
||||
|
||||
|
||||
# Option parsing, handling incompatible options, building useful-item table
|
||||
def generate_early(self):
|
||||
self.parser = Rule_AST_Transformer(self, self.player)
|
||||
|
||||
for (option_name, option) in oot_options.items():
|
||||
result = getattr(self.multiworld, option_name)[self.player]
|
||||
for option_name in self.options_dataclass.type_hints:
|
||||
result = getattr(self.options, option_name)
|
||||
if isinstance(result, Range):
|
||||
option_value = int(result)
|
||||
elif isinstance(result, Toggle):
|
||||
@@ -223,8 +225,8 @@ class OOTWorld(World):
|
||||
self.remove_from_start_inventory = [] # some items will be precollected but not in the inventory
|
||||
self.starting_items = Counter()
|
||||
self.songs_as_items = False
|
||||
self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)]
|
||||
self.connect_name = ''.join(self.multiworld.random.choices(printable, k=16))
|
||||
self.file_hash = [self.random.randint(0, 31) for i in range(5)]
|
||||
self.connect_name = ''.join(self.random.choices(printable, k=16))
|
||||
self.collectible_flag_addresses = {}
|
||||
|
||||
# Incompatible option handling
|
||||
@@ -283,7 +285,7 @@ class OOTWorld(World):
|
||||
local_types.append('BossKey')
|
||||
if self.shuffle_ganon_bosskey != 'keysanity':
|
||||
local_types.append('GanonBossKey')
|
||||
self.multiworld.local_items[self.player].value |= set(name for name, data in item_table.items() if data[0] in local_types)
|
||||
self.options.local_items.value |= set(name for name, data in item_table.items() if data[0] in local_types)
|
||||
|
||||
# If any songs are itemlinked, set songs_as_items
|
||||
for group in self.multiworld.groups.values():
|
||||
@@ -297,7 +299,7 @@ class OOTWorld(World):
|
||||
# Determine skipped trials in GT
|
||||
# This needs to be done before the logic rules in GT are parsed
|
||||
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
|
||||
chosen_trials = self.multiworld.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
chosen_trials = self.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list}
|
||||
|
||||
# Determine tricks in logic
|
||||
@@ -311,8 +313,8 @@ class OOTWorld(World):
|
||||
|
||||
# No Logic forces all tricks on, prog balancing off and beatable-only
|
||||
elif self.logic_rules == 'no_logic':
|
||||
self.multiworld.progression_balancing[self.player].value = False
|
||||
self.multiworld.accessibility[self.player].value = Accessibility.option_minimal
|
||||
self.options.progression_balancing.value = False
|
||||
self.options.accessibility.value = Accessibility.option_minimal
|
||||
for trick in normalized_name_tricks.values():
|
||||
setattr(self, trick['name'], True)
|
||||
|
||||
@@ -333,8 +335,8 @@ class OOTWorld(World):
|
||||
|
||||
# Set internal names used by the OoT generator
|
||||
self.keysanity = self.shuffle_smallkeys in ['keysanity', 'remove', 'any_dungeon', 'overworld']
|
||||
self.trials_random = self.multiworld.trials[self.player].randomized
|
||||
self.mq_dungeons_random = self.multiworld.mq_dungeons_count[self.player].randomized
|
||||
self.trials_random = self.options.trials.randomized
|
||||
self.mq_dungeons_random = self.options.mq_dungeons_count.randomized
|
||||
self.easier_fire_arrow_entry = self.fae_torch_count < 24
|
||||
|
||||
if self.misc_hints:
|
||||
@@ -393,8 +395,8 @@ class OOTWorld(World):
|
||||
elif self.key_rings == 'choose':
|
||||
self.key_rings = self.key_rings_list
|
||||
elif self.key_rings == 'random_dungeons':
|
||||
self.key_rings = self.multiworld.random.sample(keyring_dungeons,
|
||||
self.multiworld.random.randint(0, len(keyring_dungeons)))
|
||||
self.key_rings = self.random.sample(keyring_dungeons,
|
||||
self.random.randint(0, len(keyring_dungeons)))
|
||||
|
||||
# Determine which dungeons are MQ. Not compatible with glitched logic.
|
||||
mq_dungeons = set()
|
||||
@@ -405,7 +407,7 @@ class OOTWorld(World):
|
||||
elif self.mq_dungeons_mode == 'specific':
|
||||
mq_dungeons = self.mq_dungeons_specific
|
||||
elif self.mq_dungeons_mode == 'count':
|
||||
mq_dungeons = self.multiworld.random.sample(all_dungeons, self.mq_dungeons_count)
|
||||
mq_dungeons = self.random.sample(all_dungeons, self.mq_dungeons_count)
|
||||
else:
|
||||
self.mq_dungeons_mode = 'count'
|
||||
self.mq_dungeons_count = 0
|
||||
@@ -425,8 +427,8 @@ class OOTWorld(World):
|
||||
elif self.dungeon_shortcuts_choice == 'all':
|
||||
self.dungeon_shortcuts = set(shortcut_dungeons)
|
||||
elif self.dungeon_shortcuts_choice == 'random':
|
||||
self.dungeon_shortcuts = self.multiworld.random.sample(shortcut_dungeons,
|
||||
self.multiworld.random.randint(0, len(shortcut_dungeons)))
|
||||
self.dungeon_shortcuts = self.random.sample(shortcut_dungeons,
|
||||
self.random.randint(0, len(shortcut_dungeons)))
|
||||
# == 'choice', leave as previous
|
||||
else:
|
||||
self.dungeon_shortcuts = set()
|
||||
@@ -576,7 +578,7 @@ class OOTWorld(World):
|
||||
new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit.vanilla_connected_region = exit
|
||||
new_exit.rule_string = rule
|
||||
if self.multiworld.logic_rules != 'none':
|
||||
if self.options.logic_rules != 'no_logic':
|
||||
self.parser.parse_spot_rule(new_exit)
|
||||
if new_exit.never:
|
||||
logger.debug('Dropping unreachable exit: %s', new_exit.name)
|
||||
@@ -607,7 +609,7 @@ class OOTWorld(World):
|
||||
elif self.shuffle_scrubs == 'random':
|
||||
# this is a random value between 0-99
|
||||
# average value is ~33 rupees
|
||||
price = int(self.multiworld.random.betavariate(1, 2) * 99)
|
||||
price = int(self.random.betavariate(1, 2) * 99)
|
||||
|
||||
# Set price in the dictionary as well as the location.
|
||||
self.scrub_prices[scrub_item] = price
|
||||
@@ -624,7 +626,7 @@ class OOTWorld(World):
|
||||
self.shop_prices = {}
|
||||
for region in self.regions:
|
||||
if self.shopsanity == 'random':
|
||||
shop_item_count = self.multiworld.random.randint(0, 4)
|
||||
shop_item_count = self.random.randint(0, 4)
|
||||
else:
|
||||
shop_item_count = int(self.shopsanity)
|
||||
|
||||
@@ -632,17 +634,17 @@ class OOTWorld(World):
|
||||
if location.type == 'Shop':
|
||||
if location.name[-1:] in shop_item_indexes[:shop_item_count]:
|
||||
if self.shopsanity_prices == 'normal':
|
||||
self.shop_prices[location.name] = int(self.multiworld.random.betavariate(1.5, 2) * 60) * 5
|
||||
self.shop_prices[location.name] = int(self.random.betavariate(1.5, 2) * 60) * 5
|
||||
elif self.shopsanity_prices == 'affordable':
|
||||
self.shop_prices[location.name] = 10
|
||||
elif self.shopsanity_prices == 'starting_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,100,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,100,5)
|
||||
elif self.shopsanity_prices == 'adults_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,201,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,201,5)
|
||||
elif self.shopsanity_prices == 'giants_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,501,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,501,5)
|
||||
elif self.shopsanity_prices == 'tycoons_wallet':
|
||||
self.shop_prices[location.name] = self.multiworld.random.randrange(0,1000,5)
|
||||
self.shop_prices[location.name] = self.random.randrange(0,1000,5)
|
||||
|
||||
|
||||
# Fill boss prizes
|
||||
@@ -667,8 +669,8 @@ class OOTWorld(World):
|
||||
|
||||
while bossCount:
|
||||
bossCount -= 1
|
||||
self.multiworld.random.shuffle(prizepool)
|
||||
self.multiworld.random.shuffle(prize_locs)
|
||||
self.random.shuffle(prizepool)
|
||||
self.random.shuffle(prize_locs)
|
||||
item = prizepool.pop()
|
||||
loc = prize_locs.pop()
|
||||
loc.place_locked_item(item)
|
||||
@@ -778,7 +780,7 @@ class OOTWorld(World):
|
||||
# Call the junk fill and get a replacement
|
||||
if item in self.itempool:
|
||||
self.itempool.remove(item)
|
||||
self.itempool.append(self.create_item(*get_junk_item(pool=junk_pool)))
|
||||
self.itempool.append(self.create_item(*get_junk_item(self.random, pool=junk_pool)))
|
||||
if self.start_with_consumables:
|
||||
self.starting_items['Deku Sticks'] = 30
|
||||
self.starting_items['Deku Nuts'] = 40
|
||||
@@ -881,7 +883,7 @@ class OOTWorld(World):
|
||||
# Prefill shops, songs, and dungeon items
|
||||
items = self.get_pre_fill_items()
|
||||
locations = list(self.multiworld.get_unfilled_locations(self.player))
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
|
||||
# Set up initial state
|
||||
state = CollectionState(self.multiworld)
|
||||
@@ -910,7 +912,7 @@ class OOTWorld(World):
|
||||
if isinstance(locations, list):
|
||||
for item in stage_items:
|
||||
self.pre_fill_items.remove(item)
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), locations, stage_items,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
else:
|
||||
@@ -923,7 +925,7 @@ class OOTWorld(World):
|
||||
if isinstance(locations, list):
|
||||
for item in dungeon_items:
|
||||
self.pre_fill_items.remove(item)
|
||||
self.multiworld.random.shuffle(locations)
|
||||
self.random.shuffle(locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), locations, dungeon_items,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
|
||||
@@ -964,7 +966,7 @@ class OOTWorld(World):
|
||||
|
||||
while tries:
|
||||
try:
|
||||
self.multiworld.random.shuffle(song_locations)
|
||||
self.random.shuffle(song_locations)
|
||||
fill_restrictive(self.multiworld, prefill_state(state), song_locations[:], songs[:],
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
@@ -996,7 +998,7 @@ class OOTWorld(World):
|
||||
'Buy Goron Tunic': 1,
|
||||
'Buy Zora Tunic': 1,
|
||||
}.get(item.name, 0)) # place Deku Shields if needed, then tunics, then other advancement
|
||||
self.multiworld.random.shuffle(shop_locations)
|
||||
self.random.shuffle(shop_locations)
|
||||
self.pre_fill_items = [] # all prefill should be done
|
||||
fill_restrictive(self.multiworld, prefill_state(state), shop_locations, shop_prog,
|
||||
single_player_placement=True, lock=True, allow_excluded=True)
|
||||
@@ -1028,7 +1030,7 @@ class OOTWorld(World):
|
||||
ganon_junk_fill = min(1, ganon_junk_fill)
|
||||
gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons))
|
||||
locations = [loc.name for region in gc.regions for loc in region.locations if loc.item is None]
|
||||
junk_fill_locations = self.multiworld.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
junk_fill_locations = self.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.multiworld, self.player, junk_fill_locations)
|
||||
|
||||
# Locations which are not sendable must be converted to events
|
||||
@@ -1074,13 +1076,13 @@ class OOTWorld(World):
|
||||
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
|
||||
self.trap_appearances = {}
|
||||
for loc_id in trap_location_ids:
|
||||
self.trap_appearances[loc_id] = self.create_item(self.multiworld.per_slot_randoms[self.player].choice(self.fake_items).name)
|
||||
self.trap_appearances[loc_id] = self.create_item(self.random.choice(self.fake_items).name)
|
||||
|
||||
# Seed hint RNG, used for ganon text lines also
|
||||
self.hint_rng = self.multiworld.per_slot_randoms[self.player]
|
||||
self.hint_rng = self.random
|
||||
|
||||
outfile_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
rom = Rom(file=get_settings()['oot_options']['rom_file'])
|
||||
try:
|
||||
if self.hints != 'none':
|
||||
buildWorldGossipHints(self)
|
||||
@@ -1092,7 +1094,7 @@ class OOTWorld(World):
|
||||
finally:
|
||||
self.collectible_flags_available.set()
|
||||
rom.update_header()
|
||||
patch_data = create_patch_file(rom)
|
||||
patch_data = create_patch_file(rom, self.random)
|
||||
rom.restore()
|
||||
|
||||
apz5 = OoTContainer(patch_data, outfile_name, output_directory,
|
||||
@@ -1399,7 +1401,7 @@ class OOTWorld(World):
|
||||
return all_state
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return get_junk_item(count=1, pool=get_junk_pool(self))[0]
|
||||
return get_junk_item(self.random, count=1, pool=get_junk_pool(self))[0]
|
||||
|
||||
|
||||
def valid_dungeon_item_location(world: OOTWorld, option: str, dungeon: str, loc: OOTLocation) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user