Hint generation improvements
Only generate the required hint data for a world based on its hint distribution Set various major items as nonprogression never_exclude based on settings
This commit is contained in:
parent
f39defbe06
commit
9dc3f3f38b
|
@ -714,7 +714,6 @@ def buildWorldGossipHints(world, checkedLocations=None):
|
||||||
fixed_num = world.hint_dist_user['distribution'][hint_type]['fixed']
|
fixed_num = world.hint_dist_user['distribution'][hint_type]['fixed']
|
||||||
hint_weight = world.hint_dist_user['distribution'][hint_type]['weight']
|
hint_weight = world.hint_dist_user['distribution'][hint_type]['weight']
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').warning("Hint copies is zero for type %s. Assuming this hint type should be disabled.", hint_type)
|
|
||||||
fixed_num = 0
|
fixed_num = 0
|
||||||
hint_weight = 0
|
hint_weight = 0
|
||||||
hint_dist[hint_type] = (hint_weight, world.hint_dist_user['distribution'][hint_type]['copies'])
|
hint_dist[hint_type] = (hint_weight, world.hint_dist_user['distribution'][hint_type]['copies'])
|
||||||
|
|
|
@ -22,9 +22,10 @@ def ap_id_to_oot_data(ap_id):
|
||||||
class OOTItem(Item):
|
class OOTItem(Item):
|
||||||
game: str = "Ocarina of Time"
|
game: str = "Ocarina of Time"
|
||||||
|
|
||||||
def __init__(self, name, player, data, event):
|
def __init__(self, name, player, data, event, force_not_advancement):
|
||||||
(type, advancement, index, special) = data
|
(type, advancement, index, special) = data
|
||||||
adv = True if advancement else False # this looks silly but the table uses True, False, and None
|
# "advancement" is True, False or None; some items are not advancement based on settings
|
||||||
|
adv = bool(advancement) and not force_not_advancement
|
||||||
super(OOTItem, self).__init__(name, adv, oot_data_to_ap_id(data, event), player)
|
super(OOTItem, self).__init__(name, adv, oot_data_to_ap_id(data, event), player)
|
||||||
self.type = type
|
self.type = type
|
||||||
self.index = index
|
self.index = index
|
||||||
|
@ -32,6 +33,8 @@ class OOTItem(Item):
|
||||||
self.looks_like_item = None
|
self.looks_like_item = None
|
||||||
self.price = special.get('price', None) if special else None
|
self.price = special.get('price', None) if special else None
|
||||||
self.internal = False
|
self.internal = False
|
||||||
|
if force_not_advancement:
|
||||||
|
self.never_exclude = True
|
||||||
|
|
||||||
# The playthrough calculation calls a function that uses "sweep_for_events(key_only=True)"
|
# The playthrough calculation calls a function that uses "sweep_for_events(key_only=True)"
|
||||||
# This checks if the item it's looking for is a small key, using the small key property.
|
# This checks if the item it's looking for is a small key, using the small key property.
|
||||||
|
|
|
@ -244,6 +244,25 @@ class OOTWorld(World):
|
||||||
|
|
||||||
self.always_hints = [hint.name for hint in getRequiredHints(self)]
|
self.always_hints = [hint.name for hint in getRequiredHints(self)]
|
||||||
|
|
||||||
|
# Determine items which are not considered advancement based on settings. They will never be excluded.
|
||||||
|
self.nonadvancement_items = {'Double Defense', 'Ice Arrows'}
|
||||||
|
if (self.damage_multiplier != 'ohko' and self.damage_multiplier != 'quadruple' and
|
||||||
|
self.shuffle_scrubs == 'off' and not self.shuffle_grotto_entrances):
|
||||||
|
# nayru's love may be required to prevent forced damage
|
||||||
|
self.nonadvancement_items.add('Nayrus Love')
|
||||||
|
if getattr(self, 'logic_grottos_without_agony', False) and self.hints != 'agony':
|
||||||
|
# Stone of Agony skippable if not used for hints or grottos
|
||||||
|
self.nonadvancement_items.add('Stone of Agony')
|
||||||
|
if (not self.shuffle_special_interior_entrances and not self.shuffle_overworld_entrances and
|
||||||
|
not self.warp_songs and not self.spawn_positions):
|
||||||
|
# Serenade and Prelude are never required unless one of those settings is enabled
|
||||||
|
self.nonadvancement_items.add('Serenade of Water')
|
||||||
|
self.nonadvancement_items.add('Prelude of Light')
|
||||||
|
if self.logic_rules == 'glitchless':
|
||||||
|
# Both two-handed swords can be required in glitch logic, so only consider them nonprogression in glitchless
|
||||||
|
self.nonadvancement_items.add('Biggoron Sword')
|
||||||
|
self.nonadvancement_items.add('Giants Knife')
|
||||||
|
|
||||||
def load_regions_from_json(self, file_path):
|
def load_regions_from_json(self, file_path):
|
||||||
region_json = read_json(file_path)
|
region_json = read_json(file_path)
|
||||||
|
|
||||||
|
@ -390,8 +409,8 @@ class OOTWorld(World):
|
||||||
|
|
||||||
def create_item(self, name: str):
|
def create_item(self, name: str):
|
||||||
if name in item_table:
|
if name in item_table:
|
||||||
return OOTItem(name, self.player, item_table[name], False)
|
return OOTItem(name, self.player, item_table[name], False, (name in self.nonadvancement_items))
|
||||||
return OOTItem(name, self.player, ('Event', True, None, None), True)
|
return OOTItem(name, self.player, ('Event', True, None, None), True, False)
|
||||||
|
|
||||||
def make_event_item(self, name, location, item=None):
|
def make_event_item(self, name, location, item=None):
|
||||||
if item is None:
|
if item is None:
|
||||||
|
@ -665,39 +684,61 @@ class OOTWorld(World):
|
||||||
|
|
||||||
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
||||||
def stage_generate_output(world: MultiWorld, output_directory: str):
|
def stage_generate_output(world: MultiWorld, output_directory: str):
|
||||||
try:
|
def hint_type_players(hint_type: str) -> set:
|
||||||
items_by_region = {player: {} for player in world.get_game_players("Ocarina of Time") if world.worlds[player].hints != 'none'}
|
return {autoworld.player for autoworld in world.get_game_worlds("Ocarina of Time")
|
||||||
if items_by_region:
|
if autoworld.hints != 'none' and autoworld.hint_dist_user['distribution'][hint_type]['copies'] > 0}
|
||||||
for player in items_by_region:
|
|
||||||
for r in world.worlds[player].regions:
|
|
||||||
items_by_region[player][r.hint_text] = {'dungeon': False, 'weight': 0, 'prog_items': 0}
|
|
||||||
for d in world.worlds[player].dungeons:
|
|
||||||
items_by_region[player][d.hint_text] = {'dungeon': True, 'weight': 0, 'prog_items': 0}
|
|
||||||
del (items_by_region[player]["Link's Pocket"])
|
|
||||||
del (items_by_region[player][None])
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
item_hint_players = hint_type_players('item')
|
||||||
|
barren_hint_players = hint_type_players('barren')
|
||||||
|
woth_hint_players = hint_type_players('woth')
|
||||||
|
|
||||||
|
items_by_region = {}
|
||||||
|
for player in barren_hint_players:
|
||||||
|
items_by_region[player] = {}
|
||||||
|
for r in world.worlds[player].regions:
|
||||||
|
items_by_region[player][r.hint_text] = {'dungeon': False, 'weight': 0, 'is_barren': True}
|
||||||
|
for d in world.worlds[player].dungeons:
|
||||||
|
items_by_region[player][d.hint_text] = {'dungeon': True, 'weight': 0, 'is_barren': True}
|
||||||
|
del (items_by_region[player]["Link's Pocket"])
|
||||||
|
del (items_by_region[player][None])
|
||||||
|
|
||||||
|
if item_hint_players: # loop once over all locations to gather major items. Check oot locations for barren/woth if needed
|
||||||
for loc in world.get_locations():
|
for loc in world.get_locations():
|
||||||
player = loc.item.player
|
player = loc.item.player
|
||||||
autoworld = world.worlds[player]
|
autoworld = world.worlds[player]
|
||||||
if ((player in items_by_region and (autoworld.is_major_item(loc.item) or loc.item.name in autoworld.item_added_hint_types['item']))
|
if ((player in item_hint_players and (autoworld.is_major_item(loc.item) or loc.item.name in autoworld.item_added_hint_types['item']))
|
||||||
or (loc.player in items_by_region and loc.name in world.worlds[loc.player].added_hint_types['item'])):
|
or (loc.player in item_hint_players and loc.name in world.worlds[loc.player].added_hint_types['item'])):
|
||||||
autoworld.major_item_locations.append(loc)
|
autoworld.major_item_locations.append(loc)
|
||||||
|
|
||||||
if loc.game == "Ocarina of Time":
|
if loc.game == "Ocarina of Time" and loc.item.code and (not loc.locked or loc.item.type == 'Song'):
|
||||||
if loc.item.code and (not loc.locked or loc.item.type == 'Song'): # shuffled item
|
if loc.player in barren_hint_players:
|
||||||
hint_area = get_hint_area(loc)
|
hint_area = get_hint_area(loc)
|
||||||
items_by_region[loc.player][hint_area]['weight'] += 1
|
items_by_region[loc.player][hint_area]['weight'] += 1
|
||||||
if loc.item.advancement:
|
if loc.item.advancement:
|
||||||
# Non-locked progression. Increment counter
|
items_by_region[loc.player][hint_area]['is_barren'] = False
|
||||||
items_by_region[loc.player][hint_area]['prog_items'] += 1
|
if loc.player in woth_hint_players and loc.item.advancement:
|
||||||
# Skip item at location and see if game is still beatable
|
# Skip item at location and see if game is still beatable
|
||||||
|
state = CollectionState(world)
|
||||||
|
state.locations_checked.add(loc)
|
||||||
|
if not world.can_beat_game(state):
|
||||||
|
world.worlds[loc.player].required_locations.append(loc)
|
||||||
|
elif barren_hint_players or woth_hint_players: # Check only relevant oot locations for barren/woth
|
||||||
|
for player in (barren_hint_players | woth_hint_players):
|
||||||
|
for loc in world.worlds[player].get_locations():
|
||||||
|
if loc.item.code and (not loc.locked or loc.item.type == 'Song'):
|
||||||
|
if player in barren_hint_players:
|
||||||
|
hint_area = get_hint_area(loc)
|
||||||
|
items_by_region[player][hint_area]['weight'] += 1
|
||||||
|
if loc.item.advancement:
|
||||||
|
items_by_region[player][hint_area]['is_barren'] = False
|
||||||
|
if player in woth_hint_players and loc.item.advancement:
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
state.locations_checked.add(loc)
|
state.locations_checked.add(loc)
|
||||||
if not world.can_beat_game(state):
|
if not world.can_beat_game(state):
|
||||||
world.worlds[loc.player].required_locations.append(loc)
|
world.worlds[player].required_locations.append(loc)
|
||||||
|
for player in barren_hint_players:
|
||||||
for autoworld in world.get_game_worlds("Ocarina of Time"):
|
world.worlds[player].empty_areas = {region: info for (region, info) in items_by_region[player].items() if info['is_barren']}
|
||||||
autoworld.empty_areas = {region: info for (region, info) in items_by_region[autoworld.player].items() if not info['prog_items']}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
|
@ -723,6 +764,9 @@ class OOTWorld(World):
|
||||||
if item.type == 'Token':
|
if item.type == 'Token':
|
||||||
return self.bridge == 'tokens' or self.lacs_condition == 'tokens'
|
return self.bridge == 'tokens' or self.lacs_condition == 'tokens'
|
||||||
|
|
||||||
|
if item.name in self.nonadvancement_items:
|
||||||
|
return True
|
||||||
|
|
||||||
if item.type in ('Drop', 'Event', 'Shop', 'DungeonReward') or not item.advancement:
|
if item.type in ('Drop', 'Event', 'Shop', 'DungeonReward') or not item.advancement:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue