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 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)
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 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]))
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()
def test_entre(self): a = Vecteur(0, 0) b = Vecteur(10, 0) c = Vecteur(5, 0) self.assertTrue(c.est_entre(a, b))
def test_distance(self): a = Vecteur(1, 1) self.assertIsInstance(a.distance(), float) self.assertEqual(a.distance(), np.sqrt(2))
def test_creation(self): a = Vecteur() self.assertIsInstance(a, Vecteur)
def test_soustraction(self): a = Vecteur(5, 5) b = Vecteur(1, 2) self.assertEqual(str(a - b), str(Vecteur(4, 3)))
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)))
def test_addition(self): a = Vecteur(0, 1) b = Vecteur(-1, 5) self.assertEqual(str(a + b), str(Vecteur(-1, 6)))
def __init__(self, peername: str, transport: Transport): self.peername = peername self.transport = transport self.username = None self.uuid = None self.joueur = Joueur(Vecteur())
def test_position(self): e = Entite() e.position = Vecteur(200, 200) self.assertEqual(str(e.position), str(Vecteur(200, 200)))
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é !")