def capped_line_of_sight(agent, player_struct, cap=20):
    """Return the block directly in the entity's line of sight, or a point in
    the distance."""
    xsect = agent.get_player_line_of_sight(player_struct)
    if xsect is not None and euclid_dist(pos_to_np(xsect), pos_to_np(player_struct.pos)) <= cap:
        return pos_to_np(xsect)

    # default to cap blocks in front of entity
    vec = rotation.look_vec(player_struct.look.yaw, player_struct.look.pitch)
    return cap * np.array(vec) + pos_to_np(player_struct.pos)
def find_inside(entity, get_locs_from_entity):
    """Return a point inside the entity if it can find one.
    TODO: heuristic quick check to find that there aren't any,
    and maybe make this not d^3"""

    # is this a negative object? if yes, just return its mean:
    if hasattr(entity, "blocks"):
        if all(b == (0, 0) for b in entity.blocks.values()):
            m = np.mean(list(entity.blocks.keys()), axis=0)
            return [to_block_pos(m)]
    l = get_locs_from_entity(entity)
    if l is None:
        return []
    m = np.round(np.mean(l, axis=0))
    maxes = np.max(l, axis=0)
    mins = np.min(l, axis=0)
    inside = []
    for x in range(mins[0], maxes[0] + 1):
        for y in range(mins[1], maxes[1] + 1):
            for z in range(mins[2], maxes[2] + 1):
                if check_inside([(x, y, z), entity], get_locs_from_entity):
                    inside.append((x, y, z))
    return sorted(inside, key=lambda x: euclid_dist(x, m))
def check_between(entities, get_locs_from_entity, fat_scale=0.2):
    """Heuristic check if entities[0] is between entities[1] and entities[2]
    by checking if the locs of enitity[0] are in the convex hull of
    union of the max cardinal points of entity[1] and entity[2]"""
    locs = []
    means = []
    for e in entities:
        l = get_locs_from_entity(e)
        if l is not None:
            locs.append(l)
            means.append(np.mean(l, axis=0))
        else:
            # this is not a thing we know how to assign 'between' to
            return False
    mean_separation = euclid_dist(means[1], means[2])
    fat = fat_scale * mean_separation
    bounding_locs = []
    for l in locs:
        if len(l) > 1:
            bl = []
            idx = np.argmax(l, axis=0)
            for i in range(3):
                f = np.zeros(3)
                f[i] = fat
                bl.append(np.array(l[idx[i]]) + fat)
            idx = np.argmin(l, axis=0)
            for i in range(3):
                f = np.zeros(3)
                f[i] = fat
                bl.append(np.array(l[idx[i]]) - fat)
            bounding_locs.append(np.vstack(bl))
        else:
            bounding_locs.append(np.array(l))
    x = np.mean(bounding_locs[0], axis=0)
    points = np.vstack([bounding_locs[1], bounding_locs[2]])
    return in_hull(points, x)
Example #4
0
 def is_placed_block_interesting(
         self, xyz: XYZ, bid: int,
         boring_blocks: Tuple[int]) -> Tuple[bool, bool, bool]:
     """Return three values:
     - bool: is the placed block interesting?
     - bool: is it interesting because it was placed by a player?
     - bool: is it interesting because it was placed by the agent?
     """
     interesting = False
     player_placed = False
     agent_placed = False
     # TODO record *which* player placed it
     if xyz in self.pending_agent_placed_blocks:
         interesting = True
         agent_placed = True
     for player_struct in self.agent.get_other_players():
         if (euclid_dist(pos_to_np(player_struct.pos), xyz) < 5
                 and player_struct.mainHand.id == bid):
             interesting = True
             if not agent_placed:
                 player_placed = True
     if bid not in boring_blocks:
         interesting = True
     return interesting, player_placed, agent_placed
def filter_by_sublocation(interpreter,
                          speaker,
                          candidates: List[T],
                          d: Dict,
                          all_proximity=10,
                          loose=False) -> List[T]:
    """Select from a list of candidate reference_object mems given a sublocation
    also handles random sampling
    Returns a list of mems
    """
    filters_d = d.get("filters")
    assert filters_d is not None, "no filters: {}".format(d)
    default_loc = getattr(interpreter, "default_loc", SPEAKERLOOK)
    location = filters_d.get("selector", {}).get("location", default_loc)
    reldir = location.get("relative_direction")
    distance_sorted = False
    location_filtered_candidates = []
    if reldir:
        if reldir == "INSIDE":
            # FIXME formalize this better, make extensible
            if location.get("reference_object"):
                # should probably return from interpret_reference_location...
                ref_mems = interpret_reference_object(
                    interpreter, speaker, location["reference_object"])
                # FIXME !!! this should be more clearly delineated
                # between perception and memory
                I = getattr(interpreter.memory, "check_inside", None)
                if I:
                    for candidate_mem in candidates:
                        if I([candidate_mem, ref_mems[0]]):
                            location_filtered_candidates.append(candidate_mem)
                else:
                    raise ErrorWithResponse("I don't know how to check inside")
            if not location_filtered_candidates:
                raise ErrorWithResponse("I can't find something inside that")
        elif reldir == "AWAY":
            raise ErrorWithResponse("I don't know which object you mean")
        elif reldir == "NEAR":
            pass  # fall back to no reference direction
        elif reldir == "BETWEEN":
            mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                                   speaker,
                                                                   location)
            steps, reldir = interpret_relative_direction(interpreter, d)
            ref_loc, _ = interpreter.subinterpret["specify_locations"](
                interpreter, speaker, mems, steps, reldir)
            distance_sorted = True
            location_filtered_candidates = candidates
            location_filtered_candidates.sort(
                key=lambda c: euclid_dist(c.get_pos(), ref_loc))

        else:
            # reference object location, i.e. the "X" in "left of X"
            mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                                   speaker,
                                                                   location)
            if not mems:
                raise ErrorWithResponse("I don't know which object you mean")

            # FIXME!!! handle frame better, might want agent's frame instead
            # FIXME use the subinterpreter, don't directly call the attribute
            eid = interpreter.memory.get_player_by_name(speaker).eid
            self_mem = interpreter.memory.get_mem_by_id(
                interpreter.memory.self_memid)
            L = LinearExtentAttribute(interpreter.memory, {
                "frame": eid,
                "relative_direction": reldir
            },
                                      mem=self_mem)
            c_proj = L(candidates)
            m_proj = L(mems)
            # FIXME don't just take the first...
            m_proj = m_proj[0]

            # filter by relative dir, e.g. "left of Y"
            location_filtered_candidates = [
                c for (p, c) in zip(c_proj, candidates) if p > m_proj
            ]
            # "the X left of Y" = the right-most X that is left of Y
            location_filtered_candidates.sort(key=lambda p: p.get_pos())
            distance_sorted = True
    else:
        # no reference direction: sort by closest
        mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                               speaker,
                                                               location)
        steps, reldir = interpret_relative_direction(interpreter, d)
        ref_loc, _ = interpreter.subinterpret["specify_locations"](interpreter,
                                                                   speaker,
                                                                   mems, steps,
                                                                   reldir)
        location_filtered_candidates = [
            c for c in candidates
            if euclid_dist(c.get_pos(), ref_loc) <= all_proximity
        ]
        location_filtered_candidates.sort(
            key=lambda c: euclid_dist(c.get_pos(), ref_loc))
        distance_sorted = True

    mems = location_filtered_candidates
    if location_filtered_candidates:  # could be [], if so will return []
        default_selector_d = {"return_quantity": "ALL"}
        # default_selector_d = {"location": {"location_type": "SPEAKER_LOOK"}}
        selector_d = filters_d.get("selector", default_selector_d)
        S = interpret_selector(interpreter, speaker, selector_d)
        if S:
            memids, _ = S(
                [c.memid for c in location_filtered_candidates],
                [None] * len(location_filtered_candidates),
            )
            mems = [interpreter.memory.get_mem_by_id(m) for m in memids]
        else:
            pass
            # FIXME, warn/error here; mems is still the candidates

    return mems
def object_looked_at(
    agent,
    candidates: Sequence[Tuple[XYZ, T]],
    player_struct,
    limit=1,
    max_distance=30,
    loose=False,
) -> List[Tuple[XYZ, T]]:
    """Return the object that `player` is looking at.

    Args:
    - agent: agent object, for API access
    - candidates: list of (centroid, object) tuples
    - player_struct: player struct whose POV to use for calculation
         has a .pos attribute, which is an (x, y, z) tuple giving the head position
         and a .pitch attribute, which is a float
         and a .yaw attribute, which is a float
    - limit: 'ALL' or int; max candidates to return
    - loose:  if True, don't filter candaidates behind agent

    Returns: a list of (xyz, mem) tuples, max length `limit`
    """
    if len(candidates) == 0:
        return []
    # FIXME !!!! formalize this:
    # should not even end up here if true, handle above.
    if player_struct.pos.x is None:
        # speaker is "disembodied", return object closest to agent
        # todo closest to agent line of sight?
        candidates.sort(key=lambda c: base_distance(agent.pos, c[0]))
        # limit returns of things too far away
        candidates = [c for c in candidates if base_distance(agent.pos, c[0]) < max_distance]
        return [(p, o) for (p, o, r) in candidates[:limit]]

    pos = np.array(player_struct.pos)
    yaw, pitch = player_struct.look.yaw, player_struct.look.pitch

    # append to each candidate its relative position to player, rotated to
    # player-centric coordinates
    candidates_ = [(p, obj, rotation.transform(p - pos, yaw, pitch)) for (p, obj) in candidates]
    FRONT = rotation.DIRECTIONS["FRONT"]
    LEFT = rotation.DIRECTIONS["LEFT"]
    UP = rotation.DIRECTIONS["UP"]

    # reject objects behind player or not in cone of sight (but always include
    # an object if it's directly looked at)
    xsect = tuple(capped_line_of_sight(agent, player_struct, 25))
    if not loose:
        candidates_ = [
            (p, o, r)
            for (p, o, r) in candidates_
            if xsect in getattr(o, "blocks", {})
            or r @ FRONT > ((r @ LEFT) ** 2 + (r @ UP) ** 2) ** 0.5
        ]

    # if looking directly at an object, sort by proximity to look intersection
    if euclid_dist(pos, xsect) <= 25:
        candidates_.sort(key=lambda c: euclid_dist(c[0], xsect))
    else:
        # otherwise, sort by closest to look vector
        candidates_.sort(key=lambda c: ((c[2] @ LEFT) ** 2 + (c[2] @ UP) ** 2) ** 0.5)
    # linit returns of things too far away
    candidates_ = [c for c in candidates_ if euclid_dist(pos, c[0]) < max_distance]
    # limit number of returns
    if limit == "ALL":
        limit = len(candidates_)
    return [(p, o) for (p, o, r) in candidates_[:limit]]
Example #7
0
    def perceive(self, force=False):
        """
        Every n agent_steps (defined by perceive_freq), update in agent memory
        location/pose of all agents, players, mobs; item stack positions and
        changed blocks.

        Args:
            force (boolean): set to True to run all perceptual heuristics right now,
                as opposed to waiting for perceive_freq steps (default: False)
        """
        # FIXME (low pri) remove these in code, get from sql
        self.agent.pos = to_block_pos(pos_to_np(self.agent.get_player().pos))
        boring_blocks = self.agent.low_level_data["boring_blocks"]
        """
        perceive_info is a dictionary with the following possible members :
            - mobs(list) : List of mobs in perception range
            - agent_pickable_items(dict) - member with the following possible children:
                - in_perception_items(list) - List of item_stack
                - all_items(set) - Set of item stack entityIds
            - agent_attributes(NamedTuple) - returns the namedTuple Player for agent
            - other_player_list(List) - List of [player, location] of all other players
            - changed_block_attributes(dict) - Dictionary of mappings from (xyz, idm) to [
                                                                                        interesting,
                                                                                        player_placed,
                                                                                        agent_placed,
                                                                                    ]
            
        """
        perceive_info = {}
        perceive_info["mobs"] = None
        perceive_info["agent_pickable_items"] = {}
        perceive_info["agent_attributes"] = None
        perceive_info["other_player_list"] = []
        perceive_info["changed_block_attributes"] = {}

        if self.agent.count % self.perceive_freq == 0 or force:
            # Find mobs in perception range
            mobs = []
            for mob in self.agent.get_mobs():
                if (euclid_dist(self.agent.pos, pos_to_np(mob.pos)) <
                        self.agent.memory.perception_range):
                    mobs.append(mob)
            perceive_info["mobs"] = mobs if mobs else None

            # Find items that can be picked by the agent, and in perception range
            all_items = set()
            in_perception_items = []
            for item_stack in self.agent.get_item_stacks():
                all_items.add(item_stack.entityId)
                if (euclid_dist(self.agent.pos, pos_to_np(item_stack.pos)) <
                        self.agent.memory.perception_range):
                    in_perception_items.append(item_stack)
            perceive_info["agent_pickable_items"] = perceive_info.get(
                "agent_pickable_items", {})
            perceive_info["agent_pickable_items"]["in_perception_items"] = (
                in_perception_items if in_perception_items else None)
            perceive_info["agent_pickable_items"][
                "all_items"] = all_items if all_items else None

        # note: no "force"; these run on every perceive call.  assumed to be fast
        perceive_info["agent_attributes"] = self.get_agent_player(
        )  # Get Agent attributes
        # List of other players in-game
        perceive_info["other_player_list"] = self.update_other_players(
            self.agent.get_other_players())
        # Changed blocks and their attributes
        perceive_info["changed_block_attributes"] = {}
        for (xyz, idm) in self.agent.safe_get_changed_blocks():
            interesting, player_placed, agent_placed = self.on_block_changed(
                xyz, idm, boring_blocks)
            perceive_info["changed_block_attributes"][(xyz, idm)] = [
                interesting,
                player_placed,
                agent_placed,
            ]

        return CraftAssistPerceptionData(
            mobs=perceive_info["mobs"],
            agent_pickable_items=perceive_info["agent_pickable_items"],
            agent_attributes=perceive_info["agent_attributes"],
            other_player_list=perceive_info["other_player_list"],
            changed_block_attributes=perceive_info["changed_block_attributes"],
        )