def get_next_waypoint_along_path(self, agent_entity: WorldEntity) -> Optional[Tuple[int, int]]: if self.path: # ----------------------------------------------- # 1: Remove first waypoint if close enough to it # ----------------------------------------------- # TODO: Does this cause problems for specific entity sizes / movement speeds? closeness_margin = 50 if is_x_and_y_within_distance(agent_entity.get_position(), self.path[0], closeness_margin): # print("Popping " + str(self.path[0]) + " as I'm so close to it.") self.path.pop(0) if self.path: # print("After popping, returning " + str(self.path[0])) return self.path[0] else: # print("no path after popping. stopping.") return None # ----------------------------------------------- # 2: Remove first waypoint if it's opposite direction of second waypoint # ----------------------------------------------- if len(self.path) >= 2: dir_to_waypoint_0 = get_directions_to_position(agent_entity, self.path[0])[0] dir_to_waypoint_1 = get_directions_to_position(agent_entity, self.path[1])[0] if dir_to_waypoint_0 == get_opposite_direction(dir_to_waypoint_1): # print("Not gonna go back. Popping " + str(self.path[0])) self.path.pop(0) # print("Popped first position. Next waypoint: " + str(self.path[0])) return self.path[0] if self.path: return self.path[0] else: # print("no path found. stopping.") return None # print("Leaked through. returning none") return None
def __init__(self, npc_type: NpcType, world_entity: WorldEntity, health_resource: HealthOrManaResource, npc_mind, npc_category: NpcCategory, enemy_loot_table: Optional[LootTableId], death_sound_id: Optional[SoundId], max_distance_allowed_from_start_position: Optional[int], is_boss: bool = False): self.npc_type = npc_type self.world_entity = world_entity self.health_resource = health_resource self.npc_mind = npc_mind self.active_buffs: List[BuffWithDuration] = [] self.invulnerable: bool = False self.stun_status = StunStatus() self.npc_category = npc_category self.is_enemy = npc_category == NpcCategory.ENEMY self.is_neutral = npc_category == NpcCategory.NEUTRAL self.enemy_loot_table = enemy_loot_table self.death_sound_id = death_sound_id self.start_position = world_entity.get_position( ) # Only for neutral NPC self.max_distance_allowed_from_start_position = max_distance_allowed_from_start_position # Only for neutral NPC self.is_boss: bool = is_boss self.quest_giver_state: Optional[ QuestGiverState] = None # Only for neutral NPC
def update_path_towards_target(self, agent_entity: WorldEntity, game_state: GameState, target_entity: WorldEntity): agent_cell = _translate_world_position_to_cell(agent_entity.get_position(), game_state.game_world.entire_world_area) target_cell = _translate_world_position_to_cell(target_entity.get_position(), game_state.game_world.entire_world_area) agent_cell_size = (agent_entity.pygame_collision_rect.w // GRID_CELL_WIDTH + 1, agent_entity.pygame_collision_rect.h // GRID_CELL_WIDTH + 1) self.global_path_finder.register_entity_size(agent_cell_size) path_with_cells = self.global_path_finder.run(agent_cell_size, agent_cell, target_cell) if path_with_cells: # Note: Cells are expressed in non-negative values (and need to be translated to game world coordinates) path = [_translate_cell_to_world_position(cell, game_state.game_world.entire_world_area) for cell in path_with_cells] if DEBUG_RENDER_PATHFINDING: _add_visual_lines_along_path(game_state, path) self.path = path else: self.path = None
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, _is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return self._summon_trait.update(npc, game_state, time_passed) self._random_walk_trait.update(npc, game_state, time_passed) self._time_since_healing += time_passed self._time_since_shoot += time_passed if self._time_since_healing > self._healing_cooldown: self._time_since_healing = 0 self._healing_cooldown = self._random_healing_cooldown() necro_center_pos = npc.world_entity.get_center_position() nearby_hurt_enemies = [ e for e in game_state.game_world.non_player_characters if e.is_enemy and is_x_and_y_within_distance( necro_center_pos, e.world_entity.get_center_position(), 200) and e != npc and not e.health_resource.is_at_max() ] if nearby_hurt_enemies: healing_target = nearby_hurt_enemies[0] healing_target.health_resource.gain(5) healing_target_pos = healing_target.world_entity.get_center_position( ) visual_line = VisualLine((80, 200, 150), necro_center_pos, healing_target_pos, Millis(350), 3) game_state.game_world.visual_effects.append(visual_line) play_sound(SoundId.ENEMY_NECROMANCER_HEAL) if self._time_since_shoot > self._shoot_cooldown: self._time_since_shoot = 0 self._shoot_cooldown = self._random_shoot_cooldown() npc.world_entity.direction = get_directions_to_position( npc.world_entity, player_entity.get_position())[0] npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.2 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction, projectile_speed) projectile = Projectile( projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.game_world.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_ATTACK_NECRO)
def get_target(agent_entity: WorldEntity, game_state: GameState) -> EnemyTarget: # Enemies should prioritize attacking a summon over attacking the player player_summons = [ npc for npc in game_state.game_world.non_player_characters if npc.npc_category == NpcCategory.PLAYER_SUMMON ] if player_summons: player_summon = player_summons[0] agent_position = agent_entity.get_position() distance_to_npc_target = get_manhattan_distance( player_summon.world_entity.get_position(), agent_position) distance_to_player = get_manhattan_distance( game_state.game_world.player_entity.get_position(), agent_position) if distance_to_npc_target < distance_to_player: return EnemyTarget.npc(player_summon.world_entity, player_summon) return EnemyTarget.player(game_state.game_world.player_entity)
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return super().control_npc(game_state, npc, player_entity, is_player_invisible, time_passed) self.sprint_cooldown_remaining -= time_passed sprint_distance_limit = 250 if self.sprint_cooldown_remaining <= 0: is_far_away = get_manhattan_distance( npc.world_entity.get_position(), player_entity.get_position()) > sprint_distance_limit if is_far_away: npc.gain_buff_effect( get_buff_effect(BuffType.ENEMY_GOBLIN_SPEARMAN_SPRINT), Millis(2500)) self.sprint_cooldown_remaining = self.random_cooldown()
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): if npc.stun_status.is_stunned(): return super().control_npc(game_state, npc, player_entity, is_player_invisible, time_passed) self._time_since_state_change += time_passed if self._state == State.BASE: if self._time_since_state_change > NpcMind.STATE_DURATION_BASE: self._time_since_state_change -= NpcMind.STATE_DURATION_BASE self._state = State.FIRING npc.gain_buff_effect(get_buff_effect(BUFF_STUNNED), Millis(NpcMind.STATE_DURATION_FIRING)) return elif self._state == State.FIRING: if self._time_since_state_change > NpcMind.STATE_DURATION_FIRING: self._time_since_state_change -= NpcMind.STATE_DURATION_FIRING self._state = State.BASE return self._time_since_fired += time_passed if self._time_since_fired > NpcMind.FIRE_COOLDOWN: self._time_since_fired -= NpcMind.FIRE_COOLDOWN directions_to_player = get_directions_to_position(npc.world_entity, player_entity.get_position()) new_direction = directions_to_player[0] if random.random() < 0.1 and directions_to_player[1] is not None: new_direction = directions_to_player[1] npc.world_entity.direction = new_direction npc.world_entity.set_not_moving() center_position = npc.world_entity.get_center_position() distance_from_enemy = 35 projectile_pos = translate_in_direction( get_position_from_center_position(center_position, PROJECTILE_SIZE), npc.world_entity.direction, distance_from_enemy) projectile_speed = 0.3 projectile_entity = WorldEntity(projectile_pos, PROJECTILE_SIZE, Sprite.NONE, npc.world_entity.direction, projectile_speed) projectile = Projectile(projectile_entity, create_projectile_controller(PROJECTILE_TYPE)) game_state.game_world.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_MAGIC_SKELETON_BOSS)
def _add_visual_line_to_next_waypoint(destination, agent_entity: WorldEntity, game_state: GameState): start = _get_middle_of_cell_from_position(agent_entity.get_position()) end = _get_middle_of_cell_from_position(destination) game_state.game_world.visual_effects.append(VisualLine((150, 150, 150), start, end, Millis(100), 2))
def does_entity_intersect_with_wall(self, entity: WorldEntity): nearby_walls = self.get_walls_close_to_position(entity.get_position()) return any([ w for w in nearby_walls if boxes_intersect(w.rect(), entity.rect()) ])
def serialize(entity: WorldEntity): return PlayerJson.serialize_from_position(entity.get_position())
def handle_nearby_entities(self, player_entity: WorldEntity, game_state: GameState, game_engine: GameEngine): self.entity_to_interact_with = None player_position = player_entity.get_position() distance_to_closest_entity = sys.maxsize for npc in game_state.game_world.non_player_characters: if has_npc_dialog(npc.npc_type): close_to_player = is_x_and_y_within_distance( player_position, npc.world_entity.get_position(), 75) distance = get_manhattan_distance_between_rects( player_entity.rect(), npc.world_entity.rect()) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = npc distance_to_closest_entity = distance lootables_on_ground: List[LootableOnGround] = list( game_state.game_world.items_on_ground) lootables_on_ground += game_state.game_world.consumables_on_ground for lootable in lootables_on_ground: if boxes_intersect(player_entity.rect(), lootable.world_entity.rect()): self.entity_to_interact_with = lootable distance_to_closest_entity = 0 for portal in game_state.game_world.portals: close_to_player = is_x_and_y_within_distance( player_position, portal.world_entity.get_position(), 75) distance = get_manhattan_distance_between_rects( player_entity.rect(), portal.world_entity.rect()) if close_to_player: game_engine.handle_being_close_to_portal(portal) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = portal distance_to_closest_entity = distance for warp_point in game_state.game_world.warp_points: close_to_player = is_x_and_y_within_distance( player_position, warp_point.world_entity.get_position(), 75) distance = get_manhattan_distance_between_rects( player_entity.rect(), warp_point.world_entity.rect()) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = warp_point distance_to_closest_entity = distance for chest in game_state.game_world.chests: close_to_player = is_x_and_y_within_distance( player_position, chest.world_entity.get_position(), 75) distance = get_manhattan_distance_between_rects( player_entity.rect(), chest.world_entity.rect()) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = chest distance_to_closest_entity = distance for shrine in game_state.game_world.shrines: close_to_player = is_x_and_y_within_distance( player_position, shrine.world_entity.get_position(), 75) distance = get_manhattan_distance_between_rects( player_entity.rect(), shrine.world_entity.rect()) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = shrine distance_to_closest_entity = distance for dungeon_entrance in game_state.game_world.dungeon_entrances: close_to_player = is_x_and_y_within_distance( player_position, dungeon_entrance.world_entity.get_position(), 60) distance = get_manhattan_distance_between_rects( player_entity.rect(), dungeon_entrance.world_entity.rect()) if close_to_player and distance < distance_to_closest_entity: self.entity_to_interact_with = dungeon_entrance distance_to_closest_entity = distance