def require_proximity(exact=None, at_least=None, at_most=None, fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS): self, action = f_kwargs['self'], f_kwargs['action'] sub_loc = self.simulation_manager.grid_model.get_loc_of_obj(action.actor) if action.target_id is None: action.status = ActionStatus.FAILED action.reason = "No entity is targeted for proximity" return else: tar_loc = self.simulation_manager.grid_model.get_loc_of_obj(action.target_id) dist = cubic_manhattan(sub_loc, tar_loc) if exact is not None and dist != exact: action.status = ActionStatus.FAILED action.reason = "Target proximity was not exactly equal to %d, value: %d" % (exact, dist) return if at_least is not None and dist <= at_least: action.status = ActionStatus.FAILED action.reason = "Target proximity was not at least equal to %d, value: %d" % (at_least, dist) return if at_most is not None and dist >= at_most: action.status = ActionStatus.FAILED action.reason = "Target proximity was not at most equal to %d, value: %d" % (at_most, dist) return return fn(*f_args, **f_kwargs)
def move(self, entity_id, vec: np.ndarray): """ Shorthand for remove-insert with a vector. """ # Skip empty moves. if (vec == np.array([0, 0, 0])).all(): return curr_chunk = self._obj_chunk.get(entity_id) dest_chunk_id = curr_chunk.chunk_id new_offset = curr_chunk.offset_vec + vec # Determine the destination chunk if self.chunk_radius < cubic_manhattan(curr_chunk.chunk_vec, new_offset): # Compute offset and chunk from absolute. absolute = curr_chunk.chunk_vec + new_offset dest_chunk_id = self.get_chunk_of_loc( absolute, hint_chunk=curr_chunk.chunk_vec) dest_chunk_vec = self._buf2vec(dest_chunk_id) new_offset = absolute - dest_chunk_vec # Remove the entity from the original location. self.remove(entity_id) # Add it to the new location. self.insert_chunked(dest_chunk_id, new_offset, entity_id)
def _find_chunk_of_location(self, absolute_position, starting_chunk: np.ndarray = None): # Default start at the origin (inefficient). neighbor_count = 6 starting_chunk = np.array( [0, 0, 0]) if starting_chunk is None else starting_chunk starting_distance = cubic_manhattan(starting_chunk, absolute_position) destination_repeat = np.repeat([absolute_position], neighbor_count, axis=0) cur_min = [(starting_chunk, starting_distance)] destination_chunk = None # TODO: move chunk searching to chunk class? or split since this may be needed for finding unloaded chunks # Also maybe use dot product to determine direction while len(cur_min) != 0: chunk_center, remaining_dist = cur_min.pop(0) neighbors_matrix = self.neighbor_chunk_vec + np.repeat( [chunk_center], neighbor_count, axis=0) neighbor_distances = list( cubic_manhattan(neighbors_matrix, destination_repeat, axis=1)) neighbors_and_dist = list( zip(list(neighbors_matrix), neighbor_distances)) closer_neighbors = list( filter(lambda nd: nd[1] < remaining_dist, neighbors_and_dist)) # If no neighbors increase the distance, then the current chunk is the closest. if len(closer_neighbors) == 0: destination_chunk = self._vec2buf(chunk_center) break min_dist = min(closer_neighbors, key=lambda x: x[1]) cur_min.append(min_dist) return destination_chunk
def get_entities_in_radius_chunked(self, chunk_id, offset, radius): # TODO: make this more performant, and not calculate entities clearly too far away (using chunk sizes) # Determine radius-to-chunk-radius ratio, and grab surrounding chunks of relevance. # Use the chunk list to filter the _chunk dictionary, then calculate distances. center_chunk = self._buf2vec(chunk_id) entity_chunks = list( map(lambda e_cid: (e_cid[0], e_cid[1].absolute), self._obj_chunk.items())) entities, chunk_locs = map(np.array, zip(*entity_chunks)) center_repeated = np.repeat([center_chunk], repeats=len(chunk_locs), axis=0) entity_distances = list( cubic_manhattan(chunk_locs, center_repeated, axis=1)) # Ignore chunks over a certain distance (no possibility of any parts being in the radius). entity_distances = list(zip(entities, entity_distances, chunk_locs)) return list( filter(lambda ed: ed[1] + self.chunk_radius <= radius, entity_distances))
def get_locations_of_path(self, path: np.ndarray, starting_chunk: np.ndarray = None): if starting_chunk is None: # determine starting chunk. Worst case performance pass locations = [] chunk_id = self._vec2buf(starting_chunk) current_chunk = self.chunks.get(chunk_id) direction_list = [j - i for i, j in zip(path[:-1], path[1:])] offset_vec_neighbors = current_chunk.offset_vec_neighbors center, offset = starting_chunk, path[0] - starting_chunk locations.append(self.at_chunked(chunk_id, offset)) # TODO: not as performant as possible. Maybe numba-fy this function for direction in direction_list: # Calculate new offset offset = offset + direction dist = cubic_manhattan(np.array([0, 0, 0]), offset) if dist > self.chunk_radius: # Determine which chunk comes from moving in a particular direction. offset_key = self._vec2buf(offset) chunk_vec, new_offset, neighbor_idx = Chunk.neighbor_by_offset.get( offset_key) # Translate the new center and set the new offset. center = center + chunk_vec offset = new_offset chunk_id = self._vec2buf(center) current_chunk = current_chunk.neighbors[neighbor_idx] if current_chunk is None: current_chunk = self.load_chunk_v(chunk_id) locations.append(self.at_chunked(chunk_id, offset)) return locations
def resolve(self, action: ObservationAction): subject_id = action.actor target_id = action.target_id existing_obsvs = self.simulation_manager.observation_manager.get_observations( subject_id) s_center, s_offset = self.simulation_manager.grid_model.get_chunk_offset( subject_id) s_chunk = s_center.tobytes() # Determine which type of observation is being done, Passive or Direct. if target_id is not None: # TODO: incomplete # Direct observation, improve LocationObservation that already exists. center = self.simulation_manager.grid_model.get_loc_of_obj( subject_id) target_loc = self.simulation_manager.grid_model.get_loc_of_obj( target_id) distance = cubic_manhattan(center, target_loc) existing_loc_obsv = existing_obsvs.get(target_id, LocationObservation)[0] existing_noise = existing_loc_obsv.noise if existing_loc_obsv is not None else None noise, result = self._get_noise_for(subject_id, target_id, distance, 0.5) if noise is None: action.status = ActionStatus.RESOLVED return # If there was a critical failure, require an update if any observations exist. require_overwrite = False if result == RollResult.Critical_Failure: require_overwrite = True if existing_noise is not None and (require_overwrite or noise < existing_noise): # If an existing observation exists, and there is a reason to overwrite it. existing_obsvs.remove_all(target_id, LocationObservation) elif existing_noise is not None and noise >= existing_noise: # If there was an existing entry, but no reason to update it, return. action.status = ActionStatus.RESOLVED return loc_obsv = LocationObservation(center=center, noise=noise, subject_id=subject_id, target_id=target_id) self.simulation_manager.observation_manager.add_observation( loc_obsv) else: entity_distances = self.simulation_manager.grid_model.get_entities_in_radius_chunked( s_center, s_offset, 10) center = self.simulation_manager.grid_model.get_location( subject_id) for tid, d, a_loc in entity_distances: if tid == subject_id: continue # Roll for perception and get the noisiness of the observation. noise, roll_result = self._get_noise_for(subject_id, tid, d) if noise is None: continue t_center, t_offset = self.simulation_manager.grid_model.get_chunk_offset( tid) t_absolute = t_center + t_offset t_chunk = t_center.tobytes() # Check the cache for a path key1, key2 = center.tobytes() + t_absolute.tobytes( ), t_absolute.tobytes() + center.tobytes() if key1 in self.path_cache: path_to_t = self.path_cache[key1] elif key2 in self.path_cache: path_to_t = self.path_cache[key2] else: # Cast a ray and find the MAX height and associated index along the path path_to_t = cast_hex_ray(center, t_absolute) self.path_cache[key1] = path_to_t self.path_cache[key2] = np.flip(path_to_t, axis=0) # Get all the locations locations = self.simulation_manager.grid_model.get_locations_of_path( path_to_t, s_center) # Ignore ends because they wont affect visibility. inter_path = locations[1:-1] if len(inter_path) > 0: # TODO: When SM is implemented sm_s, sm_t = 1, 1 starting_elev = locations[0].get_elevation() + sm_s * 2 ending_elev = locations[-1].get_elevation() + sm_t * 2 slope = (ending_elev - starting_elev) / d index, location = max(enumerate(inter_path), key=lambda n: n[1].get_elevation()) if location.get_elevation( ) >= starting_elev + (index + 1) * slope: continue # TODO: if desired, have the percentage of the target visible determine noise instead of distance. # If an observation already exists, update it. if tid in existing_obsvs.keys(): existing_obsvs.remove_all(tid, LocationObservation) loc_obsv = LocationObservation(center=a_loc, noise=noise, subject_id=subject_id, target_id=tid) self.simulation_manager.observation_manager.add_observation( loc_obsv) # TODO: manage movement challenges (terrain difficulty, walls, etc) # TODO: in order for this to work, the move resolver must know how much speed it remaining for a given turn. # this also go for other actions, they must know the game state so they can determine whether or not to modify # the game state. action.status = ActionStatus.RESOLVED