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)
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]]
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"], )