def step(self, agent): mx, My, mz = self.origin Mx = mx + (self.width - 1) my = My - (self.depth - 1) Mz = mz + (self.length - 1) blocks = agent.get_blocks(mx, Mx, my, My, mz, Mz) # if top row is above ground, make sure you are digging into the ground if np.isin(blocks[-1, :, :, 0], PASSABLE_BLOCKS).all(): my -= 1 poss = [ (x, y, z) for x in range(mx, Mx + 1) for y in range(my, My + 1) for z in range(mz, Mz + 1) ] schematic = util.fill_idmeta(agent, poss) self.destroy_task = Destroy(agent, {"schematic": schematic, "dig_message": True}) agent.memory.task_stack_push(self.destroy_task, parent_memid=self.memid) self.finished = True
def get_all_nearby_holes(agent, location, radius=15): """Return a list of holes. Each hole is tuple(list[xyz], idm)""" sx, sy, sz = location max_height = sy + 5 map_size = radius * 2 + 1 height_map = [[sz] * map_size for i in range(map_size)] hid_map = [[-1] * map_size for i in range(map_size)] idm_map = [[(0, 0)] * map_size for i in range(map_size)] visited = set([]) global current_connected_comp, current_idm current_connected_comp = [] current_idm = (2, 0) # helper functions def get_block_info(x, z): # fudge factor 5 height = max_height while True: B = agent.get_blocks(x, x, height, height, z, z) if ((B[0, 0, 0, 0] != 0) and (x != sx or z != sz or height != sy) and (x != agent.pos[0] or z != agent.pos[2] or height != agent.pos[1]) and (B[0, 0, 0, 0] != 383)): # if it's not a mobile block (agent, speaker, mobs) return height, tuple(B[0, 0, 0]) height -= 1 gx = [0, 0, -1, 1] gz = [1, -1, 0, 0] def dfs(x, y, z): """ Traverse current connected component and return minimum height of all surrounding blocks """ build_height = 100000 if (x, y, z) in visited: return build_height global current_connected_comp, current_idm current_connected_comp.append( (x - radius + sx, y, z - radius + sz)) # absolute positions visited.add((x, y, z)) for d in range(4): nx = x + gx[d] nz = z + gz[d] if nx >= 0 and nz >= 0 and nx < map_size and nz < map_size: if height_map[x][z] == height_map[nx][nz]: build_height = min(build_height, dfs(nx, y, nz)) else: build_height = min(build_height, height_map[nx][nz]) current_idm = idm_map[nx][nz] else: # bad ... hole is not within defined radius return -100000 return build_height # find all holes blocks_queue = [] for i in range(map_size): for j in range(map_size): height_map[i][j], idm_map[i][j] = get_block_info( i - radius + sx, j - radius + sz) heapq.heappush(blocks_queue, (height_map[i][j] + 1, (i, height_map[i][j] + 1, j))) holes = [] while len(blocks_queue) > 0: hxyz = heapq.heappop(blocks_queue) h, (x, y, z) = hxyz # NB: relative positions if (x, y, z) in visited or y > max_height: continue assert h == height_map[x][z] + 1, " h=%d heightmap=%d, x,z=%d,%d" % ( h, height_map[x][z], x, z, ) # sanity check current_connected_comp = [] current_idm = (2, 0) build_height = dfs(x, y, z) if build_height >= h: holes.append((current_connected_comp.copy(), current_idm)) cur_hid = len(holes) - 1 for n, xyz in enumerate(current_connected_comp): x, y, z = xyz rx, ry, rz = x - sx + radius, y + 1, z - sz + radius heapq.heappush(blocks_queue, (ry, (rx, ry, rz))) height_map[rx][rz] += 1 if hid_map[rx][rz] != -1: holes[cur_hid][0].extend(holes[hid_map[rx][rz]][0]) holes[hid_map[rx][rz]] = ([], (0, 0)) hid_map[rx][rz] = cur_hid # A bug in the algorithm above produces holes that include non-air blocks. # Just patch the problem here, since this function will eventually be # performed by an ML model for i, (xyzs, idm) in enumerate(holes): blocks = util.fill_idmeta(agent, xyzs) xyzs = [xyz for xyz, (d, _) in blocks if d == 0] # remove non-air blocks holes[i] = (xyzs, idm) # remove 0-length holes holes = [h for h in holes if len(h[0]) > 0] return holes
def step(self, agent): self.interrupted = False # get blocks occupying build area ox, oy, oz = self.origin sy, sz, sx, _ = self.schematic.shape current = agent.get_blocks(ox, ox + sx - 1, oy, oy + sy - 1, oz, oz + sz - 1) # save state for undo() if self.old_blocks_list is None: self.old_blocks_list = npy_to_blocks_list(current, self.origin) if len(self.old_blocks_list) > 0: self.old_origin = np.min(util.strip_idmeta(self.old_blocks_list), axis=0) # are we done? # TODO: diff ignores block meta right now because placing stairs and # chests in the appropriate orientation is non-trivial diff = ( (current[:, :, :, 0] != self.schematic[:, :, :, 0]) & (self.attempts > 0) & np.isin(current[:, :, :, 0], BUILD_IGNORE_BLOCKS, invert=True) ) if self.embed: diff &= self.schematic[:, :, :, 0] != 0 # don't delete blocks for pair in BUILD_INTERCHANGEABLE_PAIRS: diff &= np.isin(current[:, :, :, 0], pair, invert=True) | np.isin( self.schematic[:, :, :, 0], pair, invert=True ) if not np.any(diff): self.finish(agent) return # blocks that would need to be removed remove_mask = diff & (current[:, :, :, 0] != 0) # destroy any blocks in the way first rel_yzxs = np.argwhere(remove_mask) xyzs = [ (x + self.origin[0], y + self.origin[1], z + self.origin[2]) for (y, z, x) in rel_yzxs ] if xyzs: logging.info("Excavating {} blocks first".format(len(xyzs))) agent.memory.task_stack_push( Destroy(agent, {"schematic": util.fill_idmeta(agent, xyzs)}), parent_memid=self.memid, ) return # get next block to place yzx = self.get_next_target(agent, current, diff) idm = self.schematic[tuple(yzx)] current_idm = current[tuple(yzx)] # try placing block target = yzx[[2, 0, 1]] + self.origin logging.debug("trying to place {} @ {}".format(idm, target)) if tuple(target) in (tuple(agent.pos), tuple(agent.pos + [0, 1, 0])): # can't place block where you're standing, so step out of the way self.step_any_dir(agent) return if util.manhat_dist(agent.pos, target) <= self.PLACE_REACH: # block is within reach assert current_idm[0] != idm[0], "current={} idm={}".format(current_idm, idm) if current_idm[0] != 0: logging.debug( "removing block {} @ {} from {}".format(current_idm, target, agent.pos) ) agent.dig(*target) if idm[0] != 0: agent.set_held_item(idm) logging.debug("placing block {} @ {} from {}".format(idm, target, agent.pos)) x, y, z = target if agent.place_block(x, y, z): B = agent.get_blocks(x, x, y, y, z, z) if B[0, 0, 0, 0] == idm[0]: agent.memory.pending_agent_placed_blocks.add((x, y, z)) else: logging.error( "failed to place block {} @ {}, but place_block returned True. \ Got {} instead.".format( idm, target, B[0, 0, 0, :] ) ) self.new_blocks.append(((x, y, z), idm)) else: logging.warn("failed to place block {} from {}".format(target, agent.pos)) self.attempts[tuple(yzx)] -= 1 if self.attempts[tuple(yzx)] == 0 and not self.giving_up_message_sent: agent.send_chat( "I'm skipping a block because I can't place it. Maybe something is in the way." ) self.giving_up_message_sent = True else: # too far to place; move first task = Move(agent, {"target": target, "approx": self.PLACE_REACH}, self.featurizer) agent.memory.task_stack_push(task, parent_memid=self.memid)