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.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.items_on_ground) lootables_on_ground += game_state.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.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.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.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
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 get_enemies_within_x_y_distance_of(self, distance: int, position: Tuple[int, int]): return [ e for e in self.non_player_characters if e.is_enemy and is_x_and_y_within_distance( e.world_entity.get_center_position(), position, distance) ]
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._time_since_attack += time_passed self._time_since_updated_path += time_passed self._time_since_reevaluated += time_passed enemy_entity = npc.world_entity target: EnemyTarget = get_target(enemy_entity, game_state) if self._time_since_updated_path > self._update_path_interval: self._time_since_updated_path = 0 if not is_player_invisible: self.pathfinder.update_path_towards_target( enemy_entity, game_state, target.entity) new_next_waypoint = self.pathfinder.get_next_waypoint_along_path( enemy_entity) should_update_waypoint = self.next_waypoint != new_next_waypoint if self._time_since_reevaluated > self._reevaluate_next_waypoint_direction_interval: self._time_since_reevaluated = 0 should_update_waypoint = True if should_update_waypoint: self.next_waypoint = new_next_waypoint if self.next_waypoint: direction = self.pathfinder.get_dir_towards_considering_collisions( game_state, enemy_entity, self.next_waypoint) _move_in_dir(enemy_entity, direction) else: enemy_entity.set_not_moving() if self._time_since_attack > self._attack_interval: if not is_player_invisible: enemy_position = enemy_entity.get_center_position() target_center_pos = target.entity.get_center_position() if is_x_and_y_within_distance(enemy_position, target_center_pos, 200): self._time_since_attack = 0 self.randomize_attack_interval() play_sound(SoundId.ENEMY_ATTACK_ICE_WITCH) damage = random.randint(DAMAGE_MIN, DAMAGE_MAX) deal_npc_damage(damage, DamageType.MAGIC, game_state, enemy_entity, npc, target) game_state.game_world.visual_effects += [ (VisualLine((100, 100, 200), enemy_position, target_center_pos, Millis(120), 3)), (VisualLine((150, 150, 250), enemy_position, target_center_pos, Millis(240), 2)) ] chance_to_resist_slow = game_state.player_state.get_effective_movement_impairing_resist_chance( ) # TODO It's error-prone that we have to check this for every negative debuff that can slow player if random.random() > chance_to_resist_slow: game_state.player_state.gain_buff_effect( get_buff_effect(SLOW_BUFF_TYPE), Millis(1500))
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 update_npc_position_within_game_world(self, npc: NonPlayerCharacter, time_passed: Millis): entity = npc.world_entity new_position = entity.get_new_position_according_to_dir_and_speed(time_passed) if new_position: if npc.max_distance_allowed_from_start_position: is_close_to_start_position = is_x_and_y_within_distance( npc.start_position, new_position, npc.max_distance_allowed_from_start_position) if not is_close_to_start_position: return new_pos_within_world = self.get_within_world( new_position, (entity.pygame_collision_rect.w, entity.pygame_collision_rect.h)) if not self.would_entity_collide_if_new_pos(entity, new_pos_within_world): entity.set_position(new_pos_within_world)
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._time_since_attack += time_passed self._time_since_updated_path += time_passed self._time_since_reevaluated += time_passed enemy_entity = npc.world_entity target: EnemyTarget = get_target(enemy_entity, game_state) if self._time_since_updated_path > self._update_path_interval: self._time_since_updated_path = 0 if not is_player_invisible: self.pathfinder.update_path_towards_target( enemy_entity, game_state, target.entity) new_next_waypoint = self.pathfinder.get_next_waypoint_along_path( enemy_entity) should_update_waypoint = self.next_waypoint != new_next_waypoint if self._time_since_reevaluated > self._reevaluate_next_waypoint_direction_interval: self._time_since_reevaluated = 0 should_update_waypoint = True if should_update_waypoint: self.next_waypoint = new_next_waypoint if self.next_waypoint: direction = self.pathfinder.get_dir_towards_considering_collisions( game_state, enemy_entity, self.next_waypoint) if random.random( ) < self.chance_to_stray_from_path and direction: direction = random.choice( get_perpendicular_directions(direction)) _move_in_dir(enemy_entity, direction) else: enemy_entity.set_not_moving() if self._time_since_attack > self._attack_interval: if not is_player_invisible: enemy_position = enemy_entity.get_center_position() target_center_pos = target.entity.get_center_position() if is_x_and_y_within_distance(enemy_position, target_center_pos, 80): self._time_since_attack = 0 self.randomize_attack_interval() deal_npc_damage(self.damage_amount, DamageType.PHYSICAL, game_state, enemy_entity, npc, target)
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, is_player_invisible: bool, time_passed: Millis): self._time_since_attack += time_passed self._time_since_updated_path += time_passed self._time_since_reevaluated += time_passed self._time_since_shield += time_passed enemy_entity = npc.world_entity if self._time_since_updated_path > self._update_path_interval: self._time_since_updated_path = 0 self.pathfinder.update_path_towards_target( enemy_entity, game_state, game_state.player_entity) new_next_waypoint = self.pathfinder.get_next_waypoint_along_path( enemy_entity) should_update_waypoint = self.next_waypoint != new_next_waypoint if self._time_since_reevaluated > self._reevaluate_next_waypoint_direction_interval: self._time_since_reevaluated = 0 should_update_waypoint = True if should_update_waypoint: self.next_waypoint = new_next_waypoint if self.next_waypoint: direction = self.pathfinder.get_dir_towards_considering_collisions( game_state, enemy_entity, self.next_waypoint) if random.random() < 0.1 and direction: direction = random.choice( get_perpendicular_directions(direction)) _move_in_dir(enemy_entity, direction) else: enemy_entity.set_not_moving() enemy_center_pos = enemy_entity.get_center_position() if self._time_since_attack > self._attack_interval: self._time_since_attack = 0 game_state.visual_effects.append( VisualCircle((100, 100, 100), enemy_center_pos, 180, 180, Millis(self._attack_interval), 1, enemy_entity)) if not is_player_invisible: player_center_pos = game_state.player_entity.get_center_position( ) if is_x_and_y_within_distance(enemy_center_pos, player_center_pos, 160): deal_damage_to_player(game_state, 3, DamageType.MAGIC, npc) game_state.visual_effects += [ VisualCircle((0, 0, 0), enemy_center_pos, 25, 50, Millis(200), 2, enemy_entity), VisualLine((0, 100, 0), enemy_center_pos, player_center_pos, Millis(200), 2), VisualCircle((0, 100, 0), player_center_pos, 20, 40, Millis(150), 2, player_entity), VisualCircle((0, 150, 0), player_center_pos, 25, 50, Millis(200), 2, player_entity), VisualCircle((0, 200, 0), player_center_pos, 30, 60, Millis(300), 2, player_entity), ] if self._time_since_shield > self._shield_interval: self._time_since_shield = 0 game_state.visual_effects.append( VisualCircle((0, 0, 150), enemy_center_pos, 60, 20, Millis(self._shield_duration), 2, enemy_entity)) npc.gain_buff_effect(get_buff_effect(BUFF_TYPE_INVULN), Millis(self._shield_duration))
def control_npc(self, game_state: GameState, npc: NonPlayerCharacter, player_entity: WorldEntity, _is_player_invisible: bool, time_passed: Millis): self._time_since_decision += time_passed self._time_since_summoning += time_passed self._time_since_healing += time_passed self._time_since_shoot += time_passed if self._time_since_summoning > self._summoning_cooldown: necro_center_pos = npc.world_entity.get_center_position() self._time_since_summoning = 0 self._alive_summons = [summon for summon in self._alive_summons if summon in game_state.non_player_characters] if len(self._alive_summons) < 3: relative_pos_from_summoner = (random.randint(-150, 150), random.randint(-150, 150)) summon_center_pos = sum_of_vectors(necro_center_pos, relative_pos_from_summoner) summon_type = random.choice([NpcType.ZOMBIE, NpcType.MUMMY]) summon_size = NON_PLAYER_CHARACTERS[summon_type].size summon_pos = game_state.get_within_world( get_position_from_center_position(summon_center_pos, summon_size), summon_size) summon_enemy = create_npc(summon_type, summon_pos) is_wall_blocking = game_state.walls_state.does_rect_intersect_with_wall( rect_from_corners(necro_center_pos, summon_center_pos)) is_position_blocked = game_state.would_entity_collide_if_new_pos(summon_enemy.world_entity, summon_pos) if not is_wall_blocking and not is_position_blocked: self._summoning_cooldown = self._random_summoning_cooldown() game_state.add_non_player_character(summon_enemy) self._alive_summons.append(summon_enemy) game_state.visual_effects.append( VisualCircle((80, 150, 100), necro_center_pos, 40, 70, Millis(120), 3)) game_state.visual_effects.append( VisualCircle((80, 150, 100), summon_center_pos, 40, 70, Millis(120), 3)) play_sound(SoundId.ENEMY_NECROMANCER_SUMMON) else: # Failed to summon, so try again without waiting full duration self._summoning_cooldown = 500 else: self._summoning_cooldown = self._random_summoning_cooldown() 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.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.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.projectile_entities.append(projectile) play_sound(SoundId.ENEMY_ATTACK_NECRO) if self._time_since_decision > self._decision_interval: self._time_since_decision = 0 if random.random() < 0.2: direction = random_direction() npc.world_entity.set_moving_in_dir(direction) else: npc.world_entity.set_not_moving()