LttP: add hint options "Vendors" and "Full"

LttP: fix hint grammar if a Location isn't an ALttPLocation
This commit is contained in:
Fabian Dill 2021-11-27 22:57:54 +01:00
parent 5ca737886b
commit 9f0a8e6d48
3 changed files with 183 additions and 164 deletions

View File

@ -255,14 +255,18 @@ class MultiWorld():
def get_items(self) -> list: def get_items(self) -> list:
return [loc.item for loc in self.get_filled_locations()] + self.itempool return [loc.item for loc in self.get_filled_locations()] + self.itempool
def find_items(self, item, player: int) -> List[Location]: def find_item_locations(self, item, player: int) -> List[Location]:
return [location for location in self.get_locations() if return [location for location in self.get_locations() if
location.item is not None and location.item.name == item and location.item.player == player] location.item and location.item.name == item and location.item.player == player]
def find_item(self, item, player: int) -> Location: def find_item(self, item, player: int) -> Location:
return next(location for location in self.get_locations() if return next(location for location in self.get_locations() if
location.item and location.item.name == item and location.item.player == player) location.item and location.item.name == item and location.item.player == player)
def find_items_in_locations(self, items: Set[str], player: int) -> List[Location]:
return [location for location in self.get_locations() if
location.item and location.item.name in items and location.item.player == player]
def create_item(self, item_name: str, player: int) -> Item: def create_item(self, item_name: str, player: int) -> Item:
return self.worlds[player].create_item(item_name) return self.worlds[player].create_item(item_name)

View File

@ -145,10 +145,17 @@ class RestrictBossItem(Toggle):
displayname = "Prevent Dungeon Item on Boss" displayname = "Prevent Dungeon Item on Boss"
class Hints(DefaultOnToggle): class Hints(Choice):
"""Put item and entrance placement hints on telepathic tiles and some NPCs. """Vendors: King Zora and Bottle Merchant say what they're selling.
Additionally King Zora and Bottle Merchant say what they're selling.""" On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints."""
displayname = "Hints" displayname = "Hints"
option_off = 0
option_vendors = 1
option_on = 2
option_full = 3
default = 2
alias_false = 0
alias_true = 2
class EnemyShuffle(Toggle): class EnemyShuffle(Toggle):

View File

@ -21,8 +21,7 @@ import concurrent.futures
import bsdiff4 import bsdiff4
from typing import Optional from typing import Optional
from BaseClasses import CollectionState, Region from BaseClasses import CollectionState, Region, Location
from worlds.alttp.SubClasses import ALttPLocation
from worlds.alttp.Shops import ShopType, ShopPriceType from worlds.alttp.Shops import ShopType, ShopPriceType
from worlds.alttp.Dungeons import dungeon_music_addresses from worlds.alttp.Dungeons import dungeon_music_addresses
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
@ -1535,7 +1534,7 @@ def patch_rom(world, rom, player, enemized):
} }
def get_reveal_bytes(itemName): def get_reveal_bytes(itemName):
locations = world.find_items(itemName, player) locations = world.find_item_locations(itemName, player)
if len(locations) < 1: if len(locations) < 1:
return 0x0000 return 0x0000
location = locations[0] location = locations[0]
@ -2114,7 +2113,7 @@ def write_strings(rom, world, player):
if dest.player != player: if dest.player != player:
if ped_hint: if ped_hint:
hint += f" for {world.player_name[dest.player]}!" hint += f" for {world.player_name[dest.player]}!"
elif type(dest) in [Region, ALttPLocation]: elif isinstance(dest, (Region, Location)):
hint += f" in {world.player_name[dest.player]}'s world" hint += f" in {world.player_name[dest.player]}'s world"
else: else:
hint += f" for {world.player_name[dest.player]}" hint += f" for {world.player_name[dest.player]}"
@ -2130,171 +2129,180 @@ def write_strings(rom, world, player):
vendor_location = world.get_location("Bottle Merchant", player) vendor_location = world.get_location("Bottle Merchant", player)
tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \ tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \
f"\n ≥ I want\n no way!\n{{CHOICE}}" f"\n ≥ I want\n no way!\n{{CHOICE}}"
if world.hints[player].value >= 2:
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' if world.hints[player] == "full":
hint_locations = HintLocations.copy() tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles have hints!'
local_random.shuffle(hint_locations)
all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player]
local_random.shuffle(all_entrances)
# First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
entrances_to_hint = {}
entrances_to_hint.update(InconvenientDungeonEntrances)
if world.shuffle_ganon:
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'})
else: else:
entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
if world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: hint_locations = HintLocations.copy()
for entrance in all_entrances: local_random.shuffle(hint_locations)
if entrance.name in entrances_to_hint: all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player]
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( local_random.shuffle(all_entrances)
entrance.connected_region) + '.'
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint = {}
break
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
hint_count = 2
else:
hint_count = 4
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
if hint_count:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
entrance.connected_region) + '.'
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint.pop(entrance.name)
hint_count -= 1
else:
break
# Next we handle hints for randomly selected other entrances, curating the selection intelligently based on shuffle. # First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']: entrances_to_hint = {}
entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(InconvenientDungeonEntrances)
entrances_to_hint.update(DungeonEntrances)
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'})
else:
entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'})
elif world.shuffle[player] == 'restricted':
entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(OtherEntrances)
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Dark Sanctuary': 'The dark sanctuary cave'})
entrances_to_hint.update({'Inverted Big Bomb Shop': 'The old hero\'s dark home'})
entrances_to_hint.update({'Inverted Links House': 'The old hero\'s light home'})
else:
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']:
entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon: if world.shuffle_ganon:
if world.mode[player] == 'inverted': if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'})
else: else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', if world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
'dungeonscrossed'] else 0 for entrance in all_entrances:
for entrance in all_entrances: if entrance.name in entrances_to_hint:
if entrance.name in entrances_to_hint: this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
if hint_count: entrance.connected_region) + '.'
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( tt[hint_locations.pop(0)] = this_hint
entrance.connected_region) + '.' entrances_to_hint = {}
tt[hint_locations.pop(0)] = this_hint break
entrances_to_hint.pop(entrance.name) # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
hint_count -= 1 entrances_to_hint.update(InconvenientOtherEntrances)
else: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
break hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. hint_count = 2
locations_to_hint = InconvenientLocations.copy()
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
locations_to_hint.extend(InconvenientVanillaLocations)
local_random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
'dungeonscrossed'] else 5
for location in locations_to_hint[:hint_count]:
if location == 'Swamp Left':
if local_random.randint(0, 1):
first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
else:
second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.')
tt[hint_locations.pop(0)] = this_hint
elif location == 'Mire Left':
if local_random.randint(0, 1):
first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
else:
second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.')
tt[hint_locations.pop(0)] = this_hint
elif location == 'Tower of Hera - Big Key Chest':
this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ganons Tower - Big Chest':
this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Thieves\' Town - Big Chest':
this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ice Palace - Big Chest':
this_hint = 'The big chest in Ice Palace contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Eastern Palace - Big Key Chest':
this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Sahasrahla':
this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Graveyard Cave':
this_hint = 'The cave north of the graveyard contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
else: else:
this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.' hint_count = 4
tt[hint_locations.pop(0)] = this_hint for entrance in all_entrances:
if entrance.name in entrances_to_hint:
if hint_count:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
entrance.connected_region) + '.'
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint.pop(entrance.name)
hint_count -= 1
else:
break
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well. # Next we handle hints for randomly selected other entrances,
items_to_hint = RelevantItems.copy() # curating the selection intelligently based on shuffle.
if world.smallkey_shuffle[player]: if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']:
items_to_hint.extend(SmallKeys) entrances_to_hint.update(ConnectorEntrances)
if world.bigkey_shuffle[player]: entrances_to_hint.update(DungeonEntrances)
items_to_hint.extend(BigKeys) if world.mode[player] == 'inverted':
local_random.shuffle(items_to_hint) entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'})
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', else:
'dungeonscrossed'] else 8 entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'})
while hint_count > 0 and items_to_hint: elif world.shuffle[player] == 'restricted':
this_item = items_to_hint.pop(0) entrances_to_hint.update(ConnectorEntrances)
this_location = world.find_items(this_item, player) entrances_to_hint.update(OtherEntrances)
if this_location: if world.mode[player] == 'inverted':
local_random.shuffle(this_location) entrances_to_hint.update({'Inverted Dark Sanctuary': 'The dark sanctuary cave'})
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.' entrances_to_hint.update({'Inverted Big Bomb Shop': 'The old hero\'s dark home'})
tt[hint_locations.pop(0)] = this_hint entrances_to_hint.update({'Inverted Links House': 'The old hero\'s light home'})
hint_count -= 1 else:
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']:
entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon:
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
'dungeonscrossed'] else 0
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
if hint_count:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
entrance.connected_region) + '.'
tt[hint_locations.pop(0)] = this_hint
entrances_to_hint.pop(entrance.name)
hint_count -= 1
else:
break
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice. # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
junk_hints = junk_texts.copy() locations_to_hint = InconvenientLocations.copy()
local_random.shuffle(junk_hints) if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
for location, text in zip(hint_locations, junk_hints): locations_to_hint.extend(InconvenientVanillaLocations)
tt[location] = text local_random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
'dungeonscrossed'] else 5
for location in locations_to_hint[:hint_count]:
if location == 'Swamp Left':
if local_random.randint(0, 1):
first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
else:
second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.')
tt[hint_locations.pop(0)] = this_hint
elif location == 'Mire Left':
if local_random.randint(0, 1):
first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
else:
second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.')
tt[hint_locations.pop(0)] = this_hint
elif location == 'Tower of Hera - Big Key Chest':
this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ganons Tower - Big Chest':
this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Thieves\' Town - Big Chest':
this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Ice Palace - Big Chest':
this_hint = 'The big chest in Ice Palace contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Eastern Palace - Big Key Chest':
this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Sahasrahla':
this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
elif location == 'Graveyard Cave':
this_hint = 'The cave north of the graveyard contains ' + hint_text(
world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
else:
this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.'
tt[hint_locations.pop(0)] = this_hint
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well.
items_to_hint = RelevantItems.copy()
if world.smallkey_shuffle[player]:
items_to_hint.extend(SmallKeys)
if world.bigkey_shuffle[player]:
items_to_hint.extend(BigKeys)
local_random.shuffle(items_to_hint)
if world.hints[player] == "full":
hint_count = len(hint_locations) # fill all remaining hint locations with Item hints.
else:
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
'dungeonscrossed'] else 8
hint_count = min(hint_count, len(items_to_hint), len(hint_locations))
if hint_count:
locations = world.find_items_in_locations(set(items_to_hint), player)
local_random.shuffle(locations)
for x in range(hint_count):
this_location = locations.pop()
this_hint = this_location.item.hint_text + ' can be found ' + hint_text(this_location) + '.'
tt[hint_locations.pop(0)] = this_hint
if hint_locations:
# All remaining hint slots are filled with junk hints.
# It is done this way to ensure the same junk hint isn't selected twice.
junk_hints = junk_texts.copy()
local_random.shuffle(junk_hints)
for location, text in zip(hint_locations, junk_hints):
tt[location] = text
# We still need the older hints of course. Those are done here. # We still need the older hints of course. Those are done here.
silverarrows = world.find_items('Silver Bow', player) silverarrows = world.find_item_locations('Silver Bow', player)
local_random.shuffle(silverarrows) local_random.shuffle(silverarrows)
silverarrow_hint = ( silverarrow_hint = (
' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' ' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!'
@ -2302,7 +2310,7 @@ def write_strings(rom, world, player):
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or ( if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or (
world.swordless[player] or world.logic[player] == 'noglitches')): world.swordless[player] or world.logic[player] == 'noglitches')):
prog_bow_locs = world.find_items('Progressive Bow', player) prog_bow_locs = world.find_item_locations('Progressive Bow', player)
world.slot_seeds[player].shuffle(prog_bow_locs) world.slot_seeds[player].shuffle(prog_bow_locs)
found_bow = False found_bow = False
found_bow_alt = False found_bow_alt = False