Beispiel #1
0
def get_entrance_hint(spoiler, world, checked):
    if not world.entrance_shuffle:
        return None

    entrance_hints = list(filter(lambda hint: hint.name not in checked, getHintGroup('entrance', world)))
    shuffled_entrance_hints = list(filter(lambda entrance_hint: world.get_entrance(entrance_hint.name).shuffled, entrance_hints))

    regions_with_hint = [hint.name for hint in getHintGroup('region', world)]
    valid_entrance_hints = list(filter(lambda entrance_hint: world.get_entrance(entrance_hint.name).connected_region.name in regions_with_hint, shuffled_entrance_hints))

    if not valid_entrance_hints:
        return None

    entrance_hint = random.choice(valid_entrance_hints)
    entrance = world.get_entrance(entrance_hint.name)
    checked.add(entrance.name)

    entrance_text = entrance_hint.text

    if '#' not in entrance_text:
        entrance_text = '#%s#' % entrance_text

    connected_region = entrance.connected_region
    if connected_region.dungeon:
        region_text = getHint(connected_region.dungeon.name, world.clearer_hints).text
    else:
        region_text = getHint(connected_region.name, world.clearer_hints).text

    if '#' not in region_text:
        region_text = '#%s#' % region_text

    return (GossipText('%s %s.' % (entrance_text, region_text), ['Light Blue', 'Green']), None)
Beispiel #2
0
def buildGossipHints(world, rom):
    stoneAddresses = [0x938e4c, 0x938EA8, 0x938F04, 0x938F60, 0x938FBC, 0x939018, 0x939074, 0x9390D0, 0x93912C, 0x939188,
                      0x9391E4, 0x939240, 0x93929C, 0x9392F8, 0x939354, 0x9393B0, 0x93940C, 0x939468, 0x9394C4, 0x939520,
                      0x93957C, 0x9395D8, 0x939634, 0x939690, 0x9396EC, 0x939748, 0x9397A4, 0x939800, 0x93985C, 0x9398B8,
                      0x939914, 0x939970] #address for gossip stone text boxes, byte limit is 92


    alwaysLocations = getHintGroup('alwaysLocation')#These location will always have a hint somewhere in the world.
    
    sometimesSpace = (int((len(stoneAddresses) - len(alwaysLocations)*2)/2))
    sometimesLocations = getHintGroup('location')#A random selection of these locations will be in the hint pool.
    random.shuffle(sometimesLocations)
    sometimesLocations = sometimesLocations[0:sometimesSpace]
    hintList = alwaysLocations
    hintList.extend(alwaysLocations)
    hintList.extend(sometimesLocations)

    locationData = []
    for hint in  hintList:
        for locationWorld in world.get_locations():
            if hint.name == locationWorld.name:
                locationData.extend([locationWorld])         

    #hopefully fixes weird VC error where the last character from a previous text box would sometimes spill over into the next box.
    for address in range(stoneAddresses[0], 0x9399D8):
        rom.write_byte(address, 0x08)

    #shuffles the stone addresses for randomization, always locations will be placed first and twice
    random.shuffle(stoneAddresses)

    #loops through shuffled locations and addresses and builds hint.
    while locationData:
        currentLoc = locationData.pop(0)
        Block_code = getBytes((getHint(currentLoc.name).text))
        if currentLoc.item.type == 'Map' or currentLoc.item.type == 'Compass' or currentLoc.item.type == 'BossKey' or currentLoc.item.type == 'SmallKey':
            Block_code.extend(getBytes((getHint(currentLoc.item.type).text)))
        else:
            Block_code.extend(getBytes((getHint(currentLoc.item.name).text)))
        endText(Block_code)

        if len(Block_code) > 92:
            print('Too many characters in hint')
            Block_code = getBytes("I am Error.")
            Block_code.extend(getBytes(currentLoc.name))
            Block_code.extend(getBytes('&'))
            Block_code.extend(getBytes(currentLoc.item.name))
     
        rom.write_bytes(stoneAddresses.pop(0), Block_code)

    junkHints = getHintGroup('junkHint')
    random.shuffle(junkHints)
    while stoneAddresses:
        junkHint = junkHints.pop()
        Block_code = getBytes(junkHint.text)
        endText(Block_code)
        rom.write_bytes(stoneAddresses.pop(0), Block_code)
        
    return rom
Beispiel #3
0
def buildGanonText(world, messages):
    # empty now unused messages to make space for ganon lines
    update_message_by_id(messages, 0x70C8, " ")
    update_message_by_id(messages, 0x70C9, " ")
    update_message_by_id(messages, 0x70CA, " ")

    # lines before battle
    text = '\x08'
    ganonLines = getHintGroup('ganonLine', world)
    random.shuffle(ganonLines)
    text = get_raw_text(ganonLines.pop().text)
    update_message_by_id(messages, 0x70CB, text)

    # light arrow hint or validation chest item
    text = '\x08'
    if world.trials == 0:
        for location in world.get_locations():
            if location.item.name == 'Light Arrows':
                text = get_raw_text(
                    getHint('Light Arrow Location', world.clearer_hints).text)
                location_hint = location.hint.replace('Ganon\'s Castle',
                                                      'my castle')
                text += get_raw_text(location_hint)
                text += '!'
                break
    else:
        text = get_raw_text(
            getHint('Validation Line', world.clearer_hints).text)
        for location in world.get_locations():
            if location.name == 'Ganons Tower Boss Key Chest':
                text += get_raw_text(
                    getHint(getItemGenericName(location.item),
                            world.clearer_hints).text)

    update_message_by_id(messages, 0x70CC, text)
Beispiel #4
0
def buildGanonText(world, messages):
    # empty now unused messages to make space for ganon lines
    update_message_by_id(messages, 0x70C8, " ")
    update_message_by_id(messages, 0x70C9, " ")
    update_message_by_id(messages, 0x70CA, " ")

    # lines before battle
    ganonLines = getHintGroup('ganonLine', world)
    random.shuffle(ganonLines)
    text = get_raw_text(ganonLines.pop().text)
    update_message_by_id(messages, 0x70CB, text)

    # light arrow hint or validation chest item
    text = get_raw_text(
        getHint('Light Arrow Location', world.clearer_hints).text)
    if world.distribution.get_starting_item('Light Arrows') > 0:
        text += "\x05\x42your pocket\x05\x40"
    else:
        location = world.light_arrow_location
        location_hint = get_hint_area(location).replace(
            'Ganon\'s Castle', 'my castle')
        if world.id != location.world.id:
            text += "\x05\x42Player %d's\x05\x40 %s" % (
                location.world.id + 1, get_raw_text(location_hint))
        else:
            text += get_raw_text(location_hint)
    text += '!'

    update_message_by_id(messages, 0x70CC, text)
Beispiel #5
0
def buildGanonText(world, messages):
    # empty now unused messages to make space for ganon lines
    update_message_by_id(messages, 0x70C8, " ")
    update_message_by_id(messages, 0x70C9, " ")
    update_message_by_id(messages, 0x70CA, " ")

    # lines before battle
    ganonLines = getHintGroup('ganonLine', world)
    random.shuffle(ganonLines)
    text = get_raw_text(ganonLines.pop().text)
    update_message_by_id(messages, 0x70CB, text)

    # light arrow hint or validation chest item
    if world.trials == 0:
        text = get_raw_text(getHint('Light Arrow Location', world.clearer_hints).text)
        if world.distribution.get_starting_item('Light Arrows') > 0:
            text += "\x05\x42your pocket\x05\x40"
        else:
            location = world.light_arrow_location
            location_hint = location.hint.replace('Ganon\'s Castle', 'my castle')
            if world.id != location.world.id:
                text += "\x05\x42Player %d's\x05\x40 %s" % (location.world.id +1, get_raw_text(location_hint))
            else:
                text += get_raw_text(location_hint)
        text += '!'
    else:
        text = get_raw_text(getHint('Validation Line', world.clearer_hints).text)
        for location in world.get_filled_locations():
            if location.name == 'Ganons Tower Boss Key Chest':
                text += get_raw_text(getHint(getItemGenericName(location.item), world.clearer_hints).text)
                text += '!'
                break

    update_message_by_id(messages, 0x70CC, text)
def get_entrance_hint(spoiler, world, checked):
    if world.entrance_shuffle == 'off':
        return None

    entrance_hints = getHintGroup('entrance', world)
    entrance_hints = list(filter(lambda hint: hint.name not in checked, entrance_hints))
    valid_entrance_hints = [entrance_hint for entrance_hint in entrance_hints if world.get_entrance(entrance_hint.name).shuffled]

    if not valid_entrance_hints:
        return None

    entrance_hint = random.choice(valid_entrance_hints)
    entrance = world.get_entrance(entrance_hint.name)
    checked.append(entrance.name)

    entrance_text = entrance_hint.text

    if '#' not in entrance_text:
        entrance_text = '#%s#' % entrance_text

    connected_region = entrance.connected_region
    if connected_region.dungeon:
        region_text = getHint(connected_region.dungeon.name, world.clearer_hints).text
    else:
        region_text = getHint(connected_region.name, world.clearer_hints).text

    if '#' not in region_text:
        region_text = '#%s#' % region_text

    return (GossipText('%s %s.' % (entrance_text, region_text), ['Light Blue', 'Green']), None)
Beispiel #7
0
def get_junk_hint(spoiler, world, checked):
    hints = getHintGroup('junkHint', world)
    hints = list(filter(lambda hint: hint.name not in checked, hints))
    if not hints:
        return None

    hint = random.choice(hints)
    checked.append(hint.name)

    return (hint.text, None)
Beispiel #8
0
def get_junk_hint(spoiler, world, checked):
    hints = getHintGroup('junk', world)
    hints = list(filter(lambda hint: hint.name not in checked, hints))
    if not hints:
        return None

    hint = random.choice(hints)
    checked.add(hint.name)

    return (GossipText(hint.text, prefix=''), None)
Beispiel #9
0
def get_good_loc_hint(spoiler, world, checked):
    locations = getHintGroup('location', world)
    locations = list(filter(lambda hint: hint.name not in checked, locations))
    if not locations:
        return None

    hint = random.choice(locations)
    location = world.get_location(hint.name)
    checked.append(location.name)

    return (buildHintString(colorText(getHint(location.name, world.clearer_hints).text, 'Green') + " " + \
                colorText(getHint(getItemGenericName(location.item), world.clearer_hints).text, 'Red') + "."), location)
Beispiel #10
0
def get_specific_hint(spoiler, world, checked, type):
    hintGroup = getHintGroup(type, world)
    hintGroup = list(filter(lambda hint: hint.name not in checked, hintGroup))
    if not hintGroup:
        return None

    hint = random.choice(hintGroup)
    location = world.get_location(hint.name)
    checked.append(location.name)

    location_text = hint.text
    if '#' not in location_text:
        location_text = '#%s#' % location_text   
    item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text

    return (GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), location)
Beispiel #11
0
def get_good_loc_hint(spoiler, world, checked):
    locations = getHintGroup('location', world)
    locations = list(filter(lambda hint: hint.name not in checked, locations))
    if not locations:
        return None

    hint = random.choice(locations)
    location = world.get_location(hint.name)
    checked.append(location.name)

    location_text = getHint(location.name, world.clearer_hints).text
    if '#' not in location_text:
        location_text = '#%s#' % location_text
    item_text = getHint(getItemGenericName(location.item),
                        world.clearer_hints).text

    return (GossipText('%s #%s#.' % (location_text, item_text),
                       ['Green', 'Red']), location)
Beispiel #12
0
def buildGanonText(world, rom):
    # reorganize text header files to make space for text
    rom.write_bytes(0xB884B1, [0x03, 0x41, 0xED])
    rom.write_bytes(0xB884B9, [0x03, 0x41, 0xEE])
    rom.write_bytes(0xB884C1, [0x03, 0x41, 0xEF])
    rom.write_bytes(0xB884C9, [0x03, 0x42, 0x99])

    # clear space for new text
    for address in range(0x9611EC, 0x961349):
        rom.write_byte(address, 0x08)

    Block_code = []
    # lines before battle, 160 characters max
    ganonLines = getHintGroup('ganonLine')
    random.shuffle(ganonLines)
    Block_code = getBytes(ganonLines.pop().text)
    endText(Block_code)
    rom.write_bytes(0x9611F1, Block_code)

    if world.fast_ganon:
        for location in world.get_locations():
            if location.item.name == 'Light Arrows':
                Block_code = getBytes(getHint('Light Arrow Location').text)
                Block_code.extend(getBytes(location.hint))
                Block_code.extend(getBytes('!'))
                break
        endText(Block_code)
    else:
        Block_code = getBytes(getHint('Validation Line').text)
        for location in world.get_locations():
            if location.name == 'Ganons Tower Boss Key Chest':
                Block_code.extend(getBytes((getHint(location.item.name).text)))
        endText(Block_code)

    rom.write_bytes(0x96129D, Block_code)

    return rom
Beispiel #13
0
def buildGossipHints(spoiler, world):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False

    max_states = State.get_states_with_items([w.state for w in spoiler.worlds], [])
    for stone in gossipLocations.values():
        stone.reachable = \
            max_states[world.id].can_reach(stone.location, resolution_hint='Location') and \
            max_states[world.id].guarantee_hint()

    checkedLocations = []

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('always', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.append(hint.name)

        location_text = getHint(location.name, world.clearer_hints).text
        if '#' not in location_text:
            location_text = '#%s#' % location_text
        item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
        add_hint(spoiler, world, stoneIDs, GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler, world, stoneIDs, GossipText("#Ganon's Tower# is protected by a powerful barrier.", ['Pink']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler, world, stoneIDs, GossipText("Sheik dispelled the barrier around #Ganon's Tower#.", ['Yellow']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial,skipped in world.skipped_trials.items():
            if skipped:
                add_hint(spoiler, world, stoneIDs,GossipText("the #%s Trial#  was dispelled by Sheik." % trial, ['Yellow']), hint_dist['trial'][1], force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial,skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler, world, stoneIDs, GossipText("the #%s Trial# protects Ganon's Tower." % trial, ['Pink']), hint_dist['trial'][1], force_reachable=True)

    hint_types = list(hint_types)
    hint_prob  = list(hint_prob)
    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = 'random'
        else:
            try:
                hint_type = random_choices(hint_types, weights=hint_prob)[0]
            except IndexError:
                raise Exception('Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, gossip_text, hint_dist[hint_type][1], location)
            if not place_ok and world.hint_dist == "tournament":
                fixed_hint_types.insert(0, hint_type)
Beispiel #14
0
def buildWorldGossipHints(spoiler, world, checkedLocations=None):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = 0
    world.woth_dungeon = 0

    search = Search.max_explore([w.state for w in spoiler.worlds])
    for stone in gossipLocations.values():
        stone.reachable = (
            search.spot_access(world.get_location(stone.location))
            and search.state_list[world.id].guarantee_hint())

    if checkedLocations is None:
        checkedLocations = set()

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    if 'disabled' in world.hint_dist_user:
        for stone_name in world.hint_dist_user['disabled']:
            try:
                stone_id = gossipLocations_reversemap[stone_name]
            except KeyError:
                raise ValueError(f'Gossip stone location "{stone_name}" is not valid')
            stoneIDs.remove(stone_id)
            (gossip_text, _) = get_junk_hint(spoiler, world, checkedLocations)
            spoiler.hints[world.id][stone_id] = gossip_text

    stoneGroups = []
    if 'groups' in world.hint_dist_user:
        for group_names in world.hint_dist_user['groups']:
            group = []
            for stone_name in group_names:
                try:
                    stone_id = gossipLocations_reversemap[stone_name]
                except KeyError:
                    raise ValueError(f'Gossip stone location "{stone_name}" is not valid')

                stoneIDs.remove(stone_id)
                group.append(stone_id)
            stoneGroups.append(group)
    # put the remaining locations into singleton groups
    stoneGroups.extend([[id] for id in stoneIDs])

    random.shuffle(stoneGroups)

    # Create list of items for which we want hints. If Bingosync URL is supplied, include items specific to that bingo.
    # If not (or if the URL is invalid), use generic bingo hints
    if world.hint_dist == "bingo":
        bingoDefaults = read_json(data_path('Bingo/generic_bingo_hints.json'))
        if world.bingosync_url is not None and world.bingosync_url.startswith("https://bingosync.com/"): # Verify that user actually entered a bingosync URL
            logger = logging.getLogger('')
            logger.info("Got Bingosync URL. Building board-specific goals.")
            world.item_hints = buildBingoHintList(world.bingosync_url)
        else:
            world.item_hints = bingoDefaults['settings']['item_hints']

        if world.tokensanity in ("overworld", "all") and "Suns Song" not in world.item_hints:
            world.item_hints.append("Suns Song")

        if world.shopsanity != "off" and "Progressive Wallet" not in world.item_hints:
            world.item_hints.append("Progressive Wallet")


    # Load hint distro from distribution file or pre-defined settings
    #
    # 'fixed' key is used to mimic the tournament distribution, creating a list of fixed hint types to fill
    # Once the fixed hint type list is exhausted, weighted random choices are taken like all non-tournament sets
    # This diverges from the tournament distribution where leftover stones are filled with sometimes hints (or random if no sometimes locations remain to be hinted)
    sorted_dist = {}
    type_count = 1
    hint_dist = OrderedDict({})
    fixed_hint_types = []
    max_order = 0
    for hint_type in world.hint_dist_user['distribution']:
        if world.hint_dist_user['distribution'][hint_type]['order'] > 0:
            hint_order = int(world.hint_dist_user['distribution'][hint_type]['order'])
            sorted_dist[hint_order] = hint_type
            if max_order < hint_order:
                max_order = hint_order
            type_count = type_count + 1
    if (type_count - 1) < max_order:
        raise Exception("There are gaps in the custom hint orders. Please revise your plando file to remove them.")
    for i in range(1, type_count):
        hint_type = sorted_dist[i]
        if world.hint_dist_user['distribution'][hint_type]['copies'] > 0:
            fixed_num = world.hint_dist_user['distribution'][hint_type]['fixed']
            hint_weight = world.hint_dist_user['distribution'][hint_type]['weight']
        else:
            logging.getLogger('').warning("Hint copies is zero for type %s. Assuming this hint type should be disabled.", hint_type)
            fixed_num = 0
            hint_weight = 0
        hint_dist[hint_type] = (hint_weight, world.hint_dist_user['distribution'][hint_type]['copies'])
        hint_dist.move_to_end(hint_type)
        fixed_hint_types.extend([hint_type] * int(fixed_num))

    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints, only if hint copies > 0
    if hint_dist['always'][1] > 0:
        alwaysLocations = getHintGroup('always', world)
        for hint in alwaysLocations:
            location = world.get_location(hint.name)
            checkedLocations.add(hint.name)
            if location.item.name in bingoBottlesForHints and world.hint_dist == 'bingo':
                always_item = 'Bottle'
            else:
                always_item = location.item.name
            if always_item in world.item_hints:
                world.item_hints.remove(always_item)

            if location.name in world.hint_text_overrides:
                location_text = world.hint_text_overrides[location.name]
            else:
                location_text = getHint(location.name, world.clearer_hints).text
            if '#' not in location_text:
                location_text = '#%s#' % location_text
            item_text = getHint(getItemGenericName(location.item), world.clearer_hints).text
            add_hint(spoiler, world, stoneGroups, GossipText('%s #%s#.' % (location_text, item_text), ['Green', 'Red']), hint_dist['always'][1], location, force_reachable=True)
            logging.getLogger('').debug('Placed always hint for %s.', location.name)

    # Add trial hints, only if hint copies > 0
    if hint_dist['trial'][1] > 0:
        if world.trials_random and world.trials == 6:
            add_hint(spoiler, world, stoneGroups, GossipText("#Ganon's Tower# is protected by a powerful barrier.", ['Pink']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials_random and world.trials == 0:
            add_hint(spoiler, world, stoneGroups, GossipText("Sheik dispelled the barrier around #Ganon's Tower#.", ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials < 6 and world.trials > 3:
            for trial,skipped in world.skipped_trials.items():
                if skipped:
                    add_hint(spoiler, world, stoneGroups,GossipText("the #%s Trial# was dispelled by Sheik." % trial, ['Yellow']), hint_dist['trial'][1], force_reachable=True)
        elif world.trials <= 3 and world.trials > 0:
            for trial,skipped in world.skipped_trials.items():
                if not skipped:
                    add_hint(spoiler, world, stoneGroups, GossipText("the #%s Trial# protects Ganon's Tower." % trial, ['Pink']), hint_dist['trial'][1], force_reachable=True)

    # Add user-specified hinted item locations if using a built-in hint distribution
    # Raise error if hint copies is zero
    if len(world.item_hints) > 0 and world.hint_dist_user['named_items_required']:
        if hint_dist['named-item'][1] == 0:
            raise Exception('User-provided item hints were requested, but copies per named-item hint is zero')
        else:
            for i in range(0, len(world.item_hints)):
                hint = get_specific_item_hint(spoiler, world, checkedLocations)
                if hint == None:
                    raise Exception('No valid hints for user-provided item')
                else:
                    gossip_text, location = hint
                    place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, hint_dist['named-item'][1], location)
                    if not place_ok:
                        raise Exception('Not enough gossip stones for user-provided item hints')

    hint_types = list(hint_types)
    hint_prob  = list(hint_prob)
    hint_counts = {}

    custom_fixed = True
    while stoneGroups:
        if fixed_hint_types:
            hint_type = fixed_hint_types.pop(0)
            copies = hint_dist[hint_type][1]
            if copies > len(stoneGroups):
                # Quiet to avoid leaking information.
                logging.getLogger('').debug(f'Not enough gossip stone locations ({len(stoneGroups)} groups) for fixed hint type {hint_type} with {copies} copies, proceeding with available stones.')
                copies = len(stoneGroups)
        else:
            custom_fixed = False
            # Make sure there are enough stones left for each hint type
            num_types = len(hint_types)
            hint_types = list(filter(lambda htype: hint_dist[htype][1] <= len(stoneGroups), hint_types))
            new_num_types = len(hint_types)
            if new_num_types == 0:
                raise Exception('Not enough gossip stone locations for remaining weighted hint types.')
            elif new_num_types < num_types:
                hint_prob = []
                for htype in hint_types:
                    hint_prob.append(hint_dist[htype][0])
            try:
                # Weight the probabilities such that hints that are over the expected proportion
                # will be drawn less, and hints that are under will be drawn more.
                # This tightens the variance quite a bit. The variance can be adjusted via the power
                weighted_hint_prob = []
                for w1_type, w1_prob in zip(hint_types, hint_prob):
                    p = w1_prob
                    if p != 0: # If the base prob is 0, then it's 0
                        for w2_type, w2_prob in zip(hint_types, hint_prob):
                            if w2_prob != 0: # If the other prob is 0, then it has no effect
                                # Raising this term to a power greater than 1 will decrease variance
                                # Conversely, a power less than 1 will increase variance
                                p = p * (((hint_counts.get(w2_type, 0) / w2_prob) + 1) / ((hint_counts.get(w1_type, 0) / w1_prob) + 1))
                    weighted_hint_prob.append(p)

                hint_type = random_choices(hint_types, weights=weighted_hint_prob)[0]
                copies = hint_dist[hint_type][1]
            except IndexError:
                raise Exception('Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
            # Zero out the probability in the base distribution in case the probability list is modified
            # to fit hint types in remaining gossip stones
            hint_dist[hint_type] = (0.0, copies)
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneGroups, gossip_text, copies, location)
            if place_ok:
                hint_counts[hint_type] = hint_counts.get(hint_type, 0) + 1
                if location is None:
                    logging.getLogger('').debug('Placed %s hint.', hint_type)
                else:
                    logging.getLogger('').debug('Placed %s hint for %s.', hint_type, location.name)
            if not place_ok and custom_fixed:
                logging.getLogger('').debug('Failed to place %s fixed hint for %s.', hint_type, location.name)
                fixed_hint_types.insert(0, hint_type)
Beispiel #15
0
def buildGossipHints(spoiler, world):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False

    max_states = State.get_states_with_items([w.state for w in spoiler.worlds],
                                             [])
    for id, stone in gossipLocations.items():
        stone.reachable = \
            max_states[world.id].can_reach(stone.location, resolution_hint='Location') and \
            max_states[world.id].guarantee_hint()

    checkedLocations = []

    stoneIDs = list(gossipLocations.keys())
    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, hint_count = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('alwaysLocation', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.append(hint.name)
        add_hint(spoiler, world, stoneIDs, buildHintString(colorText(getHint(location.name, world.clearer_hints).text, 'Green') + " " + \
            colorText(getHint(getItemGenericName(location.item), world.clearer_hints).text, 'Red') + "."), hint_dist['always'][1], location, force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 buildHintString(
                     colorText("Ganon's Tower", 'Pink') +
                     " is protected by a powerful barrier."),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 buildHintString("Sheik dispelled the barrier around " +
                                 colorText("Ganon's Tower", 'Yellow')),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial, skipped in world.skipped_trials.items():
            if skipped:
                add_hint(
                    spoiler,
                    world,
                    stoneIDs,
                    buildHintString("the " +
                                    colorText(trial + " Trial", 'Yellow') +
                                    " was dispelled by Sheik."),
                    hint_dist['trial'][1],
                    force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial, skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         buildHintString("the " +
                                         colorText(trial + " Trial", 'Pink') +
                                         " protects Ganon's Tower."),
                         hint_dist['trial'][1],
                         force_reachable=True)

    hint_types = list(hint_types)
    hint_prob = list(hint_prob)
    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = 'loc'
        else:
            try:
                [hint_type] = random_choices(hint_types, weights=hint_prob)
            except IndexError:
                raise Exception(
                    'Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
        else:
            text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, text,
                                hint_dist[hint_type][1], location)
            if not place_ok and world.hint_dist == "tournament":
                fixed_hint_types.insert(0, hint_type)
Beispiel #16
0
def buildGossipHints(world, messages):

    stoneIDs = [
        0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409,
        0x040A, 0x040B, 0x040C, 0x040D, 0x040E, 0x040F, 0x0410, 0x0411, 0x0412,
        0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B,
        0x041C, 0x041D, 0x041E, 0x041F, 0x0420
    ]

    #shuffles the stone addresses for randomization, always locations will be placed first and twice
    random.shuffle(stoneIDs)

    spoilerHintsList.append('-Way of the Hero-')
    # add required items locations for hints (good hints)
    requiredSample = []
    requiredSample1 = world.spoiler.required_locations
    #print([(location.parent_region.name, location.item.name) for location in requiredSample])
    # loop through the locations we got for "always required locatiosns"
    # if you find lens, remove it
    for l in requiredSample1:
        if l.item.name in ['Master Sword', 'Stone of Agony']:
            continue
        else:
            requiredSample.append(l)
    #print([(location.parent_region.name, location.item.name) for location in requiredSample])
    if len(requiredSample) >= 4:
        requiredSample = random.sample(requiredSample, 4)  #Pick exactly 4
    for location in requiredSample:
        for _ in range(0, 3):  #and distribute each 3 times (12 / 32)
            if location.parent_region.dungeon:
                update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(location.parent_region.dungeon.name).text + \
                    " is on the way of the hero."))
                spoilerHintsList.append(location.parent_region.dungeon.name +
                                        ': ' + location.item.name)
                #print(location.parent_region.dungeon.name, ': ', location.item.name)
            else:
                update_hint(
                    messages, stoneIDs.pop(0),
                    buildHintString(location.parent_region.name +
                                    " is on the way of the hero."))
                spoilerHintsList.append(location.parent_region.name + ": " +
                                        location.item.name)
                #print(location.parent_region.name, ': ', location.item.name)
    # Don't repeat hints
    checkedLocations = []

    spoilerHintsList.append('\n-Required Locations-')
    # Add required location hints
    alwaysLocations = getHintGroup('alwaysLocation', world)
    for hint in alwaysLocations:
        for locationWorld in world.get_locations():
            if hint.name == locationWorld.name:
                checkedLocations.append(hint.name)
                for _ in range(0, 2):  #populate each of these twice (24 / 32)
                    update_hint(messages, stoneIDs.pop(0), getHint(locationWorld.name).text + " " + \
                        getHint(getItemGenericName(locationWorld.item)).text + ".")
                    spoilerHintsList.append(locationWorld.name + ": " +
                                            locationWorld.item.name)

##    spoilerHintsList.append('\n-Good Locations-')
##    # Add good location hints
##    sometimesLocations = getHintGroup('location', world)
##    if sometimesLocations:
##        # for _ in range(0, random.randint(9,10) - len(alwaysLocations)):
##        for _ in range(0, 2): # Exactly 2 of these (26 /
##            hint = random.choice(sometimesLocations)
##            # Repick if location isn't new
##            while hint.name in checkedLocations or hint.name in alwaysLocations:
##                hint = random.choice(sometimesLocations)
##
##            for locationWorld in world.get_locations():
##                if hint.name == locationWorld.name:
##                    checkedLocations.append(locationWorld.name)
##                    update_hint(messages, stoneIDs.pop(0), getHint(locationWorld.name).text + " " + \
##                        getHint(getItemGenericName(locationWorld.item)).text + ".")
##                    spoilerHintsList.append(locationWorld.name + ': ' + locationWorld.item.name)

##    spoilerHintsList.append('\n-BadItem Dungeon-')
##    # add bad dungeon locations hints
##    for dungeon in random.sample(world.dungeons, 2): # Exactly 2 of these (28 / 32)
##        # Choose a randome dungeon location that is a non-dungeon item
##        locationWorld = random.choice([location for region in dungeon.regions for location in world.get_region(region).locations
##            if location.item.type != 'Event' and \
##            not location.name in eventlocations and \
##            not isDungeonItem(location.item) and \
##            (world.tokensanity != 'off' or location.item.name != 'Gold Skulltulla Token') and\
##            location.item.type != 'Song'])
##
##        checkedLocations.append(locationWorld.name)
##        update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(dungeon.name).text + \
##            " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
##        spoilerHintsList.append(dungeon.name + ': ' + locationWorld.item.name)
##
##    spoilerHintsList.append('\n-BadItem Overworld-')
##    # add bad overworld locations hints
##    # only choose location if it is new and a proper item from the overworld
##    overworldlocations = [locationWorld for locationWorld in world.get_locations()
##            if not locationWorld.name in checkedLocations and \
##            not locationWorld.name in alwaysLocations and \
##            not locationWorld.name in sometimesLocations and \
##            locationWorld.item.type != 'Event' and \
##            not locationWorld.name in eventlocations and \
##            (world.tokensanity == 'all' or locationWorld.item.name != 'Gold Skulltulla Token') and \
##            not locationWorld.parent_region.dungeon and \
##            not locationWorld.name in checkedLocations]
##    overworldSample = overworldlocations
##    if len(overworldSample) >= 2: # Only need to check for 2
##        overworldSample = random.sample(overworldlocations, 2) # Exactly 2 of these (30 / 32)
##    for locationWorld in overworldSample:
##        checkedLocations.append(locationWorld.name)
##        update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
##            " can be found at " + locationWorld.parent_region.name + "."))
##        spoilerHintsList.append(locationWorld.parent_region.name + ': ' + locationWorld.item.name)

#Populate 4 bad item hints
    spoilerHintsList.append('\n-Bad Items-')
    # add good item hints
    # only choose location if it is new and a good item
    if world.shuffle_weird_egg:
        gooditems.append('Weird Egg')
    baditemlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            not locationWorld.name in alwaysLocations and \
            (world.tokensanity == 'all' or locationWorld.item.name != 'Gold Skulltulla Token') and \
            locationWorld.item.type != 'Event' and \
            not locationWorld.name in eventlocations and \
            not isDungeonItem(locationWorld.item) and \
            locationWorld.item.name not in gooditems]
    baditemSample = random.sample(baditemlocations, 4)
    # Don't need this check, we'll fill the rest from this pool, usually only 2, can be more in very rare cases
    # if len(gooditemSample) >= 5:
    #    gooditemSample = random.sample(gooditemlocations, random.randint(3,5))
    # for locationWorld in gooditemSample:
    for locationWorld in baditemSample:
        #locationWorld = gooditemSample.pop()
        checkedLocations.append(locationWorld.name)
        if locationWorld.parent_region.dungeon:
            update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(locationWorld.parent_region.dungeon.name).text + \
                " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
            spoilerHintsList.append(locationWorld.parent_region.dungeon.name +
                                    ': ' + locationWorld.item.name)
        else:
            update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
                " can be found at " + locationWorld.parent_region.name + "."))
            spoilerHintsList.append(locationWorld.parent_region.name + ': ' +
                                    locationWorld.item.name)

    spoilerHintsList.append('\n-Good Items-')
    # add good item hints
    # only choose location if it is new and a good item
    if world.shuffle_weird_egg:
        gooditems.append('Weird Egg')
    gooditemlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            locationWorld.item.name in gooditems]
    gooditemSample = gooditemlocations
    # Don't need this check, we'll fill the rest from this pool, usually only 2, can be more in very rare cases
    # if len(gooditemSample) >= 5:
    #    gooditemSample = random.sample(gooditemlocations, random.randint(3,5))
    # for locationWorld in gooditemSample:
    random.shuffle(gooditemSample)
    while stoneIDs:
        locationWorld = gooditemSample.pop()
        checkedLocations.append(locationWorld.name)
        if locationWorld.parent_region.dungeon:
            update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(locationWorld.parent_region.dungeon.name).text + \
                " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
            spoilerHintsList.append(locationWorld.parent_region.dungeon.name +
                                    ': ' + locationWorld.item.name)
        else:
            update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
                " can be found at " + locationWorld.parent_region.name + "."))
            spoilerHintsList.append(locationWorld.parent_region.name + ': ' +
                                    locationWorld.item.name)

    #spoilerHintsList.append('\n-Junk-\n')

    f = open("hints.txt", "w")
    f.write('~~~ NEW HINTS ~~~\n\n')
    f.write('\n'.join(spoilerHintsList))
    f.close()
Beispiel #17
0
def update_goal_items(spoiler):
    worlds = spoiler.worlds

    # get list of all of the progressive items that can appear in hints
    # all_locations: all progressive items. have to collect from these
    # item_locations: only the ones that should appear as "required"/WotH
    all_locations = [
        location for world in worlds
        for location in world.get_filled_locations()
    ]
    # Set to test inclusion against
    item_locations = {
        location
        for location in all_locations if location.item.majoritem
        and not location.locked and location.item.name != 'Triforce Piece'
    }

    # required_locations[category.name][goal.name][world_id] = [...]
    required_locations = defaultdict(
        lambda: defaultdict(lambda: defaultdict(list)))
    priority_locations = {(world.id): {} for world in worlds}

    # rebuild hint exclusion list
    for world in worlds:
        hintExclusions(world, clear_cache=True)

    # getHintGroup relies on hint exclusion list
    always_locations = [
        location.name for world in worlds
        for location in getHintGroup('always', world)
    ]
    if spoiler.playthrough:
        # Skip even the checks
        _maybe_set_light_arrows = lambda _: None
    else:
        _maybe_set_light_arrows = maybe_set_light_arrows

    if worlds[0].enable_goal_hints:
        # References first world for goal categories only
        for cat_name, category in worlds[0].locked_goal_categories.items():
            for cat_world in worlds:
                search = Search([world.state for world in worlds])
                search.collect_pseudo_starting_items()

                cat_state = list(
                    filter(lambda s: s.world.id == cat_world.id,
                           search.state_list))
                category_locks = lock_category_entrances(category, cat_state)

                if category.is_beaten(search):
                    unlock_category_entrances(category_locks, cat_state)
                    continue

                full_search = search.copy()
                full_search.collect_locations()
                reachable_goals = {}
                # Goals are changed for beatable-only accessibility per-world
                category.update_reachable_goals(search, full_search)
                reachable_goals = full_search.beatable_goals_fast(
                    {cat_name: category}, cat_world.id)
                identified_locations = search_goals(
                    {cat_name: category}, reachable_goals, search,
                    priority_locations, all_locations, item_locations,
                    always_locations, _maybe_set_light_arrows)
                # Multiworld can have all goals for one player's bridge entirely
                # locked by another player's bridge. Therefore, we can't assume
                # accurate required location lists by locking every world's
                # entrance locks simultaneously. We must run a new search on the
                # same category for each world's entrance locks.
                for goal_name, world_location_lists in identified_locations[
                        cat_name].items():
                    required_locations[cat_name][goal_name][
                        cat_world.id] = list(identified_locations[cat_name]
                                             [goal_name][cat_world.id])

                unlock_category_entrances(category_locks, cat_state)

    search = Search([world.state for world in worlds])
    search.collect_pseudo_starting_items()
    reachable_goals = {}
    # Force all goals to be unreachable if goal hints are not part of the distro
    # Saves a minor amount of search time. Most of the benefit is skipping the
    # locked goal categories. This section still needs to run to generate WOTH.
    # WOTH isn't a goal, so it still is searched successfully.
    if worlds[0].enable_goal_hints:
        full_search = search.copy()
        full_search.collect_locations()
        for cat_name, category in worlds[0].unlocked_goal_categories.items():
            category.update_reachable_goals(search, full_search)
        reachable_goals = full_search.beatable_goals_fast(
            worlds[0].unlocked_goal_categories)
    identified_locations = search_goals(worlds[0].unlocked_goal_categories,
                                        reachable_goals,
                                        search,
                                        priority_locations,
                                        all_locations,
                                        item_locations,
                                        always_locations,
                                        _maybe_set_light_arrows,
                                        search_woth=True)
    required_locations.update(identified_locations)
    woth_locations = list(required_locations['way of the hero'])
    del required_locations['way of the hero']

    # Update WOTH items
    woth_locations_dict = {}
    for world in worlds:
        woth_locations_dict[world.id] = list(
            filter(lambda location: location.world.id == world.id,
                   woth_locations))
    spoiler.required_locations = woth_locations_dict

    # Fallback to way of the hero required items list if all goals/goal categories already satisfied.
    # Do not use if the default woth-like goal was already added for open bridge/open ganon.
    # If the woth list is also empty, fails gracefully to the next hint type for the distro in either case.
    # required_locations_dict[goal_world][category.name][goal.name][world.id] = [...]
    required_locations_dict = defaultdict(
        lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list))))
    if not required_locations and 'ganon' not in worlds[
            0].goal_categories and worlds[0].hint_dist_user[
                'use_default_goals'] and worlds[0].enable_goal_hints:
        for world in worlds:
            locations = [(location, 1, 1, [world.id])
                         for location in spoiler.required_locations[world.id]]
            c = GoalCategory('ganon', 30, goal_count=1, minimum_goals=1)
            g = Goal(world,
                     'the hero',
                     'way of the hero',
                     'White',
                     items=[{
                         'name': 'Triforce',
                         'quantity': 1,
                         'minimum': 1,
                         'hintable': True
                     }])
            g.required_locations = locations
            c.add_goal(g)
            world.goal_categories[c.name] = c
            # The real protagonist of the story
            required_locations_dict[world.id]['ganon']['the hero'][
                world.id] = [l[0] for l in locations]
            spoiler.goal_categories[world.id] = {c.name: c.copy()}
    else:
        for world in worlds:
            for cat_name, category in world.goal_categories.items():
                if cat_name not in required_locations:
                    continue
                for goal in category.goals:
                    if goal.name not in required_locations[category.name]:
                        continue
                    # Filter the required locations to only include locations in the goal's world.
                    # The fulfilled goal can be for another world, whose ID is saved previously as
                    # the fourth entry in each location tuple
                    goal_locations = defaultdict(list)
                    for goal_world, locations in required_locations[
                            category.name][goal.name].items():
                        for location in locations:
                            # filter isn't strictly necessary for the spoiler dictionary, but this way
                            # we only loop once to do that and grab the required locations for the
                            # current goal
                            if location[0].world.id != world.id:
                                continue
                            # The spoiler shows locations across worlds contributing to this world's goal
                            required_locations_dict[goal_world][category.name][
                                goal.name][world.id].append(location[0])
                            goal_locations[location[0]].append(goal_world)
                    goal.required_locations = [
                        (location, 1, 1, world_ids)
                        for location, world_ids in goal_locations.items()
                    ]
        # Copy of goal categories for the spoiler log to reference
        # since the hint algorithm mutates the world copy
        for world in worlds:
            spoiler.goal_categories[world.id] = {
                cat_name: category.copy()
                for cat_name, category in world.goal_categories.items()
            }
    spoiler.goal_locations = required_locations_dict
Beispiel #18
0
def buildWorldGossipHints(spoiler, world, checkedLocations=None):
    # rebuild hint exclusion list
    hintExclusions(world, clear_cache=True)

    world.barren_dungeon = False
    world.woth_dungeon = 0

    search = Search.max_explore([w.state for w in spoiler.worlds])
    for stone in gossipLocations.values():
        stone.reachable = (search.spot_access(
            world.get_location(stone.location))
                           and search.state_list[world.id].guarantee_hint())

    if checkedLocations is None:
        checkedLocations = set()

    stoneIDs = list(gossipLocations.keys())

    world.distribution.configure_gossip(spoiler, stoneIDs)

    random.shuffle(stoneIDs)

    hint_dist = hint_dist_sets[world.hint_dist]
    hint_types, hint_prob = zip(*hint_dist.items())
    hint_prob, _ = zip(*hint_prob)

    # Add required location hints
    alwaysLocations = getHintGroup('always', world)
    for hint in alwaysLocations:
        location = world.get_location(hint.name)
        checkedLocations.add(hint.name)

        location_text = getHint(location.name, world.clearer_hints).text
        if '#' not in location_text:
            location_text = '#%s#' % location_text
        item_text = getHint(getItemGenericName(location.item),
                            world.clearer_hints).text
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText('%s #%s#.' % (location_text, item_text),
                            ['Green', 'Red']),
                 hint_dist['always'][1],
                 location,
                 force_reachable=True)

    # Add trial hints
    if world.trials_random and world.trials == 6:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText(
                     "#Ganon's Tower# is protected by a powerful barrier.",
                     ['Pink']),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials_random and world.trials == 0:
        add_hint(spoiler,
                 world,
                 stoneIDs,
                 GossipText(
                     "Sheik dispelled the barrier around #Ganon's Tower#.",
                     ['Yellow']),
                 hint_dist['trial'][1],
                 force_reachable=True)
    elif world.trials < 6 and world.trials > 3:
        for trial, skipped in world.skipped_trials.items():
            if skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         GossipText(
                             "the #%s Trial# was dispelled by Sheik." % trial,
                             ['Yellow']),
                         hint_dist['trial'][1],
                         force_reachable=True)
    elif world.trials <= 3 and world.trials > 0:
        for trial, skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(spoiler,
                         world,
                         stoneIDs,
                         GossipText(
                             "the #%s Trial# protects Ganon's Tower." % trial,
                             ['Pink']),
                         hint_dist['trial'][1],
                         force_reachable=True)

    hint_types = list(hint_types)
    hint_prob = list(hint_prob)
    hint_counts = {}

    if world.hint_dist == "tournament":
        fixed_hint_types = []
        for hint_type in hint_types:
            fixed_hint_types.extend([hint_type] * int(hint_dist[hint_type][0]))
        fill_hint_types = ['sometimes', 'random']
        current_fill_type = fill_hint_types.pop(0)

    while stoneIDs:
        if world.hint_dist == "tournament":
            if fixed_hint_types:
                hint_type = fixed_hint_types.pop(0)
            else:
                hint_type = current_fill_type
        else:
            try:
                # Weight the probabilities such that hints that are over the expected proportion
                # will be drawn less, and hints that are under will be drawn more.
                # This tightens the variance quite a bit. The variance can be adjusted via the power
                weighted_hint_prob = []
                for w1_type, w1_prob in zip(hint_types, hint_prob):
                    p = w1_prob
                    if p != 0:  # If the base prob is 0, then it's 0
                        for w2_type, w2_prob in zip(hint_types, hint_prob):
                            if w2_prob != 0:  # If the other prob is 0, then it has no effect
                                # Raising this term to a power greater than 1 will decrease variance
                                # Conversely, a power less than 1 will increase variance
                                p = p * (
                                    ((hint_counts.get(w2_type, 0) / w2_prob) +
                                     1) /
                                    ((hint_counts.get(w1_type, 0) / w1_prob) +
                                     1))
                    weighted_hint_prob.append(p)

                hint_type = random_choices(hint_types,
                                           weights=weighted_hint_prob)[0]
            except IndexError:
                raise Exception(
                    'Not enough valid hints to fill gossip stone locations.')

        hint = hint_func[hint_type](spoiler, world, checkedLocations)

        if hint == None:
            index = hint_types.index(hint_type)
            hint_prob[index] = 0
            if world.hint_dist == "tournament" and hint_type == current_fill_type:
                logging.getLogger('').info(
                    'Not enough valid %s hints for tournament distribution.',
                    hint_type)
                if fill_hint_types:
                    current_fill_type = fill_hint_types.pop(0)
                    logging.getLogger('').info(
                        'Switching to %s hints to fill remaining gossip stone locations.',
                        current_fill_type)
                else:
                    raise Exception(
                        'Not enough valid hints for tournament distribution.')
        else:
            gossip_text, location = hint
            place_ok = add_hint(spoiler, world, stoneIDs, gossip_text,
                                hint_dist[hint_type][1], location)
            if place_ok:
                hint_counts[hint_type] = hint_counts.get(hint_type, 0) + 1
            if not place_ok and world.hint_dist == "tournament":
                logging.getLogger('').debug('Failed to place %s hint for %s.',
                                            hint_type, location.name)
                fixed_hint_types.insert(0, hint_type)
Beispiel #19
0
def buildGossipHints(world, messages):

    stoneIDs = [
        0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409,
        0x040A, 0x040B, 0x040C, 0x040D, 0x040E, 0x040F, 0x0410, 0x0411, 0x0412,
        0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B,
        0x041C, 0x041D, 0x041E, 0x041F, 0x0420
    ]

    #shuffles the stone addresses for randomization, always locations will be placed first and twice
    random.shuffle(stoneIDs)

    stoneLocations = {
        0x0401: 'Zoras Fountain - Fairy',
        0x0402: 'Zoras Fountain - Jabu',
        0x0403: 'Lake Hylia - Lab',
        0x0404: 'Death Mountain Trail - Biggoron',
        0x0405: 'Death Mountain Crater - Bombable Wall',
        0x0406: 'Temple of Time - Left',
        0x0407: 'Temple of Time - Middle-Left',
        0x0408: 'Lake Hylia - South-West Corner',
        0x0409: 'Zoras Domain - Mweep',
        0x040A: 'Graveyard - Shadow Temple',
        0x040B: 'Hyrule Castle - Rock Wall',
        0x040C: 'Zoras River - Waterfall',
        0x040D: 'Zoras River - Plateau',
        0x040E: 'Temple of Time - Middle-Right',
        0x040F: 'Lake Hylia - South-East Corner',
        0x0410: 'Temple of Time - Right',
        0x0411: 'Gerudo Valley - Waterfall',
        0x0412: 'Hyrule Castle - Malon',
        0x0413: 'Hyrule Castle - Storms Grotto',
        0x0414: 'Dodongos Cavern - Bombable Wall',
        0x0415: 'Goron City - Maze',
        0x0416: 'Sacred Forest Meadow - Maze Lower',
        0x0417: 'Sacred Forest Meadow - Maze Upper',
        0x0418: 'Generic Grotto',
        0x0419: 'Goron City - Medigoron',
        0x041A: 'Desert Colossus - Spirit Temple',
        0x041B: 'Hyrule Field - Hammer Grotto',
        0x041C: 'Sacred Forest Meadow - Saria',
        0x041D: 'Lost Woods - Bridge',
        0x041E: 'Kokiri Forest - Storms',
        0x041F: 'Kokiri Forest - Deku Tree Left',
        0x0420: 'Kokiri Forest - Deku Tree Right'
    }

    spoilerHintsList.append('-Way of the Hero-')
    # add required items locations for hints (good hints)
    requiredSample = []
    requiredSample1 = world.spoiler.required_locations
    #print([(location.parent_region.name, location.item.name) for location in requiredSample])
    # loop through the locations we got for "always required locatiosns"
    # if you find lens, remove it
    for l in requiredSample1:
        if l.item.name in ['Master Sword', 'Stone of Agony']:
            continue
        else:
            requiredSample.append(l)
    #print([(location.parent_region.name, location.item.name) for location in requiredSample])
    if len(requiredSample) >= 4:
        requiredSample = random.sample(requiredSample, 4)  #Pick exactly 4
    for location in requiredSample:
        for _ in range(0, 3):  #and distribute each 3 times (12 / 32)
            ID = stoneIDs.pop(0)
            if location.parent_region.dungeon:
                update_hint(messages, ID, buildHintString(getHint(location.parent_region.dungeon.name).text + \
                    " is on the way of the hero."))
                spoilerHintsList.append(location.parent_region.dungeon.name + ': ' + location.item.name + \
                    ' (' + stoneLocations[ID] + ')')
                #print(location.parent_region.dungeon.name, ': ', location.item.name)
            else:
                update_hint(
                    messages, ID,
                    buildHintString(location.parent_region.name +
                                    " is on the way of the hero."))
                spoilerHintsList.append(location.parent_region.name + ": " + location.item.name + \
                    ' (' + stoneLocations[ID] + ')')
                #print(location.parent_region.name, ': ', location.item.name)
    # Don't repeat hints
    checkedLocations = []

    spoilerHintsList.append('\n-Required Locations-')
    # Add required location hints
    alwaysLocations = getHintGroup('alwaysLocation', world)
    for hint in alwaysLocations:
        for locationWorld in world.get_locations():
            if hint.name == locationWorld.name:
                checkedLocations.append(hint.name)
                for _ in range(0, 2):  #populate each of these twice (24 / 32)
                    ID = stoneIDs.pop(0)
                    update_hint(messages, ID, getHint(locationWorld.name).text + " " + \
                        getHint(getItemGenericName(locationWorld.item)).text + ".")
                    spoilerHintsList.append(locationWorld.name + ": " + locationWorld.item.name + \
                        ' (' + stoneLocations[ID] + ')')

##    spoilerHintsList.append('\n-Good Locations-')
##    # Add good location hints
##    sometimesLocations = getHintGroup('location', world)
##    if sometimesLocations:
##        # for _ in range(0, random.randint(9,10) - len(alwaysLocations)):
##        for _ in range(0, 2): # Exactly 2 of these (26 /
##            hint = random.choice(sometimesLocations)
##            # Repick if location isn't new
##            while hint.name in checkedLocations or hint.name in alwaysLocations:
##                hint = random.choice(sometimesLocations)
##
##            for locationWorld in world.get_locations():
##                if hint.name == locationWorld.name:
##                    checkedLocations.append(locationWorld.name)
##                    update_hint(messages, stoneIDs.pop(0), getHint(locationWorld.name).text + " " + \
##                        getHint(getItemGenericName(locationWorld.item)).text + ".")
##                    spoilerHintsList.append(locationWorld.name + ': ' + locationWorld.item.name)

##    spoilerHintsList.append('\n-BadItem Dungeon-')
##    # add bad dungeon locations hints
##    for dungeon in random.sample(world.dungeons, 2): # Exactly 2 of these (28 / 32)
##        # Choose a randome dungeon location that is a non-dungeon item
##        locationWorld = random.choice([location for region in dungeon.regions for location in world.get_region(region).locations
##            if location.item.type != 'Event' and \
##            not location.name in eventlocations and \
##            not isDungeonItem(location.item) and \
##            (world.tokensanity != 'off' or location.item.name != 'Gold Skulltulla Token') and\
##            location.item.type != 'Song'])
##
##        checkedLocations.append(locationWorld.name)
##        update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(dungeon.name).text + \
##            " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
##        spoilerHintsList.append(dungeon.name + ': ' + locationWorld.item.name)
##
##    spoilerHintsList.append('\n-BadItem Overworld-')
##    # add bad overworld locations hints
##    # only choose location if it is new and a proper item from the overworld
##    overworldlocations = [locationWorld for locationWorld in world.get_locations()
##            if not locationWorld.name in checkedLocations and \
##            not locationWorld.name in alwaysLocations and \
##            not locationWorld.name in sometimesLocations and \
##            locationWorld.item.type != 'Event' and \
##            not locationWorld.name in eventlocations and \
##            (world.tokensanity == 'all' or locationWorld.item.name != 'Gold Skulltulla Token') and \
##            not locationWorld.parent_region.dungeon and \
##            not locationWorld.name in checkedLocations]
##    overworldSample = overworldlocations
##    if len(overworldSample) >= 2: # Only need to check for 2
##        overworldSample = random.sample(overworldlocations, 2) # Exactly 2 of these (30 / 32)
##    for locationWorld in overworldSample:
##        checkedLocations.append(locationWorld.name)
##        update_hint(messages, stoneIDs.pop(0), buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
##            " can be found at " + locationWorld.parent_region.name + "."))
##        spoilerHintsList.append(locationWorld.parent_region.name + ': ' + locationWorld.item.name)

#Populate 4 bad item hints
    spoilerHintsList.append('\n-Bad Items-')
    # add good item hints
    # only choose location if it is new and a good item
    if world.shuffle_weird_egg:
        gooditems.append('Weird Egg')
    baditemlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            not locationWorld.name in alwaysLocations and \
            (world.tokensanity == 'all' or locationWorld.item.name != 'Gold Skulltulla Token') and \
            locationWorld.item.type != 'Event' and \
            not locationWorld.name in eventlocations and \
            not isDungeonItem(locationWorld.item) and \
            locationWorld.item.name not in gooditems]
    baditemSample = random.sample(baditemlocations, 4)
    # Don't need this check, we'll fill the rest from this pool, usually only 2, can be more in very rare cases
    # if len(gooditemSample) >= 5:
    #    gooditemSample = random.sample(gooditemlocations, random.randint(3,5))
    # for locationWorld in gooditemSample:
    for locationWorld in baditemSample:
        #locationWorld = gooditemSample.pop()
        checkedLocations.append(locationWorld.name)
        ID = stoneIDs.pop(0)
        if locationWorld.parent_region.dungeon:
            update_hint(messages, ID, buildHintString(getHint(locationWorld.parent_region.dungeon.name).text + \
                " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
            spoilerHintsList.append(locationWorld.parent_region.dungeon.name + ': ' + locationWorld.item.name + \
                ' (' + stoneLocations[ID] + ')')
        else:
            update_hint(messages, ID, buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
                " can be found at " + locationWorld.parent_region.name + "."))
            spoilerHintsList.append(locationWorld.parent_region.name + ': ' + locationWorld.item.name + \
                ' (' + stoneLocations[ID] + ')')

    spoilerHintsList.append('\n-Good Items-')
    # add good item hints
    # only choose location if it is new and a good item
    if world.shuffle_weird_egg:
        gooditems.append('Weird Egg')
    gooditemlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            locationWorld.item.name in gooditems]
    gooditemSample = gooditemlocations
    # Don't need this check, we'll fill the rest from this pool, usually only 2, can be more in very rare cases
    # if len(gooditemSample) >= 5:
    #    gooditemSample = random.sample(gooditemlocations, random.randint(3,5))
    # for locationWorld in gooditemSample:
    random.shuffle(gooditemSample)
    while stoneIDs:
        locationWorld = gooditemSample.pop()
        checkedLocations.append(locationWorld.name)
        ID = stoneIDs.pop(0)
        if locationWorld.parent_region.dungeon:
            update_hint(messages, ID, buildHintString(getHint(locationWorld.parent_region.dungeon.name).text + \
                " hoards " + getHint(getItemGenericName(locationWorld.item)).text + "."))
            spoilerHintsList.append(locationWorld.parent_region.dungeon.name + ': ' + locationWorld.item.name + \
                ' (' + stoneLocations[ID] + ')')
        else:
            update_hint(messages, ID, buildHintString(getHint(getItemGenericName(locationWorld.item)).text + \
                " can be found at " + locationWorld.parent_region.name + "."))
            spoilerHintsList.append(locationWorld.parent_region.name + ': ' + locationWorld.item.name + \
                ' (' + stoneLocations[ID] + ')')

    #spoilerHintsList.append('\n-Junk-\n')

    f = open("hints.txt", "w")
    f.write('~~~ NEW HINTS ~~~\n\n')
    f.write('\n'.join(spoilerHintsList))
    f.close()
Beispiel #20
0
def buildHints(world, rom):
    stoneAddresses = [
        0x938e4c, 0x938ea7, 0x938f02, 0x938f5d, 0x938fb8, 0x939013, 0x93906e,
        0x9390c9, 0x939124, 0x93917f, 0x9391da, 0x939235, 0x939290, 0x9392eb,
        0x939346, 0x9393a1, 0x9393fc, 0x939457, 0x9394b2, 0x93950d, 0x939568,
        0x9395c3, 0x93961e, 0x939679, 0x9396d4, 0x93972f, 0x93978a, 0x9397e5,
        0x939840, 0x93989b, 0x9398f6, 0x939951
    ]  #address for gossip stone text boxes, byte limit is 92

    alwaysLocations = getHintGroup(
        'alwaysLocation'
    )  #These location will always have a hint somewhere in the world.

    sometimesSpace = (int(
        (len(stoneAddresses) - len(alwaysLocations) * 2) / 2))
    sometimesLocations = getHintGroup(
        'location'
    )  #A random selection of these locations will be in the hint pool.
    random.shuffle(sometimesLocations)
    sometimesLocations = sometimesLocations[0:sometimesSpace]
    hintList = alwaysLocations
    hintList.extend(alwaysLocations)
    hintList.extend(sometimesLocations)

    locationData = []
    for hint in hintList:
        for locationWorld in world.get_locations():
            if hint.name == locationWorld.name:
                locationData.extend([locationWorld])

    #shuffles the stone addresses for randomization, always locations will be placed first and twice
    random.shuffle(stoneAddresses)

    #loops through shuffled locations and addresses and builds hint.
    while locationData:
        currentLoc = locationData.pop(0)
        Block_code = getBytes((getHint(currentLoc.name).text))
        if currentLoc.item.type == 'Map' or currentLoc.item.type == 'Compass' or currentLoc.item.type == 'BossKey' or currentLoc.item.type == 'SmallKey':
            Block_code.extend(getBytes((getHint(currentLoc.item.type).text)))
        else:
            Block_code.extend(getBytes((getHint(currentLoc.item.name).text)))
        endText(Block_code)

        if len(Block_code) > 92:
            print('Too many characters in hint')
            Block_code = getBytes("I am Error.")
            Block_code.extend(getBytes(currentLoc.name))
            Block_code.extend(getBytes('&'))
            Block_code.extend(getBytes(currentLoc.item.name))

        rom.write_bytes(stoneAddresses.pop(0), Block_code)

    junkHints = getHintGroup('junkHint')
    random.shuffle(junkHints)
    while stoneAddresses:
        junkHint = junkHints.pop()
        Block_code = getBytes(junkHint.text)
        endText(Block_code)
        rom.write_bytes(stoneAddresses.pop(0), Block_code)

    return rom
Beispiel #21
0
def buildGossipHints(world):

    stoneIDs = [
        0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409,
        0x040A, 0x040B, 0x040C, 0x040D, 0x040E, 0x040F, 0x0410, 0x0411, 0x0412,
        0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B,
        0x041C, 0x041D, 0x041E, 0x041F, 0x0420
    ]

    #shuffles the stone addresses for randomization, always locations will be placed first
    random.shuffle(stoneIDs)

    # Add trial hints
    if world.trials < 6 and world.trials > 3:
        for trial, skipped in world.skipped_trials.items():
            if skipped:
                add_hint(
                    world, stoneIDs.pop(0),
                    buildHintString("the " +
                                    colorText(trial + " Trial", 'Yellow') +
                                    " was dispelled by Sheik."))
    elif world.trials <= 3 and world.trials > 0:
        for trial, skipped in world.skipped_trials.items():
            if not skipped:
                add_hint(
                    world, stoneIDs.pop(0),
                    buildHintString("the " +
                                    colorText(trial + " Trial", 'Pink') +
                                    " protects Ganon's Tower."))

    # add required items locations for hints (good hints)
    requiredSample = world.spoiler.required_locations
    if len(requiredSample) >= 5:
        requiredSample = random.sample(requiredSample, random.randint(3, 4))
    for location in requiredSample:
        if location.parent_region.dungeon:
            add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(location.parent_region.dungeon.name, world.clearer_hints).text, 'Light Blue') + \
                " is on the way of the hero."))
        else:
            add_hint(
                world, stoneIDs.pop(0),
                buildHintString(
                    colorText(location.hint, 'Light Blue') +
                    " is on the way of the hero."))

    # Don't repeat hints
    checkedLocations = []

    # Add required location hints
    alwaysLocations = getHintGroup('alwaysLocation', world)
    for hint in alwaysLocations:
        for locationWorld in world.get_locations():
            if hint.name == locationWorld.name:
                checkedLocations.append(hint.name)
                add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(locationWorld.name, world.clearer_hints).text, 'Green') + " " + \
                    colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + "."))

    # Add good location hints
    sometimesLocations = getHintGroup('location', world)
    if sometimesLocations:
        for _ in range(0, random.randint(11, 12) - len(alwaysLocations)):
            hint = random.choice(sometimesLocations)
            # Repick if location isn't new
            while hint.name in checkedLocations or hint.name in alwaysLocations:
                hint = random.choice(sometimesLocations)

            for locationWorld in world.get_locations():
                if hint.name == locationWorld.name:
                    checkedLocations.append(locationWorld.name)
                    add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(locationWorld.name, world.clearer_hints).text, 'Green') + " " + \
                        colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + "."))

    # add bad dungeon locations hints
    for dungeon in random.sample(world.dungeons, random.randint(3, 4)):
        # Choose a randome dungeon location that is a non-dungeon item
        locationWorld = random.choice([location for region in dungeon.regions for location in region.locations
            if location.item.type != 'Event' and \
            location.item.type != 'Shop' and \
            not location.event and \
            not isDungeonItem(location.item) and \
            (world.tokensanity != 'off' or location.item.name != 'Gold Skulltulla Token') and\
            location.item.type != 'Song'])

        checkedLocations.append(locationWorld.name)
        add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(dungeon.name, world.clearer_hints).text, 'Green') + \
            " hoards " + colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + "."))

    # add bad overworld locations hints
    # only choose location if it is new and a proper item from the overworld
    overworldlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            not locationWorld.name in alwaysLocations and \
            not locationWorld.name in sometimesLocations and \
            locationWorld.item.type != 'Event' and \
            locationWorld.item.type != 'Shop' and \
            not locationWorld.event and \
            (world.tokensanity == 'all' or locationWorld.item.name != 'Gold Skulltulla Token') and \
            not locationWorld.parent_region.dungeon]
    overworldSample = overworldlocations
    if len(overworldSample) >= 3:
        # Use this hint type to balance hints given via trials
        if world.trials == 3:
            overworldSample = random.sample(overworldlocations,
                                            random.randint(1, 2))
        elif world.trials in [2, 4]:
            overworldSample = random.sample(overworldlocations,
                                            random.randint(1, 3))
        elif world.trials in [1, 5]:
            overworldSample = random.sample(overworldlocations,
                                            random.randint(2, 3))
        else:
            overworldSample = random.sample(overworldlocations,
                                            random.randint(2, 4))
    for locationWorld in overworldSample:
        checkedLocations.append(locationWorld.name)
        add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + \
            " can be found at " + colorText(locationWorld.hint, 'Green') + "."))

    # add good item hints
    # only choose location if it is new and a good item
    gooditemlocations = [locationWorld for locationWorld in world.get_locations()
            if not locationWorld.name in checkedLocations and \
            locationWorld.item.advancement and \
            locationWorld.item.type != 'Event' and \
            locationWorld.item.type != 'Shop' and \
            not locationWorld.event and \
            locationWorld.item.name != 'Gold Skulltulla Token' and \
            not locationWorld.item.key]
    gooditemSample = gooditemlocations
    if len(gooditemSample) >= 6:
        gooditemSample = random.sample(gooditemlocations, random.randint(4, 6))
    for locationWorld in gooditemSample:
        checkedLocations.append(locationWorld.name)
        if locationWorld.parent_region.dungeon:
            add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(locationWorld.parent_region.dungeon.name, world.clearer_hints).text, 'Green') + \
                " hoards " + colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + "."))
        else:
            add_hint(world, stoneIDs.pop(0), buildHintString(colorText(getHint(getItemGenericName(locationWorld.item), world.clearer_hints).text, 'Red') + \
                " can be found at " + colorText(locationWorld.hint, 'Green') + "."))

    # fill the remaining hints with junk
    junkHints = getHintGroup('junkHint', world)
    random.shuffle(junkHints)
    while stoneIDs:
        add_hint(world, stoneIDs.pop(0), junkHints.pop().text)