From 4267c2dde5f4cb01c87fb3c3c7c47e5e328e2c57 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 25 Jul 2022 12:13:50 -0600 Subject: [PATCH 1/3] Minor bug in intensity 3, pottery setting not drop shuffle for Desert Tiles 2. --- DoorShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 0d36a2b0..ecb1b503 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -380,7 +380,7 @@ def choose_portals(world, player): if world.doorShuffle[player] in ['basic', 'crossed']: cross_flag = world.doorShuffle[player] == 'crossed' # key drops allow the big key in the right place in Desert Tiles 2 - bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player] + bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave'] std_flag = world.mode[player] == 'standard' # roast incognito doors world.get_room(0x60, player).delete(5) From 32714b4c6a35f4bdd4779aed8619f43423b6c021 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 5 Aug 2022 16:48:34 -0600 Subject: [PATCH 2/3] Fix for colorized pots in "dark desert hint" aka mire storyteller --- Main.py | 2 +- PotShuffle.py | 13 +++++++------ RELEASENOTES.md | 4 +++- Rom.py | 25 +++++++++++++------------ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Main.py b/Main.py index 7aba1276..3787e81b 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.0.1.0-u' +__version__ = '1.0.1.1-u' from source.classes.BabelFish import BabelFish diff --git a/PotShuffle.py b/PotShuffle.py index a1c96783..7806f495 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -787,12 +787,13 @@ vanilla_pots = { Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))], 0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))], 0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))], - 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x23, 0xFA])), - Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x23, 0xFA])), - Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x2B, 0xFA])), - Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x2B, 0xFA])), - Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7AC, [0xBB, 0x53, 0xFA])), - Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7AF, [0xC3, 0x53, 0xFA]))], + # note: these addresses got moved thanks to waterfall fairy edit + 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])), + Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])), + Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])), + Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])), + Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])), + Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))], 0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])), Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])), diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 65293af1..ce3e948c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -183,6 +183,8 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable +* 1.0.1.1 + * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be * 1.0.1.0 * Large features * New pottery modes - see notes above @@ -235,7 +237,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count * Fixed usestartinventory flag (can be use on a per player basis) * Sprite selector fix for systems with SSL issues - * Fix for Standard ER where locations in rain state could be in logic + * Fix for Standard ER where locations in rain state could be in logic * 1.0.0.3 * overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown) * Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix) diff --git a/Rom.py b/Rom.py index 7861a5cd..3a4661da 100644 --- a/Rom.py +++ b/Rom.py @@ -663,18 +663,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.mapshuffle[player]: rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle - if world.pottery[player] not in ['none']: - rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) - # make hammer pegs use different tiles - Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) - - if world.pot_contents[player]: - colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] - and (world.colorizepots[player] - or world.pottery[player] in ['reduced', 'clustered'])) - if world.pot_contents[player].size() > 0x2800: - raise Exception('Pot table is too big for current area') - world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots) # fix for swamp drains if necessary swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player) if not swamp1location.pot.indicator: @@ -1545,6 +1533,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if room.player == player and room.modified: rom.write_bytes(room.address(), room.rom_data()) + if world.pottery[player] not in ['none']: + rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) + # make hammer pegs use different tiles + Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) + + if world.pot_contents[player]: + colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] + and (world.colorizepots[player] + or world.pottery[player] in ['reduced', 'clustered'])) + if world.pot_contents[player].size() > 0x2800: + raise Exception('Pot table is too big for current area') + world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots) + write_strings(rom, world, player, team) # write initial sram From 2ed0a806092d19c8713abbdb70bf73ac75f52e13 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 11 Aug 2022 15:25:09 -0600 Subject: [PATCH 3/3] Fix for pot items to not reload with the supertile Key distribution change Unique boss shuffle make gt bosses also unique Removed text color in hints due to bug --- Bosses.py | 5 ++- Fill.py | 85 ++++++++++++++++++++++-------------------- RELEASENOTES.md | 7 +++- Rom.py | 10 ++--- data/base2current.bps | Bin 93156 -> 93186 bytes 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/Bosses.py b/Bosses.py index 53393d5f..206b797c 100644 --- a/Bosses.py +++ b/Bosses.py @@ -202,12 +202,15 @@ def place_bosses(world, player): place_boss(boss, level, loc, loc_text, world, player) elif world.boss_shuffle[player] == 'unique': bosses = list(placeable_bosses) + gt_bosses = [] for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') try: if level: - boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)]) + boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level) + and b not in gt_bosses]) + gt_bosses.append(boss) else: boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)]) bosses.remove(boss) diff --git a/Fill.py b/Fill.py index 74274543..bb6e70cf 100644 --- a/Fill.py +++ b/Fill.py @@ -3,6 +3,7 @@ import collections import itertools import logging import math +from contextlib import suppress from BaseClasses import CollectionState, FillError, LocationType from Items import ItemFactory @@ -35,17 +36,6 @@ def dungeon_tracking(world): def fill_dungeons_restrictive(world, shuffled_locations): dungeon_tracking(world) - all_state_base = world.get_all_state() - - # for player in range(1, world.players + 1): - # pinball_room = world.get_location('Skull Woods - Pinball Room', player) - # if world.retro[player]: - # world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False) - # else: - # world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False) - # pinball_room.event = True - # pinball_room.locked = True - # shuffled_locations.remove(pinball_room) # with shuffled dungeon items they are distributed as part of the normal item pool for item in world.get_items(): @@ -55,17 +45,28 @@ def fill_dungeons_restrictive(world, shuffled_locations): item.priority = True dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)] + bigs, smalls, others = [], [], [] + for i in dungeon_items: + (bigs if i.bigkey else smalls if i.smallkey else others).append(i) - # sort in the order Big Key, Small Key, Other before placing dungeon items - sort_order = {"BigKey": 3, "SmallKey": 2} - dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1)) + def fill(base_state, items, key_pool): + fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) - fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, - keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, - single_player_placement=True) + all_state_base = world.get_all_state() + big_state_base = all_state_base.copy() + for x in smalls + others: + big_state_base.collect(x, True) + fill(big_state_base, bigs, smalls) + random.shuffle(shuffled_locations) + small_state_base = all_state_base.copy() + for x in others: + small_state_base.collect(x, True) + fill(small_state_base, smalls, smalls) + random.shuffle(shuffled_locations) + fill(all_state_base, others, None) -def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False, +def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, vanilla=False): def sweep_from_pool(): new_state = base_state.copy() @@ -101,8 +102,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No item_locations = filter_locations(item_to_place, locations, world, vanilla) for location in item_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state, - single_player_placement, perform_access_check, itempool, - keys_in_itempool, world) + single_player_placement, perform_access_check, key_pool, world) if spot_to_fill: break if spot_to_fill is None: @@ -111,7 +111,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No continue spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state, base_state, itempool, perform_access_check, item_locations, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if spot_to_fill is None: # we filled all reachable spots. Maybe the game can be beaten anyway? unplaced_items.insert(0, item_to_place) @@ -123,6 +123,10 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) + # todo: remove key item from key_pool + if item_to_place.smallkey: + with suppress(ValueError): + key_pool.remove(item_to_place) track_outside_keys(item_to_place, spot_to_fill, world) track_dungeon_items(item_to_place, spot_to_fill, world) locations.remove(spot_to_fill) @@ -132,7 +136,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check, - itempool, keys_in_itempool, world): + key_pool, world): if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there location.item = item_to_place test_state = max_exp_state.copy() @@ -141,8 +145,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl test_state = max_exp_state if not single_player_placement or location.player == item_to_place.player: if location.can_fill(test_state, item_to_place, perform_access_check): - test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool - if valid_key_placement(item_to_place, location, test_pool, world): + if valid_key_placement(item_to_place, location, key_pool, world): if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world): return location if item_to_place.smallkey or item_to_place.bigkey: @@ -150,7 +153,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl return None -def valid_key_placement(item, location, itempool, world): +def valid_key_placement(item, location, key_pool, world): if not valid_reserved_placement(item, location, world): return False if ((not item.smallkey and not item.bigkey) or item.player != location.player @@ -161,7 +164,7 @@ def valid_key_placement(item, location, itempool, world): if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True key_logic = world.key_logic[item.player][dungeon.name] - unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) + unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None if key_logic.prize_location: prize_loc = world.get_location(key_logic.prize_location, location.player) @@ -216,16 +219,16 @@ def is_dungeon_item(item, world): def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery') if world.algorithm in ['balanced', 'equitable']: - return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool, + return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool, single_player_placement) elif world.algorithm == 'vanilla_fill': if item_to_place.type == 'Crystal': possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) else: i, config = 0, world.item_pool_config tried = set(attempted) @@ -235,7 +238,7 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locs = [x for x in locations if x.name in fallback_locations] for location in other_locs: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill i += 1 @@ -244,14 +247,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locations = vanilla_fallback(item_to_place, locations, world) for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill tried.update(other_locations) other_locations = [x for x in locations if x not in tried] for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill return None @@ -259,14 +262,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locations = [x for x in locations if x not in attempted] for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill return None def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): def location_preference(loc): if not loc.item.advancement: return 1 @@ -284,21 +287,21 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] swap_locations = sorted(possible_swaps, key=location_preference) return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): for location in swap_locations: old_item = location.item new_pool = list(itempool) + [old_item] new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if new_spot: restore_item = new_spot.item new_spot.item = item_to_place swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if swap_spot: logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}') world.push_item(swap_spot, old_item, False) @@ -414,13 +417,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # todo: crossed progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) - keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)} + key_pool = [x for x in progitempool if x.smallkey] # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1) if world.algorithm == 'vanilla_fill': - fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True) - fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool) + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True) + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool) random.shuffle(fill_locations) if world.algorithm == 'balanced': fast_fill(world, prioitempool, fill_locations) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ce3e948c..56ecca32 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -184,7 +184,11 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable * 1.0.1.1 - * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be + * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be + * Certain pot items no longer reload when reloading the supertile (matches original pot behavior better) + * Changed the key distribution that made small keys placement more random when keys are in their own dungeon + * Unique boss shuffle no longer allows repeat bosses in GT (e.g. only one Trinexx in GT, so exactly 3 bosses are repeated in the seed. This is a difference process than full which does affect the probability distribution.) + * Removed text color in hints due to vanilla bug * 1.0.1.0 * Large features * New pottery modes - see notes above @@ -208,7 +212,6 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work. * Updated tourney winners (included Doors Async League winners) * Some textual changes for hints (capitalization standardization) - * Item will be highlighted in red if experimental is on. This will likely be removed. * Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill. * Expanded Mystery logic options (e.g. owglitches) * Updated indicators on keysanity menu for overworld map option diff --git a/Rom.py b/Rom.py index 3a4661da..c3312b9a 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9008f4335101689f01184e58295fdbc5' +RANDOMIZERBASEHASH = '0f96237c73cccaf7a250343fe3e8c887' class JsonRom(object): @@ -1979,8 +1979,6 @@ def write_strings(rom, world, player, team): else: if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon: hint = dest.dungeon.name - elif isinstance(dest, Item) and world.experimental[player]: - hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something' else: hint = dest.hint_text if dest.hint_text else "something" if dest.player != player: @@ -2149,8 +2147,7 @@ def write_strings(rom, world, player, team): if this_location: item_name = this_location[0].item.hint_text item_name = item_name[0].upper() + item_name[1:] - item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name - this_hint = f'{item_format} can be found {hint_text(this_location[0])}.' + this_hint = f'{item_name} can be found {hint_text(this_location[0])}.' tt[hint_locations.pop(0)] = this_hint hint_count -= 1 @@ -2204,8 +2201,7 @@ def write_strings(rom, world, player, team): elif hint_type == 'path': if item_count == 1: the_item = text_for_item(next(iter(choice_set)), world, player, team) - item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item - hint_candidates.append((hint_type, f'{name} conceals only {item_format}')) + hint_candidates.append((hint_type, f'{name} conceals only {the_item}')) else: hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items')) district_hints = min(len(hint_candidates), len(hint_locations)) diff --git a/data/base2current.bps b/data/base2current.bps index 464ceaf6105a2637981d2d107a868a08a143c8e4..55525ee50a0d8f30527d68ac4de47956a9acb906 100644 GIT binary patch delta 476 zcmV<20VDq8*9C&u1(1pYAF`2*#tV%w0t*F*sWpwta+6*GE&<)MjRB1X22-)9#EFEH zAP0W|xs#v=kU9;osOsvNl^?LEfC1_}h?OE4v8b>ImnkxVr3oTnu&5d$keDZ+l_F@b zsOstzfsG<3)SyrefRQ5FlWYh&6`iiA>U4mK1InOWfQc`$sA#jE03Nfu2tok}!m+68 zxQ!yyvjqxX0th>45vQ=0)Vp=Or+2X?XIYW3Xbs6Al8?{9062b34x6u z2aX~K?j{GQsR#hTHUQ8CsTKf$BEUod&;_X%0Du_4C;-p}sTcr&4!|)0&;_X(0DuII zoC|A6iVB4QsUHC40GA_xlS6%j#*m?CjhqW>Xq=o2pFVzrj@Oq`9RX?qZkN3s0b&7v zmmnSiDh!(p9FC3py>JJcD|we!9sws0N`8ZZ8LNVguObJ2n`MWWjUEAB0x%hu?H&Or z0qK_(9|0c`MzHi0fLuD#25Ta6vZ%*JR+mN}0Wtyqmw6unH39LLuO9(A0r8jW9|2+v z^sS3atGQ6It6(5%mslVHrU7V|{~!TE3mvn760?M{!XT;5mrNl6Hv$+fmxUn#Is!{$ Sx4|I+8%hYs1;u%{PR&`PFuCsl delta 471 zcmV;|0Vw{0*ahU*1(1pY?6HxI#tg+U0t*EI0RY>iBwLeR0WJZmvy1_a1qL9osKkj; zlOG3v0fv*G2ar74uBhtjnlHVT?60VR0qQ)6l_KV_sIUi@DKdej2_h}9s2U=Wm?xl> zA~mn5>gp7MjUp%1pim8fks_y)X$U$MYObj2bbyHi%Aj0;iTtprXtSOmT(h_cLIDVm zv8d{}jUu440t#LN2nepI9Dz%c;O1F0DR zjhqXgk&dI6aUB6_0SK4e9RXqi8J9mE0V)lbA{ma60Q$Xf2b({3mvtTiCl6$OgMk^V zf{m{t2Y#D>hL@`z0bT-17?%ql0VpxHv8ZTkB7y%NNWFj#$t2JnljMO4lY-R*sU(06 zXiM}Hu=EswTsqPQYa&0gsK;Mbmt-FSG6Bq&mmdK&0lt^b9|1Z6zLy9f0b&fnt&2;m zxgD{qU?4Y_b|3+!0X3H$Apt@Q>au_mvxKq2AgP*{Y9RqP0x~R@qagu00vBbs-ys1T NN(i8Xyg-eA7zmvww~7D&