class Asteroid(pygame.sprite.Sprite):

    containers = None
    image = None

    def __init__(self,
                 asteroid_name_: str,
                 gl_,
                 blend_: int = 0,
                 rotation_: int = 0,
                 scale_: int = 1,
                 timing_: int = 8,
                 layer_: int = 0):
        """

        :param asteroid_name_: strings, MirroredAsteroidClass name
        :param gl_: class GL (contains all the game constant)
        :param blend_: integer, Sprite blend effect, must be > 0
        :param rotation_: integer, Object rotation in degrees
        :param scale_: integer, Object scale value, default 1 -> no transformation. must be > 0
        :param timing_: integer; Refreshing time in milliseconds, must be > 0
        :param layer_: integer; Layer used. must be <= 0 (0 is the top layer)
        """
        """
        assert isinstance(asteroid_name_, str), \
            "Positional argument <asteroid_name_> is type %s , expecting string." % type(asteroid_name_)
        assert isinstance(blend_, int), \
            "Positional argument <blend_> is type %s , expecting integer." % type(blend_)
        if blend_ < 0:
            raise ValueError('Positional attribute blend_ must be > 0')
        assert isinstance(rotation_, (float, int)), \
            "Positional argument <rotation_> is type %s , expecting float or integer." % type(rotation_)
        assert isinstance(scale_, (float, int)), \
            "Positional argument <scale_> is type %s , expecting float or integer." % type(scale_)
        if scale_ < 0:
            raise ValueError('Positional attribute scale_ must be > 0')
        assert isinstance(timing_, int), \
            "Positional argument <timing_> is type %s , expecting integer." % type(timing_)
        if timing_ < 0:
            raise ValueError('Positional attribute timing_ must be >= 0')

        assert isinstance(layer_, int), \
            "Positional argument <layer_> is type %s , expecting integer." % type(layer_)
        if layer_ > 0:
            raise ValueError('Positional attribute layer_ must be <= 0')
        """
        self.layer = layer_

        pygame.sprite.Sprite.__init__(self, self.containers)

        # change sprite layer
        if isinstance(gl_.All, pygame.sprite.LayeredUpdates):
            gl_.All.change_layer(self, layer_)

        self.images_copy = Asteroid.image.copy()
        self.image = self.images_copy[0] if isinstance(
            Asteroid.image, list) else self.images_copy
        self.w, self.h = pygame.display.get_surface().get_size()

        self.appearance_frame = 0  # randint(100, 100240)  # When the asteroid start moving

        if asteroid_name_ == 'MAJOR_ASTEROID':
            self.speed = pygame.math.Vector2(0, 1)
            self.rect = self.image.get_rect(center=((self.w >> 1),
                                                    -self.image.get_height()))
            # MirroredAsteroidClass life points (proportional to its size)
            self.life = 5000
            self.damage = self.life * 2
        else:

            self.rect = self.image.get_rect(center=(randint(0, self.w),
                                                    -randint(0, self.h) -
                                                    self.image.get_height()))
            # self.rect = self.image.get_rect(center=(self.w // 2, - self.image.get_height()))

            self.speed = pygame.math.Vector2(0, uniform(
                +4, +8))  # * round(self.appearance_frame / 4000))

            if self.speed.length() == 0:
                self.speed = pygame.math.Vector2(0, 1)

            # MirroredAsteroidClass life points (proportional to its size)
            self.life = randint(self.rect.w,
                                max(self.rect.w, (self.rect.w >> 1) * 10))
            # Collision damage, how much life point
            # will be removed from players and transport in case of collision
            self.damage = self.life >> 1

        self.timing = timing_
        self.rotation = rotation_
        self.scale = scale_
        self.dt = 0
        self.gl = gl_
        self.asteroid_name = asteroid_name_
        self.blend = blend_
        # No need to pre-calculate the mask as all asteroid instanciation is
        # done before the main loop
        self.mask = pygame.mask.from_surface(self.image)

        # MirroredAsteroidClass value in case of destruction.
        self.points = self.life

        self.layer = layer_
        self.index = 0
        self.has_been_hit = False
        self.id_ = id(self)
        self.asteroid_object = Broadcast(self.make_object())
        self.impact_sound_object = Broadcast(self.make_sound_object('IMPACT'))

        Broadcast.add_object_id(self.id_)

    def delete_object(self) -> DeleteSpriteCommand:
        """
        Send a command to kill an object on client side.

        :return: DetectCollisionSprite object
        """
        return DeleteSpriteCommand(frame_=self.gl.FRAME,
                                   to_delete_={self.id_: self.asteroid_name})

    def make_sound_object(self, sound_name_: str) -> SoundAttr:
        """
        Create a network sound object

        :param sound_name_: string; represent the sound name e.g 'EXPLOSION_SOUND"
        :return: SoundAttr object
        """
        assert isinstance(sound_name_, str), \
            "Positional argument <sound_name_> is type %s , expecting string." % type(sound_name_)

        if sound_name_ not in globals():
            raise NameError('Sound %s is not define.' % sound_name_)

        return SoundAttr(frame_=self.gl.FRAME,
                         id_=self.id_,
                         sound_name_=sound_name_,
                         rect_=self.rect)

    def make_object(self) -> DetectCollisionSprite:
        """
        Create a network sprite object

        :return: DetectCollisionSprite object
        """
        return DetectCollisionSprite(frame_=self.gl.FRAME,
                                     id_=self.id_,
                                     surface_=self.asteroid_name,
                                     layer_=self.layer,
                                     blend_=self.blend,
                                     rect_=self.rect,
                                     rotation_=self.rotation,
                                     scale_=self.scale,
                                     damage_=self.damage,
                                     life_=self.life,
                                     points_=self.points)

    def create_gems(self, player_) -> None:
        """
        Create collectable gems after asteroid disintegration.
        :param player_: player causing asteroid explosion
        :return: None
        """
        if player_ is None:
            raise ValueError("Argument player_ cannot be none!.")
        if hasattr(player_, 'alive'):
            if player_.alive():
                number = randint(3, 15)
                for _ in range(number):
                    if _ < number:
                        MakeGems.inventory = set()
                    MakeGems(gl_=self.gl,
                             player_=player_,
                             object_=self,
                             ratio_=1.0,
                             timing_=8,
                             offset_=pygame.Rect(self.rect.centerx,
                                                 self.rect.centery,
                                                 randint(-100, 100),
                                                 randint(-100, 20)))

    def make_debris(self) -> None:
        """
        Create sprite debris (different sizes 32x32, 64x64 pixels)
        :return:None
        """

        if not globals().__contains__('MULT_ASTEROID_64'):
            raise NameError(
                "Texture MULT_ASTEROID_64 is missing!"
                "\nCheck file Texture.py for MULT_ASTEROID_64 assigment. ")

        if not globals().__contains__('MULT_ASTEROID_32'):
            raise NameError(
                "Texture MULT_ASTEROID_32 is missing!"
                "\nCheck file Texture.py for MULT_ASTEROID_32 assigment. ")

        size_x, size_y = self.image.get_size()

        if size_x > 128:
            aster = MULT_ASTEROID_64
            name = 'MULT_ASTEROID_64'

        else:
            aster = MULT_ASTEROID_32
            name = 'MULT_ASTEROID_32'

        length = len(aster) - 1
        if self.asteroid_name != 'MAJOR_ASTEROID':
            Debris.containers = self.gl.All, self.gl.ASTEROID
            for _ in range(8 if size_x > 255 else 6):
                element = randint(0, length)
                Debris.image = aster[element]
                Debris(asteroid_name_=name + '[' + str(element) + ']',
                       pos_=self.rect.center,
                       gl_=self.gl,
                       blend_=0,
                       timing_=16,
                       layer_=-2,
                       particles_=True)
        else:
            Debris.containers = self.gl.All, self.gl.ASTEROID
            aster = MULT_ASTEROID_64
            name = 'MULT_ASTEROID_64'
            length = len(aster) - 1
            for _ in range(10):
                element = randint(0, length)
                Debris.image = aster[element]
                Debris(asteroid_name_=name + '[' + str(element) + ']',
                       pos_=(self.rect.centerx + randint(-size_x, size_y),
                             self.rect.centery + randint(-size_x, size_y)),
                       gl_=self.gl,
                       blend_=0,
                       timing_=20,
                       layer_=randint(-2, 0),
                       particles_=False)

            aster = MULT_ASTEROID_32
            name = 'MULT_ASTEROID_32'
            length = len(aster) - 1
            for _ in range(10):
                element = randint(0, length)
                Debris.image = aster[element]
                Debris(asteroid_name_=name + '[' + str(element) + ']',
                       pos_=(self.rect.centerx + randint(-size_x, size_y),
                             self.rect.centery + randint(-size_x, size_y)),
                       gl_=self.gl,
                       blend_=0,
                       timing_=8,
                       layer_=randint(-2, 0),
                       particles_=False)

    def explode(self, player_) -> None:
        """
        Create an explosion sprite when asteroid life points < 1
        :param player_: Player causing asteroid explosion
        :return: None
        """

        if not globals().__contains__('EXPLOSION1'):
            raise NameError(
                "Texture EXPLOSION1 is missing!"
                "\nCheck file Texture.py for EXPLOSION1 assigment. ")
        # Create queue sprite
        Explosion.images = EXPLOSION1
        Explosion(self,
                  self.rect.center,
                  self.gl,
                  8,
                  0,
                  texture_name_='EXPLOSION1')  # self.layer)

        if not globals().__contains__('HALO_SPRITE12'):
            raise NameError(
                "Texture HALO_SPRITE12 is missing!"
                "\nCheck file Texture.py for HALO_SPRITE12 assigment. ")

        if not globals().__contains__('HALO_SPRITE14'):
            raise NameError(
                "Texture HALO_SPRITE14 is missing!"
                "\nCheck file Texture.py for HALO_SPRITE14 assigment. ")

        # Create Halo sprite
        AsteroidHalo.images = choice([HALO_SPRITE12, HALO_SPRITE14])
        AsteroidHalo.containers = self.gl.All
        AsteroidHalo(texture_name_='HALO_SPRITE12' if
                     AsteroidHalo.images is HALO_SPRITE12 else 'HALO_SPRITE14',
                     object_=self,
                     timing_=8)

        self.make_debris()
        if player_ is not None:
            self.create_gems(player_)
        self.quit()

    def hit(self, player_=None, damage_: int = 0) -> None:
        """
        Check asteroid life after laser collision.

        :param player_: Player instance
        :param damage_: integer; Damage received
        :return: None
        """
        assert isinstance(damage_, int), \
            "Positional argument <damage_> is type %s , expecting integer." % type(damage_)
        if damage_ < 0:
            raise ValueError('positional argument damage_ cannot be < 0')

        self.life -= damage_
        self.has_been_hit = True if self.asteroid_name != 'MAJOR_ASTEROID' else False

        if self.life < 1:
            if player_ is not None:
                if hasattr(player_, 'update_score'):
                    player_.update_score(self.points)
            self.explode(player_)

    def collide(self, player_=None, damage_: int = 0) -> None:
        """
        Check asteroid life after collision with players or transport

        :param player_: Player instance or transport
        :param damage_: integer; Damage received
        :return: None
        """
        assert isinstance(damage_, int), \
            "Positional argument <damage_> is type %s , expecting integer." % type(damage_)
        if damage_ < 0:
            raise ValueError('positional argument damage_ cannot be < 0')

        if not globals().__contains__('IMPACT1'):
            raise NameError("Sound IMPACT1 is missing!"
                            "\nCheck file Sounds.py for IMPACT1 assigment. ")
        if hasattr(self, 'life'):
            self.life -= damage_  # transfer damage to the asteroid (decrease life)
        else:
            raise AttributeError('self %s, %s does not have attribute life ' %
                                 (self, type(self)))

        # play asteroid burst sound locally
        self.gl.MIXER.play(sound_=IMPACT1,
                           loop_=False,
                           priority_=0,
                           volume_=1.0,
                           fade_out_ms=0,
                           panning_=True,
                           name_='IMPACT1',
                           x_=self.rect.centerx,
                           object_id_=id(IMPACT1),
                           screenrect_=self.gl.SCREENRECT)

        # broadcast asteroid burst sound
        self.impact_sound_object.play()

        # check if asteroid life is still > 0
        if self.life < 1:

            # check who is colliding with the asteroid
            # if not colliding with transport, transfer score to player.
            if not type(player_).__name__ == 'Transport':
                if player_ is not None:
                    # player has collide with asteroid, player1 or player 2 get the points
                    player_.update_score(self.points)
            else:
                # Transport does not get points
                ...
            # Split asteroid
            self.make_debris()
            self.quit()
        ...

    def quit(self) -> None:
        Broadcast.remove_object_id(self.id_)
        obj = Broadcast(self.delete_object())
        obj.queue()
        self.kill()

    def update(self) -> None:
        """
        Update asteroid sprites.

        :return: None
        """

        if self.gl.FRAME > self.appearance_frame:

            # start to move asteroid when frame number is over
            # self.appearance_frame (random frame number)
            self.rect.move_ip(self.speed)

            # asteroid is moving but not visible yet?
            # The rectangle bottom edge must be > 0 to start the code below
            if self.rect.midbottom[1] > 0:

                # Inside the 60 FPS Area
                if self.dt > self.timing:

                    # self.image = self.images_copy.copy()
                    if self.has_been_hit:
                        if not globals().__contains__('LAVA'):
                            raise NameError("Texture LAVA not available")
                        self.image.blit(LAVA[self.index % len(LAVA) - 1],
                                        (0, 0),
                                        special_flags=pygame.BLEND_RGB_ADD)
                        self.index += 1
                        self.has_been_hit = False

                    # if self.rotation != 0:
                    #    self.mask = pygame.mask.from_surface(self.image)

                    self.asteroid_object.update({
                        'frame': self.gl.FRAME,
                        'rect': self.rect,
                        'life': self.life
                    })
                    self.asteroid_object.queue()

                    self.dt = 0

                else:
                    self.dt += self.gl.TIME_PASSED_SECONDS

            if self.rect.midtop[1] > self.gl.SCREENRECT.h:
                self.quit()
class Explosion(pygame.sprite.Sprite):
    images = None
    containers = None

    def __init__(self,
                 parent_,
                 pos_,
                 gl_,
                 timing_,
                 layer_,
                 texture_name_,
                 mute_=False):

        self.layer = layer_
        pygame.sprite.Sprite.__init__(self, self.containers)
        if isinstance(gl_.All, pygame.sprite.LayeredUpdates):
            if layer_:
                gl_.All.change_layer(self, layer_)

        self.images_copy = Explosion.images.copy()
        self.image = self.images_copy[0] if isinstance(
            self.images_copy, list) else self.images_copy
        self.timing = timing_
        self.length = len(self.images) - 1
        self.pos = pos_
        self.gl = gl_
        self.position = pygame.math.Vector2(*self.pos)
        self.rect = self.image.get_rect(center=self.pos)
        self.dt = 0
        self.blend = pygame.BLEND_RGB_ADD
        self.parent = parent_
        self.index = 0
        self.id_ = id(self)
        self.texture_name = texture_name_
        self.mute = mute_
        # Create the network object
        self.explosion_object = Broadcast(self.make_object())
        # Create sound object
        self.explosion_sound_object = Broadcast(
            self.make_sound_object('EXPLOSION_SOUND'))

        Broadcast.add_object_id(self.id_)

    def delete_object(self) -> DeleteSpriteCommand:
        """
        Send a command to kill an object on client side.

        :return: DetectCollisionSprite object
        """
        return DeleteSpriteCommand(frame_=self.gl.FRAME,
                                   to_delete_={self.id_: self.texture_name})

    def play_explosion_sound(self) -> None:
        """
        Play the sound explosion locally and forward the sound object to the client(s).

        :return: None
        """

        # play the sound locally
        self.gl.MIXER.play(sound_=EXPLOSION_SOUND,
                           loop_=False,
                           priority_=0,
                           volume_=1.0,
                           fade_out_ms=0,
                           panning_=True,
                           name_='EXPLOSION_SOUND',
                           x_=self.rect.centerx,
                           object_id_=id(EXPLOSION_SOUND),
                           screenrect_=self.gl.SCREENRECT)
        # Add the sound object to the queue
        self.explosion_sound_object.play()

    def make_sound_object(self, sound_name_: str) -> SoundAttr:
        """
        Create a network sound object

        :param sound_name_: string; represent the sound name e.g 'EXPLOSION_SOUND"
        :return: SoundAttr object
        """
        assert isinstance(sound_name_, str), \
            "Positional argument <sound_name_> is type %s , expecting string." % type(sound_name_)

        if sound_name_ not in globals():
            raise NameError('Sound %s is not define.' % sound_name_)

        return SoundAttr(frame_=self.gl.FRAME,
                         id_=self.id_,
                         sound_name_=sound_name_,
                         rect_=self.rect)

    def make_object(self) -> AnimatedSprite:
        return AnimatedSprite(frame_=self.gl.FRAME,
                              id_=self.id_,
                              surface_=self.texture_name,
                              layer_=self.layer,
                              blend_=self.blend,
                              rect_=self.rect,
                              index_=self.index)

    def quit(self) -> None:
        Broadcast.remove_object_id(self.id_)
        obj = Broadcast(self.delete_object())
        obj.queue()
        self.kill()

    def update(self):

        if self.dt > self.timing:

            if self.rect.colliderect(self.gl.SCREENRECT):

                if self.index == 0 and not self.mute:
                    self.play_explosion_sound()

                self.image = self.images_copy[self.index]
                self.rect = self.image.get_rect(center=self.rect.center)
                self.index += 1

                if self.index > self.length:
                    self.quit()
                    return

                self.dt = 0

                self.explosion_object.update({
                    'frame': self.gl.FRAME,
                    'rect': self.rect,
                    'index': self.index,
                    'blend': self.blend
                })
                self.explosion_object.queue()

            else:
                self.quit()
                return
        else:
            self.dt += self.gl.TIME_PASSED_SECONDS
class Transport(pygame.sprite.Sprite):
    containers = None
    image = None

    def __init__(self, gl_, timing_, pos_, surface_name_, layer_=0):

        pygame.sprite.Sprite.__init__(self, self.containers)

        if isinstance(gl_.All, pygame.sprite.LayeredUpdates):
            if layer_:
                gl_.All.change_layer(self, layer_)

        self.image = Transport.image
        self.image_copy = self.image.copy()
        self.rect = self.image.get_rect(center=pos_)
        self.timing = timing_
        self.gl = gl_
        self.dt = 0
        self.fxdt = 0
        self.layer = layer_
        self.blend = 0
        self.previous_pos = pygame.math.Vector2()            # previous position
        self.max_life = 5000
        self.life = 5000                                     # MirroredTransportClass max hit points
        self.damage = 10000                                  # Damage transfer after collision
        self.mask = pygame.mask.from_surface(self.image)     # Image have to be convert_alpha compatible
        self.pos = pos_
        self.index = 0
        self.impact = False
        self.vertex_array = []
        self.engine = self.engine_on()
        self.surface_name = surface_name_
        self.id_ = id(self)
        self.transport_object = Broadcast(self.make_object())
        self.impact_sound_object = Broadcast(self.make_sound_object('IMPACT'))
        half = self.gl.SCREENRECT.w >> 1
        self.safe_zone = pygame.Rect(half - 200, half, 400, self.gl.SCREENRECT.bottom - half)
        self.half_life = (self.max_life >> 1)

        Broadcast.add_object_id(self.id_)   # this is now obsolete since it is done from main loop

    def delete_object(self) -> DeleteSpriteCommand:
        """
        Send a command to kill an object on client side.

        :return: DetectCollisionSprite object
        """
        return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.surface_name})

    def make_sound_object(self, sound_name_: str) -> SoundAttr:
        return SoundAttr(frame_=self.gl.FRAME, id_=self.id_, sound_name_=sound_name_, rect_=self.rect)
    
    def make_object(self) -> StaticSprite:
        return StaticSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.surface_name,
                            layer_=self.layer, blend_=self.blend, rect_=self.rect,
                            damage=self.damage, life=self.life, impact=self.impact)

    def engine_on(self) -> AfterBurner:
        AfterBurner.images = EXHAUST2
        calc_pos = (self.pos[0] - 400, self.pos[1] - 205)   # Top left corner position
        return AfterBurner(self, self.gl,
                           calc_pos, 8, pygame.BLEND_RGB_ADD,
                           self.layer - 1, texture_name_='EXHAUST2')

    def player_lost(self) -> None:
        PlayerLost.containers = self.gl.All
        PlayerLost.DIALOGBOX_READOUT_RED = DIALOGBOX_READOUT_RED
        PlayerLost.SKULL = SKULL
        font = freetype.Font('Assets\\Fonts\\Gtek Technology.ttf', size=14)
        PlayerLost(gl_=self.gl, font_=font, image_=FINAL_MISSION, layer_=0)
        # todo kill player 1 and 2 game is over

    def explode(self):
        Explosion.images = EXPLOSION2
        for i in range(10):
            Explosion(self, (self.rect.centerx + randint(-400, 400),
                             self.rect.centery + randint(-400, 400)),
                      self.gl, 8, self.layer,
                      texture_name_='EXPLOSION2', mute_=False if i > 0 else True)

        PlayerHalo.images = HALO_SPRITE13
        PlayerHalo.containers = self.gl.All
        PlayerHalo(texture_name_='HALO_SPRITE13', object_=self, timing_=8)
        self.quit()

    def collide(self, damage_: int)-> None:
        """
        Asteroid collide with object (e.g Asteroids)
        :param damage_: int; damage transfer to the transport (damage must be positive)
        :return: None
        """
        assert isinstance(damage_, int), \
            'Positional arguement damage_, expecting int type got %s ' % type(damage_)
        if self.alive():
            if damage_ is None or damage_ < 0:
                raise ValueError('positional arguement damage_ cannot be None or < 0.')
            self.impact = True      # variable used for blending, (electric effect on the transport's hull )
            self.index = 0
            self.life -= damage_    # Transport life decrease
            # Play an impact sound locally
            self.gl.MIXER.play(sound_=IMPACT1, loop_=False, priority_=0,
                               volume_=1.0, fade_out_ms=0, panning_=True,
                               name_='IMPACT1', x_=self.rect.centerx,
                               object_id_=id(IMPACT1),
                               screenrect_=self.gl.SCREENRECT)
            # Play impact sound on client computer.
            self.impact_sound_object.play()
        else:
            self.quit()
            
    def hit(self, damage_):
        if self.alive():
            self.life -= damage_
        else:
            self.quit()

    def get_centre(self) -> tuple:
        return self.rect.center

    def display_fire_particle_fx(self) -> None:
        # Display fire particles when the player has taken bad hits
        # Use the additive blend mode.
        if self.fxdt > self.timing:
            for p_ in self.vertex_array:

                # queue the particle in the vector direction
                p_.rect.move_ip(p_.vector)
                p_.image = p_.images[p_.index]
                if p_.index > len(p_.images) - 2:
                    p_.kill()
                    self.vertex_array.remove(p_)

                p_.index += 1
            self.fxdt = 0
        else:
            self.fxdt += self.gl.TIME_PASSED_SECONDS

    def fire_particles_fx(self,
                          position_,  # particle starting location (tuple or pygame.math.Vector2)
                          vector_,    # particle speed, pygame.math.Vector2
                          images_,    # surface used for the particle, (list of pygame.Surface)
                          layer_=0,   # Layer used to display the particles (int)
                          blend_=pygame.BLEND_RGB_ADD  # Blend mode (int)
                          ) -> None:
        # Create fire particles around the aircraft hull when player is taking serious damages

        # Cap the number of particles to avoid lag
        # if len(self.gl.FIRE_PARTICLES_FX) > 100:
        #    return
        # Create fire particles when the aircraft is disintegrating
        sprite_ = pygame.sprite.Sprite()
        self.gl.All.add(sprite_)
        # self.gl.FIRE_PARTICLES_FX.add(sprite__)
        # assign the particle to a specific layer
        if isinstance(self.gl.All, pygame.sprite.LayeredUpdates):
            self.gl.All.change_layer(sprite_, layer_)
        sprite_.layer = layer_
        sprite_.blend = blend_  # use the additive mode
        sprite_.images = images_
        sprite_.image = images_[0]
        sprite_.rect = sprite_.image.get_rect(center=position_)
        sprite_.vector = vector_  # vector
        sprite_.index = 0
        # assign update method to self.display_fire_particle_fx
        # (local method to display the particles)
        sprite_.update = self.display_fire_particle_fx
        self.vertex_array.append(sprite_)

    def quit(self) -> None:
        Broadcast.remove_object_id(self.id_)
        obj = Broadcast(self.delete_object())
        obj.queue()
        self.kill()

    def update(self):

        self.rect.clamp_ip(self.safe_zone)

        self.image = self.image_copy.copy()

        # in the 16ms area (60 FPS)
        if self.dt > self.timing:

            if self.life < self.half_life:
                position = pygame.math.Vector2(randint(-50, 50), randint(-100, 100))
                self.fire_particles_fx(position_=position + pygame.math.Vector2(self.rect.center),
                                       vector_=pygame.math.Vector2(uniform(-1, 1), uniform(+1, +3)),
                                       images_=FIRE_PARTICLES,
                                       layer_=0, blend_=pygame.BLEND_RGB_ADD)
            if self.life < 1:
                self.explode()
                return

            if self.previous_pos == self.rect.center:
                self.rect.centerx += randint(-1, 1)
                self.rect.centery += randint(-1, 1)

            if self.gl.FRAME < 100:
                self.rect.centery -= 3

            self.previous_pos = self.rect.center
            self.engine.update()

            self.transport_object.update(
                {'frame': self.gl.FRAME, 'rect': self.rect,
                 'damage': self.damage, 'life': self.life, 'impact': self.impact})
            # Broadcast the spaceship position
            self.transport_object.queue()
            self.dt = 0

        else:
            self.dt += self.gl.TIME_PASSED_SECONDS

        # outside the 60 FPS area.
        # Below code processed every frames.
        if self.impact:
            self.image.blit(DISRUPTION_ORG[self.index % len(DISRUPTION_ORG) - 1],
                            (0, 0), special_flags=pygame.BLEND_RGB_ADD)
            self.index += 1
            if self.index > len(DISRUPTION_ORG) - 2:
                self.impact = False
                self.index = 0