import pygame
import random


class Jeu():
    '''
    Gère les éléments principaux du jeu.
    
    Attributs:
        assets: dictionnaire avec toutes les ressources du jeu
        screen: la fenêtre PyGame
        width: largeur de l'écran en px
        height: hauteur de l'écran en px
        fps: le nombre maximum d'images par seconde
        scene: le menu / écran à afficher
        bg_stop: booléen qui arrête l'actualisation du fond d'écran

    Méthodes:
        background: retourne la ressource du fond d'écran à afficher
        show_text: affiche un texte avec des paramètres précis
        home: affiche les composants de la page d'accueil
        levels: affiche les composants de la page de sélection des niveaux
        campaign: lance une partie en mode campagne
        free: lance une partie en mode libre
        interface: affiche les composants pour les pouvoirs du joueur en jeu
        world_free: gestion des ennemis dans le mode libre
        world_campaign: gestion des ennemis et des boss dans le mode campagne
        world_transition: gestion de l'animation de transition entre les mondes du mode campagne
        ingame_menu: affiche les composants pour le menu de pause en jeu
        ending: gestion de la fin et du générique du mode campagne
    '''
    def __init__(self, screen, width, height, fps):
        '''
        Constructeur des paramètres du jeu et des ressources pour PyGame.

        Args:
            screen: la fenêtre PyGame
            width: largeur de l'écran en px
            height: hauteur de l'écran en px
            fps: le nombre maximum d'images par seconde
        '''
        # Assets
        self.assets = {
            # Background
            'bg_home': pygame.image.load('assets/backgrounds/home.jpeg').convert(),
            'bg_free': pygame.image.load('assets/backgrounds/free.jpeg').convert(),
            'bg_world_1': pygame.image.load('assets/backgrounds/world_1.jpeg').convert(),
            'bg_world_2': pygame.image.load('assets/backgrounds/world_2.jpeg').convert(),
            'bg_world_3': pygame.image.load('assets/backgrounds/world_3.jpeg').convert(),
            'bg_world_4': pygame.image.load('assets/backgrounds/world_4.jpeg').convert(),
            'bg_world_5': pygame.image.load('assets/backgrounds/world_5.jpeg').convert(),
            'bg_world_6': pygame.image.load('assets/backgrounds/world_6.jpeg').convert(),
            'bg_world_7': pygame.image.load('assets/backgrounds/world_7.jpeg').convert(),
            'bg_world_8': pygame.image.load('assets/backgrounds/world_8.jpeg').convert(),
            # UI
            'ui_logo': pygame.image.load('assets/ui/logo.png').convert_alpha(),
            'ui_tornado': pygame.image.load('assets/ui/tornado.png').convert_alpha(),
            'ui_teleportation': pygame.image.load('assets/ui/teleportation.png').convert_alpha(),
            'ui_heal': pygame.image.load('assets/ui/heal.png').convert_alpha(),
            'ui_wave': pygame.image.load('assets/ui/wave.png').convert_alpha(),
            'ui_jammer': pygame.image.load('assets/ui/jammer.png').convert_alpha(),
            'ui_laser': pygame.image.load('assets/ui/laser.png').convert_alpha(),
            # Player
            'player_vaisseau': pygame.image.load('assets/player/vaisseau.png').convert_alpha(),
            'player_vaisseau_jammer': pygame.image.load('assets/player/vaisseau_jammer.png').convert_alpha(),
            # Entities
            'entities_balle': pygame.image.load('assets/entities/balle.png').convert_alpha(),
            'entities_balle_enemy': pygame.image.load('assets/entities/balle_enemy.png').convert_alpha(),
            'entities_cannon': pygame.image.load('assets/entities/cannon.png').convert_alpha(),
            'entities_destruction': pygame.image.load('assets/entities/destruction.png').convert_alpha(),
            'entities_tornado': pygame.image.load('assets/entities/tornado.png').convert_alpha(),
            'entities_teleportation': pygame.image.load('assets/entities/teleportation.png').convert_alpha(),
            'entities_shock_wave': pygame.image.load('assets/entities/shock_wave.png').convert_alpha(),
            'entities_laser': pygame.image.load('assets/entities/laser.png').convert_alpha(),
            # Enemies
            'enemies_healing_ally_ship': pygame.image.load('assets/enemies/healing_ally_ship.png').convert_alpha(),
            'enemies_combat_ship': pygame.image.load('assets/enemies/combat_ship.png').convert_alpha(),
            'enemies_double_ship': pygame.image.load('assets/enemies/double_ship.png').convert_alpha(),
            'enemies_teleport_ship': pygame.image.load('assets/enemies/teleport_ship.png').convert_alpha(),
            'enemies_healer_ship': pygame.image.load('assets/enemies/healer_ship.png').convert_alpha(),
            'enemies_healing_ship': pygame.image.load('assets/enemies/healing_ship.png').convert_alpha(),
            'enemies_cannon_ship': pygame.image.load('assets/enemies/cannon_ship.png').convert_alpha(),
            'enemies_jammer_ship': pygame.image.load('assets/enemies/jammer_ship.png').convert_alpha(),
            'enemies_jammer_ship_jammer': pygame.image.load('assets/enemies/jammer_ship_jammer.png').convert_alpha(),
            'enemies_sniper_ship': pygame.image.load('assets/enemies/sniper_ship.png').convert_alpha(),
            # Boss
            'bosses_vortex_stellarien': pygame.image.load('assets/bosses/vortex_stellarien.png').convert_alpha(),
            'bosses_nova_etherienne': pygame.image.load('assets/bosses/nova_etherienne.png').convert_alpha(),
            'bosses_serenite_astrale': pygame.image.load('assets/bosses/serenite_astrale.png').convert_alpha(),
            'bosses_tempete_ondulaire': pygame.image.load('assets/bosses/tempete_ondulaire.png').convert_alpha(),
            'bosses_nexus_anomalique': pygame.image.load('assets/bosses/nexus_anomalique.png').convert_alpha(),
            'bosses_nexus_anomalique_jammer': pygame.image.load('assets/bosses/nexus_anomalique_jammer.png').convert_alpha(),
            'bosses_apogee_celeste': pygame.image.load('assets/bosses/apogee_celeste.png').convert_alpha(),
            'bosses_final': pygame.image.load('assets/bosses/final.png').convert_alpha(),
            'bosses_final_jammer': pygame.image.load('assets/bosses/final_jammer.png').convert_alpha(),
            'bosses_end': pygame.image.load('assets/bosses/end.png').convert_alpha(),
            'bosses_end_jammer': pygame.image.load('assets/bosses/end_jammer.png').convert_alpha(),
            # Musics
            'musics_main_menu': 'assets/musics/main_menu.mp3',
            'musics_ambiance': 'assets/musics/ambiance.mp3',
            'musics_world_1_boss': 'assets/musics/world_1_boss.mp3',
            'musics_world_2_boss': 'assets/musics/world_2_boss.mp3',
            'musics_world_3_boss': 'assets/musics/world_3_boss.mp3',
            'musics_world_4_boss': 'assets/musics/world_4_boss.mp3',
            'musics_world_5_boss': 'assets/musics/world_5_boss.mp3',
            'musics_world_6_boss': 'assets/musics/world_6_boss.mp3',
            'musics_world_7_boss': 'assets/musics/world_7_boss.mp3',
            'musics_world_8_boss': 'assets/musics/world_8_boss.mp3',
            'musics_end': 'assets/musics/end.mp3',
            # Sounds
            'sounds_balle': 'assets/sounds/balle.mp3',
            'sounds_cannon': 'assets/sounds/cannon.mp3',
            'sounds_death': 'assets/sounds/death.mp3',
            'sounds_laser': 'assets/sounds/laser.mp3',
            'sounds_sniper': 'assets/sounds/sniper.mp3',
            'sounds_teleport': 'assets/sounds/teleport.mp3',
            'sounds_tornado': 'assets/sounds/tornado.mp3',
            'sounds_wave': 'assets/sounds/wave.mp3',
        }
        # Paramètres de PyGame
        self.screen = screen
        self.width = width
        self.height = height
        self.fps = fps
        # Paramètres du jeu
        self.scene = 'home'
        self.bg_stop = False

    def background(self):
        '''Retourne la ressource du fond d'écran à afficher.'''
        if self.scene in ['home', 'levels']:
            return pygame.transform.scale(self.assets['bg_home'], (self.height*16/9, self.height))
        elif self.scene in ['free', 'free_menu']:
            return pygame.transform.scale(self.assets['bg_free'], (self.height*16/9, self.height))
        elif self.scene in ['campaign', 'transition', 'campaign_menu', 'ending']:
            return pygame.transform.scale(self.assets[f'bg_world_{self.world}'], (self.height*16/9, self.height))

    def show_text(self, texte, position, taille=36, color=(255, 255, 255)):
        '''
        Affiche un texte avec des paramètres précis.

        Args:
            texte: chaîne de characètre à afficher
            position: tutle avec les coordonées x et y
            taille: un entier
            color: tutle sous le format rgb
        '''
        self.screen.blit(pygame.font.Font(None, taille).render(texte, True, color), position)

    def home(self):
        '''Affiche les composants de la page d'accueil.'''
        # Logo
        self.screen.blit(self.assets['ui_logo'], (7.5 * self.width / 100, 10 * self.height / 100))
        # Menu
        self.show_text('Lancer une campagne', (10 * self.width / 100, 45 * self.height / 100))
        self.show_text('Choix du niveau', (10 * self.width / 100, 55 * self.height / 100))
        self.show_text('Mode Libre', (10 * self.width / 100, 65 * self.height / 100))
        self.show_text('Quitter', (10 * self.width / 100, 75 * self.height / 100))
        # Copyright
        self.show_text('© Benji Corp.', (self.width - 130, self.height - 30), 26)
        # Music
        if pygame.mixer.music.get_busy() is False:
            pygame.mixer.music.load(self.assets[f'musics_main_menu'])
            pygame.mixer.music.play(-1, 0, 1500)

    def levels(self):
        '''Affiche les composants de la page de sélection des niveaux.'''
        # Boîte
        rect = pygame.Rect(100, 100, self.width - 200, self.height - 200)
        pygame.draw.rect(self.screen, (0, 0, 0, 0.76), rect, border_radius=12)
        # Retour Accueil
        self.show_text('Retour', (120, 120), 28)
        # Title
        self.show_text('Choix du niveau', (self.width / 3 + 35, self.height / 3 - 60))
        ### Niveaux
        taille_image = 70
        ## Ligne 1
        # Monde 1
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_1'], (taille_image*16/9, taille_image)), (140, 200))
        self.show_text('Monde 1', (170, 285), 22)
        # Monde 2
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_2'], (taille_image*16/9, taille_image)), (270, 200))
        self.show_text('Monde 2', (300, 285), 22)
        # Monde 3
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_3'], (taille_image*16/9, taille_image)), (400, 200))
        self.show_text('Monde 3', (430, 285), 22)
        # Monde 4
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_4'], (taille_image*16/9, taille_image)), (530, 200))
        self.show_text('Monde 4', (560, 285), 22)
        ## Ligne 2
        # Monde 5
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_5'], (taille_image*16/9, taille_image)), (140, 330))
        self.show_text('Monde 5', (170, 415), 22)
        # Monde 6
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_6'], (taille_image*16/9, taille_image)), (270, 330))
        self.show_text('Monde 6', (300, 415), 22)
        # Monde 7
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_7'], (taille_image*16/9, taille_image)), (400, 330))
        self.show_text('Monde 7', (430, 415), 22)
        # Monde 8
        self.screen.blit(pygame.transform.scale(self.assets[f'bg_world_8'], (taille_image*16/9, taille_image)), (530, 330))
        self.show_text('Monde 8', (560, 415), 22)

    def campaign(self, world=1):
        '''
        Lance une partie en mode campagne.

        Args:
            world: entier définissant le monde / niveau dans lequel commencer
        '''
        # Reset
        Boss.boss = None
        self.bg_stop = False
        Effect.listeEffects = []
        Ennemi.listeEnnemis = []
        Ennemi.spawn = 0
        # Création du joueur
        self.player = Joueur(self)
        # Creation de la balle
        self.tir = Balle(self.player, self)
        # Transition Monde
        self.transition_step = 0
        self.transition_duration = 5 * self.fps
        self.transition_time = 0
        # Générique de fin
        self.ending_step = 0
        self.ending_duration = 60 * self.fps
        self.ending_time = 0
        # Changement de l'état du jeu
        self.world = world
        self.scene = 'campaign'
        pygame.mixer.music.load(self.assets[f'musics_ambiance'])
        pygame.mixer.music.play(-1, 0, 1500)

    def free(self):
        '''Lance une partie en mode libre.'''
        # Reset
        self.bg_stop = False
        Effect.listeEffects = []
        Ennemi.listeEnnemis = []
        # Création du joueur
        self.player = Joueur(self)
        # Creation de la balle
        self.tir = Balle(self.player, self)
        # Changement de l'état du jeu
        self.world = 9
        self.scene = 'free'
        pygame.mixer.music.load(self.assets[f'musics_ambiance'])
        pygame.mixer.music.play(-1, 0, 1500)

    def interface(self):
        '''Affiche les composants pour les pouvoirs du joueur en jeu.'''
        if self.scene in ['campaign', 'free']:
            # Pouvoir Tornade
            if self.world >= 2:
                self.screen.blit(self.assets['ui_tornado'], [10, self.height//2 - 100])
                self.show_text('Z' if self.player.tornado.etat == 'chargee' else f'{int((self.player.tornado.cooldown_duration - self.player.tornado.cooldown_time) / self.fps)}', (45, self.height // 2 - 91), 25, (249, 246, 183))
            # Pouvoir Téléportation
            if self.world >= 3:
                self.screen.blit(self.assets['ui_teleportation'], [10, self.height//2 - 65])
                self.show_text('E' if self.player.teleportation.etat == 'chargee' else f'{int((self.player.teleportation.cooldown_duration - self.player.teleportation.cooldown_time) / self.fps)}', (45, self.height // 2 - 56), 25, (249, 246, 183))
            # Pouvoir Heal
            if self.world >= 4:
                self.screen.blit(self.assets['ui_heal'], [10, self.height//2 - 30])
                self.show_text('Q' if self.player.heal.etat == 'chargee' else f'{int((self.player.heal.cooldown_duration - self.player.heal.cooldown_time) / self.fps)}', (45, self.height // 2 - 21), 25, (249, 246, 183))
            # Pouvoir Wave
            if self.world >= 5:
                self.screen.blit(self.assets['ui_wave'], [10, self.height//2 + 5])
                self.show_text('S' if self.player.wave.etat == 'chargee' else f'{int((self.player.wave.cooldown_duration - self.player.wave.cooldown_time) / self.fps)}', (45, self.height // 2 + 14), 25, (249, 246, 183))
            # Pouvoir Jammer
            if self.world >= 6:
                self.screen.blit(self.assets['ui_jammer'], [10, self.height//2 + 40])
                self.show_text('D' if self.player.jammer.etat == 'chargee' else f'{int((self.player.jammer.cooldown_duration - self.player.jammer.cooldown_time) / self.fps)}', (45, self.height // 2 + 49), 25, (249, 246, 183))
            # Pouvoir Laser
            if self.world >= 7:
                self.screen.blit(self.assets['ui_laser'], [10, self.height//2 + 75])
                self.show_text('F' if self.player.laser.etat == 'chargee' else f'{int((self.player.laser.cooldown_duration - self.player.laser.cooldown_time) / self.fps)}', (45, self.height // 2 + 84), 25, (249, 246, 183))

    def world_free(self):
        '''Gestion des ennemis dans le mode libre.'''
        if len(Ennemi.listeEnnemis) < Ennemi.world[f'free']['max_per_screen']:
            # Apparition des ennemis
            vaisseau = Ennemi(Ennemi.world[f'world_{random.randint(1, 6)}'][f'type_{random.randint(1, 2)}'], self)
            Ennemi.listeEnnemis.append(vaisseau)
    
    def world_campaign(self):
        '''Gestion des ennemis et des boss dans le mode campagne.'''
        if 1 <= self.world <= 6:
            # Gestion des ennemis et boss
            if len(Ennemi.listeEnnemis) < Ennemi.world[f'world_{self.world}']['max_per_screen']:
                # Apparition des ennemis
                if Ennemi.spawn < Ennemi.world[f'world_{self.world}']['number']:
                    vaisseau = Ennemi(Ennemi.world[f'world_{self.world}'][f'type_{random.randint(1, 2)}'], self)
                    Ennemi.listeEnnemis.append(vaisseau)
                    Ennemi.spawn += 1
                # Apparition du boss
                elif (Boss.boss == None) and len(Ennemi.listeEnnemis) == 0:
                    Boss.boss = Boss(self, Boss.type[f'world_{self.world}'])
                    pygame.mixer.music.load(self.assets[f'musics_world_{self.world}_boss'])
                    pygame.mixer.music.play(-1, 0, 3000)
                # Mort du boss
                elif Boss.boss is not None:
                    if Boss.boss.vie <= 0:
                        Effect.listeEffects = []
                        Ennemi.listeEnnemis = []
                        Ennemi.spawn = 0
                        Boss.boss = None
                        pygame.mixer.music.load(self.assets[f'musics_ambiance'])
                        pygame.mixer.music.play(-1, 0, 1500)
                        self.scene = 'transition'
        elif self.world == 7:
            # Premier boss de fin
            if Boss.boss == None:
                Boss.boss = Boss(self, Boss.type[f'world_{self.world}_1'])
                pygame.mixer.music.load(self.assets['musics_world_7_boss'])
                pygame.mixer.music.play(-1, 0, 3000)
            elif Boss.boss is not None:
                # Deuxième boss de fin
                if (Boss.boss.vie <= 0) and (Boss.boss.name == 'Voile de l\'Abysse'):
                    Effect.listeEffects = []
                    Boss.boss = Boss(self, Boss.type[f'world_{self.world}_2'])
                # Troisième boss de fin
                elif (Boss.boss.vie <= 0) and (Boss.boss.name == 'Nébuleuse Éternelle'):
                    Effect.listeEffects = []
                    Boss.boss = Boss(self, Boss.type[f'world_{self.world}_3'])
                # Fin de la campagne
                elif (Boss.boss.vie <= 0) and (Boss.boss.name == 'Éclipse Primordiale'):
                    Effect.listeEffects = []
                    Boss.boss = None
                    pygame.mixer.music.load(self.assets[f'musics_ambiance'])
                    pygame.mixer.music.play(-1, 0, 1500)
                    self.scene = 'transition'
        elif self.world == 8:
            if Boss.boss == None:
                Boss.boss = Boss(self, Boss.type[f'world_{self.world}'])
                pygame.mixer.music.load(self.assets['musics_world_8_boss'])
                pygame.mixer.music.play(-1, 0, 3000)
            elif (Boss.boss.vie <= 0):
                Boss.boss = None
                self.bg_stop = False
                Effect.listeEffects = []
                self.scene = 'ending'

    def world_transition(self):
        '''Gestion de l'animation de transition entre les mondes du mode campagne.'''
        # Faire sortir le joueur par le haut de l'écran
        if self.transition_step == 0:
            self.screen.blit(self.background(), (0, 0))
            self.screen.blit(self.player.image, [self.player.position, self.player.hauteur])
            # Si le joueur est au milieu de l'écran
            if self.width / 2 - 33 <= self.player.position <= self.width / 2 - 31:
                self.player.position = self.width / 2 - 32
                self.player.hauteur -= self.player.vitesse
            ## Placer le joueur au milieu de l'écran
            # Si le joueur est à gauche de l'écran
            elif self.player.position <= self.width / 2 - 32:
                self.player.position += self.player.vitesse
            # Si le jouer est à droite de l'écran
            else:
                self.player.position -= self.player.vitesse
            if self.player.hauteur <= -100:
                self.transition_step +=1
        # Animation des mondes battus
        if self.transition_step == 1:
            self.transition_time += 1
            for i in range(7):
                if self.transition_time >= (i + 1) * (self.transition_duration / 7):
                    if i + 1 <= self.world:
                        world_image = pygame.transform.scale(self.assets[f'bg_world_{i + 1}'], (self.height*16/9, self.height))
                        self.screen.blit(world_image, (int(i * (self.width / 7)), 0), (0, 0, int(self.width / 7) + 1, self.height))
                    else:
                        rect = pygame.Rect(int(i * (self.width / 7)), 0, int(self.width / 7) + 1, self.height)
                        pygame.draw.rect(self.screen, (0, 0, 0), rect)
            if self.transition_time >= self.transition_duration + 1 * self.fps:
                self.transition_step +=1
        # Passer au monde suivant & positionner le joueur
        elif self.transition_step == 2:
            self.world += 1
            self.screen.blit(self.background(), (0, 0))
            self.player.position = self.width / 2 - 32
            self.player.hauteur = self.height + 64
            self.transition_step +=1
        # Faire entrer le joueur par le bas de l'écran
        elif self.transition_step == 3:
            self.screen.blit(self.background(), (0, 0))
            self.screen.blit(self.player.image, [self.player.position, self.player.hauteur])
            if self.player.hauteur - self.player.vitesse <= self.height - 100:
                self.player.hauteur = self.height - 100
                self.transition_step += 1
            else:
                self.player.hauteur -= self.player.vitesse
        # Présentation nouveau pouvoir
        elif self.transition_step == 4:
            if self.world <= 7:
                rect = pygame.Rect(self.width / 4, self.height / 4 - 20, self.width / 2, self.height / 2)
                pygame.draw.rect(self.screen, (0, 0, 0, 0.76), rect, border_radius=12)
                self.show_text('Vous avez obtenu :', (self.width / 3 + 20, self.height / 3 - 50))
                # Apparences du titre du pouvoir
                title_hauteur = self.height / 3 + 5
                title_size = 30
                title_color = (246, 83, 83)
                # Apparences de la description du pouvoir
                description_hauteur = self.height / 3 + 60
                description_size = 25
                # Vortex Stellarien (Tornado)
                if self.world == 2:
                    self.show_text('Vortex Stellarien', (self.width / 3 + 50, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'Z' pour lancer une tornade qui", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("repousse vos adversaires vers le haut de", (self.width / 4 + 15, description_hauteur + 30), description_size)
                    self.show_text("l'écran.", (self.width / 4 + 15, description_hauteur + 60), description_size)
                # Nova Étherienne (Teleportation)
                elif self.world == 3:
                    self.show_text('Nova Étherienne', (self.width / 3 + 55, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'E' pour vous téléporter à ", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("l'opposé de l'écran.", (self.width / 4 + 15, description_hauteur + 30), description_size)
                # Sérénité Astrale (Heal)
                elif self.world == 4:
                    self.show_text('Sérénité Astrale', (self.width / 3 + 52, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'Q' pour invoquer un vaisseau", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("qui vous protège des balles ennemies tout", (self.width / 4 + 15, description_hauteur + 30), description_size)
                    self.show_text("en vous redonnant de la vie.", (self.width / 4 + 15, description_hauteur + 60), description_size)
                # Tempête Ondulaire (Wave)
                elif self.world == 5:
                    self.show_text('Tempête Ondulaire', (self.width / 3 + 40, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'S' pour envoyer une charge", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("d'onde infligeant des dégâts importants", (self.width / 4 + 15, description_hauteur + 30), description_size)
                    self.show_text("sur son chemin.", (self.width / 4 + 15, description_hauteur + 60), description_size)
                # Nexus Anomalique (Jammer)
                elif self.world == 6:
                    self.show_text('Nexus Anomalique', (self.width / 3 + 40, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'D' pour rendre votre vaisseau", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("invisible auprès de vos ennemis.", (self.width / 4 + 15, description_hauteur + 30), description_size)
                # Apogée Céleste (Laser)
                elif self.world == 7:
                    self.show_text('Apogée Céleste', (self.width / 3 + 58, title_hauteur), title_size, title_color)
                    self.show_text("Appuyez sur 'F' pour activer un rayon", (self.width / 4 + 15, description_hauteur), description_size)
                    self.show_text("laser infligeant des dégâts importants", (self.width / 4 + 15, description_hauteur + 30), description_size)
                    self.show_text("sur toute la hauteur de l'écran.", (self.width / 4 + 15, description_hauteur + 60), description_size)
                # Bouton continuer
                rect = pygame.Rect(self.width / 2 - 60, self.height * 0.63, 120, 35)
                pygame.draw.rect(self.screen, (255, 255, 255), rect, border_radius=12)
                self.show_text('Continuer', (self.width / 2 - 42, self.height * 0.645), 25, (0, 0, 0))
            else:
                self.transition_step += 1
        # Fin de la transition
        elif self.transition_step == 5:
            self.transition_time = 0
            self.transition_step = 0
            self.scene = 'campaign'

    def ingame_menu(self):
        '''Affiche les composants pour le menu de pause en jeu.'''
        if self.scene == 'campaign':
            self.scene = 'campaign_menu'
        elif self.scene == 'free':
            self.scene = 'free_menu'
        self.bg_stop = False
        rect = pygame.Rect(self.width / 4, self.height / 4 - 20, self.width / 2, self.height / 2)
        pygame.draw.rect(self.screen, (0, 0, 0, 0.76), rect, border_radius=12)
        self.show_text('Reprendre', (self.width / 2 - 50, self.height / 2 - 120), 30)
        self.show_text('Accueil', (self.width / 2 - 40, self.height / 2 - 30), 30)
        self.show_text('Quitter', (self.width / 2 - 40, self.height / 2 + 60), 30)

    def ending(self):
        '''Gestion de la fin et du générique du mode campagne.'''
        # Début du générique
        if self.ending_step == 0:
            self.screen.blit(self.background(), (0, 0))
            self.screen.blit(self.player.image, [self.player.position, self.player.hauteur])
            # Si le joueur est au milieu de l'écran
            if self.width / 2 - 33 <= self.player.position <= self.width / 2 - 31:
                self.player.position = self.width / 2 - 32
                self.player.hauteur -= self.player.vitesse
            ## Placer le joueur au milieu de l'écran
            # Si le joueur est à gauche de l'écran
            elif self.player.position <= self.width / 2 - 32:
                self.player.position += self.player.vitesse
            # Si le jouer est à droite de l'écran
            else:
                self.player.position -= self.player.vitesse
            if self.player.hauteur <= -100:
                self.ending_step +=1
        if self.ending_step == 1:
            pygame.mixer.music.fadeout(1500)
            pygame.mixer.music.load(self.assets['musics_end'])
            pygame.mixer.music.play(-1, 0, 3000)
            self.ending_step += 1
        # Crédits
        elif self.ending_step == 2:
            # Fond noir
            rect = pygame.Rect(0, 0, self.width, self.height)
            pygame.draw.rect(self.screen, (0, 0, 0), rect)
            # Rôles
            self.ending_time += 1
            if self.ending_time <= 1 * (self.ending_duration / 12):
                self.show_text("Développement et Programmation", (self.width / 4 + 5, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 2 * (self.ending_duration / 12):
                self.show_text("Direction Artistique", (self.width / 4 + 82, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 3 * (self.ending_duration / 12):
                self.show_text("Conception des Personnages", (self.width / 4 + 30, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 4 * (self.ending_duration / 12):
                self.show_text("Graphismes et Animations", (self.width / 4 + 35, self.height / 2 - 40))
                self.show_text("Midjourney", (self.width / 3 + 60, self.height / 2))
            elif self.ending_time <= 5 * (self.ending_duration / 12):
                self.show_text("Scénario et Dialogues", (self.width / 4 + 75, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 6 * (self.ending_duration / 12):
                self.show_text("Design des Niveaux", (self.width / 3 + 18, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 7 * (self.ending_duration / 12):
                self.show_text("Musique et Effets Sonores", (self.width / 4 + 35, self.height / 2 - 40))
                self.show_text("Soundraw", (self.width / 3 + 60, self.height / 2))
            elif self.ending_time <= 8 * (self.ending_duration / 12):
                self.show_text("Test et Débogage", (self.width / 3 + 35, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 9 * (self.ending_duration / 12):
                self.show_text("Marketing et Promotion", (self.width / 4 + 65, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 10 * (self.ending_duration / 12):
                self.show_text("Relations Publiques", (self.width / 3 + 15, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 11 * (self.ending_duration / 12):
                self.show_text("Service Après-Vente", (self.width / 3 + 18, self.height / 2 - 40))
                self.show_text("Benjamin Bonneton", (self.width / 3 + 20, self.height / 2))
            elif self.ending_time <= 12 * (self.ending_duration / 12):
                self.show_text("© Benji Corp.", (self.width / 3 + 50, self.height / 2 - 10))
            elif self.ending_time > self.ending_duration:
                self.ending_step += 1
        # Retour Accueil
        elif self.ending_step == 3:
            self.scene = 'home'
            pygame.mixer.music.fadeout(1500)


class Joueur():
    '''
    Gestion du vaisseau du joueur.
    
    Attributs:
        jeu: gère les éléments principaux du jeu
        sens: direction du vaisseau ('gauche' / 'droite' / 'O')
        vitesse: float définissant la vitesse de déplacement du vaisseau
        score: entier représentant le nombre de vaisseau détruit
        vie: float pour le nombre de point de vie restant du joueur
        max_vie: float pour le nombre de point maximum que peut avoir un joueur
        position: float pour la coordonée x du vaisseau
        hauteur: float pour la coordonée y du vaisseau
        image: ressource du vaisseau
        mask: collision pour la ressource du vaisseau
        tornado: instance de la classe Tornado
        teleportation: instance de la classe Teleportation
        heal: instance de la classe Heal
        wave: instance de la classe Wave
        jammer: instance de la classe Jammer
        laser: instance de la classe Laser

    Méthodes:
        deplacer: gestion du déplacement du vaisseau sur l'axe x et de la mort du joueur
        marquer: ajout d'un point à l'attribut score
        cooldown: mettre à jour le temps de récupération des pouvoirs
        barre_vie: affiche la barre de vie du joueur sous le vaisseau
    '''
    def __init__(self, jeu):
        '''Constructeur des paramètres du joueur.'''
        self.jeu = jeu
        # Paramètres
        self.sens = 'O'
        self.vitesse = 180 / self.jeu.fps
        self.score = 0
        self.vie = 100
        self.max_vie = 100
        # Positions
        self.position = self.jeu.width / 2
        self.hauteur = self.jeu.height - 100
        # Apparences
        self.image = self.jeu.assets['player_vaisseau']
        self.mask = pygame.mask.from_surface(self.image)
        # Pouvoirs
        self.tornado = Tornado(self, self.jeu)
        self.teleportation = Teleportation(self, self.jeu)
        self.heal = Heal(self, self.jeu)
        self.wave = Wave(self, self.jeu)
        self.jammer = Jammer(self, self.jeu)
        self.laser = Laser(self, self.jeu)

    def deplacer(self):
        '''Destion du déplacement du vaisseau sur l'axe x et de la mort du joueur.'''
        if self.vie > 0:
            if (self.sens == 'gauche') and (self.position - self.vitesse > 0):
                self.position -= self.vitesse
            elif (self.sens == 'droite') and (self.position + self.vitesse <= self.jeu.width - 64):
                self.position += self.vitesse
            self.barre_vie()
        elif self.jeu.scene == 'free':
            self.bg_stop = False
            pygame.mixer.music.fadeout(1500)
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_death']))
            self.jeu.scene = 'free_death'
        elif self.jeu.scene == 'campaign':
            self.bg_stop = False
            pygame.mixer.music.fadeout(1500)
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_death']))
            self.jeu.scene = 'campaign_death'

    def marquer(self):
        '''Ajout d'un point à l'attribut score.'''
        self.score += 1

    def cooldown(self):
        '''Mettre à jour le temps de récupération des pouvoirs.'''
        self.tornado.cooldown()
        self.teleportation.cooldown()
        self.heal.cooldown()
        self.wave.cooldown()
        self.jammer.cooldown()
        self.laser.cooldown()

    def barre_vie(self):
        '''Affiche la barre de vie du joueur sous le vaisseau.'''
        # Barre de non vie
        rect1 = pygame.Rect(self.position + 8, self.hauteur + 70, 48, 5)
        pygame.draw.rect(self.jeu.screen, (205, 92, 92), rect1)
        # Barre de vie
        rect2 = pygame.Rect(self.position + 8, self.hauteur + 70, self.vie * 48 / self.max_vie, 5)
        pygame.draw.rect(self.jeu.screen, (57, 231, 95), rect2)


class Balle():
    '''
    Gestion de la balle d'une entité.

    Attributs:
        jeu: gère les éléments principaux du jeu
        tireur: instance de l'entité à qui possède la balle
        vitesse: float définissant la vitesse de déplacement
        damage: entier définissant les dégâts infligés lors du contact
        etat: chargee / tiree
        image: ressource
        mask: collision pour la ressource
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y

    Méthodes:
        bouger: gestion du déplacement de la balle sur l'axe y
        toucher_healer: gestion de la collision avec un pouvoir de régénération
        toucher: gestion de la collision avec une entité
    '''
    def __init__(self, tireur, jeu, vitesse=425):
        '''Constructeur des paramètres de la balle.'''
        self.jeu = jeu
        # Paramètres
        self.tireur = tireur
        self.vitesse = vitesse / self.jeu.fps
        self.damage = 10 # 10
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_balle']
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.depart = self.tireur.position + 16
        self.hauteur = self.jeu.height - 116

    def bouger(self):
        '''Gestion du déplacement de la balle sur l'axe y.'''
        if self.etat == 'tiree':
            if self.hauteur - self.vitesse < -32:
                self.etat = 'chargee'
            else:
                self.hauteur -= self.vitesse
        else:
            self.hauteur = self.jeu.height - 116
            self.depart = self.tireur.position + 16

    def toucher_healer(self, Healer):
        '''
        Gestion de la collision avec un pouvoir de régénération.
        
        Args:
            Healer: instance de l'entité où la collision doit être vérifiée
        '''
        if isinstance(Healer, Heal):
            if self.etat == 'tiree' and Healer.etat == 'actif':
                if self.mask.overlap(Healer.mask, (Healer.position - self.depart, Healer.hauteur - self.hauteur)):
                    self.etat = 'chargee'
                    Healer.vie -= self.damage
                    return True
        return False

    def toucher(self, Ennemi):
        '''
        Gestion de la collision avec une entité.
        
        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                Ennemi.vie -= self.damage
                self.etat = 'chargee'
                if Ennemi.vie <= 0:
                    return True
        return False


class Tornado():
    '''
    Gestion du pouvoir de la tornade.

    Attributs:
        jeu: gère les éléments principaux du jeu
        cible: instance de l'entité à qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        etat: chargee / tiree / cooldown
        image: ressource
        mask: collision pour la ressource
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        tirer: gestion du lancement
        bouger: gestion du déplacement sur l'axe y
        toucher: gestion de la collision avec une entité
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, cible, jeu, vitesse=400, cooldown=30):
        '''
        Constructeur des paramètres de la Tornade.
        
        Args:
            cible: instance de l'entité à qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            vitesse: float définissant la vitesse de déplacement
            cooldown: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.cible = cible
        self.vitesse = vitesse / self.jeu.fps
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_tornado']
        self.mask = pygame.mask.from_surface(self.image)
        # Cooldown
        self.cooldown_duration = cooldown * self.jeu.fps
        self.cooldown_time = 0

    def tirer(self):
        '''Gestion du lancement.'''
        if self.etat == 'chargee':
            self.cooldown_time = 0
            self.hauteur = self.cible.hauteur - 46 if isinstance(self.cible, Joueur) else self.cible.hauteur + 82
            self.depart = self.cible.position - 10 if isinstance(self.cible, Joueur) else self.cible.position + 18
            self.etat = 'tiree'
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_tornado']))

    def bouger(self):
        '''Gestion du déplacement sur l'axe y.'''
        if (self.hauteur < 0 and isinstance(self.cible, Joueur)) or (self.hauteur > self.jeu.height and isinstance(self.cible, Boss)):
            self.etat = 'cooldown'
        else:
            self.hauteur = self.hauteur - self.vitesse if isinstance(self.cible, Joueur) else self.hauteur + self.vitesse
            self.jeu.screen.blit(self.image, [self.depart, self.hauteur])

    def toucher(self, Ennemi):
        '''
        Gestion de la collision avec une entité.
        
        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if isinstance(self.cible, Joueur):
                if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                    Ennemi.hauteur = self.hauteur
            else:
                if Ennemi.image != self.jeu.assets['player_vaisseau_jammer']:
                    if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                        Ennemi.sens = 'droite' if Ennemi.sens == 'gauche' else 'gauche'

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.etat = 'chargee'


class Teleportation():
    '''
    Gestion du pouvoir de la téléportation.

    Attributs:
        jeu: gère les éléments principaux du jeu
        cible: instance de l'entité à qui possède le pouvoir
        etat: chargee / cooldown
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        teleport: changer l'emplacement de l'entité sur le côté opposé de l'écran
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, cible, jeu, cooldown_duration=30):
        '''
            Constructeur des paramètres de la téléportation.
            
            Args:
                cible: instance de l'entité à qui possède le pouvoir
                jeu: gère les éléments principaux du jeu
                cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.cible = cible
        self.etat = 'chargee'
        # Cooldown
        self.cooldown_duration = cooldown_duration * self.jeu.fps
        self.cooldown_time = 0

    def teleport(self):
        '''Changer l'emplacement de l'entité sur le côté opposé de l'écran.'''
        if self.etat == 'chargee':
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_teleport']))
            Effect.listeEffects.append(Effect(self.cible.position - 16, self.cible.hauteur - 16, 2, self.jeu.assets['entities_teleportation'], self.jeu))
            if self.cible.position <= self.jeu.width / 2:
                self.cible.position = self.jeu.width - self.cible.position if self.jeu.width - self.cible.position <= self.jeu.width - 64 else self.jeu.width - 64
            else:
                self.cible.position = self.jeu.width - self.cible.position - 64 if self.jeu.width - self.cible.position - 64 >= 0 else 0
            Effect.listeEffects.append(Effect(self.cible.position - 16, self.cible.hauteur - 16, 2, self.jeu.assets['entities_teleportation'], self.jeu))
            self.etat = 'cooldown'

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.cooldown_time = 0
                self.etat = 'chargee'


class Heal():
    '''
    Gestion du pouvoir de la régénération.

    Attributs:
        jeu: gère les éléments principaux du jeu
        cible: instance de l'entité à qui possède le pouvoir
        healing: float pour la vitesse de régénération
        vie: float pour le nombre de point de vie restant du vaisseau
        max_vie: float pour le nombre de point maximum que peut avoir un vaisseau
        etat: chargee / actif / cooldown
        image: ressource
        mask: collision pour la ressource
        position: float pour la coordonée x
        hauteur: float pour la coordonée y
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        active: gestion de l'activation du pouvoir
        afficher: gestion de l'affichage du pouvoir
        cooldown: gestion du temps de régénération
        barre_vie: affiche la barre de vie du pouvoir au dessus de l'entité
    '''
    def __init__(self, cible, jeu, vie=50, cooldown_duration=60):
        '''
        Constructeur des paramètres de la régénération.

        Args:
            cible: instance de l'entité à qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            vie: entier pour le nombre de point de vie du vaisseau
            cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.cible = cible
        self.healing = 4 / self.jeu.fps
        self.vie = vie
        self.vie_max = vie
        self.etat = 'chargee'
        # Positions
        self.position = -100
        self.hauteur = -100
        # Apparences
        self.image = self.jeu.assets['enemies_healing_ally_ship'] if isinstance(cible, Joueur) else self.jeu.assets['enemies_healing_ship']
        self.mask = pygame.mask.from_surface(self.image)
        # Cooldown
        self.cooldown_duration = cooldown_duration * self.jeu.fps
        self.cooldown_time = 0

    def active(self):
        '''Gestion de l'activation du pouvoir.'''
        if self.etat == 'chargee':
            self.cooldown_time = 0
            self.vie = self.vie_max
            self.etat = 'actif'

    def afficher(self):
        '''Gestion de l'affichage du pouvoir.'''
        if self.vie > 0:
            # Calcul position
            if isinstance(self.cible, Joueur):
                self.position = self.cible.position
                self.hauteur = self.cible.hauteur - 75
            elif isinstance(self.cible, Ennemi):
                self.position = self.cible.position
                self.hauteur = self.cible.hauteur + 75
            elif isinstance(self.cible, Boss):
                self.position = self.cible.position + 32
                self.hauteur = self.cible.hauteur + 139
            # Affichage
            self.jeu.screen.blit(self.image, [self.position, self.hauteur])
            # Heal
            if self.cible.vie + self.healing < self.cible.max_vie:
                self.cible.vie += self.healing
            else:
                self.cible.vie = self.cible.max_vie
            self.vie -= self.healing
            # Barre de vie
            self.barre_vie()
        else:
            self.etat = 'cooldown'

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.cooldown_time = 0
                self.etat = 'chargee'

    def barre_vie(self):
        '''Affiche la barre de vie du pouvoir au dessus de l'entité.'''
        if isinstance(self.cible, Joueur):
            # Barre de non vie
            rect1 = pygame.Rect(self.cible.position + 8, self.cible.hauteur + 80, 48, 5)
            # Barre de vie
            rect2 = pygame.Rect(self.cible.position + 8, self.cible.hauteur + 80, self.vie * 48 / self.vie_max, 5)
        elif isinstance(self.cible, Ennemi):
            # Barre de non vie
            rect1 = pygame.Rect(self.cible.position + 8, self.cible.hauteur - 22, 48, 4)
            # Barre de vie
            rect2 = pygame.Rect(self.cible.position + 8, self.cible.hauteur - 22, self.vie * 48 / self.vie_max, 4)
        elif isinstance(self.cible, Boss):
            # Barre de non vie
            rect1 = pygame.Rect(self.cible.position + 40, self.cible.hauteur + 209, 48, 4)
            # Barre de vie
            rect2 = pygame.Rect(self.cible.position + 40, self.cible.hauteur + 209, self.vie * 48 / self.vie_max, 4)
        pygame.draw.rect(self.jeu.screen, (192, 192, 192), rect1)
        pygame.draw.rect(self.jeu.screen, (49, 159, 255), rect2)


class Wave():
    '''
    Gestion du pouvoir de la charge électrique.

    Attributs:
        jeu: gère les éléments principaux du jeu
        cible: instance de l'entité à qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        damage: entier définissant les dégâts infligés lors du contact
        etat: chargee / tiree / cooldown
        image: ressource
        mask: collision pour la ressource
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        tirer: gestion du lancement
        bouger: gestion du déplacement sur l'axe y
        toucher: gestion de la collision avec une entité
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, cible, jeu, vitesse=533, cooldown_duration=60):
        '''
        Constructeur des paramètres de la charge électrique.

        Args:
        cible: instance de l'entité à qui possède le pouvoir
        jeu: gère les éléments principaux du jeu
        vitesse: float définissant la vitesse de déplacement
        cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.cible = cible
        self.vitesse = vitesse / self.jeu.fps
        self.damage = 3
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_shock_wave']
        self.mask = pygame.mask.from_surface(self.image)
        # Cooldown
        self.cooldown_duration = cooldown_duration * self.jeu.fps
        self.cooldown_time = 0

    def tirer(self):
        '''Gestion du lancement.'''
        if self.etat == 'chargee':
            self.cooldown_time = 0
            self.hauteur = self.cible.hauteur if isinstance(self.cible, Joueur) else self.cible.hauteur + 128
            self.depart = self.cible.position - 17.5 if isinstance(self.cible, Joueur) else self.cible.position + 14.5
            self.etat = 'tiree'
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_wave']))

    def bouger(self):
        '''Gestion du déplacement sur l'axe y.'''
        if (self.hauteur < 0 and isinstance(self.cible, Joueur)) or (self.hauteur > self.jeu.height and isinstance(self.cible, Boss)):
            self.etat = 'cooldown'
        else:
            self.hauteur = self.hauteur - self.vitesse if isinstance(self.cible, Joueur) else self.hauteur + self.vitesse
            self.jeu.screen.blit(self.image, [self.depart, self.hauteur])

    def toucher(self, Ennemi):
        '''
        Gestion de la collision avec une entité.
        
        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if isinstance(Ennemi, Boss):
                if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                    Ennemi.vie -= self.damage
            elif isinstance(Ennemi, Joueur):
                if Ennemi.image != self.jeu.assets['player_vaisseau_jammer']:
                    if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                        Ennemi.vie -= self.damage
            else:
                if self.mask.overlap(Ennemi.mask, (Ennemi.position - self.depart, Ennemi.hauteur - self.hauteur)):
                    Ennemi.vie -= self.damage
                    if Ennemi.vie <= 0:
                        return True
        return False

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.etat = 'chargee'


class Jammer():
    '''
    Gestion du pouvoir de l'invisibilité.

    Attributs:
        jeu: gère les éléments principaux du jeu
        cible: instance de l'entité à qui possède le pouvoir
        etat: chargee / actif / cooldown
        duration: durée du pouvoir
        time: temps passé du pouvoir
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        active: gestion de la transformation
        afficher: gestion du changement des images de l'entité
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, cible, jeu, cooldown_duration=70):
        '''
        Constructeur des paramètres de l'invisibilité.
    
        Args:
            cible: instance de l'entité à qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.cible = cible
        self.etat = 'chargee'
        # Duration
        self.duration = 10 * self.jeu.fps
        self.time = 0
        # Cooldown
        self.cooldown_duration = cooldown_duration * self.jeu.fps
        self.cooldown_time = 0

    def active(self):
        '''Gestion de la transformation.'''
        if self.etat == 'chargee':
            self.etat = 'actif'
            self.time = 0
            self.cooldown_time = 0

    def afficher(self):
        '''Gestion du changement des images de l'entité.'''
        if isinstance(self.cible, Joueur):
            self.cible.image = self.jeu.assets['player_vaisseau_jammer']
        elif isinstance(self.cible, Ennemi):
            self.cible.image = self.jeu.assets['enemies_jammer_ship_jammer']
        elif isinstance(self.cible, Boss):
            if self.cible.name == 'Nexus Anomalique':
                self.cible.image = self.jeu.assets['bosses_nexus_anomalique_jammer']
            elif self.cible.name == 'Éclipse Primordiale':
                self.cible.image = self.jeu.assets['bosses_final_jammer']
            elif self.cible.name == 'The Master':
                self.cible.image = self.jeu.assets['bosses_end_jammer']
        self.time += 1
        if self.time >= self.duration:
            self.etat = 'cooldown'
            if isinstance(self.cible, Joueur):
                self.cible.image = self.jeu.assets['player_vaisseau']
            elif isinstance(self.cible, Ennemi):
                self.cible.image = self.jeu.assets['enemies_jammer_ship']
            elif isinstance(self.cible, Boss):
                if self.cible.name == 'Nexus Anomalique':
                    self.cible.image = self.jeu.assets['bosses_nexus_anomalique']
                elif self.cible.name == 'Éclipse Primordiale':
                    self.cible.image = self.jeu.assets['bosses_final']
                elif self.cible.name == 'The Master':
                    self.cible.image = self.jeu.assets['bosses_end']

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.time = 0
                self.cooldown_time = 0
                self.etat = 'chargee'


class Laser():
    '''
    Gestion du pouvoir du laser.

    Attributs:
        jeu: gère les éléments principaux du jeu
        tireur: instance de l'entité à qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        etat: chargee / tiree / cooldown
        damage: entier définissant les dégâts infligés lors du contact
        rect: ressource
        mask: collision pour la ressource
        duration: durée du pouvoir
        time: temps passé du pouvoir
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        tirer: gestion du lancement
        bouger: gestion du déplacement sur l'axe y
        afficher: gestion de l'apparence
        toucher: gestion de la collision avec une entité
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, tireur, jeu, duration=5, cooldown_duration=30):
        '''
        Constructeur des paramètres du laser.
    
        Args:
            tireur: instance de l'entité à qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            duration: durée du pouvoir
            cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.tireur = tireur
        self.damage = 40 / self.jeu.fps
        self.etat = 'chargee'
        # Apparences
        if isinstance(self.tireur, Joueur):
            self.rect = pygame.Rect(self.tireur.position + 31, 0, 3, self.jeu.height - 90)
            self.mask = pygame.mask.from_surface(pygame.Surface((3, self.jeu.height - 90)))
        elif isinstance(self.tireur, Ennemi):
            self.rect = pygame.Rect(self.tireur.position + 31, self.tireur.hauteur + 64, 3, self.jeu.height)
            self.mask = pygame.mask.from_surface(pygame.Surface((3, self.jeu.height)))
        elif isinstance(self.tireur, Boss):
            self.rect = pygame.Rect(self.tireur.position + 61.5, self.tireur.hauteur + 115, 5, self.jeu.height)
            self.mask = pygame.mask.from_surface(pygame.Surface((3, self.jeu.height)))
        # Duration
        self.duration = duration * self.jeu.fps
        self.time = 0
        # Cooldown
        self.cooldown_duration = cooldown_duration * self.jeu.fps
        self.cooldown_time = 0

    def tirer(self):
        '''Gestion du lancement.'''
        if self.etat == 'chargee':
            self.etat = 'tiree'
            self.time = 0
            self.cooldown_time = 0
            pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_laser']))

    def bouger(self):
        '''Gestion du déplacement sur l'axe y.'''
        if isinstance(self.tireur, Joueur):
            self.rect = pygame.Rect(self.tireur.position + 31, 0, 3, self.jeu.height - 90)
        elif isinstance(self.tireur, Ennemi):
            self.rect = pygame.Rect(self.tireur.position + 31, self.tireur.hauteur + 64, 3, self.jeu.height)
        elif isinstance(self.tireur, Boss):
            self.rect = pygame.Rect(self.tireur.position + 61.5, self.tireur.hauteur + 115, 5, self.jeu.height)

    def afficher(self):
        '''Gestion de l'apparence.'''
        self.bouger()
        pygame.draw.rect(self.jeu.screen, (255, 0, 0), self.rect)
        self.time += 1
        if self.time >= self.duration:
            self.etat = 'cooldown'

    def toucher(self, Ennemi):
        '''
        Gestion de la collision avec une entité.
        
        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if isinstance(Ennemi, Boss):
                if self.mask.overlap(Ennemi.mask, (self.tireur.position - 95 - Ennemi.position, self.tireur.hauteur - Ennemi.hauteur)):
                    Ennemi.vie -= self.damage
            elif isinstance(Ennemi, Joueur):
                if Ennemi.image != self.jeu.assets['player_vaisseau_jammer']:
                    if self.mask.overlap(Ennemi.mask, (Ennemi.position - 61.5 - self.tireur.position, Ennemi.hauteur - self.tireur.hauteur)):
                        Ennemi.vie -= self.damage
            else:
                if self.mask.overlap(Ennemi.mask, (self.tireur.position - Ennemi.position, self.tireur.hauteur - Ennemi.hauteur)):
                    Ennemi.vie -= self.damage
                    if Ennemi.vie <= 0:
                        return True
        return False

    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.time = 0
                self.cooldown_time = 0
                self.etat = 'chargee'


class Combat():
    '''
    Gestion d'une entité de combat.

    Attributs:
        jeu: gère les éléments principaux du jeu
        tireur: instance de l'entité qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        damage: entier définissant les dégâts infligés lors du contact
        etat: chargee / tiree
        image: ressource
        mask: collision pour la ressource
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y

    Méthodes:
        bouger: gestion du déplacement de la balle sur l'axe y
        toucher_healer: gestion de la collision avec un pouvoir de régénération
        toucher: gestion de la collision avec une entité
    '''
    def __init__(self, tireur, jeu, vitesse=400):
        '''
        Constructeur des paramètres d'une entité de combat.
    
        Args:
            tireur: instance de l'entité qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            vitesse: float définissant la vitesse de déplacement
        '''
        self.jeu = jeu
        # Paramètres
        self.tireur = tireur
        self.vitesse = vitesse / self.jeu.fps
        self.damage = 15
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_balle_enemy']
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.depart = self.tireur.position + 16
        self.hauteur = self.tireur.hauteur + 45

    def bouger(self):
        '''Gestion du déplacement de la balle sur l'axe y.'''
        if self.etat == 'tiree':
            if self.hauteur + self.vitesse >= self.jeu.height:
                self.etat = 'chargee'
            else:
                self.hauteur += self.vitesse
        else:
            self.depart = self.tireur.position + 16
            self.hauteur = self.tireur.hauteur + 45
        self.jeu.screen.blit(self.image, [self.depart, self.hauteur])

    def toucher_healer(self, Player):
        '''
        Gestion de la collision avec un pouvoir de régénération.

        Args:
            Player: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree' and Player.heal.etat == 'actif':
            if self.mask.overlap(Player.heal.mask, (Player.heal.position - self.depart, Player.heal.hauteur - self.hauteur)):
                self.etat = 'chargee'
                Player.heal.vie -= self.damage
                return True
        return False
    
    def toucher(self, Player):
        '''
        Gestion de la collision avec une entité.

        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if Player.image != self.jeu.assets['player_vaisseau_jammer']:
                if self.mask.overlap(Player.mask, (Player.position - self.depart, Player.hauteur - self.hauteur)):
                    self.etat = 'chargee'
                    Player.vie -= self.damage
                    if Player.vie <= 0:
                        return True
        return False


class Cannon():
    '''
    Gestion d'une entité de canon.

    Attributs:
        jeu: gère les éléments principaux du jeu
        tireur: instance de l'entité qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        damage: entier définissant les dégâts infligés lors du contact
        etat: chargee / tiree
        image: ressource
        mask: collision pour la ressource
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y

    Méthodes:
        bouger: gestion du déplacement de la balle sur l'axe y
        toucher_healer: gestion de la collision avec un pouvoir de régénération
        toucher: gestion de la collision avec une entité
    '''
    def __init__(self, tireur, jeu, vitesse=250):
        '''
        Constructeur des paramètres d'une entité de canon.
    
        Args:
            tireur: instance de l'entité qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            vitesse: float définissant la vitesse de déplacement
        '''
        self.jeu = jeu
        # Paramètres
        self.tireur = tireur
        self.vitesse = vitesse / self.jeu.fps
        self.damage = 20
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_cannon']
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.depart = self.tireur.position + 8
        self.hauteur = self.tireur.hauteur + 32

    def bouger(self):
        '''Gestion du déplacement de la balle sur l'axe y.'''
        if self.etat == 'tiree':
            if self.hauteur + self.vitesse >= self.jeu.height:
                self.etat = 'chargee'
            else:
                self.hauteur += self.vitesse
        else:
            self.depart = self.tireur.position + 8
            self.hauteur = self.tireur.hauteur + 32
        self.jeu.screen.blit(self.image, [self.depart, self.hauteur])

    def toucher_healer(self, Player):
        '''
        Gestion de la collision avec un pouvoir de régénération.

        Args:
            Player: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree' and Player.heal.etat == 'actif':
            if self.mask.overlap(Player.heal.mask, (Player.heal.position - self.depart, Player.heal.hauteur - self.hauteur)):
                self.etat = 'chargee'
                Player.heal.vie -= self.damage
                return True
        return False

    def toucher(self, Player):
        '''
        Gestion de la collision avec une entité.

        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if Player.image != self.jeu.assets['player_vaisseau_jammer']:
                if self.mask.overlap(Player.mask, (Player.position - self.depart, Player.hauteur - self.hauteur)):
                    self.etat = 'chargee'
                    Player.vie -= self.damage
                    if Player.vie <= 0:
                        return True
        return False


class Sniper():
    '''
    Gestion d'une entité de sniper.

    Attributs:
        jeu: gère les éléments principaux du jeu
        tireur: instance de l'entité qui possède le pouvoir
        vitesse: float définissant la vitesse de déplacement
        damage: entier définissant les dégâts infligés lors du contact
        etat: chargee / tiree
        image: ressource
        mask: collision pour la ressource
        init_depart: float pour la coordonée x de départ
        init_hauteur: float pour la coordonée y de départ
        depart: float pour la coordonée x
        hauteur: float pour la coordonée y

    Méthodes:
        bouger: gestion du déplacement de la balle sur l'axe y
        toucher_healer: gestion de la collision avec un pouvoir de régénération
        toucher: gestion de la collision avec une entité
    '''
    def __init__(self, tireur, jeu, vitesse=550, position=30.5, hauteur=32):
        '''
        Constructeur des paramètres d'une entité de combat.
    
        Args:
            tireur: instance de l'entité qui possède le pouvoir
            jeu: gère les éléments principaux du jeu
            vitesse: float définissant la vitesse de déplacement
            position: float pour la coordonée x
            hauteur: float pour la coordonée y
        '''
        self.jeu = jeu
        # Paramètres
        self.tireur = tireur
        self.vitesse = vitesse / self.jeu.fps
        self.damage = 25
        self.etat = 'chargee'
        # Apparences
        self.image = self.jeu.assets['entities_laser']
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.init_depart = position
        self.init_hauteur = hauteur
        self.depart = self.tireur.position + self.init_depart
        self.hauteur = self.tireur.hauteur + self.init_hauteur

    def bouger(self):
        '''Gestion du déplacement de la balle sur l'axe y.'''
        if self.etat == 'tiree':
            if self.hauteur + self.vitesse >= self.jeu.height:
                self.etat = 'chargee'
            else:
                self.hauteur += self.vitesse
        else:
            self.depart = self.tireur.position + self.init_depart
            self.hauteur = self.tireur.hauteur + self.init_hauteur
        self.jeu.screen.blit(self.image, [self.depart, self.hauteur])

    def toucher_healer(self, Player):
        '''
        Gestion de la collision avec un pouvoir de régénération.

        Args:
            Player: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree' and Player.heal.etat == 'actif':
            if self.mask.overlap(Player.heal.mask, (Player.heal.position - self.depart, Player.heal.hauteur - self.hauteur)):
                self.etat = 'chargee'
                Player.heal.vie -= self.damage
                return True
        return False

    def toucher(self, Player):
        '''
        Gestion de la collision avec une entité.

        Args:
            Ennemi: instance de l'entité où la collision doit être vérifiée
        '''
        if self.etat == 'tiree':
            if Player.image != self.jeu.assets['player_vaisseau_jammer']:
                if self.mask.overlap(Player.mask, (Player.position - self.depart, Player.hauteur - self.hauteur)):
                    self.etat = 'chargee'
                    Player.vie -= self.damage
                    if Player.vie <= 0:
                        return True
        return False


class Stop_Update():
    '''
    Gestion du pouvoir d'arrêter l'actualisation.

    Attributs:
        jeu: gère les éléments principaux du jeu
        etat: chargee / actif / cooldown
        duration: durée du pouvoir
        time: temps passé du pouvoir
        cooldown_duration: durée de régénération du pouvoir
        cooldown_time: temps passé pour la régénération du pouvoir

    Méthodes:
        active: gestion du lancement
        cooldown: gestion du temps de régénération
    '''
    def __init__(self, jeu, duration=10, cooldown_duration=10):
        '''
        Constructeur des paramètres du pouvoir d'arrêter l'actualisation.
    
        Args:
            jeu: gère les éléments principaux du jeu
            duration: durée du pouvoir
            cooldown_duration: durée de régénération du pouvoir
        '''
        self.jeu = jeu
        # Paramètres
        self.etat = 'chargee'
        # Durée
        self.duration = duration * jeu.fps
        self.time = 0
        # Cooldown
        self.cooldown_duration = cooldown_duration * jeu.fps
        self.cooldown_time = 0

    def active(self):
        '''Gestion du lancement.'''
        if self.jeu.bg_stop == False:
            self.jeu.bg_stop = True
            self.etat = 'actif'
            self.time = 0
            self.cooldown_time = 0
        self.time += 1
        if self.time >= self.duration:
            self.jeu.bg_stop = False
            self.etat = 'cooldown'
    
    def cooldown(self):
        '''Gestion du temps de régénération.'''
        if self.etat == 'cooldown':
            self.cooldown_time += 1
            if self.cooldown_time >= self.cooldown_duration:
                self.etat = 'chargee'


class Boss():
    '''
    Gestion des boss.

    Variables:
        type: dictionnaire comportant chaque boss et leurs caractéristiques
        boss: instance du boss actuel

    Attributs:
        jeu: gère les éléments principaux du jeu
        player: gère les éléments du joueur
        vitesse: float définissant la vitesse de déplacement du vaisseau
        vie: float pour le nombre de point de vie restant du joueur
        max_vie: float pour le nombre de point maximum que peut avoir un joueur
        name: nom du boss
        image: ressource du vaisseau
        mask: collision pour la ressource du vaisseau
        position: float pour la coordonée x du vaisseau
        hauteur: float pour la coordonée y du vaisseau
        balle: instance d'une balle de type sniper
        balle_cooldown_duration: float pour la durée entre le lancement de la balle
        balle_cooldown_time: float pour le temps actuel pour le lancement de la balle
        pouvoir: instance du premier pouvoir
        pouvoir2: instance du deuxième pouvoir
        pouvoir_cooldown_duration: durée de régénération du premier pouvoir
        pouvoir_cooldown_time: temps actuel de régénération du premier pouvoir
        pouvoir2_cooldown_duration: durée de régénération du deuxième pouvoir
        pouvoir2_cooldown_time: temps actuel de régénération du deuxième pouvoir
    
    Méthodes:
        bouger: gestion du déplacement du boss par rapport au joueur
        tirer: gestion du lancement de la balle
        ability: gestion de l'activation des pouvoirs
        random_ability: attribution aléatoire d'un pouvoir
        barre_vie: gestion de l'affichage d'une barre de vie en haut de l'écran
    '''
    type = {
        'world_1': {
            'name': 'Vortex Stellarien',
            'image': 'vortex_stellarien',
            'vie': 150,
        },
        'world_2': {
            'name': 'Nova Étherienne',
            'image': 'nova_etherienne',
            'vie': 250,
        },
        'world_3': {
            'name': 'Sérénité Astrale',
            'image': 'serenite_astrale',
            'vie': 350,
        },
        'world_4': {
            'name': 'Tempête Ondulaire',
            'image': 'tempete_ondulaire',
            'vie': 450,
        },
        'world_5': {
            'name': 'Nexus Anomalique',
            'image': 'nexus_anomalique',
            'vie': 550,
        },
        'world_6': {
            'name': 'Apogée Céleste',
            'image': 'apogee_celeste',
            'vie': 650,
        },
        'world_7_1': {
            'name': 'Voile de l\'Abysse',
            'image': 'final',
            'vie': 750,
        },
        'world_7_2': {
            'name': 'Nébuleuse Éternelle',
            'image': 'final',
            'vie': 750,
        },
        'world_7_3': {
            'name': 'Éclipse Primordiale',
            'image': 'final',
            'vie': 750,
        },
        'world_8': {
            'name': 'The Master',
            'image': 'end',
            'vie': 1500,
        },
    }
    boss = None
    def __init__(self, jeu, type, position=None, hauteur=None):
        '''
        Constructeur des paramètres du boss.
    
        Args:
            jeu: gère les éléments principaux du jeu
            type: dictionnaire des caractéristiques du boss
            position: float pour la coordonée x
            hauteur: float pour la coordonée y
        '''
        self.jeu = jeu
        # Paramètres
        self.player = self.jeu.player
        self.vitesse = 50 / self.jeu.fps
        self.vie = type['vie']
        self.max_vie = type['vie']
        # Apparences
        self.name = type['name']
        self.image = self.jeu.assets[f"bosses_{type['image']}"]
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.position = self.jeu.width / 2 - 64 if position is None else position
        self.hauteur = -64 if hauteur is None else hauteur
        # Balle
        self.balle = Sniper(self, jeu, position=62.5, hauteur=100)
        self.balle_cooldown_duration = 1 * self.jeu.fps
        self.balle_cooldown_time = 0
        # Pouvoirs
        self.pouvoir2 = None
        if type['name'] == 'Vortex Stellarien':
            self.pouvoir = Tornado(self, self.jeu, 400, 3)
        elif type['name'] == 'Nova Étherienne':
            self.pouvoir = Teleportation(self, self.jeu, 10)
        elif type['name'] == 'Sérénité Astrale':
            self.pouvoir = Heal(self, self.jeu, 100, 10)
        elif type['name'] == 'Tempête Ondulaire':
            self.pouvoir = Wave(self, self.jeu, 533, 5)
        elif type['name'] == 'Nexus Anomalique':
            self.pouvoir = Jammer(self, self.jeu, 15)
        elif type['name'] == 'Apogée Céleste':
            self.pouvoir = Laser(self, self.jeu, 5, 10)
        elif type['name'] == 'Voile de l\'Abysse':
            self.pouvoir = Tornado(self, self.jeu, 400, 3)
            self.pouvoir2 = Teleportation(self, self.jeu, 10)
        elif type['name'] == 'Nébuleuse Éternelle':
            self.pouvoir = Heal(self, self.jeu, 200, 10)
            self.pouvoir2 = Wave(self, self.jeu, 533, 5)
        elif type['name'] == 'Éclipse Primordiale':
            self.pouvoir = Jammer(self, self.jeu, 15)
            self.pouvoir2 = Laser(self, self.jeu, 5, 10)
        elif type['name'] == 'The Master':
            self.pouvoir = random.choice([
                Tornado(self, self.jeu, 400, 3),
                Teleportation(self, self.jeu, 8),
                Heal(self, self.jeu, 100, 8),
                Wave(self, self.jeu, 533, 8),
                Jammer(self, self.jeu, 10),
                Laser(self, self.jeu, 5, 8)
            ])
            self.pouvoir2 = Stop_Update(self.jeu)
        # Cooldown
        self.pouvoir_cooldown_duration = random.randint(1, 4) * self.jeu.fps
        self.pouvoir_cooldown_time = 0
        self.pouvoir2_cooldown_duration = random.randint(1, 4) * self.jeu.fps
        self.pouvoir2_cooldown_time = 0

    def bouger(self):
        '''Gestion du déplacement du boss par rapport au joueur.'''
        self.barre_vie()
        if (self.hauteur <= 75):
            self.hauteur += self.vitesse
        elif (self.player.position + 31 < self.position + 64 < self.player.position + 33) and (self.position != self.player.position):
            self.position = self.player.position - 32
        elif self.position + 64 < self.player.position + 32:
            self.position += self.vitesse
        elif self.position + 64 > self.player.position + 32:
            self.position -= self.vitesse

    def tirer(self):
        '''Gestion du lancement de la balle.'''
        self.balle.bouger()
        if self.balle.etat == 'chargee':
            self.balle_cooldown_time += 1
            if self.balle_cooldown_time >= self.balle_cooldown_duration:
                self.balle_cooldown_time = 0
                self.balle.etat = 'tiree'
                pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_sniper']))
        elif self.balle.etat == 'tiree':
            if self.balle.toucher_healer(self.player) is False:
                self.balle.toucher(self.player)

    def ability(self):
        '''Gestion de l'activation des pouvoirs.'''
        if self.hauteur >= 0:
            ## Pouvoir 1
            # Tornado
            if isinstance(self.pouvoir, Tornado):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.tirer()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'tiree':
                    self.pouvoir.bouger()
                    self.pouvoir.toucher(self.player)
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            # Teleportation
            elif isinstance(self.pouvoir, Teleportation):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.teleport()
                        self.pouvoir_cooldown_time = 0
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            # Heal
            elif isinstance(self.pouvoir, Heal):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.active()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'actif':
                    self.pouvoir.afficher()
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            # Wave
            elif isinstance(self.pouvoir, Wave):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.tirer()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'tiree':
                    self.pouvoir.bouger()
                    self.pouvoir.toucher(self.player)
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            # Jammer
            elif isinstance(self.pouvoir, Jammer):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.active()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'actif':
                    self.pouvoir.afficher()
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            # Laser
            elif isinstance(self.pouvoir, Laser):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.tirer()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'tiree':
                    self.pouvoir.bouger()
                    self.pouvoir.afficher()
                    self.pouvoir.toucher(self.player)
                else:
                    if self.name == 'The Master':
                        self.random_ability()
                    else:
                        self.pouvoir.cooldown()
            ## Pouvoir 2
            if self.pouvoir2 is not None:
                # Tornado
                if isinstance(self.pouvoir2, Tornado):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.tirer()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'tiree':
                        self.pouvoir2.bouger()
                        self.pouvoir2.toucher(self.player)
                    else:
                        self.pouvoir2.cooldown()
                # Teleportation
                elif isinstance(self.pouvoir2, Teleportation):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.teleport()
                            self.pouvoir2_cooldown_time = 0
                    else:
                        self.pouvoir2.cooldown()
                # Heal
                elif isinstance(self.pouvoir2, Heal):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.active()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'actif':
                        self.pouvoir2.afficher()
                    else:
                        self.pouvoir2.cooldown()
                # Wave
                elif isinstance(self.pouvoir2, Wave):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.tirer()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'tiree':
                        self.pouvoir2.bouger()
                        self.pouvoir2.toucher(self.player)
                    else:
                        self.pouvoir2.cooldown()
                # Jammer
                elif isinstance(self.pouvoir2, Jammer):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.active()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'actif':
                        self.pouvoir2.afficher()
                    else:
                        self.pouvoir2.cooldown()
                # Laser
                elif isinstance(self.pouvoir2, Laser):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.tirer()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'tiree':
                        self.pouvoir2.bouger()
                        self.pouvoir2.afficher()
                        self.pouvoir2.toucher(self.player)
                    else:
                        self.pouvoir2.cooldown()
                # Stop Update
                elif isinstance(self.pouvoir2, Stop_Update):
                    if self.pouvoir2.etat == 'chargee':
                        self.pouvoir2_cooldown_time += 1
                        if self.pouvoir2_cooldown_time >= self.pouvoir2_cooldown_duration:
                            self.pouvoir2.active()
                            self.pouvoir2_cooldown_time = 0
                    elif self.pouvoir2.etat == 'actif':
                        self.pouvoir2.active()
                    else:
                        self.pouvoir2.cooldown()  

    def random_ability(self):
        '''Attribution aléatoire d'un pouvoir.'''
        self.pouvoir = random.choice([
            Tornado(self, self.jeu, 400, 3),
            Teleportation(self, self.jeu, 10),
            Heal(self, self.jeu, 200, 10),
            Wave(self, self.jeu, 533, 5),
            Jammer(self, self.jeu, 15),
            Laser(self, self.jeu, 5, 10)
        ])

    def barre_vie(self):
        # Nom boss
        if self.name in ['Nexus Anomalique', 'Tempête Ondulaire']:
            self.jeu.show_text(self.name, (self.jeu.width / 3 + 25, 10))
        elif self.name == 'Nébuleuse Éternelle':
            self.jeu.show_text(self.name, (self.jeu.width / 3 + 20, 10))
        elif self.name == 'Éclipse Primordiale':
            self.jeu.show_text(self.name, (self.jeu.width / 3 + 30, 10))
        elif self.name == 'The Master':
            self.jeu.show_text(self.name, (self.jeu.width / 3 + 70, 10))
        else:
            self.jeu.show_text(self.name, (self.jeu.width / 3 + 45, 10))
        # Barre de non vie
        rect1 = pygame.Rect(self.jeu.width / 4, 40, self.jeu.width / 2, 5)
        pygame.draw.rect(self.jeu.screen, (192, 192, 192), rect1)
        # Barre de vie
        rect2 = pygame.Rect(self.jeu.width / 4, 40, self.vie * (self.jeu.width / 2) / self.max_vie, 5)
        pygame.draw.rect(self.jeu.screen, (205, 92, 92), rect2)


class Ennemi():
    '''
    Gestion des ennemis.

    Variables:
        type: dictionnaire comportant chaque ennemis et leurs caractéristiques
        world: dictionnaire comportant chaque niveaux et leurs caractéristiques
        spawn: entier représentant le nombre d'ennemis générés dans un niveau
        listeEnnemis: liste comportant les instances des ennemis du niveau actuel
    
    Attributs:
        jeu: gère les éléments principaux du jeu
        player: gère les éléments du joueur
        type: dictionnaire avec les caractéristiques d'un ennemi
        vitesse: float définissant la vitesse de déplacement du vaisseau
        vie: float pour le nombre de point de vie restant du joueur
        max_vie: float pour le nombre de point maximum que peut avoir un joueur
        image: ressource du vaisseau
        mask: collision pour la ressource du vaisseau
        position: float pour la coordonée x du vaisseau
        hauteur: float pour la coordonée y du vaisseau
        old_hauteur: hauteur de départ lorsque le vaisseau est au bord de l'écran
        sens: direction de déplacement ('gauche' / 'droite' / 'O')
        pouvoir: instance du pouvoir
        pouvoir_cooldown_duration: durée de régénération du pouvoir
        pouvoir_cooldown_time: temps actuel de régénération du pouvoir

    Méthodes:
        avancer: gestion du déplacement
        ability: gestion de l'activation des pouvoirs
        disparaitre: gestion de la disparition d'un ennemi lorsqu'il sort de l'écran ou qu'il meurt
        barre_vie: gestion de l'affichage d'une barre de vie en haut de l'ennemi
    '''
    type = {
        'combat_ship': {
            'name': 'combat_ship',
            'vitesse': 50,
            'vie': 20
        },
        'double_ship': {
            'name': 'double_ship',
            'vitesse': 52,
            'vie': 20
        },
        'teleport_ship': {
            'name': 'teleport_ship',
            'vitesse': 54,
            'vie': 30
        },
        'healer_ship': {
            'name': 'healer_ship',
            'vitesse': 56,
            'vie': 35
        },
        'cannon_ship': {
            'name': 'cannon_ship',
            'vitesse': 58,
            'vie': 30
        },
        'jammer_ship': {
            'name': 'jammer_ship',
            'vitesse': 60,
            'vie': 20
        },
        'sniper_ship': {
            'name': 'sniper_ship',
            'vitesse': 62,
            'vie': 40
        }
    }
    world = {
        'world_1': {
            'number': 16,
            'max_per_screen': 4,
            'type_1': type['combat_ship'],
            'type_2': type['double_ship']
        },
        'world_2': {
            'number': 20,
            'max_per_screen': 5,
            'type_1': type['double_ship'],
            'type_2': type['teleport_ship']
        },
        'world_3': {
            'number': 30,
            'max_per_screen': 8,
            'type_1': type['combat_ship'],
            'type_2': type['healer_ship']
        },
        'world_4': {
            'number': 20,
            'max_per_screen': 5,
            'type_1': type['cannon_ship'],
            'type_2': type['double_ship']
        },
        'world_5': {
            'number': 24,
            'max_per_screen': 6,
            'type_1': type['jammer_ship'],
            'type_2': type['double_ship']
        },
        'world_6': {
            'number': 25,
            'max_per_screen': 5,
            'type_1': type['sniper_ship'],
            'type_2': type['combat_ship']
        },
        'free': {
            'max_per_screen': 8,
        }
    }
    spawn = 0
    listeEnnemis = []
    def __init__(self, type, jeu, position=None, hauteur=None, sens=None):
        '''
        Constructeur des paramètres de l'ennemi.
    
        Args:
            type: dictionnaire des caractéristiques de l'ennemi
            jeu: gère les éléments principaux du jeu
            position: float pour la coordonée x
            hauteur: float pour la coordonée y
            sens: direction de déplacement ('gauche' / 'droite' / 'O')
        '''
        self.jeu = jeu
        # Paramètres
        self.player = self.jeu.player
        self.type = type
        self.vitesse = type['vitesse'] / self.jeu.fps
        self.vie = type['vie']
        self.max_vie = type['vie']
        # Apparences
        self.image = self.jeu.assets[f"enemies_{self.type['name']}"]
        self.mask = pygame.mask.from_surface(self.image)
        # Positions
        self.position = random.randint(64, self.jeu.width-128) if position is None else position
        self.hauteur = -64 if hauteur is None else hauteur
        self.old_hauteur = None
        if sens is None:
            if self.position <= self.jeu.width / 2:
                self.sens = 'gauche'
            else:
                self.sens = 'droite'
        else:
            self.sens = sens
        # Pouvoirs
        if self.type['name'] == 'combat_ship':
            self.pouvoir = Combat(self, self.jeu)
        elif self.type['name'] == 'teleport_ship':
            self.pouvoir = Teleportation(self, self.jeu, 15)
        elif self.type['name'] == 'healer_ship':
            self.pouvoir = Heal(self, self.jeu, 20, 20)
        elif self.type['name'] == 'double_ship':
            self.pouvoir = None
        elif self.type['name'] == 'cannon_ship':
            self.pouvoir = Cannon(self, self.jeu)
        elif self.type['name'] == 'jammer_ship':
            self.pouvoir = Jammer(self, self.jeu, 20)
        elif self.type['name'] == 'sniper_ship':
            self.pouvoir = Sniper(self, self.jeu)
        # Cooldown
        if self.type['name'] in ['teleport_ship', 'healer_ship', 'jammer_ship']:
            self.pouvoir_cooldown_duration = random.randint(5, 8) * self.jeu.fps
        else:
            self.pouvoir_cooldown_duration = random.randint(2, 4) * self.jeu.fps
        self.pouvoir_cooldown_time = 0

    def avancer(self):
        '''Gestion du déplacement.'''
        if (self.hauteur <= 20) or (self.jeu.height + 20 >= self.hauteur >= self.jeu.height - 200):
            self.hauteur += self.vitesse
            self.barre_vie()
        elif self.hauteur < self.jeu.height - 200:
            if ((self.position - self.vitesse <= 0) and (self.sens == 'gauche')) or ((self.position + self.vitesse > self.jeu.width - 64) and (self.sens == 'droite')):
                if self.old_hauteur is None:
                    self.old_hauteur = self.hauteur
                self.hauteur += 40 / self.jeu.fps
                if self.hauteur >= self.old_hauteur + 100:
                    self.old_hauteur = None
                    self.sens = 'droite' if self.sens == 'gauche' else 'gauche'
            elif self.sens == 'gauche':
                self.position -= self.vitesse
            elif self.sens == 'droite':
                self.position += self.vitesse
            self.barre_vie()
        elif self.hauteur > self.jeu.height + 20:
            self.jeu.player.vie -= 10
            self.listeEnnemis.remove(self)

    def ability(self):
        '''Gestion de l'activation des pouvoirs.'''
        if self.hauteur <= self.jeu.height - 200:
            # Teleportation
            if isinstance(self.pouvoir, Teleportation):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        if ((self.position > self.jeu.width / 2) and (self.sens == 'gauche')) or ((self.position < self.jeu.width / 2) and (self.sens == 'droite')):
                            if 64 <= self.position <= self.jeu.width - 128:
                                self.pouvoir.teleport()
                                self.pouvoir_cooldown_time = 0
                else:
                    self.pouvoir.cooldown()
            # Heal
            elif isinstance(self.pouvoir, Heal):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.active()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'actif':
                    self.pouvoir.afficher()
                else:
                    self.pouvoir.cooldown()
            # Combat
            elif isinstance(self.pouvoir, Combat):
                self.pouvoir.bouger()
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir_cooldown_time = 0
                        self.pouvoir.etat = 'tiree'
                elif self.pouvoir.etat == 'tiree':
                    if self.pouvoir.toucher_healer(self.player) is False:
                        self.pouvoir.toucher(self.player)
            # Cannon
            elif isinstance(self.pouvoir, Cannon):
                self.pouvoir.bouger()
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir_cooldown_time = 0
                        self.pouvoir.etat = 'tiree'
                        pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_cannon']))
                elif self.pouvoir.etat == 'tiree':
                    if self.pouvoir.toucher_healer(self.player) is False:
                        self.pouvoir.toucher(self.player)
            # Jammer
            elif isinstance(self.pouvoir, Jammer):
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir.active()
                        self.pouvoir_cooldown_time = 0
                elif self.pouvoir.etat == 'actif':
                    self.pouvoir.afficher()
                else:
                    self.pouvoir.cooldown()
            # Sniper
            elif isinstance(self.pouvoir, Sniper):
                self.pouvoir.bouger()
                if self.pouvoir.etat == 'chargee':
                    self.pouvoir_cooldown_time += 1
                    if self.pouvoir_cooldown_time >= self.pouvoir_cooldown_duration:
                        self.pouvoir_cooldown_time = 0
                        self.pouvoir.etat = 'tiree'
                        pygame.mixer.Sound.play(pygame.mixer.Sound(self.jeu.assets['sounds_sniper']))
                elif self.pouvoir.etat == 'tiree':
                    if self.pouvoir.toucher_healer(self.player) is False:
                        self.pouvoir.toucher(self.player)

    def disparaitre(self):
        '''Gestion de la disparition d'un ennemi lorsqu'il sort de l'écran ou qu'il meurt.'''
        if self.type['name'] == 'double_ship':
            if self.position - 32 < 0:
                position1 = 0
                position2 = 64
            elif self.position + 32 > self.jeu.width - 64:
                position1 =  self.position
                position2 =  self.position - 64
            else:
                position1 = self.position - 32
                position2 = self.position + 32
            vaisseau = Ennemi(Ennemi.type['combat_ship'], self.jeu, position1, self.hauteur, self.sens)
            Ennemi.listeEnnemis.append(vaisseau)
            vaisseau = Ennemi(Ennemi.type['combat_ship'], self.jeu, position2, self.hauteur, self.sens)
            Ennemi.listeEnnemis.append(vaisseau)
        Ennemi.listeEnnemis.remove(self)

    def barre_vie(self):
        '''Gestion de l'affichage d'une barre de vie en haut de l'ennemi.'''
        # Barre de non vie
        rect1 = pygame.Rect(self.position + 8, self.hauteur - 16, 48, 4)
        pygame.draw.rect(self.jeu.screen, (205, 92, 92), rect1)
        # Barre de vie
        rect2 = pygame.Rect(self.position + 8, self.hauteur - 16, self.vie * 48 / self.max_vie, 4)
        pygame.draw.rect(self.jeu.screen, (57, 231, 95), rect2)


class Effect():
    '''
    Gestion des effets.

    Variables:
        listeEffects: liste comportant les instances des effets du niveau actuel

    Attributs:
        jeu: gère les éléments principaux du jeu
        image: ressource de l'effet
        x: float pour la coordonée x
        y: float pour la coordonée y
        duration: entier pour la durée de l'effet
        time: temps actuel de la durée de l'effet

    Méthodes:
        afficher: gestion de l'affichage de l'effet
    '''
    listeEffects = []
    def __init__(self, x, y, duration, image, jeu):
        '''
        Constructeur des paramètres de l'effet.
    
        Args:
            x: float pour la coordonée x
            y: float pour la coordonée y
            duration: entier pour la durée de l'effet
            image: ressource de l'effet
            jeu: gère les éléments principaux du jeu
        '''
        self.jeu = jeu
        # Apparences
        self.image = image
        # Positions
        self.x = x
        self.y = y
        # Duration
        self.duration = duration * self.jeu.fps
        self.time = 0

    def afficher(self):
        '''Gestion de l'affichage de l'effet.'''
        if self.time <= self.duration:
            self.jeu.screen.blit(self.image, [self.x, self.y])
            self.time += 1
        else:
            self.listeEffects.remove(self)
