def damage(target: NPC):

    player = Player.current()

    # check if player is at target place_location
    if player.place != target.place:
        Message.error("You are not at the target '%s's location" % target)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      target)
        Message.error("Player does not know where the NPC is located")
        return False

    target.health_meter -= 0.3
    if target.health_meter <= 0:
        return kill(target)
    else:
        Message.debug("NPC '%s' has been damaged, current health meter: %s" %
                      (target, target.health_meter))
        Message.achievement("NPC '%s' has been damaged" % target)
        target.save()

    return True
def gather(item_to_gather: Item):

    player = Player.current()

    # check if player is at item location
    if item_to_gather.place != player.place:
        Message.debug(
            "Player is not at the item '%s's location (%s) to gather it" %
            (item_to_gather, item_to_gather.place))
        Message.error("You are not at the item '%s's location to gather it" %
                      item_to_gather)
        return False

    # check if player know where the item is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(item_place=item_to_gather)):
        Message.debug("Player does not know where the item (%s) is located" %
                      item_to_gather)
        Message.error("Player does not know where the item is located")
        return False

    # update Player's belongings
    item_to_gather.belongs_to_player = player
    item_to_gather.save()

    Message.achievement("Item '%s' gathered" % item_to_gather)
    return True
def report(intel: Intel, target: NPC):

    player = Player.current()

    # check if player has the intel
    if not PlayerKnowledgeBook.get_or_none(player=player, intel=intel):
        Message.error("You don't have the intel '%s'" % intel)
        return False

    # check if player is in the target place_location
    if target.place != player.place:
        Message.debug("Player is not at the target (%s) location (%s)" %
                      (target, target.place))
        Message.error("You are not at the target's (%s) location" % target)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      target)
        Message.error("Player does not know where the NPC is located")
        return False

    # update Player's favours book if target hasn't have it already
    if not NPCKnowledgeBook.get_or_none(npc=target, intel=intel):
        FavoursBook.construct(target, intel.worth_())
        # update target's intel list
        NPCKnowledgeBook.create(npc=target, intel=intel)

    Message.achievement("Intel '%s' reported to the NPC '%s'" %
                        (intel.detail(), target))
    return True
def spy(spy_on: NPC, intel_target: Intel):

    player = Player.current()

    # check if player is at target's location
    if player.place != spy_on.place:
        Message.debug("Player is not at the NPC (%s) location (%s) to spy" %
                      (spy_on, spy_on.place))
        Message.error("You are not at the NPC '%s's location to spy" % spy_on)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=intel_target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      intel_target)
        Message.error("Player does not know where the NPC is located")
        return False

    # check if the target has the piece of intel
    if not NPCKnowledgeBook.get_or_none(npc=spy_on, intel=intel_target):
        Message.debug(
            "Target (%s) does not have the intel (%s) player wanted" %
            (spy_on, intel_target))
        Message.error(
            "Target (%s) does not have the intel (%s) player wanted" %
            (spy_on, intel_target))
        return False

    # update Player's intel
    NarrativeHelper.add_intel(intel_target)

    Message.achievement("Intel '%s' gathered by spying on '%s'" %
                        (intel_target.detail(), spy_on))
    return True
def take_loot(item_to_take: Item, loot_npc: NPCDead = None):

    player = Player.current()

    # check if the item belongs to dead
    if not loot_npc:
        # NPC confirmed dead already, object among dead is given
        try:
            holder = item_to_take.belongs_to
        except:
            # holder not found = dead
            Message.debug("Item '%s' holder is dead")
            holder = None

        if holder:
            # holder is alive
            return take(item_to_take, item_holder=holder)

    # remove item from holder's belongings and add to player's
    item_to_take.belongs_to = None
    item_to_take.belongs_to_player = player
    item_to_take.save()

    # FavoursBook.construct(item_holder, -item_to_take.worth_(), player)

    Message.achievement("Item '%s' has taken by looting" % item_to_take)
    return True
def use(item_to_use: Item, target: NPC):

    player = Player.current()
    # check if player has the item
    if item_to_use.belongs_to_player != player:
        Message.error("You don't have the item (%s)" % item_to_use)
        return False

    # check if player at target's place_location
    if target.place != player.place:
        Message.debug("Player is not at the target '%s's location (%s)" %
                      (target, target.place))
        Message.error("You are not at the target '%s's location" % target)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      target)
        Message.error("Player does not know where the NPC is located")
        return False

    item_to_use.use(npc=target)

    # depending on positive or negative impact_factor of the item usage, target record in player's favour gets updated
    FavoursBook.construct(target, float(item_to_use.impact_factor or 0.0))

    Message.achievement("Item '%s' used on the '%s'" % (item_to_use, target))
    return True
def steal_1(item_to_steal: Item, item_holder: NPC):
    """
    Go someplace, sneak up on somebody, and take something.
    :return:
    """
    # item[1] is to be stolen from NPC[1] who has it

    # place_location[1] is where NPC[1] lives
    item_holder_place = item_holder.place

    player = Player.current()
    player.next_location = item_holder_place
    player.save()

    intel = Intel.construct(item_place=item_to_steal)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # steps:
    #   goto: place_location[1]
    #   T.stealth: stealth NPC[1]
    #   T.take: take item[1] from NPC[1]
    steps = [[item_holder_place, item_holder], [item_holder],
             [item_to_steal, item_holder]]
    Message.instruction("Sneak up on '%s', and take '%s'" %
                        (item_holder, item_to_steal))

    return steps
def give(item: Item, receiver: NPC):

    # check if player has the item
    player = Player.current()
    if item.belongs_to_player != player:
        Message.error("You don't have the item (%s) to give" % item)
        return False

    # check if player is at receiver's location
    if player.place != receiver.place:
        Message.debug("Player is not at the receiver NPC (%s) location (%s)" %
                      (receiver, receiver.place))
        Message.error("You are not at the receiver's (%s) location" % receiver)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=receiver)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      receiver)
        Message.error("Player does not know where the NPC is located")
        return False

    item.belongs_to_player = None
    item.belongs_to = receiver
    item.save()

    # update favours book
    FavoursBook.construct(npc=receiver,
                          owe_factor=item.worth_(),
                          player=player)

    Message.achievement("Item '%s' has been given to the NPC '%s'" %
                        (item, receiver))
    return True
def listen(intel: Intel, informer: NPC):

    # check if informer has the intel
    if not NPCKnowledgeBook.get_or_none(intel=intel, npc=informer):
        Message.error("Informer doesn't have the intel (%s) player wants" %
                      intel)
        return False

    player = Player.current()

    # check if player is in the informer place_location
    if informer.place != player.place:
        Message.error("You are not at the informer's (%s) location" % informer)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=informer)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      informer)
        Message.error("Player does not know where the NPC is located")
        return False

    # update Player's intel
    NarrativeHelper.add_intel(intel)
    FavoursBook.construct(informer, -intel.worth_(), player)

    Message.achievement("Intel '%s' acquired by listening to '%s'" %
                        (intel.detail(), informer))
    return True
def talk(npc: NPC):
    player = Player.current()
    if player.place != npc.place:
        Message.error("You are not at the NPC '%s's place, can't talk to him" %
                      npc)
        return False

    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=npc)):
        Message.error("NPC '%s's current location is unknown" % npc)
        return False

    Message.achievement("Talking to '%s'" % npc)
    return True
def explore(area_location: Place, npc: NPC = None, item: Item = None):

    player = Player.current()

    if player.place != area_location:
        Message.error("You are not at the area '%s'" % area_location)
        return False

    if (npc and npc.place != area_location) or (item and
                                                item.place != area_location):
        Message.error("What you're looking for, is not here")
        return False

    # check if player knows the location
    results = PlayerKnowledgeBook.select().join(Intel)\
        .where(PlayerKnowledgeBook.player == player, Intel.place_location == area_location).limit(1)
    if not results:
        Message.debug(
            "Location %s unknown (Intel not found in player's knowledge book)"
            % area_location)
        Message.error("Location %s is unknown" % area_location)
        return False

    # update Player's location
    player.place = area_location
    player.save()

    if npc:
        target = npc
    elif item:
        target = item
    else:
        target = ''

    Message.achievement("You have found '%s' by exploring '%s'" %
                        (target, area_location))

    if npc:
        # find npc for player (give npc place intel to player)
        intel = Intel.construct(npc_place=npc)
    elif item:
        intel = Intel.construct(item_place=item)
    else:
        intel = None
    if intel:
        PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
        Message.achievement("Intel '%s' learned" % intel.detail())

    return True
def exchange(item_holder: NPC, item_to_give: Item, item_to_take: Item):

    player = Player.current()

    # check if player has the item_to_give and holder has the item_to_take
    if item_to_give.belongs_to_player != player:
        Message.debug("item_to_give: '%s', belongs_to_player: '%s'" %
                      (item_to_give, item_to_give.belongs_to_player))
        return False
    if item_to_take.belongs_to != item_holder:
        Message.debug(
            "item_to_take: '%s', belongs_to: '%s', item_holder: '%s'" %
            (item_to_take, item_to_give.belongs_to, item_holder))
        return False

    # check if player is at item_holder's place_location
    if item_holder.place != player.place:
        Message.debug(
            "Player is not at the item_holder (%s) place_location (%s)" %
            (item_holder, item_holder.place))
        Message.error("You are not at the item holder (%s) location" %
                      item_holder)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=item_holder)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      item_holder)
        Message.error("Player does not know where the NPC is located")
        return False

    # update Player's belongings
    item_to_take.belongs_to = None
    item_to_take.belongs_to_player = player
    item_to_take.save()

    item_to_give.belongs_to_player = None
    item_to_give.belongs_to = item_holder
    item_to_give.save()

    npc_owing = item_to_give.worth_() - item_to_take.worth_()
    FavoursBook.construct(item_holder, npc_owing, player)

    Message.achievement("Item '%s' exchanged for '%s', with NPC '%s'" %
                        (item_to_give, item_to_take, item_holder))
    return True
def goto(destination: Place):

    player = Player.current()

    # check if player knows the location
    results = PlayerKnowledgeBook.select().join(Intel)\
        .where(PlayerKnowledgeBook.player == player, Intel.place_location == destination).limit(1)
    if not results:
        Message.error("Location '%s' is unknown" % destination)
        return False

    # update Player's location
    player.place = destination
    player.save()

    Message.achievement("Player went to '%s'" % destination)
    return True
def take(item_to_take: Item, item_holder: NPC = None):

    if item_holder is None:
        return take_loot(item_to_take)

    player = Player.current()

    # check if NPC has the item
    if item_to_take.belongs_to != item_holder:
        Message.debug(
            "NPC '%s' doesn't have the item '%s' to give. It belongs to '%s'" %
            (item_holder, item_to_take, item_to_take.belongs_to))
        Message.error("NPC '%s' doesn't have the item '%s' to give" %
                      (item_holder, item_to_take))
        return False

    # check if player is at item_holder's place_location
    if item_holder.place != player.place:
        Message.debug(
            "Player is not at the item_holder (%s) place_location (%s)" %
            (item_holder, item_holder.place))
        Message.error("You are not at the NPC '%s's location" % item_holder)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=item_holder)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      item_holder)
        Message.error("Player does not know where the NPC is located")
        return False

    # remove item from holder's belongings and add to player's
    item_to_take.belongs_to = None
    item_to_take.belongs_to_player = player
    item_to_take.save()

    FavoursBook.construct(item_holder, -item_to_take.worth_(), player)

    Message.achievement("Item '%s' taken" % item_to_take)
    return True
def stealth(target: NPC):

    player = Player.current()

    # check if player at target's place_location
    if player.place != target.place:
        Message.debug("Player is not at the target (%s) place_location (%s)" %
                      (target, target.place))
        Message.error("You are not at the target '%s's location" % target)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      target)
        Message.error("Player does not know where the NPC is located")
        return False

    Message.achievement("Successfully snuck on '%s'" % target)
    return True
def get_3(item_to_fetch: Item):
    """
    Go someplace and pick something up that’s lying around there
    :param item_to_fetch:
    :return:
    """
    if item_to_fetch.place:
        dest = item_to_fetch.place
    elif item_to_fetch.belongs_to:
        # someone took it and put it in a specific place, put it where the NPC is
        Message.event(
            "Someone has stolen the item '%s' and put it somewhere else" %
            item_to_fetch)
        dest = item_to_fetch.belongs_to.place
        item_to_fetch.belongs_to = None
        item_to_fetch.place = dest
        item_to_fetch.save()
    else:
        # player already has the item or no one has it, put it in a random place
        Message.event(
            "Someone has stolen the item '%s' and has put it in a random place"
            % item_to_fetch)
        dest = Place.select().order_by(fn.Random()).get()
        item_to_fetch.place = dest
        item_to_fetch.save()

    player = Player.current()
    player.next_location = dest
    player.save()

    intel = Intel.construct(item_place=item_to_fetch)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # steps:
    # goto
    # gather
    steps = [[dest, None, item_to_fetch], [item_to_fetch]]
    Message.instruction("Gather '%s' from '%s'" % (item_to_fetch, dest))
    return steps
def read(intel: Intel, readable: Item):

    # check if the readable has the intel
    if not ReadableKnowledgeBook.get_or_none(
            ReadableKnowledgeBook.intel == intel,
            ReadableKnowledgeBook.readable == readable):
        Message.debug(
            "Readable '%s' does not contain the intel '%s', ReadableKnowledgeBook not found"
            % (readable, intel))
        Message.error(
            "Readable '%s' does not contain the intel player looking for" %
            readable)
        return False

    player = Player.current()
    # check if player owns the readable
    if readable.belongs_to_player != player:
        Message.debug("Player doesn't have the readable '%s'" % readable)
        if readable.place_() == player.place:
            readable.belongs_to = None
            readable.belongs_to_player = player
            readable.save()
            Message.debug(
                "Player didn't own the readable (%s) but at its place so he take it"
                % readable)
        else:
            Message.debug(
                "Player neither own the readable (%s), nor at the item's place_location (%s)"
                % (readable, readable.place_()))
            Message.error(
                "You neither own the readable (%s), nor are at the item's location"
                % readable)
            return False

    # update Player's intel
    NarrativeHelper.add_intel(intel)

    Message.achievement("By reading '%s', intel '%s' has been learned" %
                        (readable, intel.detail()))
    return True
def kill(target: NPC):

    player = Player.current()

    # check if player is at target place_location
    if player.place != target.place:
        Message.error("You are not at the target '%s's location" % target)
        return False

    # check if player know where the receiver is
    if not PlayerKnowledgeBook.get_or_none(
            player=player, intel=Intel.construct(npc_place=target)):
        Message.debug("Player does not know where the NPC (%s) is located" %
                      target)
        Message.error("Player does not know where the NPC is located")
        return False

    NPCDead.create(name=target.name, place=target.place, clan=target.clan)

    Message.achievement("NPC '%s' has been killed" % target)
    target.delete_instance(recursive=True)
    return True
def steal_2(item_to_steal: Item, item_holder: NPC):
    """
    Go someplace, kill somebody and take something
    :param item_to_steal:
    :param item_holder:
    :return:
    """
    player = Player.current()
    player.next_location = item_holder.place
    player.save()

    intel = Intel.construct(item_place=item_to_steal)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # steps:
    # goto holder
    # kill holder
    # T.take item from holder
    steps = [[item_holder.place, item_holder], [item_holder], [item_to_steal]]
    Message.instruction("Kill '%s' and take '%s'" %
                        (item_holder, item_to_steal))

    return steps
def get_4(item_to_fetch: Item):
    """
    an NPC have the item, but you need to give the NPC something in an exchange
    :param item_to_fetch:
    :return:
    """
    player = Player.current()

    # find an NPC who has the needed item, and has it in exchange list
    exchanges = Exchange.select().join(Item)
    if item_to_fetch.is_singleton():
        exchanges = exchanges.where(Exchange.item == item_to_fetch)
    else:
        exchanges = exchanges.where(
            Exchange.item.generic == item_to_fetch.generic)

    if exchanges:

        locations_scores = [
            player.distance(exc.need.npc.place) for exc in exchanges
        ]
        exchanges = sort_by_list(exchanges, locations_scores)

        exchange = exchanges[0]
        item_to_give = exchange.need.item
        item_holder = exchange.need.npc
    else:
        # no one wants to offer the "item_to_fetch" in exchange for something else
        # you can even trade with your enemies so alliance doesn't really matters here
        # first try find someone who needs this item
        Message.debug(
            "No one wants to offer the '%s' in exchange for something else" %
            item_to_fetch)
        needs = Need.select().where(Need.item == item_to_fetch).group_by(
            Need.npc)
        if needs.count():
            need = needs.order_by(fn.Random()).get()
            item_holder = need.npc
            Message.debug(
                "NPC '%s' needs something, ideal to create an exchange motive with the item '%s' for him"
                % (item_holder, item_to_fetch))
        else:
            # no one need anything create a need for coin for an NPC
            results = NPC.select()
            locations_scores = [player.distance(npc.place) for npc in results]
            results = sort_by_list(results, locations_scores)
            item_holder = results[0]
            Message.debug(
                "No one need anything create a need for 'coin' for a close-by NPC '%s'"
                % item_holder)

            need = Need.create(
                npc=item_holder,
                item=Item.get(generic=GenericItem.get(name='coin')))

        Exchange.create(need=need, item=item_to_fetch)

        if item_to_fetch.belongs_to != item_holder:
            # ensure NPC has the item
            item_to_fetch.belongs_to = item_holder
            item_to_fetch.save()

        item_to_give = need.item

    player.next_location = item_holder.place
    player.save()

    intel = Intel.construct(item_place=item_to_fetch)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # check for player belonging for the exchange item
    if item_to_give.is_singleton():
        player_owns = (item_to_give.belongs_to_player == player)
    else:
        player_owns = Item.select().where(Item.generic == item_to_give.generic,
                                          Item.belongs_to_player
                                          == player) is True

    if player_owns:
        # player has the item
        # goto
        # exchange
        steps = [[], [], [], [item_holder.place, item_holder],
                 [item_holder, item_to_give, item_to_fetch]]
        Message.instruction(
            "Do a sub-quest, meet '%s' and exchange '%s' with '%s'" %
            (item_holder, item_to_give, item_to_fetch))
        return steps

    # steps:
    # goto
    # get
    # sub-quest
    # goto
    # exchange
    steps = [[item_to_give.place_(), None, item_to_give], [item_to_give], [],
             [item_holder.place, item_holder],
             [item_holder, item_to_give, item_to_fetch]]
    Message.instruction(
        "Get '%s', do a sub-quest, meet '%s' and exchange '%s' with '%s'" %
        (item_to_give, item_holder, item_to_give, item_to_fetch))
    return steps
def learn_4(required_intel: Intel):
    # find an NPC who has the required intel in exchange, get the NPC's needed item to give

    if required_intel.npc_place:
        bad_needs = Need.select(Need.id).join(Item).where(
            (Need.npc == required_intel.npc_place)
            | (Item.belongs_to == required_intel.npc_place))
    elif required_intel.item_place:
        bad_needs = Need.select(
            Need.id).where(Need.item == required_intel.item_place)
    else:
        bad_needs = []

    # NPC who has the intel
    knowledgeable = NPC.select(NPC.id).join(NPCKnowledgeBook).where(
        NPC.id != required_intel.npc_place,
        NPCKnowledgeBook.intel == required_intel.id)
    exchange = None
    if knowledgeable.count():
        needs = Need.select()\
            .join(NPC)\
            .where(NPC.id.in_(knowledgeable),
                   Need.id.not_in(bad_needs),
                   Need.item_id.is_null(False))
        if needs:
            exchanges = Exchange.select().where(
                Exchange.need.in_(needs),
                Exchange.intel == required_intel.id).limit(1)
            if exchanges:
                exchange = exchanges[0]
            else:
                exchange = Exchange.create(need=needs[0], intel=required_intel)

    if not exchange:
        # no NPC has the intel or no need found
        accessible_items = Item.select().where(
            (Item.id != required_intel.item_place)
            & (Item.belongs_to_player.is_null())
            & ((Item.belongs_to.is_null())
               | Item.belongs_to != required_intel.npc_place)).order_by(
                   fn.Random()).limit(1)
        if accessible_items:
            item = accessible_items[0]
        else:
            holder = NPC.select().where(
                NPC.id != required_intel.npc_place).order_by(
                    fn.Random()).limit(1)
            if holder:
                holder = holder[0]
            else:
                holder = NPC.create(
                    name=NPCName.fetch_new(),
                    clan=Clan.select().order_by(fn.Random()).get(),
                    place=Place.select().order_by(fn.Random()).get())
            item = Item.create(name='arbitrary_item_' + str(randint(100, 999)),
                               generic=GenericItem.get_or_create(
                                   name=ItemTypes.singleton.name)[0],
                               belongs_to=holder,
                               type=ItemTypes.unknown.name)
        if knowledgeable:
            informer = knowledgeable.order_by(fn.Random()).get()
        else:
            npc = NPC.select().where(
                NPC.id != required_intel.npc_place).order_by(
                    fn.Random()).limit(1)
            if npc:
                informer = npc[0]
            else:
                informer = NPC.create(
                    name=NPCName.fetch_new(),
                    clan=Clan.select().order_by(fn.Random()).get(),
                    place=Place.select().order_by(fn.Random()).get())
            NPCKnowledgeBook.create(npc=informer, intel=required_intel)
        need = Need.create(npc=informer, item=item)
        exchange = Exchange.create(need=need, intel=required_intel)

    informer = exchange.need.npc
    item_to_get_for_exchange = exchange.need.item

    player = Player.current()
    player.next_location = informer.place
    player.save()

    intel = Intel.construct(item_place=item_to_get_for_exchange)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # steps:
    # get
    # sub-quest
    # give
    # listen
    steps = [[item_to_get_for_exchange], [informer.place],
             [item_to_get_for_exchange, informer], [required_intel, informer]]
    Message.instruction(
        "Get '%s', perform a sub-quest, give the acquired item to '%s' in return get an intel on '%s'"
        % (item_to_get_for_exchange, informer, required_intel))
    return steps
def learn_3(required_intel: Intel):
    """
    Go someplace, get something, and read what is written on it.
    :param required_intel:
    :return:
    """
    player = Player.current()
    # intel[1] is to be learned
    # find a book[1] (readable, it could be a sign) that has intel[1] on it
    results = ReadableKnowledgeBook.select()\
        .join(Item, on=(ReadableKnowledgeBook.readable == Item.id))\
        .where(ReadableKnowledgeBook.intel == required_intel,
               Item.belongs_to_player != player)

    # sort by readable place_ triangle
    locations_scores = [
        player.distance(knowledge_book.readable.place_())
        for knowledge_book in results
    ]
    results = sort_by_list(results, locations_scores)

    if results:
        book_containing_intel = results[0].readable  # type: Item
    else:
        # create an address book containing the intel player is looking for
        known_places = Place.select()\
            .join(Intel, on=(Intel.place_location == Place.id))\
            .join(PlayerKnowledgeBook, on=(PlayerKnowledgeBook.intel == Intel.id))\
            .where(PlayerKnowledgeBook.player == player)
        if known_places:
            known_place = known_places.order_by(fn.Random()).get()
        else:
            known_place = narrative_helper.create_place()

        book_containing_intel = Item.create(
            type=ItemTypes.readable.name,
            generic=GenericItem.get_or_create(
                name=ItemTypes.singleton.name)[0],
            name=ItemName.fetch_new(),
            place=known_place)
        ReadableKnowledgeBook.create(readable=book_containing_intel,
                                     intel=required_intel)
        PlayerKnowledgeBook.create(
            player=player,
            intel=Intel.construct(item_place=book_containing_intel))

    player.next_location = book_containing_intel.place_()
    player.save()

    intel = Intel.construct(item_place=book_containing_intel)
    PlayerKnowledgeBook.get_or_create(player=player, intel=intel)
    Message.achievement("Intel '%s' learned" % intel.detail())

    # steps:
    # goto: place_location[1]
    # get: book[1]
    # T.read: intel[1] from book[1]
    steps = [[book_containing_intel.place_(), None, book_containing_intel],
             [book_containing_intel], [required_intel, book_containing_intel]]
    Message.instruction("Get '%s', and read the intel '%s' from it" %
                        (book_containing_intel, required_intel))
    return steps