def render(self, qp: QPainter, x: float, y: float) -> None:
        """
        Fait le rendu de l'entité sur l'écran à l'aide du painter de
        ce dernier.

        Args:
            qp (QPainter): painter de la surface sur laquelle dessiner
            x (float): position X du milieu de l'entité par rapport au joueur
            y (float): position Y du milieu de l'entité par rapport au joueur
        """
        # Si on a définit une image on la dessine
        if self.image is not None:
            img = QPixmap(self.image)
            if self.image_direction.equal(Vecteur(0.0, 1.0)):  # direction sud
                rotation = 180
            elif self.image_direction.equal(Vecteur(1.0, 0.0)):  # direction est
                rotation = 90
            elif self.image_direction.equal(Vecteur(-1.0, 0.0)):  # direction ouest
                rotation = 270
            else:  # direction nord
                rotation = 0
            img_rotated = img.transformed(QTransform().rotate(rotation))
            # xoffset = (img_rotated.width() - img.width()) / 2
            # yoffset = (img_rotated.height() - img.height()) / 2
            # img_rotated = img_rotated.copy(xoffset, yoffset, img.width(), img.height())
            img_rot_scal = img_rotated.scaled(*self.size)
            qp.drawPixmap(QPoint(x - self.size[0] // 2, y - self.size[1] // 2), img_rot_scal)
            self.draw_life_bar(qp, x, y)
Example #2
0
    def update(self, delta):
        """
        Met à jour l'entité ainsi que son cerveau

        Args:
            delta (float): temps écoulé depuis la dernière mise à jour
        """
        self.brain.update()
        self.isDead()
        self.level_up()
        self.current_weapon.update()
        self.takeDamage(self.current_target, 1 / 30)

        # Si on est sur le serveur, on ne veut pas que le client déplace les entités
        if GSR.carte is not None:
            new_position = (self.position + self.direction * self.vitesse)
            # Si il n'y a pas de collision avec la map
            if not GSR.carte.is_colliding(int(new_position.x),
                                          int(new_position.y)):
                self.position += self.direction * self.vitesse
            # Si il y en a
            else:
                self.direction = Vecteur()
        # On retient la dernière direction prise par le bateau
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction
    def mousePressEvent(self, e: QMouseEvent) -> None:
        """
        Fonction dérivée de Qt, qui est appelée à chaque fois qu'un
        bouton de la souris est pressé

        Args:
            e (PyQt5.QtGui.QMouseEvent): évènement du type souris
        """
        x, y = mouseToWorldPosition(e.x(), e.y())
        GCR.log.log(Logger.DEBUG, f"Click à la pos : ({x}, {y})")
        # On met le focus sur le canvas pour récupérer les autres évènements ici
        self.setFocus()
        for e in GCR.entities:
            # Si l'entité n'est pas le joueur on peut cibler
            if e != GCR.joueur:
                diff = Vecteur(x, y) - e.position
                distance = diff.distance()
                # Si le click est proche d'une cible
                if distance < max(e.size[0], e.size[1]) // (8*2) * 1.5:
                    GCR.joueur.ciblage(e)
                    GCR.log.log(Logger.DEBUG, f"Nouvelle cible : {e}")
                    break
        # Sinon le click est trop loin d'une cible, on déselectionne
        else:
            GCR.joueur.ciblage(None)
Example #4
0
    def update(self, delta: float) -> None:
        """
        Met à jour les éléments essentiels au fonctionnement de l'entité comme sa position
        ou bien sa direction.

        Args:
            delta (float): temps mis entre l'itération précédente et l'itération actuelle, ce temps est cruciale dans les performances du programme
        """
        #test de mort ou du montée de niveau
        self.isDead()
        self.level_up()
        #on vérifie que l'arme est bien équipé
        self.current_weapon.update()
        #on interroge la fonction takeDamage pour effectuer ou non des dégats
        self.takeDamage(self.current_target, 1 / 30)
        #maj de la position
        self.position += self.direction * self.vitesse
        if GCR.current_map is not None:
            new_position = (self.position + self.direction * self.vitesse)
            # Si il n'y a pas de collision avec la map
            if not GCR.current_map.is_colliding(int(new_position.x), int(new_position.y)):
                self.position += self.direction * self.vitesse
            # Si il y en a
            else:
                self.direction = Vecteur()
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction
Example #5
0
    def update(self):
        """
        Mise à jour de l'état
        """
        # On se déplace aléaoirement une fois sur 4
        if random.random() < 1 / 4:
            vecteurs = [
                Vecteur(-1, 0),
                Vecteur(1, 0),
                Vecteur(0, -1),
                Vecteur(0, 1),
                Vecteur(0, 0)
            ]
            self.parent.direction = random.choice(vecteurs)
            GSR.entities_to_update.append(self.parent)

        # On essaie de voir si un client n'est pas à portée
        for entity in GSR.entities + [client.joueur for client in GSR.clients]:
            # On vérifie que l'on ne se vise pas
            if entity.id != self.parent.id:
                # Si une entité de niveau inferieur est a portée
                # ou bien une entité de même niveau mais de santé inférieur est à portée
                if entity.current_ship.tier < self.parent.current_ship.tier or \
                        (entity.current_ship.tier == self.parent.current_ship.tier and entity.vie <= self.parent.vie):
                    diff = entity.position - self.parent.position
                    if diff.distance() < 20:
                        self.parent.current_target = entity.id
                        self.parent.brain.prochain_etat = "follow"
                        self.en_vie = False
                        break
    def __init__(self):

        self.image = None
        self.position = Vecteur(200, 200)
        self.direction = Vecteur()
        self.image_direction = self.direction
        self.current_ship = None
        self.vie = 20
        self.current_weapon = None
        self.vitesse = None
        self.current_target = None
        self.id = uuid4()
        self.exp = 0
        self.size = (16, 16)
        self.firing = False

        self.spawnShip(random.choice(Entite.Tierlist[0]))
Example #7
0
class IA(Entite):
    """
    Classe héritée de Entite qui a pour particularité de possèder une FSM donc d'agir
    par états. Ce que nous appelerons ici "IA".

    Attributes:
        brain (FSM): cerveau qui controle les gestes de l'entité
    """
    def __init__(self):
        super().__init__()

        self.brain = FSM(self)

        # On ajoute les états au FSM
        self.brain.ajouter_etat("idle", Gambader(self))
        self.brain.ajouter_etat("follow", SuivreJoueur(self))
        self.brain.ajouter_etat("attack", Attaquer(self))
        self.brain.ajouter_etat("flee", Fuir(self))

        # On programme un premier état pour le cerveau
        self.brain.prochain_etat = "idle"

    def update(self, delta):
        """
        Met à jour l'entité ainsi que son cerveau

        Args:
            delta (float): temps écoulé depuis la dernière mise à jour
        """
        self.brain.update()
        self.isDead()
        self.level_up()
        self.current_weapon.update()
        self.takeDamage(self.current_target, 1 / 30)

        # Si on est sur le serveur, on ne veut pas que le client déplace les entités
        if GSR.carte is not None:
            new_position = (self.position + self.direction * self.vitesse)
            # Si il n'y a pas de collision avec la map
            if not GSR.carte.is_colliding(int(new_position.x),
                                          int(new_position.y)):
                self.position += self.direction * self.vitesse
            # Si il y en a
            else:
                self.direction = Vecteur()
        # On retient la dernière direction prise par le bateau
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction

    def __str__(self):
        return f"I.A. ({self.id}) : position ({self.position.x}, {self.position.y}), vie : {self.vie}\n" \
               f"Etats : {list(self.brain.etats.keys())}, Etat en cours : {self.brain.nom_etat_courant}"
    def __init__(self, parent: QObject = None, update_delta: float = 1/60 * 1000):
        super().__init__(parent)
        uic.loadUi(os.path.join(os.getcwd(), "assets", "ecran_jeu.ui"), self)

        # On cherche les éléments de l'écran
        self._game_scr_widget = self.findChild(QWidget, 'game_canvas')
        self._radar_holder = self.findChild(QLabel, 'minimap')
        self._chatbox_widget = self.findChild(QWidget, 'chatbox_anchor')
        self.input_chatbox = self.findChild(QLineEdit, 'input_chat')
        self.btn_send_chatbox = self.findChild(QPushButton, 'btn_sendchat')

        # On connecte les signaux/évènements
        self.btn_send_chatbox.clicked.connect(self.send_chat)
        self.input_chatbox.editingFinished.connect(self.send_chat)

        # Paramétrage de la minimap
        # minimap_background = QPixmap("assets/images/rade_brest.png")
        self.radar = Radar(125, 90)
        self.radar_widget = RadarWidget(self.radar, self._radar_holder)

        # Création du jeu
        self.game_canvas = CanvasJeu(self._game_scr_widget)
        self.game_canvas.setMouseTracking(True)
        self.game_canvas.setFocus()

        # Création de la chatbox
        self.chatbox = ChatBox(self._chatbox_widget)
        self.chatbox.add_line("[+] Vous avez rejoint la partie")
        self.chatbox.add_line("[+] Pensez à faire /start pour lancer le jeu !")
        self.chatbox.update()
        GCR.chatbox = self.chatbox

        # Création du joueur
        GCR.joueur = Joueur(position=Vecteur(700, 300))

        # Création de la map
        rade_data = img_vers_array("assets/carte_rade_brest.jpg")
        carte = Carte(rade_data.shape, (8, 8), rade_data)
        GCR.current_map = carte

        # On donne un titre à la fenêtre
        self.setWindowTitle("La Bataille Brestoise - Alexandre F. & Guillaume L.")

        # On itère toutes les X secondes pour mettre à jour l'écran
        self._timer = QTimer()
        # la fonction partial permet d'envoyer des arguments dans la fonction connect
        self._timer.timeout.connect(functools.partial(self.update, update_delta / 1000))
        self._timer.start(int(update_delta))
    def update(self, delta: float) -> None:
        """
        Met à jour les éléments essentiels au fonctionnement de l'entité comme sa position
        ou bien sa direction.
        Le déplacement est implémenté du coté client (joueur) ou serveur (entite).

        Args:
            delta (float): temps mis entre l'itération précédente et l'itération actuelle
        """
        self.isDead()
        self.level_up()
        self.current_weapon.update()
        self.takeDamage(self.current_target, 1 / 30)
        self.position += self.direction * self.vitesse
        # On retient la dernière direction prise par le bateau
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction
    def handle_input(self, message: str) -> None:
        """
        Prend en charge l'entrée utilisateur et essaie de parser la commande
        envoyée.

        Args:
            message (str): commande entrée par l'utilisateur
        """
        message = message.lower()
        cmd, *args = message.split(" ")

        if cmd == "help":
            print("Liste des commandes disponibles :")
            for c in self.COMMANDS:
                print("- " + c)
        elif cmd == "playerlist":
            print(f"Il y a {len(GSR.clients)} joueurs connectés :")
            for c in GSR.clients:
                print(f"-  {c.username} ({c.joueur.id})")
        elif cmd == "stop":
            print("Arrêt du serveur ...")
            GSR.running = False
        elif cmd == "entities":
            print("Nombre d'entitées existantes : {}".format(len(
                GSR.entities)))
        elif cmd == "getentity" and len(args) == 1:
            print("{} : {}".format(args[0], GSR.entities[int(args[0])]))
        elif cmd == "spawn" and len(args) == 3:
            t, x, y = args
            e = None
            if t == "entite":
                e = Entite()
            elif t == "ia":
                e = IA()
            else:
                print(f"Type d'entité non reconnu : {t}")
                return
            e.position = Vecteur(int(x), int(y))
            e.set_image("assets/images/plaisance.png")
            GSR.entities.append(e)
            print(f"Apparition de {e}")
        else:
            print(
                "Commande non reconnue ... Tapez 'help' pour la liste des commandes"
            )
    def setup(self) -> None:
        """
        Appelée une seule fois, permet de mettre en place le jeu.
        """
        rade_data = img_vers_array("assets/carte_rade_brest.jpg")
        GSR.carte = Carte(rade_data.shape, (8, 8), rade_data)

        proportion = [0] * 5 + [1] * 4 + [2] * 3 + [3] * 2 + [4] * 1
        for k in range(self.max_entities):
            e = IA()
            x = y = -1
            while GSR.carte.is_colliding(x, y):
                x = random.randint(0, GSR.carte.shape[0])
                y = random.randint(0, GSR.carte.shape[1])
            e.position = Vecteur(x, y)
            # On donne un tier aléatoire à l'entité
            tier = random.choice(proportion)
            e.exp = 0 if tier == 0 else Entite.exp_treshold[tier - 1]
            GSR.entities.append(e)
    def isDead(self) -> None:
        """
        Test si le joueur à encore assez de points de vie. si les points de vie sont à 0,
        le joueur respawn dans un navire du tier inferieur, l'exp est reset au treshold du tier inferieur
        """

        if self.vie <= 0:
            if self.current_ship.tier < 3:
                self.spawnShip(random.choice(Entite.Tierlist[0]))
                self.exp = 0
            else:
                self.spawnShip(random.choice(Entite.Tierlist[self.current_ship.tier - 1]))
                self.exp = Entite.exp_treshold[self.current_ship.tier - 2]

            # L'entité ou joueur respawn dans un endroit aléatoire pour éviter le respawnkill
            carte = GCR.current_map if GCR.current_map is not None else GSR.carte
            x = y = -1
            while GSR.carte.is_colliding(x, y):
                x = random.randint(0, carte.shape[0])
                y = random.randint(0, carte.shape[1])
            self.position = Vecteur(x, y)
    def keyPressEvent(self, e: QKeyEvent) -> None:
        """
        Fonction héritée de Qt qui permet de prendre en charge les évènements du type
        touche clavier.

        Args:
            e (PyQt5.QtGui.QKeyEvent): évènement de type touche de clavier
        """

        # Si on appuie sur Echap on veut fermer la fenètre de jeu
        if e.key() == Qt.Key_Escape:
            self.parent().parent().parent().close()
        # si c'est la touche espace on autorise ou non le tir
        elif e.key() == Qt.Key_Space:
            GCR.joueur.firing = not GCR.joueur.firing
            if GCR.joueur.firing:  #si tir on joue le son
                playlist = QMediaPlaylist(self.sound_player)
                url = None
                if type(GCR.joueur.current_weapon) in (CanonAutomatique, C50):
                    url = QUrl.fromLocalFile(os.path.join(os.getcwd(), "assets", "sfx", "canonauto.mp3"))
                elif type(GCR.joueur.current_weapon) in (Canon, CanonSuperRapido):
                    url = QUrl.fromLocalFile(os.path.join(os.getcwd(), "assets", "sfx", "large_gun.mp3"))
                elif type(GCR.joueur.current_weapon) in (Mistral, MM40, Rafale):
                    url = QUrl.fromLocalFile(os.path.join(os.getcwd(), "assets", "sfx", "missile.mp3"))
                elif type(GCR.joueur.current_weapon) in (TorpilleLourde, TorpilleLegere):
                    url = QUrl.fromLocalFile(os.path.join(os.getcwd(), "assets", "sfx", "sousmarin.mp3"))
                if url is not None:
                    playlist.addMedia(QMediaContent(url))
                    playlist.setPlaybackMode(QMediaPlaylist.Loop)
                    self.sound_player.setPlaylist(playlist)
                    self.sound_player.play()
            else:
                self.sound_player.stop()

            GCR.log.log(Logger.DEBUG, "Space")
        elif e.key() in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]:
            # On crée un vecteur qui donnera la direction voulue par le joueur
            dir = Vecteur()
            if e.key() == Qt.Key_Left:
                GCR.log.log(Logger.DEBUG, "Flèche gauche")
                dir.x -= 1
            elif e.key() == Qt.Key_Right:
                GCR.log.log(Logger.DEBUG, "Flèche droite")
                dir.x += 1
            elif e.key() == Qt.Key_Up:
                GCR.log.log(Logger.DEBUG, "Flèche haut")
                dir.y -= 1
            elif e.key() == Qt.Key_Down:
                GCR.log.log(Logger.DEBUG, "Flèche bas")
                dir.y += 1

            # Si c'est une touche de déplacement on regarde si c'est pour arrêter le bateau
            if e.key() in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down] and e.key() == self.last_key:
                self.last_key = None
                dir = Vecteur()
            # Sinon on enregistre juste la touche
            elif e.key() in [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down]:
                self.last_key = e.key()

            # On indique à l'entité joueur quelle est la direction à prendre
            GCR.joueur.direction = dir
class Entite:
    """
    Definition d'une entité du jeu.

    Attributes:
        vie (int): vie de l'entité
        vitesse (int): vitesse de déplacement de l'unité
        image (str): chemin vers l'image de référence de l'entité
        position (Vecteur): position du joueur
        direction (Vecteur): direction vers lequel se dirige le joueur
        image_direction (Vecteur): direction vers laquelle l'image doit être tournée
        current_ship(batiment) : batiment actuellement manoeuvré par l'entité
        current_weapon(arme) : arme actuellement équipé par l'entité
        current_target(entite) : entité actuellement visée par l'entité
        id (str): identifiant unique attribué à l'entité
        exp (float): expérience actuelle de l'entité
        size (list): taille de l'image de l'entité
    """
    # paliers d'experience pour passer au niveau suivant
    exp_treshold = [1000, 3000, 8000, 24000]
    # expérience nécessaire pour gagner
    exp_win = 50000
    # Modificateur d'expérience gagnée
    taux_exp_gain = 0.1
    #bonus d'exp pour un kill
    exp_boost = 100
    #liste des batiments par tier
    Tierlist = [[BE, BIN], [AVISO, CMT, BH], [FS, F70], [FREMM, FDA, SNA], [PA, SNLE]]
    def __init__(self):

        self.image = None
        self.position = Vecteur(200, 200)
        self.direction = Vecteur()
        self.image_direction = self.direction
        self.current_ship = None
        self.vie = 20
        self.current_weapon = None
        self.vitesse = None
        self.current_target = None
        self.id = uuid4()
        self.exp = 0
        self.size = (16, 16)
        self.firing = False

        self.spawnShip(random.choice(Entite.Tierlist[0]))

    def set_image(self, img_path: str) -> None:
        """
        Affecte une image à l'entité à partir d'un path (valable pour batiment ou arme par exemple)

        Args:
            img_path (str): chemin d'accès relatif à l'image
        """
        self.image = img_path

    def is_alive(self) -> bool:
        """
        Vérifie si l'entité est encore en vie.

        Returns:
            is_alive (bool): si l'entité est encore en vie
        """
        return self.vie > 0

    def render(self, qp: QPainter, x: float, y: float) -> None:
        """
        Fait le rendu de l'entité sur l'écran à l'aide du painter de
        ce dernier.

        Args:
            qp (QPainter): painter de la surface sur laquelle dessiner
            x (float): position X du milieu de l'entité par rapport au joueur
            y (float): position Y du milieu de l'entité par rapport au joueur
        """
        # Si on a définit une image on la dessine
        if self.image is not None:
            img = QPixmap(self.image)
            if self.image_direction.equal(Vecteur(0.0, 1.0)):  # direction sud
                rotation = 180
            elif self.image_direction.equal(Vecteur(1.0, 0.0)):  # direction est
                rotation = 90
            elif self.image_direction.equal(Vecteur(-1.0, 0.0)):  # direction ouest
                rotation = 270
            else:  # direction nord
                rotation = 0
            img_rotated = img.transformed(QTransform().rotate(rotation))
            # xoffset = (img_rotated.width() - img.width()) / 2
            # yoffset = (img_rotated.height() - img.height()) / 2
            # img_rotated = img_rotated.copy(xoffset, yoffset, img.width(), img.height())
            img_rot_scal = img_rotated.scaled(*self.size)
            qp.drawPixmap(QPoint(x - self.size[0] // 2, y - self.size[1] // 2), img_rot_scal)
            self.draw_life_bar(qp, x, y)

    def draw_life_bar(self, qp: QPainter, x: float, y: float) -> None:
        """
        Fait le rendu de la barre de vie de l'entité à l'aide du painter de l'écran

        Args:
            qp (QPainter): painter de la surface sur laquelle dessiner
            x (float): position X du milieu de l'entité par rapport au joueur
            y (float): position Y du milieu de l'entité par rapport au joueur
        """
        bar_size = (40, 4)
        text_size = 12

        # On dessine le carré rouge
        pen = QPen(Qt.black)
        qp.setPen(pen)
        qp.setBrush(QColor(125, 125, 125))
        qp.drawRect(x - bar_size[0] // 2 + text_size - 2,
                    y - self.size[1] // 2 - 10,
                    bar_size[0] - text_size,
                    bar_size[1])

        # On dessine le carré vert
        life_col = QColor(0, 255, 0)
        enemy_col = QColor(255, 0, 0)
        pen = None
        if self.id == GCR.joueur.id:
            qp.setBrush(life_col)
            pen = QPen(life_col)
        else:
            qp.setBrush(enemy_col)
            pen = QPen(enemy_col)
        qp.setPen(pen)
        life_size = (bar_size[0] - text_size) * self.vie / self.current_ship.hitpoints
        qp.drawRect(1 + x - bar_size[0] // 2 + text_size - 2,
                    1 + y - self.size[1] // 2 - 10,
                    life_size - 2,
                    bar_size[1] - 2)

        # On dessine le tier du bâtiment
        tier = self.current_ship.tier
        qp.setPen(QPen(Qt.white))
        qp.drawText(QRect(x - bar_size[0] // 2,
                          y - self.size[1] // 2 - 10 - text_size // 2,
                          text_size,
                          text_size), 0, str(tier))

    def draw_target(self, qp: QPainter, x: float, y: float, tar_width: int = 2) -> None:
        """
        Fait le rendu du contour (carré rouge) lors du ciblage de l'entité à l'aide du painter
        de l'écran.

        Args:
            qp (QPainter): painter de la surface sur laquelle dessiner
            x (float): position X du milieu de l'entité par rapport au joueur
            y (float): position Y du milieu de l'entité par rapport au joueur
            tar_width (int): épaisseur du trait lors du dessin de la cible

        Returns:

        """
        pen = QPen(Qt.red)
        pen.setWidth(tar_width)
        # On cherche à avoir le fond transparent pour ne garder que le contour
        qp.setBrush(QColor(255, 255, 255, 0))
        qp.setPen(pen)
        qp.drawRect(x - self.size[0] // 2, y - self.size[1] // 2, *self.size)

    def __str__(self) -> str:
        """
        Retourne la représentation en chaine de caractères de l'entité
        """
        return f"Entité ({self.id}) : position ({self.position.x}, {self.position.y}), vie : {self.vie}"

    def update(self, delta: float) -> None:
        """
        Met à jour les éléments essentiels au fonctionnement de l'entité comme sa position
        ou bien sa direction.
        Le déplacement est implémenté du coté client (joueur) ou serveur (entite).

        Args:
            delta (float): temps mis entre l'itération précédente et l'itération actuelle
        """
        self.isDead()
        self.level_up()
        self.current_weapon.update()
        self.takeDamage(self.current_target, 1 / 30)
        self.position += self.direction * self.vitesse
        # On retient la dernière direction prise par le bateau
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction
        # self.direction = Vecteur()

    def ciblage(self, entite: "Entite") -> None:
        """
        Permet de définir une cible pour l'attaque notamment.
        La référence à la cible est l'identifiant unique.

        Args:
            entite (Entite): l'entité à cibler, doit posséder un uuid
        """
        if entite is None:
            self.current_target = None
        else:
            self.current_target = entite.id

    @staticmethod
    def findById(e_id: str, entities: list) -> "Entite":
        """
        Methode outil pour trouver une entité à partir de son
        identifiant dans une liste donnée. (permet de retrouver une cible notamment pour TakeDamage)

        Args:
            e_id (str): identifiant de l'entité à trouver
            entities (list): liste des entités dans laquelle chercher
        """
        for e in entities:
            if e.id == e_id:
                return e
        return None

    def spawnShip(self, ship: Batiment) -> None:
        """
        Permet de générer un nouveau batiment au joueur par exemple
        quand il meurt ou bien si il monte de niveau

        Args:
            ship (Batiment): bâtiment à générer
        """
        self.current_ship = ship()
        self.set_image(self.current_ship.imgpath)
        self.size = self.current_ship.size
        self.current_weapon = self.current_ship.armes[0](self)
        self.vie = self.current_ship.hitpoints
        self.vitesse = self.current_ship.vmax

    def level_up(self) -> None:
        """
        Fonction testant la montée de niveau à chaque tour. Le joueur passe au niveau superieur
        si l'experience est supérieure à un des paliers de niveau (définis dans exp_treshold).
        le test est exécuté dans le gameloop.
        """
        for i in range(0, 4):
            if self.exp > Entite.exp_treshold[i] and self.current_ship.tier == i + 1:
                # le joueur passe au niveau superieur
                tier = self.current_ship.tier
                self.spawnShip(random.choice(Entite.Tierlist[tier]))

                # Si on est du côté client
                if GCR.joueur is not None:
                    GCR.chatbox.add_line(f"[+] Vous avez monté au niveau {self.current_ship.tier} !")
                    GCR.chatbox.add_line(f"[+] Vous avez obtenu le bâtiment : {self.current_ship.nom_unite} !")
                break

    # TODO : a implementer dans le gameloop

    def isDead(self) -> None:
        """
        Test si le joueur à encore assez de points de vie. si les points de vie sont à 0,
        le joueur respawn dans un navire du tier inferieur, l'exp est reset au treshold du tier inferieur
        """

        if self.vie <= 0:
            if self.current_ship.tier < 3:
                self.spawnShip(random.choice(Entite.Tierlist[0]))
                self.exp = 0
            else:
                self.spawnShip(random.choice(Entite.Tierlist[self.current_ship.tier - 1]))
                self.exp = Entite.exp_treshold[self.current_ship.tier - 2]

            # L'entité ou joueur respawn dans un endroit aléatoire pour éviter le respawnkill
            carte = GCR.current_map if GCR.current_map is not None else GSR.carte
            x = y = -1
            while GSR.carte.is_colliding(x, y):
                x = random.randint(0, carte.shape[0])
                y = random.randint(0, carte.shape[1])
            self.position = Vecteur(x, y)

    def isWinning(self) -> bool:
        """
        Fonction testant si le joueur à gagné ou non, en comparant l'exp à la valeur exp_win

        Returns:
            isWinning (bool): le joueur à gagné ou non
        """
        if self.exp >= Entite.exp_win:
            return True
        else:
            return False

    # TODO : à implementer dans le gameloop

    def takeDamage(self, target_id: str, refresh_rate: float) -> None:
        """
        Fonction implementant les dégats infligés à une entite si la touche espace à été pressé. Elle test si le joueur à encore
        assez de PV, sinon elle provoque le Respawn. l'EXP gagné par le joueur ennemi est proportionnel au dégats infligés.
        le joueur obtient un boost d'XP proportionnel à son tier en cas de frag ( IE il tue un ennemi).

        Args:
            target_id (str): entite ennemie qui subit les dégats
            refresh_rate (float): fréquence de rafraichissement du jeu
        """
        if self.firing:
            # Géré niveau client
            if GCR.joueur is not None:
                #entite_ennemie = self.findById(target_id, GCR.entities)
                GCR.tcp_client.send({"action": "damage", "attacker": self.id, "target": target_id})
            # On est bien côté serveur
            else :
                entite_ennemie = self.findById(target_id, GSR.entities + [client.joueur for client in GSR.clients])
                if entite_ennemie == None :
                    return
                degats = self.current_weapon.DPS * refresh_rate
                exp = 0
                if entite_ennemie.vie - degats < 0:
                    exp += Entite.exp_boost * self.current_ship.tier ** 2
                    entite_ennemie.vie = 0
                else:
                    entite_ennemie.vie = entite_ennemie.vie - degats
                self.exp += (Entite.taux_exp_gain * degats)

    def equiper(self, arme: Arme):
        """
        Permet à un joueur d'équiper une arme. Elle sert à attendre un délai de mise en oeuvre d'une arme avant de
        pouvoir tirer.
        Plus l'arme est importante et plus son temps d'équipement est grand. Une arme de conception plus récente
        aura un temps d'équipement plus court.
        Les valeurs précises sont disponibles dans le tableau d'équillibrage. (elle n'est pas implémenté à l'heure actuelle car le widget n'est pas codé)

        Args:
            arme (Arme): l'arme à équiper

        """

        self.current_weapon = arme
        self.current_weapon.first_equip()
Example #15
0
    def test_entre(self):
        a = Vecteur(0, 0)
        b = Vecteur(10, 0)
        c = Vecteur(5, 0)

        self.assertTrue(c.est_entre(a, b))
Example #16
0
 def test_distance(self):
     a = Vecteur(1, 1)
     self.assertIsInstance(a.distance(), float)
     self.assertEqual(a.distance(), np.sqrt(2))
Example #17
0
 def test_creation(self):
     a = Vecteur()
     self.assertIsInstance(a, Vecteur)
Example #18
0
 def test_soustraction(self):
     a = Vecteur(5, 5)
     b = Vecteur(1, 2)
     self.assertEqual(str(a - b), str(Vecteur(4, 3)))
Example #19
0
 def test_multiplication(self):
     a = Vecteur(0, 0)
     b = Vecteur(5, 9)
     self.assertEqual(str(a * b), str(Vecteur(0, 0)))
     c = 5
     self.assertEqual(str(b * c), str(Vecteur(25, 45)))
Example #20
0
 def test_addition(self):
     a = Vecteur(0, 1)
     b = Vecteur(-1, 5)
     self.assertEqual(str(a + b), str(Vecteur(-1, 6)))
Example #21
0
 def __init__(self, peername: str, transport: Transport):
     self.peername = peername
     self.transport = transport
     self.username = None
     self.uuid = None
     self.joueur = Joueur(Vecteur())
Example #22
0
 def test_position(self):
     e = Entite()
     e.position = Vecteur(200, 200)
     self.assertEqual(str(e.position), str(Vecteur(200, 200)))
Example #23
0
class Joueur(Entite):
    """
    Définition d'un joueur par héritage d'une entite ( comprendre Joueur = Entité humaine)

    Attributes:
        detection_radius(int): distance de détection en cases
    """
    def __init__(self, position: Vecteur):
        super().__init__()
        self.position = position
        self.detection_radius = 10 # en cases

    def update(self, delta: float) -> None:
        """
        Met à jour les éléments essentiels au fonctionnement de l'entité comme sa position
        ou bien sa direction.

        Args:
            delta (float): temps mis entre l'itération précédente et l'itération actuelle, ce temps est cruciale dans les performances du programme
        """
        #test de mort ou du montée de niveau
        self.isDead()
        self.level_up()
        #on vérifie que l'arme est bien équipé
        self.current_weapon.update()
        #on interroge la fonction takeDamage pour effectuer ou non des dégats
        self.takeDamage(self.current_target, 1 / 30)
        #maj de la position
        self.position += self.direction * self.vitesse
        if GCR.current_map is not None:
            new_position = (self.position + self.direction * self.vitesse)
            # Si il n'y a pas de collision avec la map
            if not GCR.current_map.is_colliding(int(new_position.x), int(new_position.y)):
                self.position += self.direction * self.vitesse
            # Si il y en a
            else:
                self.direction = Vecteur()
        if not self.direction.equal(Vecteur(0.0, 0.0)):
            self.image_direction = self.direction

    def isDead(self) -> None:
        """
        Test si le joueur à encore assez de points de vie. si les points de vie sont à 0,
        le joueur respawn dans un navire du tier inferieur, l'exp est reset au threshold du tier inferieur
        """
        if self.vie <= 0 and GCR.chatbox is not None:
            #la surcharge de la fonction est nécessaire pour afficher un message dans le chat (un bot ne peut pas ecrire dans le chat car il n'est pas relié à un client)
            GCR.chatbox.add_line(f"Vous êtes mort, respawn au tier inferieur")
        super().isDead()

    def takeDamage(self, target_id: str, refresh_rate: float) -> None:
        """
        Fonction implementant les dégats infligés à une entite si la touche espace à été pressé. Elle test si le joueur à encore
        assez de PV, sinon elle provoque le Respawn. l'EXP gagné par le joueur ennemi est proportionnel au dégats infligés.
        le joueur obtient un boost d'XP proportionnel à son tier en cas de frag ( IE il tue un ennemi).

        Args:
            entite_ennemie (Entite): entite ennemie qui inflige les dégats
            refresh_rate (float): fréquence de rafraichissement du jeu
        """
        if self.firing:
            # Géré niveau client
            if GCR.joueur is not None:
                #entite_ennemie = self.findById(target_id, GCR.entities)
                GCR.tcp_client.send({"action": "damage", "attacker": self.id, "target": target_id})
            # On est bien côté serveur
            else :
                entite_ennemie = self.findById(target_id, GSR.entities + [client.joueur for client in GSR.clients])
                if entite_ennemie == None :
                    return
                degats = self.current_weapon.DPS * refresh_rate
                exp = 0
                if entite_ennemie.vie - degats < 0:
                    exp += Entite.exp_boost * self.current_ship.tier ** 2
                    entite_ennemie.vie = 0
                else:
                    entite_ennemie.vie = entite_ennemie.vie - degats
                # Si c'est un joueur
                for client in GSR.clients:
                    if client.joueur.id == self.id:
                        exp += (Entite.taux_exp_gain * degats)
                        client.transport.write(pickle.dumps({"action": "gain_exp",
                                                             "exp": exp}))
                        break
                else:
                    GSR.log.log(Logger.ERREUR, f"Joueur {self.id} non trouvé !")