示例#1
0
    def __init__(self, start_pos, explosion_sheet, size, damage_amount,
                 damage_type, collision_grid, *groups):

        super().__init__(*groups)
        self.collision_grid = collision_grid
        self.radius = size
        self.collide_radius = self.radius
        self.explosion_sheet = explosion_sheet
        self.explosion_frames = 16
        self.explosion_images = []
        random_explosion_int = random.randrange(0, 512, 64)
        for i in range(0, self.explosion_frames):
            x_start_index = (i * 64)
            explosion_frame = self.explosion_sheet.subsurface(
                pygame.Rect(x_start_index + 1, random_explosion_int + 1, 62,
                            62))
            explosion_frame = pygame.transform.scale(
                explosion_frame, (self.radius * 2, self.radius * 2))
            self.explosion_images.append(explosion_frame)

        self.image = self.explosion_images[0]

        self.rect = self.explosion_images[0].get_rect()
        self.rect.center = start_pos

        self.position = [
            float(self.rect.center[0]),
            float(self.rect.center[1])
        ]

        # handle collision shape setup
        self.collision_grid = collision_grid
        handlers_by_type = {
            GameCollisionType.MONSTER: self.collision_grid.no_handler
        }
        self.collision_shape = CollisionCircle(self.position[0],
                                               self.position[1],
                                               self.collide_radius,
                                               handlers_by_type,
                                               GameCollisionType.EXPLOSION,
                                               [GameCollisionType.MONSTER])
        self.collision_shape.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_shape)

        self.should_die = False
        self.life_time = 0.45
        self.time = self.life_time
        self.frame_time = self.life_time / self.explosion_frames
        self.frame = 1

        self.damage = Damage(damage_amount, damage_type)

        self.should_kill_explosion_collision = False
        self.has_collision = True
示例#2
0
    def __init__(self, origin_centre_pos, facing_direction, fov=90.0, length=1000.0):

        self.facing_direction = Vector2(facing_direction[:])
        self.origin_centre_position = Vector2(origin_centre_pos[:])

        self.field_of_view = math.radians(fov)
        self.length = length
        self.length_squared = length ** 2

        self.epsilon = 0.075  # to handle floating point inaccuracy at small values (I think)
        self.arc_epsilon = 1.5  # to handle small gaps between arc points
        self.halfAuxRayTilt = 8.72664625995e-3 # half degree in radians
        self.half_aux_ray_tilt_cos = math.cos(self.halfAuxRayTilt)  # 0.999961923064
        self.half_aux_ray_tilt_sin = math.sin(self.halfAuxRayTilt)  # 8.72653549837e-3

        self.collision_circle = CollisionCircle(self.origin_centre_position.x,
                                                self.origin_centre_position.y,
                                                self.length,
                                                {CollisionType.WORLD_SOLID: CollisionNoHandler(),
                                                 CollisionType.WORLD_JUMP_THROUGH: CollisionNoHandler(),
                                                 CollisionType.WORLD_PLATFORM_EDGE: CollisionNoHandler(),
                                                 CollisionType.WORLD_JUMP_THROUGH_EDGE: CollisionNoHandler()},
                                                CollisionType.VIEW_CONE,
                                                [CollisionType.WORLD_SOLID,
                                                 CollisionType.WORLD_JUMP_THROUGH,
                                                 CollisionType.WORLD_PLATFORM_EDGE,
                                                 CollisionType.WORLD_JUMP_THROUGH_EDGE]
                                                )

        self.neg_cos_fov = math.cos(-self.field_of_view / 2)
        self.neg_sin_fov = math.sin(-self.field_of_view / 2)
        self.sin_fov = math.sin(self.field_of_view / 2)
        self.cos_fov = math.cos(self.field_of_view / 2)
        self.perp_facing_cos = math.cos(math.radians(90))
        self.perp_facing_sin = math.sin(math.radians(90))

        self.angle_points_array = []
        self.rays = []
        self.hit_points = []
        self.ctrl_points = []
        self.blocking_edges = []

        self.perp_facing_vec = None
        self.cone_extent_facings = None
        self.on_cone_changed_direction()

        self.end_positions = None
        self.on_cone_moved()

        self.fov_edges = [Edge("min", Vector2(self.origin_centre_position), Vector2(self.end_positions[0])),
                          Edge("max", Vector2(self.origin_centre_position), Vector2(self.end_positions[1]))]

        self.timing_clock = pygame.time.Clock()
示例#3
0
class PickUp(pygame.sprite.Sprite):
    def __init__(self, start_pos, image, type_name, collision_grid, *groups):
        super().__init__(*groups)
        self.collision_grid = collision_grid
        self.world_position = [start_pos[0], start_pos[1]]
        self.type_name = type_name
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.center = start_pos

        self.position = [
            float(self.rect.center[0]),
            float(self.rect.center[1])
        ]

        self.should_die = False

        self.collide_radius = 8
        self.collision_circle = CollisionCircle(
            self.position[0], self.position[1], self.collide_radius,
            {GameCollisionType.PLAYER: CollisionNoHandler()},
            GameCollisionType.PICKUP, [GameCollisionType.PLAYER])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)

    def remove_from_grid(self):
        self.collision_grid.remove_shape_from_grid(self.collision_circle)

    def react_to_collision(self):
        for shape in self.collision_circle.collided_shapes_this_frame:
            if shape.game_type == GameCollisionType.PLAYER:
                self.should_die = True
                player = shape.owner
                if self.type_name == "health":
                    player.add_health(0.25 * player.max_health)
                elif self.type_name == "mana":
                    player.add_mana(25)

    def update(self, tiled_level):
        self.position[
            0] = self.world_position[0] - tiled_level.position_offset[0]
        self.position[
            1] = self.world_position[1] - tiled_level.position_offset[1]
        self.rect.center = self.position

        self.collision_circle.set_position(self.world_position)
        if self.should_die:
            self.collision_grid.remove_shape_from_grid(self.collision_circle)
            self.kill()
示例#4
0
    def __init__(self, start_pos, image, type_name, collision_grid, *groups):
        super().__init__(*groups)
        self.collision_grid = collision_grid
        self.world_position = [start_pos[0], start_pos[1]]
        self.type_name = type_name
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.center = start_pos

        self.position = [
            float(self.rect.center[0]),
            float(self.rect.center[1])
        ]

        self.should_die = False

        self.collide_radius = 8
        self.collision_circle = CollisionCircle(
            self.position[0], self.position[1], self.collide_radius,
            {GameCollisionType.PLAYER: CollisionNoHandler()},
            GameCollisionType.PICKUP, [GameCollisionType.PLAYER])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)
示例#5
0
class Explosion(pygame.sprite.DirtySprite):
    def __init__(self, start_pos, explosion_sheet, size, damage_amount,
                 damage_type, collision_grid, *groups):

        super().__init__(*groups)
        self.collision_grid = collision_grid
        self.radius = size
        self.collide_radius = self.radius
        self.explosion_sheet = explosion_sheet
        self.explosion_frames = 16
        self.explosion_images = []
        random_explosion_int = random.randrange(0, 512, 64)
        for i in range(0, self.explosion_frames):
            x_start_index = (i * 64)
            explosion_frame = self.explosion_sheet.subsurface(
                pygame.Rect(x_start_index + 1, random_explosion_int + 1, 62,
                            62))
            explosion_frame = pygame.transform.scale(
                explosion_frame, (self.radius * 2, self.radius * 2))
            self.explosion_images.append(explosion_frame)

        self.image = self.explosion_images[0]

        self.rect = self.explosion_images[0].get_rect()
        self.rect.center = start_pos

        self.position = [
            float(self.rect.center[0]),
            float(self.rect.center[1])
        ]

        # handle collision shape setup
        self.collision_grid = collision_grid
        handlers_by_type = {
            GameCollisionType.MONSTER: self.collision_grid.no_handler
        }
        self.collision_shape = CollisionCircle(self.position[0],
                                               self.position[1],
                                               self.collide_radius,
                                               handlers_by_type,
                                               GameCollisionType.EXPLOSION,
                                               [GameCollisionType.MONSTER])
        self.collision_shape.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_shape)

        self.should_die = False
        self.life_time = 0.45
        self.time = self.life_time
        self.frame_time = self.life_time / self.explosion_frames
        self.frame = 1

        self.damage = Damage(damage_amount, damage_type)

        self.should_kill_explosion_collision = False
        self.has_collision = True

    def update_sprite(self, all_explosion_sprites, time_delta):
        self.time -= time_delta
        if self.time < 0.0:
            self.should_die = True
            if self.has_collision:  # destroy collision shape if it's not gone already
                self.collision_grid.remove_shape_from_grid(
                    self.collision_shape)
                self.has_collision = False

        if self.frame < self.explosion_frames and (
                self.life_time - self.time) > (self.frame_time * self.frame):
            self.image = self.explosion_images[self.frame]
            self.frame += 1

        all_explosion_sprites.add(self)

        # for now we only one one frame's worth of collision tests from an explosion
        if self.should_kill_explosion_collision and self.has_collision:
            self.collision_grid.remove_shape_from_grid(self.collision_shape)
            self.has_collision = False
        else:
            self.should_kill_explosion_collision = True

        return all_explosion_sprites

    def update_movement_and_collision(self):
        pass

    def react_to_collision(self):
        pass
示例#6
0
    def __init__(self, monster_path, monster_id, loaded_image, point_cost,
                 all_monster_sprites, offset, collision_grid, splat_loader,
                 ui_manager, *groups):
        super().__init__(*groups)
        self.id = monster_id
        self.point_cost = point_cost
        self.cash_value = 0
        self.move_speed = 0.0
        self.monster_path = monster_path
        self.collision_grid = collision_grid
        self.splat_loader = splat_loader
        self.ui_manager = ui_manager
        self.splat_image = None
        self.original_image = loaded_image
        self.image = self.original_image.copy()

        self.rect = self.image.get_rect()
        self.collide_radius = int(self.rect[2] / 2.0)

        self.position = [0.0, 0.0]
        int_position = self.get_random_point_in_radius_of_point(
            self.monster_path.start_waypoint,
            self.monster_path.waypoint_radius)
        self.position[0] = float(int_position[0])
        self.position[1] = float(int_position[1])
        self.position[0] -= offset[0]
        self.position[1] -= offset[1]

        self.collision_grid = collision_grid
        handlers_by_type = {
            GameCollisionType.TURRET_PROJECTILE: self.collision_grid.no_handler
        }
        self.collision_shape = CollisionCircle(
            self.position[0], self.position[1], self.collide_radius,
            handlers_by_type, GameCollisionType.MONSTER,
            [GameCollisionType.TURRET_PROJECTILE])
        self.collision_shape.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_shape)

        self.drawable_collision_circle = DrawableCollisionCircle(
            self.collision_shape)

        self.rect.centerx = self.position[0]
        self.rect.centery = self.position[1]

        self.change_direction_time = 5.0
        self.change_direction_accumulator = 0.0

        self.next_way_point_index = 0
        next_way_point_centre = self.monster_path.waypoints[
            self.next_way_point_index]
        self.next_way_point = self.get_random_point_in_radius_of_point(
            next_way_point_centre, self.monster_path.waypoint_radius)
        # apply screen position offset
        self.next_way_point[0] -= offset[0]
        self.next_way_point[1] -= offset[1]
        x_dist = float(self.next_way_point[0]) - float(self.position[0])
        y_dist = float(self.next_way_point[1]) - float(self.position[1])
        self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) +
                                                    (y_dist * y_dist))
        self.current_vector = [
            x_dist / self.distance_to_next_way_point,
            y_dist / self.distance_to_next_way_point
        ]

        direction_magnitude = math.sqrt(self.current_vector[0]**2 +
                                        self.current_vector[1]**2)
        if direction_magnitude > 0.0:
            unit_dir_vector = [
                self.current_vector[0] / direction_magnitude,
                self.current_vector[1] / direction_magnitude
            ]
            self.oldFacingAngle = math.atan2(
                -unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi
            monster_centre_position = self.rect.center
            self.image = pygame.transform.rotate(self.original_image,
                                                 self.oldFacingAngle)
            self.rect = self.image.get_rect()
            self.rect.center = monster_centre_position

        self.should_die = False

        self.sprite_needs_update = True
        self.all_monster_sprites = all_monster_sprites
        self.all_monster_sprites.add(self)

        self.health_capacity = 100
        self.current_health = self.health_capacity

        self.slow_down_percentage = 1.0

        self.health_bar = UIWorldSpaceHealthBar(
            pygame.Rect((0, 0), (self.rect.width + 4, 8)), self,
            self.ui_manager)
示例#7
0
class BaseMonster(pygame.sprite.Sprite):
    def __init__(self, monster_path, monster_id, loaded_image, point_cost,
                 all_monster_sprites, offset, collision_grid, splat_loader,
                 ui_manager, *groups):
        super().__init__(*groups)
        self.id = monster_id
        self.point_cost = point_cost
        self.cash_value = 0
        self.move_speed = 0.0
        self.monster_path = monster_path
        self.collision_grid = collision_grid
        self.splat_loader = splat_loader
        self.ui_manager = ui_manager
        self.splat_image = None
        self.original_image = loaded_image
        self.image = self.original_image.copy()

        self.rect = self.image.get_rect()
        self.collide_radius = int(self.rect[2] / 2.0)

        self.position = [0.0, 0.0]
        int_position = self.get_random_point_in_radius_of_point(
            self.monster_path.start_waypoint,
            self.monster_path.waypoint_radius)
        self.position[0] = float(int_position[0])
        self.position[1] = float(int_position[1])
        self.position[0] -= offset[0]
        self.position[1] -= offset[1]

        self.collision_grid = collision_grid
        handlers_by_type = {
            GameCollisionType.TURRET_PROJECTILE: self.collision_grid.no_handler
        }
        self.collision_shape = CollisionCircle(
            self.position[0], self.position[1], self.collide_radius,
            handlers_by_type, GameCollisionType.MONSTER,
            [GameCollisionType.TURRET_PROJECTILE])
        self.collision_shape.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_shape)

        self.drawable_collision_circle = DrawableCollisionCircle(
            self.collision_shape)

        self.rect.centerx = self.position[0]
        self.rect.centery = self.position[1]

        self.change_direction_time = 5.0
        self.change_direction_accumulator = 0.0

        self.next_way_point_index = 0
        next_way_point_centre = self.monster_path.waypoints[
            self.next_way_point_index]
        self.next_way_point = self.get_random_point_in_radius_of_point(
            next_way_point_centre, self.monster_path.waypoint_radius)
        # apply screen position offset
        self.next_way_point[0] -= offset[0]
        self.next_way_point[1] -= offset[1]
        x_dist = float(self.next_way_point[0]) - float(self.position[0])
        y_dist = float(self.next_way_point[1]) - float(self.position[1])
        self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) +
                                                    (y_dist * y_dist))
        self.current_vector = [
            x_dist / self.distance_to_next_way_point,
            y_dist / self.distance_to_next_way_point
        ]

        direction_magnitude = math.sqrt(self.current_vector[0]**2 +
                                        self.current_vector[1]**2)
        if direction_magnitude > 0.0:
            unit_dir_vector = [
                self.current_vector[0] / direction_magnitude,
                self.current_vector[1] / direction_magnitude
            ]
            self.oldFacingAngle = math.atan2(
                -unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi
            monster_centre_position = self.rect.center
            self.image = pygame.transform.rotate(self.original_image,
                                                 self.oldFacingAngle)
            self.rect = self.image.get_rect()
            self.rect.center = monster_centre_position

        self.should_die = False

        self.sprite_needs_update = True
        self.all_monster_sprites = all_monster_sprites
        self.all_monster_sprites.add(self)

        self.health_capacity = 100
        self.current_health = self.health_capacity

        self.slow_down_percentage = 1.0

        self.health_bar = UIWorldSpaceHealthBar(
            pygame.Rect((0, 0), (self.rect.width + 4, 8)), self,
            self.ui_manager)

    def set_starting_health(self, value):
        self.health_capacity = value
        self.current_health = self.health_capacity

    def kill(self):
        self.health_bar.kill()
        super().kill()

    def setup_splat(self, colour):
        self.splat_image = self.splat_loader.create_random_coloured_splat(
            colour)

    def draw_collision_circle(self, screen):
        self.drawable_collision_circle.update_collided_colours()
        self.drawable_collision_circle.draw(screen)

    def update_sprite(self):
        if self.sprite_needs_update:
            self.sprite_needs_update = False

    def react_to_collision(self):
        for shape in self.collision_shape.collided_shapes_this_frame:
            if shape.game_type == GameCollisionType.EXPLOSION:
                explosion = shape.owner
                self.take_damage(explosion.damage)

    def update_movement_and_collision(self, time_delta, player_resources,
                                      offset, splat_sprites):
        if self.current_health <= 0:
            self.should_die = True
            player_resources.current_cash += self.cash_value

        if self.distance_to_next_way_point <= 0.0:
            if self.next_way_point_index < (len(self.monster_path.waypoints) -
                                            1):
                self.next_way_point_index += 1
                next_way_point_centre = self.monster_path.waypoints[
                    self.next_way_point_index]
                self.next_way_point = self.get_random_point_in_radius_of_point(
                    next_way_point_centre, self.monster_path.waypoint_radius)
                # apply screen position offset
                self.next_way_point[0] -= offset[0]
                self.next_way_point[1] -= offset[1]
                x_dist = float(self.next_way_point[0]) - float(
                    self.position[0])
                y_dist = float(self.next_way_point[1]) - float(
                    self.position[1])
                self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) +
                                                            (y_dist * y_dist))
                self.current_vector = [
                    x_dist / self.distance_to_next_way_point,
                    y_dist / self.distance_to_next_way_point
                ]

                # calc facing angle
                direction_magnitude = math.sqrt(self.current_vector[0]**2 +
                                                self.current_vector[1]**2)
                if direction_magnitude > 0.0:
                    unit_dir_vector = [
                        self.current_vector[0] / direction_magnitude,
                        self.current_vector[1] / direction_magnitude
                    ]
                    facing_angle = math.atan2(
                        -unit_dir_vector[0],
                        -unit_dir_vector[1]) * 180 / math.pi

                    if facing_angle != self.oldFacingAngle:
                        self.sprite_needs_update = True
                        self.oldFacingAngle = facing_angle
                        monster_centre_position = self.rect.center
                        self.image = pygame.transform.rotate(
                            self.original_image, facing_angle)
                        self.rect = self.image.get_rect()
                        self.rect.center = monster_centre_position
            else:
                # monster has reached base
                player_resources.current_base_health -= 10
                self.should_die = True

        # move
        self.position[0] += (self.current_vector[0] * time_delta *
                             self.move_speed * self.slow_down_percentage)
        self.position[1] += (self.current_vector[1] * time_delta *
                             self.move_speed * self.slow_down_percentage)
        self.distance_to_next_way_point -= time_delta * self.move_speed * self.slow_down_percentage

        # reset any slowdown from turrets
        self.slow_down_percentage = 1.0
        # set sprite & collision shape positions
        self.rect.center = self.position
        self.collision_shape.set_position(self.position)

        if self.should_die:
            self.collision_grid.remove_shape_from_grid(self.collision_shape)
            self.all_monster_sprites.remove(self)
            self.kill()
            if self.splat_image is not None:
                Splat(self.position, self.splat_image, splat_sprites)

    @staticmethod
    def get_random_point_in_radius_of_point(point, radius):
        t = 2 * math.pi * random.random()
        u = random.random() + random.random()
        if u > 1:
            r = 2 - u
        else:
            r = u
        return [
            point[0] + radius * r * math.cos(t),
            point[1] + radius * r * math.sin(t)
        ]

    def guess_position_at_time(self, time):
        guess_position = [0.0, 0.0]
        # make sure we don't overshoot monster waypoints with our guesses, or turrets
        # will aim at impossible positions for monsters inside walls.
        if (time * self.move_speed *
                self.slow_down_percentage) > self.distance_to_next_way_point:
            guess_position[0] = self.next_way_point[0]
            guess_position[1] = self.next_way_point[1]
        else:
            x_move = self.current_vector[
                0] * time * self.move_speed * self.slow_down_percentage
            y_move = self.current_vector[
                1] * time * self.move_speed * self.slow_down_percentage
            guess_position[0] = self.position[0] + x_move
            guess_position[1] = self.position[1] + y_move
        return guess_position

    def set_average_speed(self, average_speed):
        self.move_speed = random.randint(int(average_speed * 0.75),
                                         int(average_speed * 1.25))
        return self.move_speed

    def take_damage(self, damage):
        self.current_health -= damage.amount

    def set_slowdown(self, percentage):
        self.slow_down_percentage = percentage
示例#8
0
    def __init__(self, monster_id, start_pos, sprite_map, all_monster_sprites, play_area, tiled_level,
                 collision_grid, *groups):

        super().__init__(*groups)
        self.id = monster_id
        self.start_pos = start_pos
        self.play_area = play_area
        self.tiled_level = tiled_level
        self.score = 100
        self.xp = 25
        self.collision_grid = collision_grid

        self.point_cost = 10  # point cost
        if self.id == "goblin":
            self.sprite_map_row = 0
            self.walk_cycle_length = 4
        elif self.id == "ent":
            self.sprite_map_row = 1
            self.walk_cycle_length = 8
        elif self.id == "spider":
            self.sprite_map_row = 2
            self.walk_cycle_length = 7

        self.anim_stand = sprite_map[0][self.sprite_map_row]
        self.walk_anim_speed = 64.0 / self.walk_cycle_length
        self.walk_cycle = []
        for anim_index in range(0, self.walk_cycle_length):
            self.walk_cycle.append(sprite_map[anim_index][self.sprite_map_row])

        self.attack_time_delay = 0.5

        self.sprite_rot_centre_offset = [0.0, 0.0]

        self.image = self.anim_stand.copy()
        # self.sprite = pygame.sprite.Sprite()
        self.test_collision_sprite = pygame.sprite.Sprite()

        self.rect = self.image.get_rect()

        self.rect.center = self.start_pos

        self.position = [float(self.rect.center[0]), float(self.rect.center[1])]

        self.screen_position = [0, 0]
        self.screen_position[0] = self.position[0]
        self.screen_position[1] = self.position[1]

        self.collide_radius = 20

        # we do collisions in world space
        self.collision_circle = CollisionCircle(self.position[0], self.position[1], self.collide_radius,
                                                {GameCollisionType.PLAYER_WEAPON: CollisionNoHandler(),
                                                 GameCollisionType.TILE: CollisionRubHandler(),
                                                 GameCollisionType.PLAYER: CollisionRubHandler(),
                                                 GameCollisionType.MONSTER: CollisionRubHandler()},
                                                GameCollisionType.MONSTER,
                                                [GameCollisionType.PLAYER_WEAPON,
                                                 GameCollisionType.TILE,
                                                 GameCollisionType.PLAYER,
                                                 GameCollisionType.MONSTER])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)

        self.drawable_circle = DrawableCollisionCircle(self.collision_circle)

        self.update_screen_position(self.tiled_level.position_offset)

        self.change_direction_time = 5.0
        self.change_direction_accumulator = 0.0

        self.next_way_point = self.get_random_point_in_radius_of_point([500, 400], 96)

        x_dist = float(self.next_way_point[0]) - float(self.position[0])
        y_dist = float(self.next_way_point[1]) - float(self.position[1])
        self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
        self.current_vector = [x_dist / self.distance_to_next_way_point,
                               y_dist / self.distance_to_next_way_point]

        self.old_facing_angle = 0.0
        self.rotate_sprite(self)

        self.should_die = False
        self.is_dead = False

        self.sprite_needs_update = True
        self.all_monster_sprites = all_monster_sprites
        self.is_on_screen = False

        self.health = 100

        self.slow_down_percentage = 1.0

        self.is_wandering_aimlessly = True
        self.random_target_change_time = random.uniform(3.0, 15.0)
        self.random_target_change_acc = 0.0

        self.time_to_home_in_on_player = False
        self.monster_home_on_target_time = random.uniform(0.3, 1.5)
        self.monster_home_on_target_acc = 0.0

        self.is_time_to_start_attack = True
        self.attack_time_acc = 0.0
        self.attack_time_delay = 3.0

        self.is_attacking = False
        self.should_do_attack_damage = False
        self.attack_anim_acc = 0.0
        self.attack_anim_total_time = 0.8

        self.attack_damage = 15

        self.sprite_flash_acc = 0.0
        self.sprite_flash_time = 0.15
        self.should_flash_sprite = False
        self.active_flash_sprite = False

        self.flash_sprite = pygame.sprite.Sprite()

        self.player_distance = 1000

        self.air_timer = 0.0
        self.air_velocity_vector = [0.0, 0.0]

        self.attack_range = 86.0

        self.collision_obj_rects = []
        self.collision_obj_rect = pygame.Rect(0.0, 0.0, 2.0, 2.0)
        self.max_coll_handling_attempts = 10

        self.move_accumulator = 0.0

        self.move_speed = 0.0
        self.idle_move_speed = 0.0
        self.attack_move_speed = 0.0
示例#9
0
class BaseMonster(pygame.sprite.Sprite):
    def __init__(self, monster_id, start_pos, sprite_map, all_monster_sprites, play_area, tiled_level,
                 collision_grid, *groups):

        super().__init__(*groups)
        self.id = monster_id
        self.start_pos = start_pos
        self.play_area = play_area
        self.tiled_level = tiled_level
        self.score = 100
        self.xp = 25
        self.collision_grid = collision_grid

        self.point_cost = 10  # point cost
        if self.id == "goblin":
            self.sprite_map_row = 0
            self.walk_cycle_length = 4
        elif self.id == "ent":
            self.sprite_map_row = 1
            self.walk_cycle_length = 8
        elif self.id == "spider":
            self.sprite_map_row = 2
            self.walk_cycle_length = 7

        self.anim_stand = sprite_map[0][self.sprite_map_row]
        self.walk_anim_speed = 64.0 / self.walk_cycle_length
        self.walk_cycle = []
        for anim_index in range(0, self.walk_cycle_length):
            self.walk_cycle.append(sprite_map[anim_index][self.sprite_map_row])

        self.attack_time_delay = 0.5

        self.sprite_rot_centre_offset = [0.0, 0.0]

        self.image = self.anim_stand.copy()
        # self.sprite = pygame.sprite.Sprite()
        self.test_collision_sprite = pygame.sprite.Sprite()

        self.rect = self.image.get_rect()

        self.rect.center = self.start_pos

        self.position = [float(self.rect.center[0]), float(self.rect.center[1])]

        self.screen_position = [0, 0]
        self.screen_position[0] = self.position[0]
        self.screen_position[1] = self.position[1]

        self.collide_radius = 20

        # we do collisions in world space
        self.collision_circle = CollisionCircle(self.position[0], self.position[1], self.collide_radius,
                                                {GameCollisionType.PLAYER_WEAPON: CollisionNoHandler(),
                                                 GameCollisionType.TILE: CollisionRubHandler(),
                                                 GameCollisionType.PLAYER: CollisionRubHandler(),
                                                 GameCollisionType.MONSTER: CollisionRubHandler()},
                                                GameCollisionType.MONSTER,
                                                [GameCollisionType.PLAYER_WEAPON,
                                                 GameCollisionType.TILE,
                                                 GameCollisionType.PLAYER,
                                                 GameCollisionType.MONSTER])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)

        self.drawable_circle = DrawableCollisionCircle(self.collision_circle)

        self.update_screen_position(self.tiled_level.position_offset)

        self.change_direction_time = 5.0
        self.change_direction_accumulator = 0.0

        self.next_way_point = self.get_random_point_in_radius_of_point([500, 400], 96)

        x_dist = float(self.next_way_point[0]) - float(self.position[0])
        y_dist = float(self.next_way_point[1]) - float(self.position[1])
        self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
        self.current_vector = [x_dist / self.distance_to_next_way_point,
                               y_dist / self.distance_to_next_way_point]

        self.old_facing_angle = 0.0
        self.rotate_sprite(self)

        self.should_die = False
        self.is_dead = False

        self.sprite_needs_update = True
        self.all_monster_sprites = all_monster_sprites
        self.is_on_screen = False

        self.health = 100

        self.slow_down_percentage = 1.0

        self.is_wandering_aimlessly = True
        self.random_target_change_time = random.uniform(3.0, 15.0)
        self.random_target_change_acc = 0.0

        self.time_to_home_in_on_player = False
        self.monster_home_on_target_time = random.uniform(0.3, 1.5)
        self.monster_home_on_target_acc = 0.0

        self.is_time_to_start_attack = True
        self.attack_time_acc = 0.0
        self.attack_time_delay = 3.0

        self.is_attacking = False
        self.should_do_attack_damage = False
        self.attack_anim_acc = 0.0
        self.attack_anim_total_time = 0.8

        self.attack_damage = 15

        self.sprite_flash_acc = 0.0
        self.sprite_flash_time = 0.15
        self.should_flash_sprite = False
        self.active_flash_sprite = False

        self.flash_sprite = pygame.sprite.Sprite()

        self.player_distance = 1000

        self.air_timer = 0.0
        self.air_velocity_vector = [0.0, 0.0]

        self.attack_range = 86.0

        self.collision_obj_rects = []
        self.collision_obj_rect = pygame.Rect(0.0, 0.0, 2.0, 2.0)
        self.max_coll_handling_attempts = 10

        self.move_accumulator = 0.0

        self.move_speed = 0.0
        self.idle_move_speed = 0.0
        self.attack_move_speed = 0.0

    def draw_collision_shape(self, screen, camera_position, camera_half_dimensions):
        self.drawable_circle.update_collided_colours()
        self.drawable_circle.draw(screen, camera_position, camera_half_dimensions)

    def react_to_collision(self):
        for shape in self.collision_circle.collided_shapes_this_frame:
            if shape.game_type == GameCollisionType.TILE or shape.game_type == GameCollisionType.PLAYER \
                    or shape.game_type == GameCollisionType.MONSTER:
                self.position[0] = self.collision_circle.x
                self.position[1] = self.collision_circle.y

    def fling_monster(self, vector, time):
        self.air_timer = time
        self.air_velocity_vector = vector

    def update_sprite(self, time_delta):
        if self.sprite_needs_update:
            self.sprite_needs_update = False
            self.image = self.image

        if self.should_flash_sprite and not self.should_die and not self.is_dead:
            self.sprite_flash_acc += time_delta
            if self.sprite_flash_acc > self.sprite_flash_time:
                self.sprite_flash_acc = 0.0
                self.should_flash_sprite = False
                self.active_flash_sprite = False
                self.all_monster_sprites.remove(self.flash_sprite)
            else:
                lerp_value = self.sprite_flash_acc / self.sprite_flash_time
                flash_alpha = self.lerp(255, 0, lerp_value)
                flash_image = self.image.copy()
                flash_image.fill((0, 0, 0, flash_alpha), None, pygame.BLEND_RGBA_MULT)
                flash_image.fill((255, 255, 255, 0), None, pygame.BLEND_RGBA_ADD)
                self.flash_sprite.image = flash_image
                self.flash_sprite.rect = self.flash_sprite.image.get_rect()
                self.flash_sprite.rect.center = self.screen_position
                if not self.active_flash_sprite:
                    self.all_monster_sprites.add(self.flash_sprite)
                    self.active_flash_sprite = True

    def attack(self, time_delta, player):
        self.attack_anim_acc += time_delta
        if self.attack_anim_acc > self.attack_anim_total_time:
            self.attack_anim_acc = 0.0
            self.is_attacking = False
        elif self.attack_anim_acc > (self.attack_anim_total_time * 0.75):
            pass  # finish attack frame
        elif self.attack_anim_acc > (self.attack_anim_total_time * 0.5):
            if self.should_do_attack_damage:
                self.should_do_attack_damage = False
                player.take_damage(self.attack_damage)
        elif self.attack_anim_acc > (self.attack_anim_total_time * 0.25):
            pass  # start attack frame

    def update_movement_and_collision(self, time_delta, player, pick_up_spawner):
        if player is not None:
            player_x_dist = float(player.position[0]) - float(self.position[0])
            player_y_dist = float(player.position[1]) - float(self.position[1])
            self.player_distance = math.sqrt((player_x_dist ** 2) + (player_y_dist ** 2))

        if self.health <= 0:
            self.should_die = True

        if self.air_timer > 0.0:
            self.distance_to_next_way_point = 0.0
            self.air_timer -= time_delta
            if self.air_timer < 0.0:
                self.air_timer = 0.0

            self.position[0] += (self.air_velocity_vector[0] * time_delta)
            self.position[1] += (self.air_velocity_vector[1] * time_delta)

        elif self.is_wandering_aimlessly and player is not None and not player.should_die:
            self.move_speed = self.idle_move_speed

            if self.player_distance < 256.0:
                self.is_wandering_aimlessly = False
            elif self.random_target_change_acc < self.random_target_change_time:
                self.random_target_change_acc += time_delta
            else:
                self.random_target_change_acc = 0.0
                self.random_target_change_time = random.uniform(3.0, 15.0)

                self.next_way_point = self.get_random_point_in_world()

                x_dist = float(self.next_way_point[0]) - float(self.position[0])
                y_dist = float(self.next_way_point[1]) - float(self.position[1])
                self.distance_to_next_way_point = math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
                self.current_vector = [x_dist / self.distance_to_next_way_point,
                                       y_dist / self.distance_to_next_way_point]

                self.rotate_sprite(self)

        elif not self.is_wandering_aimlessly and player is not None and not player.should_die:
            self.move_speed = self.attack_move_speed

            if self.monster_home_on_target_acc < self.monster_home_on_target_time:
                self.monster_home_on_target_acc += time_delta
            else:
                self.monster_home_on_target_acc = 0.0
                self.monster_home_on_target_time = random.uniform(0.3, 1.5)
                self.time_to_home_in_on_player = True

            if self.time_to_home_in_on_player:
                self.time_to_home_in_on_player = False

                if self.player_distance > 384.0:
                    self.is_wandering_aimlessly = True
                else:
                    x_dist = float(player.position[0]) - float(self.position[0])
                    y_dist = float(player.position[1]) - float(self.position[1])
                    self.distance_to_next_way_point = (math.sqrt((x_dist * x_dist) + (y_dist * y_dist)))
                    self.current_vector = [x_dist / self.distance_to_next_way_point,
                                           y_dist / self.distance_to_next_way_point]
                    # calculate a position in attack range minus the rough size
                    # of the sprites radius so we are going from edge to edge
                    self.distance_to_next_way_point -= (self.attack_range - self.collide_radius - player.collide_radius)
                    self.rotate_sprite(self)

            if self.attack_time_acc < self.attack_time_delay:
                self.attack_time_acc += time_delta
            else:
                self.attack_time_acc = 0.0
                self.is_time_to_start_attack = True

            if self.player_distance <= self.attack_range and self.is_time_to_start_attack:
                self.is_time_to_start_attack = False
                self.is_attacking = True
                self.should_do_attack_damage = True

        if self.air_timer == 0.0 and self.is_attacking:
            self.attack(time_delta, player)

        if self.air_timer == 0.0 and self.distance_to_next_way_point > 0.0:
            self.position[0] += (self.current_vector[0] * time_delta * self.move_speed)
            self.position[1] += (self.current_vector[1] * time_delta * self.move_speed)
            self.distance_to_next_way_point -= time_delta * self.move_speed
            self.move_accumulator += time_delta * self.move_speed

        # move
        if self.on_screen():
            if not self.is_on_screen:
                self.is_on_screen = True
                self.all_monster_sprites.add(self)

            self.update_screen_position(self.tiled_level.position_offset)

            # if walking about
            walk_cycle_index = int(self.move_accumulator / self.walk_anim_speed)
            if walk_cycle_index >= len(self.walk_cycle):
                self.move_accumulator = 0.0
                walk_cycle_index = 0
            self.image = pygame.transform.rotate(self.walk_cycle[walk_cycle_index], self.old_facing_angle)
            self.rect = self.image.get_rect()
            self.rect.center = self.rot_point([self.screen_position[0],
                                               self.screen_position[1] + self.sprite_rot_centre_offset[1]],
                                              self.screen_position, -self.old_facing_angle)

        else:
            if self.is_on_screen:
                self.is_on_screen = False
                self.all_monster_sprites.remove(self)
                if self.active_flash_sprite:
                    self.all_monster_sprites.remove(self.flash_sprite)
                    self.active_flash_sprite = False

        self.collision_circle.set_position(self.position)

        if self.should_die:
            self.should_die = False
            self.is_dead = True
            if self.active_flash_sprite:
                self.all_monster_sprites.remove(self.flash_sprite)
                self.active_flash_sprite = False
            self.all_monster_sprites.remove(self)
            self.try_pick_up_spawn(pick_up_spawner)
            player.add_score(self.score)
            player.add_xp(self.xp)
            self.collision_grid.remove_shape_from_grid(self.collision_circle)

    def remove_from_grid(self):
        self.collision_grid.remove_shape_from_grid(self.collision_circle)

    def on_screen(self):
        if self.position[0] + self.rect[2] / 2 > self.tiled_level.position_offset[0] and self.position[0] - \
                self.rect[2] / 2 < self.tiled_level.position_offset[0] + self.play_area[0]:
            if self.position[1] + self.rect[3] / 2 > self.tiled_level.position_offset[1] and self.position[1] - \
                    self.rect[3] / 2 < self.tiled_level.position_offset[1] + self.play_area[1]:
                return True
        return False

    def update_screen_position(self, world_offset):
        self.screen_position[0] = self.position[0] - world_offset[0]
        self.screen_position[1] = self.position[1] - world_offset[1]

        self.collision_circle.x = self.screen_position[0]
        self.collision_circle.y = self.screen_position[1]

    @staticmethod
    def get_random_point_in_radius_of_point(point, radius):
        t = 2 * math.pi * random.random()
        u = random.random() + random.random()
        if u > 1:
            r = 2 - u
        else:
            r = u
        return [point[0] + radius * r * math.cos(t), point[1] + radius * r * math.sin(t)]

    def set_average_speed(self, average_speed):
        self.move_speed = random.randint(int(average_speed * 0.75), int(average_speed * 1.25))
        return self.move_speed

    def take_damage(self, damage):
        self.health -= damage.amount
        self.should_flash_sprite = True

    def get_random_point_in_world(self):
        random_x = random.randint(32, self.tiled_level.level_pixel_size[0] - 32)
        random_y = random.randint(32, self.tiled_level.level_pixel_size[1] - 32)
        return [random_x, random_y]

    def rotate_sprite(self, sprite_to_rot):
        direction_magnitude = math.sqrt(
            self.current_vector[0] * self.current_vector[0] + self.current_vector[1] * self.current_vector[1])

        if direction_magnitude > 0.0:
            unit_dir_vector = [self.current_vector[0] / direction_magnitude,
                               self.current_vector[1] / direction_magnitude]
            self.old_facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi

        return sprite_to_rot

    def try_pick_up_spawn(self, pickup_spawner):
        pickup_spawner.try_spawn(self.position)

    @staticmethod
    def rot_point(point, axis, ang):
        """ Orbit. calculates the new loc for a point that rotates a given num of degrees around an axis point,
        +clockwise, -anticlockwise -> tuple x,y
        """
        ang -= 90
        x, y = point[0] - axis[0], point[1] - axis[1]
        radius = math.sqrt(x * x + y * y)  # get the distance between points

        r_ang = math.radians(ang)  # convert ang to radians.

        h = axis[0] + (radius * math.cos(r_ang))
        v = axis[1] + (radius * math.sin(r_ang))

        return [h, v]

    @staticmethod
    def lerp(a, b, c):
        return (c * b) + ((1.0 - c) * a)

    @staticmethod
    def get_distance(from_x, from_y, to_x, to_y):
        dx = from_x - to_x
        dy = from_y - to_y
        return math.sqrt((dx ** 2) + (dy ** 2))
示例#10
0
class ViewCone:
    class PointInConeStatus:
        FRONT_SEMICIRCLE = 0,  # point within sector - containing semicircle but outside sector
        BEHIND = 1,  # point behind sector
        OUTSIDE = 2,  # point outside bounding circle
        WITHIN = 4

    class LineSegConfig:
        DISJOINT = 0,
        PARALLEL = 1,
        INTERSECT = 2

    def __init__(self, origin_centre_pos, facing_direction, fov=90.0, length=1000.0):

        self.facing_direction = Vector2(facing_direction[:])
        self.origin_centre_position = Vector2(origin_centre_pos[:])

        self.field_of_view = math.radians(fov)
        self.length = length
        self.length_squared = length ** 2

        self.epsilon = 0.075  # to handle floating point inaccuracy at small values (I think)
        self.arc_epsilon = 1.5  # to handle small gaps between arc points
        self.halfAuxRayTilt = 8.72664625995e-3 # half degree in radians
        self.half_aux_ray_tilt_cos = math.cos(self.halfAuxRayTilt)  # 0.999961923064
        self.half_aux_ray_tilt_sin = math.sin(self.halfAuxRayTilt)  # 8.72653549837e-3

        self.collision_circle = CollisionCircle(self.origin_centre_position.x,
                                                self.origin_centre_position.y,
                                                self.length,
                                                {CollisionType.WORLD_SOLID: CollisionNoHandler(),
                                                 CollisionType.WORLD_JUMP_THROUGH: CollisionNoHandler(),
                                                 CollisionType.WORLD_PLATFORM_EDGE: CollisionNoHandler(),
                                                 CollisionType.WORLD_JUMP_THROUGH_EDGE: CollisionNoHandler()},
                                                CollisionType.VIEW_CONE,
                                                [CollisionType.WORLD_SOLID,
                                                 CollisionType.WORLD_JUMP_THROUGH,
                                                 CollisionType.WORLD_PLATFORM_EDGE,
                                                 CollisionType.WORLD_JUMP_THROUGH_EDGE]
                                                )

        self.neg_cos_fov = math.cos(-self.field_of_view / 2)
        self.neg_sin_fov = math.sin(-self.field_of_view / 2)
        self.sin_fov = math.sin(self.field_of_view / 2)
        self.cos_fov = math.cos(self.field_of_view / 2)
        self.perp_facing_cos = math.cos(math.radians(90))
        self.perp_facing_sin = math.sin(math.radians(90))

        self.angle_points_array = []
        self.rays = []
        self.hit_points = []
        self.ctrl_points = []
        self.blocking_edges = []

        self.perp_facing_vec = None
        self.cone_extent_facings = None
        self.on_cone_changed_direction()

        self.end_positions = None
        self.on_cone_moved()

        self.fov_edges = [Edge("min", Vector2(self.origin_centre_position), Vector2(self.end_positions[0])),
                          Edge("max", Vector2(self.origin_centre_position), Vector2(self.end_positions[1]))]

        self.timing_clock = pygame.time.Clock()

    def on_cone_changed_direction(self):
        self.cone_extent_facings = [Vector2(self.facing_direction.x * self.cos_fov - self.facing_direction.y * self.sin_fov,
                                     self.facing_direction.x * self.sin_fov + self.facing_direction.y * self.cos_fov),
                                    Vector2(self.facing_direction.x * self.neg_cos_fov - self.facing_direction.y * self.neg_sin_fov,
                                        self.facing_direction.x * self.neg_sin_fov + self.facing_direction.y * self.neg_cos_fov)
                                    ]

        self.perp_facing_vec = Vector2(
            self.facing_direction.x * self.perp_facing_cos - self.facing_direction.y * self.perp_facing_sin,
            self.facing_direction.x * self.perp_facing_sin + self.facing_direction.y * self.perp_facing_cos)

    def on_cone_moved(self):
        self.end_positions = [[self.origin_centre_position.x + self.cone_extent_facings[0].x * self.length,
                               self.origin_centre_position.y + self.cone_extent_facings[0].y * self.length],
                              [self.origin_centre_position.x + self.cone_extent_facings[1].x * self.length,
                               self.origin_centre_position.y + self.cone_extent_facings[1].y * self.length]
                              ]

        self.fov_edges = [Edge("min", Vector2(self.origin_centre_position), Vector2(self.end_positions[0])),
                          Edge("max", Vector2(self.origin_centre_position), Vector2(self.end_positions[1]))]

    def set_facing_direction(self, direction):
        if direction[0] != self.facing_direction.x or direction.y != self.facing_direction.y:
            self.facing_direction.x = direction.x
            self.facing_direction.y = direction.y
            self.on_cone_changed_direction()
            self.on_cone_moved()

    def set_position(self, position):
        if position[0] != self.origin_centre_position.x or position.y != self.origin_centre_position.y:
            self.origin_centre_position.x = position.x
            self.origin_centre_position.y = position.y
            self.on_cone_moved()
            self.collision_circle.set_position((self.origin_centre_position.x, self.origin_centre_position.y))

    def is_point_in_sector(self, point_to_test):
        """
        Tests if a point is inside our cone and, if not roughly where it is in relation to our circle for future
        processing.

        :param point_to_test:
        :return: status class variable
        """
        vector_to_point = point_to_test - self.origin_centre_position

        dot = self.facing_direction.dot(vector_to_point)
        if dot < 0:
            return ViewCone.PointInConeStatus.BEHIND

        if (vector_to_point.length_squared() - self.length_squared) > self.epsilon:
            return ViewCone.PointInConeStatus.OUTSIDE

        self.cone_extent_facings[0].cross(vector_to_point)
        cross_1 = self.cone_extent_facings[0].cross(vector_to_point)  # self.cone_extent_facings[0][0] * min_vector_to_point[1] - self.cone_extent_facings[0][1] * min_vector_to_point[0]
        cross_2 = self.cone_extent_facings[1].cross(vector_to_point)  # self.cone_extent_facings[1][0] * max_vector_to_point[1] - self.cone_extent_facings[1][1] * max_vector_to_point[0]

        if cross_1 < 0 < cross_2 or cross_1 > 0 > cross_2:
            return ViewCone.PointInConeStatus.WITHIN

        return ViewCone.PointInConeStatus.FRONT_SEMICIRCLE

    def draw(self, surface, camera=None):
        if camera is not None:
            view_top_left_position = (camera.position[0] - (camera.dimensions[0] / 2),
                                      camera.position[1] - (camera.dimensions[1] / 2))
        else:
            view_top_left_position = [0.0, 0.0]

        arc_rect = pygame.Rect((0, 0), (self.length * 2,
                                        self.length * 2))
        arc_rect.center = [self.origin_centre_position[0] - view_top_left_position[0],
                           self.origin_centre_position[1] - view_top_left_position[1]]
        arc_angle = math.atan2(-self.facing_direction[1], self.facing_direction[0])
        #pygame.draw.arc(surface, pygame.Color("#FFFFFF"), arc_rect, arc_angle, arc_angle + (self.field_of_view / 2))
        #pygame.draw.arc(surface, pygame.Color("#FFFFFF"), arc_rect, arc_angle - (self.field_of_view / 2), arc_angle)

        view_top_left_vec = pygame.math.Vector2(view_top_left_position)
        #pygame.draw.line(surface, pygame.Color("#FFFFFF"), self.origin_centre_position - view_top_left_vec,
        #                 self.origin_centre_position + (self.facing_direction * self.length) - view_top_left_vec)

        #for ray in self.rays:
        #    pygame.draw.line(surface, pygame.Color("#999999"), self.origin_centre_position - view_top_left_vec, self.origin_centre_position + ray - view_top_left_vec)

        for j in range(0, len(self.hit_points)):
            hit_point = self.hit_points[j]
            pygame.draw.circle(surface, pygame.Color("#FF7700"), [int(hit_point.x - view_top_left_position[0]),
                                                                  int(hit_point.y - view_top_left_position[1])], 5)

        # draw los cone shape
        if len(self.hit_points) > 0:
            pygame.draw.line(surface, pygame.Color("#FFFF00"), self.origin_centre_position - view_top_left_vec, self.hit_points[0] - view_top_left_vec)

        for i in range(0, len(self.hit_points) - 1):
            if self.ctrl_points[i+1] is None:
                pygame.draw.line(surface, pygame.Color("#FFFF00"), self.hit_points[i] - view_top_left_vec, self.hit_points[i+1] - view_top_left_vec)
            else:
                start_angle_vec = self.hit_points[i] - self.origin_centre_position
                finish_angle_vec = self.hit_points[i+1] - self.origin_centre_position
                start_arc_angle = math.atan2(-start_angle_vec[1], start_angle_vec[0])
                finish_arc_angle = math.atan2(-finish_angle_vec[1], finish_angle_vec[0])

                x_diff = abs(start_angle_vec.x - finish_angle_vec.x)
                y_diff = abs(start_angle_vec.y - finish_angle_vec.y)
                if not (x_diff <= self.arc_epsilon and y_diff <= self.arc_epsilon):
                    pygame.draw.arc(surface, pygame.Color("#FFFF00"), arc_rect, start_arc_angle, finish_arc_angle)
                # else:
                # pygame.draw.arc(surface, pygame.Color("#FFFF00"), arc_rect, finish_arc_angle, start_arc_angle)

        if len(self.hit_points) > 0:
            pygame.draw.line(surface, pygame.Color("#FFFF00"), self.hit_points[len(self.hit_points)-1] - view_top_left_vec, self.origin_centre_position - view_top_left_vec)
        # end draw los cone shape

    def edge_within_radius(self, edge):
        return self.closest_point_on_edge(edge).distance_squared_to(self.origin_centre_position) <= self.length_squared

    def is_active_facing_edge(self, polygon, edge):
        edge_normal = polygon.normals[edge.name]
        if edge_normal.should_skip:
            return False
        return True

    def check_polygon(self, polygon, angle_points, blocking_edges):
        edges_list = [edge for edge in polygon.edges.values()
                      if self.edge_within_radius(edge) and self.is_active_facing_edge(polygon, edge)]
        n = len(edges_list)
        if n > 0:
            prev_edge = edges_list[n - 1]  # TODO: this might be wrong?
        else:
            prev_edge = None
        for i in range(0, len(edges_list)):
            edge = edges_list[i]

            point_a_status = self.is_point_in_sector(edge.a)
            point_b_status = self.is_point_in_sector(edge.b)
            if point_a_status == ViewCone.PointInConeStatus.BEHIND and point_b_status == ViewCone.PointInConeStatus.BEHIND:
                continue

            if point_a_status == ViewCone.PointInConeStatus.WITHIN and point_b_status == ViewCone.PointInConeStatus.WITHIN:

                self.add_angle_point_with_aux(edge.a, prev_edge, edge, angle_points)

                # for the last edge, send undefined as nextEdge to
                # addAnglePointWithAux; it should never get used since
                # both endpoints of the last edge would be handled by now
                # due to edges 0 and n − 2
                if i < len(edges_list) - 1:
                    self.add_angle_point_with_aux(edge.b, edge, edges_list[i + 1], angle_points)
                else:
                    self.add_angle_point_with_aux(edge.b, edge, None, angle_points)
                blocking_edges.append(edge)

            else:
                """
                ANGLE POINTS
                Either one or both the points are outside the sector; add
                the one which is inside. Perform edge – arc intersection
                test, if this edge has a possibility of intersecting the
                arc, add resultant intersection point(s) to angle_points.

                BLOCKING EDGE
                If one of the points is inside, then the edge is blocking,
                add it without any checks. If one or both are out, and the
                edge cuts the sector's arc then too the edge is blocking,
                add it to blocking_edges. If both are out and edge doesn't
                cut the arc, check if it cuts one of the sector's edges and
                add to blocking_edges if it does.
                """
                blocking = False
                if point_a_status == ViewCone.PointInConeStatus.WITHIN:
                    self.add_angle_point_with_aux(edge.a, prev_edge, edge, angle_points)
                    blocking = True
                if point_b_status == ViewCone.PointInConeStatus.WITHIN:
                    if i < len(edges_list) - 1:
                        self.add_angle_point_with_aux(edge.b, edge, edges_list[i+1], angle_points)
                    else:
                        self.add_angle_point_with_aux(edge.b, edge, None, angle_points)
                    blocking = True

                edge_may_intersect_arc = point_a_status == ViewCone.PointInConeStatus.OUTSIDE or point_b_status == ViewCone.PointInConeStatus.OUTSIDE

                test_seg_seg_xsect = True
                if edge_may_intersect_arc:
                    # perform line segment – sector arc intersection test to
                    # check if there're more angle points i.e. if the edge
                    # intersects the sector's arc then the intersection points
                    # would also become angle points.
                    arc_xsect_result = self.line_seg_arc_x_sect(edge)
                    if arc_xsect_result is not None:
                        if arc_xsect_result['config'] == ViewCone.PointInConeStatus.WITHIN:
                            # just add intersection point to Set without any
                            # auxiliarys as it's an intersection angle point
                            for point in arc_xsect_result['points']:
                                angle_points.add(point)
                            blocking = True

                        # edge – edge intersection test is not needed when the
                        # intersection point(s) are within or behind; the
                        # within case is ignored since it's already blocking
                        # and hence won't reach the lineSegLineSegXsect code
                        test_seg_seg_xsect = arc_xsect_result['config'] != ViewCone.PointInConeStatus.BEHIND

                # If there was an angle point added due to this edge, then it
                # is blocking; add and continue to avoid further processing.
                if blocking:
                    blocking_edges.append(edge)
                elif test_seg_seg_xsect and \
                        any([fov_edge for fov_edge in self.fov_edges
                             if self.edges_intersect(fov_edge, edge)]):
                    blocking_edges.append(edge)

                    """
                    If any angle point(s) would occur because of this edge, they
                    would have been found by now and the edge would have been
                    tagged as a blocking one. Even if no angle points were found
                    due to this edge it still may be a blocking, or not. Perform
                    a couple of segment – segment intersection tests with the
                    sector's edges to check if the edge is indeed blocking. This
                    is worth the expenditure incurred; say we have 10 angle
                    points, for every redundant, non-blocking edge added without
                    such a check means we waste time in performing 10 futile
                    line segment intersection tests. Prune them early on by
                    performing the tests beforehand.
    
                    Perform segment – segment testing if testSegSegXsect is
                    true; this will be so if the arc intersection was never
                    performed (say when both points are in FrontSemicircle and
                    their edge occluding vision) or if the intersection points
                    aren't behind the sector; there can be cases where not both
                    points are behind (if so they'd have gotten pruned by now),
                    but the intersection points are behind, prune them.
                    """

            prev_edge = edge

    def is_zero(self, vec):
        return (abs(vec.x) + abs(vec.y)) <= self.epsilon

    def are_parallel(self, vec_a, vec_b):
        return abs(vec_a.cross(vec_b)) <= self.epsilon

    def is_point_on_line(self, pt_vec, line):
        v = pt_vec - line.a
        return self.are_parallel(v, line.vec)

    @staticmethod
    def rotate_dir(direction, cos_a, sin_a):
        """ rotates dir based on cosA and sinA both counter-clockwise and clockwise
            doing it manually instead of using mat2d as these can be reused for
            rotation in both directions, avoiding their recalculation"""
        x_c = direction.x * cos_a
        y_c = direction.y * cos_a
        x_s = direction.x * sin_a
        y_s = direction.y * sin_a
        return [Vector2(x_c - y_s, x_s + y_c), Vector2(x_c + y_s, -x_s + y_c)]

    @staticmethod
    def point_on_line(line, t):
        return line.a + (line.b - line.a) * t

    @staticmethod
    def perp2d(v, clockwise=False):
        if clockwise:
            return Vector2(v.y, -v.x)
        return Vector2(-v.y, v.x)

    @staticmethod
    def inv_lerp(line, point):
        t = point - line.a
        return t.dot(line.vec) / line.length_squared

    def edges_intersect(self, edge_a, edge_b):
        """
        Simple wrapper function to turn complicated intersection of line segment test into a boolean True or False
        result.

        :param edge_a: first edge to test
        :param edge_b: second edge to test
        :return: True or False if the two segments intersect or not
        """
        return ViewCone.LineSegConfig.INTERSECT == self.line_seg_line_seg_x_sect(edge_a, edge_b)['config']

    def line_seg_line_seg_x_sect(self, line_1, line_2, should_compute_point=False, is_line_1_ray=False):
        """
        converted from here:
        https://github.com/legends2k/2d-fov/blob/gh-pages/index.html

        originally from §16.16.1, Real-Time Rendering, 3rd Edition with Antonio's optimisation.
        Code gets around eh?

        The intent of this function is to determine if two line segments intersect, it also provides additional
        information (on request) giving the point of intersection (if any) and if the non-intersecting lines are
        parallel or entirely disjoint.

        :param line_1: The first line segment to test
        :param line_2: The second line segment to test
        :param should_compute_point: Should we compute the point of intersection or not
        :param is_line_1_ray: Is line 1 a 'ray' which I believe in this context is asking if it is a view cone ray
        :return:
        """
        result = {'config': ViewCone.LineSegConfig.DISJOINT, 't': None, 'point': None}
        line_1_vec = line_1.b - line_1.a
        line_2_vec = line_2.b - line_2.a
        l1p = self.perp2d(line_1_vec)
        f = line_2_vec.dot(l1p)
        if abs(f) <= self.epsilon:
            result['config'] = ViewCone.LineSegConfig.PARALLEL
            # if line1 is a ray and an intersection point is needed, then filter
            # cases where line and ray are parallel, but the line isn't part of
            # ray e.g. ---> ____ should be filterd, but ---> ----- should not be.
            if is_line_1_ray and should_compute_point and self.is_point_on_line(line_2.a, line_1):
                # find the ray origin position with regards to the line segment
                alpha = self.inv_lerp(line_2, line_1.a)
                if 0 <= alpha <= 1:
                    result['t'] = 0
                    result['point'] = Vector2(line_1.a)
                elif alpha < 0:
                    result['point'] = Vector2(line_2.a)
                    result['t'] = self.inv_lerp(line_1, result['point'])
        else:
            c = line_1.a - line_2.a
            e = c.dot(l1p)
            #  t = e ÷ f, but computing t isn't necessary, just checking the values
            # of e and f we deduce if t ∈ [0, 1], if not the division and further
            # calculations are avoided: Antonio's optimisation.
            # f should never be zero here which means they're parallel
            if (f > 0 and 0 <= e <= f) or (f < 0 and 0 >= e >= f):
                l2p = self.perp2d(line_2_vec)
                d = c.dot(l2p)
                # if line 1 is a ray, checks relevant to restricting s to 1
                # isn't needed, just check if it wouldn't become < 0
                if (is_line_1_ray and ((f > 0 and d >= 0) or (f < 0 and d <= 0))) or (
                        (f > 0 and 0 <= d <= f) or (f < 0 and 0 >= d >= f)):
                    result['config'] = ViewCone.LineSegConfig.INTERSECT
                    if should_compute_point:
                        s = d / f
                        result['t'] = s
                        result['point'] = self.point_on_line(line_1, s)

        return result

    def line_seg_arc_x_sect(self, line):
        """
        Checks for intersection between a line and the 'arc' of a view cone/sector. Should be a fairly rarely used test
        as blocking edges have to be in a  very specific place to intersect the cone at this point.

        :param line: the line to test against the view cone's sector for intersection
        :return: None if no intersection at all, or {'config': ViewCone.PointInConeStatus.BEHIND} if both points of line
                 are behind the sector/cone. If there is an intersection returns a dictionary like so -
                 {'config': ViewCone.PointInConeStatus.WITHIN, 'points': points}
        """
        delta = line.a - self.origin_centre_position
        b = line.vec.dot(delta)
        d_2 = line.length_squared
        c = delta.length_squared() - self.length_squared
        det = (b * b) - (d_2 * c)
        if det > 0:
            det_sqrt = math.sqrt(det)
            if b >= 0:
                t = b + det_sqrt
                t1 = -t / d_2
                t2 = -c / t
            else:
                t = det_sqrt - b
                t1 = c / t
                t2 = t / d_2

            p1 = None
            p2 = None
            points = []
            p1_in_sector = None
            p2_in_sector = None
            if 0 <= t1 <= 1:
                p1 = self.point_on_line(line, t1)
                p1_in_sector = self.is_point_in_sector(p1)
                if p1_in_sector == ViewCone.PointInConeStatus.WITHIN:
                    points.append(p1)
            if 0 <= t2 <= 1:
                p2 = self.point_on_line(line, t2)
                p2_in_sector = self.is_point_in_sector(p2)
                if p2_in_sector == ViewCone.PointInConeStatus.WITHIN:
                    points.append(p2)

            # line segment is contained within circle; it may be cutting
            # the sector, but not the arc, so return false, as there're
            # no angle points
            if p1 is None and p2 is None:
                return None

            # both intersection points are behind, the edge has no way of cutting
            # the sector
            if p1_in_sector == ViewCone.PointInConeStatus.BEHIND and p2_in_sector == ViewCone.PointInConeStatus.BEHIND:
                return {'config': ViewCone.PointInConeStatus.BEHIND}

            if len(points) > 0:
                return {'config': ViewCone.PointInConeStatus.WITHIN, 'points': points}

        return None

    def add_angle_point_with_aux(self, point, prev_edge, next_edge, angle_points):
        """
        /*
          * Auxiliary rays (primary ray rotated both counter-clockwise and clockwise by
          * an iota angle). These are needed for cases where a primary ray would get hit
          * an edge's vertex and get past it to hit things behind it too.
          *
          *   ALLOW PENETRATION             DISALLOW PENETRATION
          *
          *  ----------X                        \  polygon  /
          *  polygon  / \  <- ray                X---------X
          *          /   \                                  \  <- ray
          *                                                  \
          * References:
          * 1: http://ncase.me/sight-and-light
          * 2: http://www.redblobgames.com/articles/visibility
          */
        :param angle_points:
        :param next_edge:
        :param prev_edge:
        :param point: the point to add
        :return:
        """
        current_size = len(angle_points.points)

        point_index = angle_points.add(point)
        # Add aux points only if the addition of the primary point was successful.
        # When a corner vertex of a polygon is added twice for edges A and B,
        # although the primary point would not be added since constructEdges would
        # have used the same vec2 object to make the end points of both edges,
        # this isn't case for the auxiliary points created in this function afresh
        # on each call. This check avoids redundant auxiliary point addition.
        if current_size != len(angle_points.points):
            ray = point - self.origin_centre_position
            auxiliaries = self.rotate_dir(ray, self.half_aux_ray_tilt_cos, self.half_aux_ray_tilt_sin)

            auxiliaries[0] += self.origin_centre_position
            auxiliaries[1] += self.origin_centre_position
            proj_axis = self.perp2d(ray)
            if (next_edge is None) or (next_edge == prev_edge):
                # line_vec should originate from the added endpoint going to the
                # other end; if added point is second in edge, flip edge's vector
                if point == prev_edge.a:
                    line_vec = prev_edge.b - prev_edge.a
                else:
                    prev_edge_vec = prev_edge.b - prev_edge.a
                    line_vec = Vector2(-prev_edge_vec.x, -prev_edge_vec.y)

                p = line_vec.dot(proj_axis)
                #  if line_vec is in −ve halfspace of proj_axis, add the auxiliary
                #  ray that would be in the +ve halfspace (i.e. the auxiliary ray
                #  due to rotating ray counter-clockwise by iota) and vice-versa
                if p <= 0:
                    angle_points.add_aux(point_index, auxiliaries[0])
                # use if instead of else if to deal with the case where ray and
                # edge are parallel, in which case both auxiliary rays are needed
                if p >= 0:
                    angle_points.add_aux(point_index, auxiliaries[1])
            else:
                # refer to vision_beyond.html workout to understand in which
                # situation vision can extend beyond corners and auxiliary rays
                #  are needed (we'll take that on trust since I'm not including that file)
                prev_edge_vec = prev_edge.b - prev_edge.a
                next_edge_vec = next_edge.b - next_edge.a
                p1 = prev_edge_vec.dot(proj_axis)
                p2 = next_edge_vec.dot(proj_axis)
                if (p1 >= 0) and (p2 <= 0):
                    angle_points.add_aux(point_index, auxiliaries[0])
                elif (p1 <= 0) and (p2 >= 0):
                    angle_points.add_aux(point_index, auxiliaries[1])

    def make_rays(self, angle_points):
        ray = angle_points[0] - self.origin_centre_position
        rays = [ray]
        # i for angle_points, j for rays to avoid doing len(angle_points) - 1
        j = 0
        for i in range(1, len(angle_points)):
            ray = angle_points[i] - self.origin_centre_position
            if not self.are_parallel(ray, rays[j]):
                rays.append(ray)
                j += 1
        return rays

    def angular_points_sorter(self, a, b):
        a_v = a[0] - self.origin_centre_position
        b_v = b[0] - self.origin_centre_position
        # sort expects a negative value when a should come before b; since
        # cross2d gives a negative value when the rotation from a to b is
        # counter-clockwise we use it as-is; see comment in isPointInSector
        return a_v.cross(b_v)

    def aux_angular_points_sorter(self, a, b):
        a_v = a - self.origin_centre_position
        b_v = b - self.origin_centre_position
        # sort expects a negative value when a should come before b; since
        # cross2d gives a negative value when the rotation from a to b is
        # counter-clockwise we use it as-is; see comment in isPointInSector
        return a_v.cross(b_v)

    def sort_angular_points(self, angle_points, blocking_edges):
        # need to determine if two co-planar edges share a point so we can strip it
        points_to_remove = []
        for edge in blocking_edges:
            for other_edge in blocking_edges:
                if edge != other_edge:
                    if 1.0 - abs(edge.vec.dot(other_edge.vec)) <= self.epsilon:
                        # edges are co_planar, do they share a point?
                        shared_point = None
                        if abs(edge.a.x - other_edge.a.x) <= self.epsilon and abs(edge.a.y - other_edge.a.y) <= self.epsilon:
                            shared_point = edge.a
                        elif abs(edge.a.x - other_edge.b.x) <= self.epsilon and abs(edge.a.y - other_edge.b.y) <= self.epsilon:
                            shared_point = edge.a
                        elif abs(edge.b.x - other_edge.a.x) <= self.epsilon and abs(edge.b.y - other_edge.a.y) <= self.epsilon:
                            shared_point = edge.b
                        elif abs(edge.b.x - other_edge.b.x) <= self.epsilon and abs(edge.b.y - other_edge.b.y) <= self.epsilon:
                            shared_point = edge.b
                        if shared_point is not None:
                            points_to_remove.append(shared_point)
        angle_points.points = [angle_points.points[i] for i in range(0, len(angle_points.points)) if
                               angle_points.points[i][0] not in points_to_remove]
        angle_points.points.sort(key=functools.cmp_to_key(self.angular_points_sorter))

        final_angle_points = []
        for point_group in angle_points.points:
            point_group.sort(key=functools.cmp_to_key(self.aux_angular_points_sorter))
            for point in point_group:
                final_angle_points.append(point)
        return final_angle_points

    @staticmethod
    def calc_quad_bez_curve_ctrl_point(v1, v2, centre, radius):
        ctrl_point = v1 + v2
        ctrl_point.normalize_ip()
        scale = radius * (2 - v1.dot(ctrl_point))
        ctrl_point = centre + (ctrl_point * scale)
        return ctrl_point

    def shoot_rays(self, rays, blocking_edges):
        line_1_is_ray = True
        should_compute_point = True
        n = len(rays)
        hit_points = [None for _ in range(0, n)]
        ctrl_points = [None for _ in range(0, n)]
        # rays is an array of vectors only, however the intersection functions
        # work on edges i.e. it also needs the end points and square length; hence
        # this_ray would act as the ray with additional edge data
        prev_point_on_arc = False
        prev_unit_ray = Vector2()
        for i in range(0, n):
            #  set edge data on this_ray specific to the ray currently shot
            this_ray = Edge("ray", self.origin_centre_position, self.origin_centre_position + rays[i])
            hit_point = Vector2()
            t = None
            blocker = None
            hit_dist_2 = None
            for j in range(0, len(blocking_edges)):
                res = self.line_seg_line_seg_x_sect(this_ray, blocking_edges[j], should_compute_point, line_1_is_ray)
                # both parallel and intersecting cases are valid for inspection;
                # both have the parameter and point defined
                if (res['t'] is not None) and ((t is None) or (res['t'] < t)):
                    # This is needed when the observer is exactly at a polygon's
                    # vertex, from where both worlds (outside and inside the
                    # polygon/building) are visible as the observer is standing at
                    # a pillar point where two walls meet. In such case, all rays
                    # emanating from the centre would hit one of these edges with
                    # t = 0 but this point should be discounted from calculations.
                    # However, the value of t can vary depending on the length of
                    # the ray, hence using the distance between the points as a
                    # better measure of proximity
                    hit_dist_2 = res['point'].distance_squared_to(self.origin_centre_position)
                    if hit_dist_2 > self.epsilon:
                        t = res['t']
                        hit_point = Vector2(res['point'])
                        blocker = blocking_edges[j]
            """
            the ray could've hit
            
              i. nothing (no blocking edge was in its way; t is None)
             ii. blocking edge(s) of which the closest intersecting point is
                 a. within the sector
                 b. on the sector's arc
                 c. beyond the sector's arc
            
            For (ii.c) t may be defined but the point would be beyond the
            sector's radius. For everything except (ii.a), the hit point would
            be on the arc and the unit vector along the ray would be needed to
            draw the Bézier curve, if the next point is also going to be on the
            arc. For cases (i) and (ii.c), a new hit point needs to be
            calculated too, which can use the unit vector.
            
            One can avoid sqrt and call atan2 to get the angle directly which
            would also help in drawing the actual arc (using ctx.arc) and not an
            approximation of the arc using ctx.quadraticCurveTo. However, sqrt
            is chosen over atan2 since it's usually faster:
            http://stackoverflow.com/a/9318108.
            """
            point_on_arc = (t is None) or ((hit_dist_2 + self.epsilon - self.length_squared) >= 0)
            if point_on_arc:
                unit_ray = rays[i].normalize()
                # for cases (i), (ii.b) and (ii.c) set the hit point; this would
                # be redundant for case (ii.b) but checking for it would be
                # cumbersome, so just reassign
                hit_point = self.origin_centre_position + (unit_ray * self.length)
                if prev_point_on_arc:
                    needs_arc = True
                    """
                    the case where part of the arc is cut by a blocking edge
                    needs to be handled differently:
                    
                                         /---  +----------+
                                     /---    \-|          |
                                 /---          X          |
                              /--              |\         |
                          /---                 | \        |
                         o                     |  |       |
                          ---\                 | /        |
                              --\              |/         |
                                 ---\          X          |
                                     ---\    /-|          |
                                         ----  +----------+
                    
                    although both hit points would be on the arc, they shouldn't
                    be connected by an arc since the blocking edge wouldn't
                    allow vision beyond; hence check if this ray hit a blocking
                    edge, if yes, then check if it's parallel to the edge formed
                    by the connection between this and the previous hit points,
                    if so don't make an arc.
                    
                    the check i > 0 isn't needed since if that was the case the
                    variable prevPointOnArc would be false and the control
                    would've not reached here, so doing i - 1 is safe here
                    """
                    if blocker:
                        connector = hit_points[i - 1] - hit_point
                        needs_arc = not self.are_parallel(blocker.b - blocker.a, connector)
                    if needs_arc:
                        ctrl_points[i] = self.calc_quad_bez_curve_ctrl_point(unit_ray, prev_unit_ray,
                                                                             self.origin_centre_position,
                                                                             self.length)

                prev_unit_ray = Vector2(unit_ray)

            prev_point_on_arc = point_on_arc
            hit_points[i] = hit_point

        return {'hit_points': hit_points, 'ctrl_points': ctrl_points}

    def los_blocked_test(self, ray, edge):
        res = self.line_seg_line_seg_x_sect(ray, edge, True)
        if ViewCone.LineSegConfig.INTERSECT == res['config']:
            return res['point'].distance_squared_to(self.origin_centre_position) > self.epsilon

    def is_subject_visible(self, target):
        if self.is_point_in_sector(target) == ViewCone.PointInConeStatus.WITHIN:
            ray = Edge("ray", self.origin_centre_position, target)
            return not any([True for edge in self.blocking_edges if self.los_blocked_test(ray, edge)])
        return False

    def update(self):
        """
        Based on method established here:
        https://legends2k.github.io/2d-fov/design.html

        :return:
        """
        #tick_1 = self.timing_clock.tick()
        # each loop we first grab the edges that are attached to world objects in our cone length/radius
        # then we cull down only to the edges whose closest point is in our radius
        if len(self.collision_circle.collided_shapes_this_frame) != 0:
            blocking_edges = []
            angle_points = AnglePointSet()

            for shape in self.collision_circle.collided_shapes_this_frame:
                if shape.type == BaseCollisionShape.RECT:
                    self.check_polygon(shape, angle_points, blocking_edges)

            sorted_angle_points = self.sort_angular_points(angle_points, blocking_edges)

            angle_points_array = [Vector2(self.end_positions[0])]
            angle_points_array.extend(sorted_angle_points)
            angle_points_array.append(Vector2(self.end_positions[1]))

            rays = self.make_rays(angle_points_array)
            result = self.shoot_rays(rays, blocking_edges)

            self.angle_points_array = angle_points_array
            self.rays = rays
            self.hit_points = result['hit_points']
            self.ctrl_points = result['ctrl_points']
            self.blocking_edges = blocking_edges

        #tick_2 = self.timing_clock.tick()

        #if tick_2 > 7:
        #   print("view_cone_timing:", tick_2, "ms")

    def clear(self):
        self.angle_points_array = []
        self.rays = []
        self.hit_points = []
        self.ctrl_points = []
        self.blocking_edges = []

    def closest_point_on_edge(self, edge):
        """
        Calculate closest point on a line segment to another point, in this case our cone's origin.

        Gathered from explanation here:
        http://doswa.com/2009/07/13/circle-segment-intersectioncollision.html
        :param edge: a line segment or an 'edge' in this case of a polygon or rectangle
        :return: the closest point on the segment to the origin point of our view cone
        """
        seg_v = edge.vec
        pt_v = self.origin_centre_position - edge.a
        seg_length_squared = edge.length_squared

        if seg_length_squared <= 0:
            raise ValueError("Segment length is less than or equal to zero")

        if seg_length_squared != 1:
            seg_len = math.sqrt(seg_length_squared)
            seg_v_unit = seg_v / seg_len
        else:
            seg_len = 1
            seg_v_unit = seg_v

        proj = pt_v.dot(seg_v_unit)
        if proj <= 0:
            return Vector2(edge.a)
        if proj >= seg_len:
            return Vector2(edge.b)

        return (seg_v_unit * proj) + edge.a
示例#11
0
    def __init__(self, character, start_pos, tiled_level, control_scheme, hud_buttons,
                 collision_grid, projectile_sprite_group, player_sprites):
        super().__init__(player_sprites)
        self.collision_grid = collision_grid
        self.player_sprites = player_sprites
        self.character = character
        self.xp = 0
        self.level = 1
        self.score = 0
        self.scheme = control_scheme
        self.image_name = "images/player_2.png"
        self.original_image = pygame.image.load(self.image_name)
        self.sprite_sheet = self.original_image.copy()

        self.hud_buttons = hud_buttons

        self.player_world_target = [0.0, 0.0]

        # ------------------------------------------
        # Add new weapon objects here
        # ------------------------------------------
        self.bow_weapon = BowWeapon(self, self.sprite_sheet, self.collision_grid, projectile_sprite_group)
        self.sword_weapon = SwordWeapon(self, self.sprite_sheet, self.collision_grid, projectile_sprite_group)
        self.magic_weapon = MagicWeapon(self, self.sprite_sheet)
        self.active_weapon = self.bow_weapon

        for button in self.hud_buttons:
            if button.button_image_name == "bow_icon":
                button.set_selected()
            else:
                button.clear_selected()

        # self.sprite = pygame.sprite.Sprite()
        self.test_collision_sprite = pygame.sprite.Sprite()
        self.flash_sprite = pygame.sprite.Sprite()
        self.image = self.active_weapon.anim_set.stand
        self.rect = self.active_weapon.anim_set.stand.get_rect()
        self.rect.center = start_pos

        self.sprite_rot_centre_offset = [0.0, 11.0]
        self.speed = 0.0
        self.acceleration = 200.0
        self.max_speed = 250.0

        self.collide_radius = 18

        self.max_health = 100 + (50 * self.level * (self.character.strength / 20))
        self.health = self.max_health

        self.max_mana = 50.0 + (50.0 * self.level * (self.character.magic / 20))
        self.mana = self.max_mana
        self.mana_recharge = 1.0 + (1.0 * self.level * (self.character.magic / 20))
        
        self.should_die = False

        self.move_accumulator = 0.0
       
        self.position = [float(self.rect.center[0]), float(self.rect.center[1])]
        self.player_move_target = self.position
        self.distance_to_move_target = 0.0
        self.current_vector = [0.0, -1.0]
        self.new_facing_angle = 0

        self.screen_position = [0, 0]
        self.screen_position[0] = self.position[0]
        self.screen_position[1] = self.position[1]

        self.update_screen_position(tiled_level.position_offset)

        direction_magnitude = math.sqrt(self.current_vector[0] ** 2 + self.current_vector[1] ** 2)
        if direction_magnitude > 0.0:
            unit_dir_vector = [self.current_vector[0] / direction_magnitude,
                               self.current_vector[1] / direction_magnitude]
            self.new_facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi

        self.old_facing_angle = self.new_facing_angle

        self.rect.center = self.rot_point([self.screen_position[0],
                                           self.screen_position[1] + self.sprite_rot_centre_offset[1]],
                                          self.screen_position, -self.new_facing_angle)

        self.left_mouse_held = False
        self.right_mouse_held = False

        self.per_bullet_damage = 25
        
        self.player_fire_target = [10000, 10000]

        self.switch_to_bow = False
        self.switch_to_sword = False
        self.switch_to_magic = False
       
        self.sprite_flash_acc = 0.0
        self.sprite_flash_time = 0.15
        self.should_flash_sprite = False
        self.sprite_flashing = False

        self.is_collided = False

        self.firing = False
        self.firing_timer = 0.0

        self.has_new_high_score = False

        self.should_draw_collision_obj = False
        self.collision_obj_rects = []
        self.collision_obj_rect = pygame.Rect(0.0, 0.0, 2.0, 2.0)

        self.world_click_pos = [0, 0]

        # we do collisions in world space
        self.collision_circle = CollisionCircle(self.position[0], self.position[1], self.collide_radius,
                                                {GameCollisionType.MONSTER_WEAPON: CollisionNoHandler(),
                                                 GameCollisionType.TILE: CollisionRubHandler(),
                                                 GameCollisionType.MONSTER: CollisionRubHandler(),
                                                 GameCollisionType.PICKUP: CollisionNoHandler()},
                                                GameCollisionType.PLAYER,
                                                [GameCollisionType.MONSTER_WEAPON,
                                                 GameCollisionType.TILE,
                                                 GameCollisionType.MONSTER,
                                                 GameCollisionType.PICKUP])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)

        self.drawable_circle = DrawableCollisionCircle(self.collision_circle)
示例#12
0
class Player(pygame.sprite.Sprite):
    def __init__(self, character, start_pos, tiled_level, control_scheme, hud_buttons,
                 collision_grid, projectile_sprite_group, player_sprites):
        super().__init__(player_sprites)
        self.collision_grid = collision_grid
        self.player_sprites = player_sprites
        self.character = character
        self.xp = 0
        self.level = 1
        self.score = 0
        self.scheme = control_scheme
        self.image_name = "images/player_2.png"
        self.original_image = pygame.image.load(self.image_name)
        self.sprite_sheet = self.original_image.copy()

        self.hud_buttons = hud_buttons

        self.player_world_target = [0.0, 0.0]

        # ------------------------------------------
        # Add new weapon objects here
        # ------------------------------------------
        self.bow_weapon = BowWeapon(self, self.sprite_sheet, self.collision_grid, projectile_sprite_group)
        self.sword_weapon = SwordWeapon(self, self.sprite_sheet, self.collision_grid, projectile_sprite_group)
        self.magic_weapon = MagicWeapon(self, self.sprite_sheet)
        self.active_weapon = self.bow_weapon

        for button in self.hud_buttons:
            if button.button_image_name == "bow_icon":
                button.set_selected()
            else:
                button.clear_selected()

        # self.sprite = pygame.sprite.Sprite()
        self.test_collision_sprite = pygame.sprite.Sprite()
        self.flash_sprite = pygame.sprite.Sprite()
        self.image = self.active_weapon.anim_set.stand
        self.rect = self.active_weapon.anim_set.stand.get_rect()
        self.rect.center = start_pos

        self.sprite_rot_centre_offset = [0.0, 11.0]
        self.speed = 0.0
        self.acceleration = 200.0
        self.max_speed = 250.0

        self.collide_radius = 18

        self.max_health = 100 + (50 * self.level * (self.character.strength / 20))
        self.health = self.max_health

        self.max_mana = 50.0 + (50.0 * self.level * (self.character.magic / 20))
        self.mana = self.max_mana
        self.mana_recharge = 1.0 + (1.0 * self.level * (self.character.magic / 20))
        
        self.should_die = False

        self.move_accumulator = 0.0
       
        self.position = [float(self.rect.center[0]), float(self.rect.center[1])]
        self.player_move_target = self.position
        self.distance_to_move_target = 0.0
        self.current_vector = [0.0, -1.0]
        self.new_facing_angle = 0

        self.screen_position = [0, 0]
        self.screen_position[0] = self.position[0]
        self.screen_position[1] = self.position[1]

        self.update_screen_position(tiled_level.position_offset)

        direction_magnitude = math.sqrt(self.current_vector[0] ** 2 + self.current_vector[1] ** 2)
        if direction_magnitude > 0.0:
            unit_dir_vector = [self.current_vector[0] / direction_magnitude,
                               self.current_vector[1] / direction_magnitude]
            self.new_facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi

        self.old_facing_angle = self.new_facing_angle

        self.rect.center = self.rot_point([self.screen_position[0],
                                           self.screen_position[1] + self.sprite_rot_centre_offset[1]],
                                          self.screen_position, -self.new_facing_angle)

        self.left_mouse_held = False
        self.right_mouse_held = False

        self.per_bullet_damage = 25
        
        self.player_fire_target = [10000, 10000]

        self.switch_to_bow = False
        self.switch_to_sword = False
        self.switch_to_magic = False
       
        self.sprite_flash_acc = 0.0
        self.sprite_flash_time = 0.15
        self.should_flash_sprite = False
        self.sprite_flashing = False

        self.is_collided = False

        self.firing = False
        self.firing_timer = 0.0

        self.has_new_high_score = False

        self.should_draw_collision_obj = False
        self.collision_obj_rects = []
        self.collision_obj_rect = pygame.Rect(0.0, 0.0, 2.0, 2.0)

        self.world_click_pos = [0, 0]

        # we do collisions in world space
        self.collision_circle = CollisionCircle(self.position[0], self.position[1], self.collide_radius,
                                                {GameCollisionType.MONSTER_WEAPON: CollisionNoHandler(),
                                                 GameCollisionType.TILE: CollisionRubHandler(),
                                                 GameCollisionType.MONSTER: CollisionRubHandler(),
                                                 GameCollisionType.PICKUP: CollisionNoHandler()},
                                                GameCollisionType.PLAYER,
                                                [GameCollisionType.MONSTER_WEAPON,
                                                 GameCollisionType.TILE,
                                                 GameCollisionType.MONSTER,
                                                 GameCollisionType.PICKUP])
        self.collision_circle.set_owner(self)
        self.collision_grid.add_new_shape_to_grid(self.collision_circle)

        self.drawable_circle = DrawableCollisionCircle(self.collision_circle)

    def draw_collision_shape(self, screen, camera_position, camera_half_dimensions):
        self.drawable_circle.update_collided_colours()
        self.drawable_circle.draw(screen, camera_position, camera_half_dimensions)

    def remove_from_grid(self):
        self.collision_grid.remove_shape_from_grid(self.collision_circle)

    def react_to_collision(self):
        for shape in self.collision_circle.collided_shapes_this_frame:
            if shape.game_type == GameCollisionType.MONSTER:
                self.position[0] = self.collision_circle.x
                self.position[1] = self.collision_circle.y
            elif shape.game_type == GameCollisionType.TILE:
                self.position[0] = self.collision_circle.x
                self.position[1] = self.collision_circle.y

    def add_xp(self, xp):
        self.xp += xp
        
    def add_score(self, score):
        self.score += score

    def xp_for_next_level(self):
        # 2 = 100
        # 3 = 250
        # 4 = 500
        # 5 = 850
        # 6 = 1300
        # 7 = 1850
        return 50 + (100 * ((self.level * self.level)/2))

    def update_screen_position(self, world_offset):
        self.screen_position[0] = self.position[0] - world_offset[0]
        self.screen_position[1] = self.position[1] - world_offset[1]
    
    def update_sprite(self, time_delta):
        if self.should_flash_sprite:
            self.should_flash_sprite = False
            self.player_sprites.add(self.flash_sprite)
            self.sprite_flashing = True

        if self.sprite_flashing:
            self.sprite_flash_acc += time_delta
            if self.sprite_flash_acc > self.sprite_flash_time:
                self.sprite_flash_acc = 0.0
                self.sprite_flashing = False
                self.flash_sprite.kill()
            else:
                lerp_value = self.sprite_flash_acc / self.sprite_flash_time
                flash_alpha = self.lerp(255, 0, lerp_value)
                flash_image = self.image.copy()
                flash_image.fill((0, 0, 0, flash_alpha), None, pygame.BLEND_RGBA_MULT)
                flash_image.fill((255, 255, 255, 0), None, pygame.BLEND_RGBA_ADD)
                self.flash_sprite.image = flash_image
                self.flash_sprite.rect = self.flash_sprite.image.get_rect()
                
                anim_centre_offset = self.active_weapon.anim_set.centre_offset
                if self.firing:
                    anim_centre_offset = self.active_weapon.anim_set.firing_centre_offset
                y_pos = self.screen_position[1]+self.sprite_rot_centre_offset[1]+anim_centre_offset[1]
                self.flash_sprite.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                               self.screen_position, -self.new_facing_angle)

    def process_event(self, event):
        if event.type == MOUSEBUTTONDOWN:
            if event.button == 1:
                self.left_mouse_held = True
            if event.button == 3:
                self.right_mouse_held = True
        if event.type == MOUSEBUTTONUP:
            if event.button == 1:
                self.left_mouse_held = False
            if event.button == 3:
                self.right_mouse_held = False
        if event.type == KEYDOWN:     
            if event.key == self.scheme.bow:
                self.switch_to_bow = True
            if event.key == self.scheme.sword:
                self.switch_to_sword = True
            if event.key == self.scheme.magic:
                self.switch_to_magic = True

    def check_and_save_high_score(self):
        if self.score > self.character.score:
            self.has_new_high_score = True
            self.character.score = self.score
            self.character.save()

    def on_level_up(self):
        self.level += 1
        old_max_health = self.max_health
        self.max_health = 100 + (50 * self.level * (self.character.strength / 20))
        self.health += (self.max_health - old_max_health)  # add change in health to current health
        self.max_mana = 50 + (50 * self.level * (self.character.magic / 20))
        self.acceleration = 200.0 + (25 * self.level * (self.character.dexterity/20))
        self.max_speed = 200.0 + (25 * self.level * (self.character.dexterity / 20))

        self.bow_weapon.on_level_up()
        self.sword_weapon.on_level_up()
        self.magic_weapon.on_level_up()
            
    def update_movement_and_collision(self, time_delta, tiled_level, monsters):
        if self.xp_for_next_level() <= self.xp:
            self.on_level_up()

        if self.health == 0:
            self.should_die = True
            self.check_and_save_high_score()

        self.mana += self.mana_recharge * time_delta
        if self.mana > self.max_mana:
            self.mana = self.max_mana

        if self.switch_to_bow:
            self.switch_to_bow = False
            self.active_weapon = self.bow_weapon

            for button in self.hud_buttons:
                if button.button_image_name == "bow_icon":
                    button.set_selected()
                else:
                    button.clear_selected()

            self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand, self.new_facing_angle)
            self.rect = self.image.get_rect()
            anim_centre_offset = self.active_weapon.anim_set.centre_offset
            y_pos = self.screen_position[1]+self.sprite_rot_centre_offset[1]+anim_centre_offset[1]
            self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                              self.screen_position, -self.new_facing_angle)
            
        if self.switch_to_sword:
            self.switch_to_sword = False
            self.active_weapon = self.sword_weapon

            for button in self.hud_buttons:
                if button.button_image_name == "sword_icon":
                    button.set_selected()
                else:
                    button.clear_selected()

            self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand, self.new_facing_angle)
            self.rect = self.image.get_rect()
            anim_centre_offset = self.active_weapon.anim_set.centre_offset
            y_pos = self.screen_position[1]+self.sprite_rot_centre_offset[1]+anim_centre_offset[1]
            self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                              self.screen_position, -self.new_facing_angle)
               
        if self.switch_to_magic:
            self.switch_to_magic = False
            self.active_weapon = self.magic_weapon

            for button in self.hud_buttons:
                if button.button_image_name == "magic_icon":
                    button.set_selected()
                else:
                    button.clear_selected()

            self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand, self.new_facing_angle)
            self.rect = self.image.get_rect()
            anim_centre_offset = self.active_weapon.anim_set.centre_offset
            y_pos = self.screen_position[1] + self.sprite_rot_centre_offset[1] + anim_centre_offset[1]
            self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                              self.screen_position, -self.new_facing_angle)
            
        fire_this_update = False
        if self.active_weapon.can_fire and self.right_mouse_held:
            fire_this_update = True
            self.player_move_target = self.position
            self.distance_to_move_target = 0.0
            self.active_weapon.fire_rate_acc = 0.0
            self.active_weapon.can_fire = False

            if self.player_fire_target != pygame.mouse.get_pos():
                new_target = pygame.mouse.get_pos()
                x_dist = float(new_target[0]) - float(self.screen_position[0])
                y_dist = float(new_target[1]) - float(self.screen_position[1])
                distance = math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
                if distance > 0.0:
                    self.player_fire_target = new_target
                    self.current_vector = [x_dist / distance, y_dist / distance]
                    direction_magnitude = math.sqrt(self.current_vector[0] ** 2 + self.current_vector[1] ** 2)
                    if direction_magnitude > 0.0:
                        unit_dir_vector = [self.current_vector[0] / direction_magnitude,
                                           self.current_vector[1] / direction_magnitude]
                        self.new_facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi

                self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand, self.new_facing_angle)
                self.rect = self.image.get_rect()
                anim_centre_offset = self.active_weapon.anim_set.centre_offset
                y_pos = self.screen_position[1]+self.sprite_rot_centre_offset[1]+anim_centre_offset[1]
                self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                  self.screen_position, -self.new_facing_angle)
            
        self.active_weapon.update(time_delta, self.position, self.current_vector)
        barrel_screen_x = float(self.active_weapon.barrel_exit_pos[0] - tiled_level.position_offset[0])
        barrel_screen_y = float(self.active_weapon.barrel_exit_pos[1] - tiled_level.position_offset[1])
        x_dist = float(self.player_fire_target[0]) - barrel_screen_x
        y_dist = float(self.player_fire_target[1]) - barrel_screen_y
        distance = math.sqrt(x_dist**2 + y_dist**2)
        if distance > 0.0:
            projectile_vector = [x_dist/distance, y_dist/distance]
            self.active_weapon.set_projectile_vector(projectile_vector)
        
        if fire_this_update:
            self.active_weapon.fire(monsters)
            self.firing_timer = self.active_weapon.fire_anim_speed
            self.firing = True

        if self.firing:
            anim_progress = self.active_weapon.fire_anim_speed - self.firing_timer
            fire_anim_percentage = anim_progress / self.active_weapon.fire_anim_speed

            number_of_anims = len(self.active_weapon.anim_set.fire_sprites)
            anim_index = max(1, int(fire_anim_percentage * number_of_anims)) - 1

            current_anim = self.active_weapon.anim_set.fire_sprites[anim_index]
            anim_centre_offset = self.active_weapon.anim_set.firing_centre_offset
            self.image = pygame.transform.rotate(current_anim, self.new_facing_angle)
            self.rect = self.image.get_rect()
            y_pos = self.screen_position[1] + self.sprite_rot_centre_offset[1] + anim_centre_offset[1]
            self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                              self.screen_position, -self.new_facing_angle)
                
            self.firing_timer -= time_delta
            if self.firing_timer <= 0.0:
                self.firing_timer = 0.0
                self.firing = False
                
        if self.left_mouse_held:
            self.firing = False
            self.firing_timer = 0.0
            self.world_click_pos = [pygame.mouse.get_pos()[0] + tiled_level.position_offset[0],
                                    pygame.mouse.get_pos()[1] + tiled_level.position_offset[1]]

            # fake quick distance, just to check we are away from target
            click_x_dist = abs(self.world_click_pos[0] - self.player_world_target[0])
            click_y_dist = abs(self.world_click_pos[1] - self.player_world_target[1])
            click_target_dist = click_x_dist + click_y_dist

            if click_target_dist > 2.0:
                new_target = pygame.mouse.get_pos()
                x_dist = float(new_target[0]) - float(self.screen_position[0])
                y_dist = float(new_target[1]) - float(self.screen_position[1])
                distance = math.sqrt((x_dist * x_dist) + (y_dist * y_dist))
                if distance > 0.0:
                    self.player_move_target = new_target
                    self.player_world_target = [self.player_move_target[0] + tiled_level.position_offset[0],
                                                self.player_move_target[1] + tiled_level.position_offset[1]]
                    self.distance_to_move_target = distance
                    self.current_vector = [x_dist / self.distance_to_move_target, y_dist / self.distance_to_move_target]
                    direction_magnitude = math.sqrt(self.current_vector[0] ** 2 + self.current_vector[1] ** 2)
                    if direction_magnitude > 0.0:
                        unit_dir_vector = [self.current_vector[0] / direction_magnitude,
                                           self.current_vector[1] / direction_magnitude]
                        self.new_facing_angle = math.atan2(-unit_dir_vector[0], -unit_dir_vector[1]) * 180 / math.pi

        if self.distance_to_move_target > 0.0:
            self.speed += self.acceleration * time_delta
            if self.speed > self.max_speed:
                self.speed = self.max_speed

            self.position[0] += (self.current_vector[0] * time_delta * self.speed)
            self.position[1] += (self.current_vector[1] * time_delta * self.speed)
            self.move_accumulator += self.speed * time_delta
            self.distance_to_move_target -= self.speed * time_delta

            self.update_screen_position(tiled_level.position_offset)
            
            if not self.firing:
                y_pos = self.screen_position[1] + self.sprite_rot_centre_offset[1]
                if abs(self.move_accumulator) > 64.0:
                    self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand,
                                                         self.new_facing_angle)
                    self.rect = self.image.get_rect()
                    self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                      self.screen_position, -self.new_facing_angle)
                    self.move_accumulator = 0.0
                elif abs(self.move_accumulator) > 48.0:
                    self.image = pygame.transform.rotate(self.active_weapon.anim_set.step_left,
                                                         self.new_facing_angle)
                    self.rect = self.image.get_rect()
                    self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                      self.screen_position, -self.new_facing_angle)
                elif abs(self.move_accumulator) > 32.0:
                    self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand,
                                                         self.new_facing_angle)
                    self.rect = self.image.get_rect()
                    self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                      self.screen_position, -self.new_facing_angle)
                elif abs(self.move_accumulator) > 16.0:
                    self.image = pygame.transform.rotate(self.active_weapon.anim_set.step_right,
                                                         self.new_facing_angle)
                    self.rect = self.image.get_rect()
                    self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                      self.screen_position, -self.new_facing_angle)
                else:
                    self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand,
                                                         self.new_facing_angle)
                    self.rect = self.image.get_rect()
                    self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                      self.screen_position, -self.new_facing_angle)

        else:
            self.update_screen_position(tiled_level.position_offset)
            self.speed = 0.0
            if not self.firing:
                self.image = pygame.transform.rotate(self.active_weapon.anim_set.stand, self.new_facing_angle)
                self.rect = self.image.get_rect()
                y_pos = self.screen_position[1] + self.sprite_rot_centre_offset[1]
                self.rect.center = self.rot_point([self.screen_position[0], y_pos],
                                                  self.screen_position, -self.new_facing_angle)

        self.collision_circle.set_position(self.position)

        if self.should_die:
            self.flash_sprite.kill()
            self.kill()
            self.collision_grid.remove_shape_from_grid(self.collision_circle)

    def add_health(self, health):
        self.health += health
        if self.health > self.max_health:
            self.health = self.max_health

    def add_mana(self, mana):
        self.mana += mana
        if self.mana > self.max_mana:
            self.mana = self.max_mana

    def take_damage(self, damage):
        self.health -= damage
        if self.health < 0:
            self.health = 0
        self.should_flash_sprite = True

    @staticmethod
    def rot_point(point, axis, ang):
        """ Orbit. calculates the new loc for a point that rotates a given num of degrees around an axis point,
        +clockwise, -anticlockwise -> tuple x,y
        """
        ang -= 90
        x, y = point[0] - axis[0], point[1] - axis[1]
        radius = math.sqrt(x*x + y*y)  # get the distance between points

        r_ang = math.radians(ang)       # convert ang to radians.

        h = axis[0] + (radius * math.cos(r_ang))
        v = axis[1] + (radius * math.sin(r_ang))

        return [h, v]

    @staticmethod
    def lerp(a, b, c):
        return (c * b) + ((1.0 - c) * a)