diff --git a/-Client_Launch.sh b/-Client_Launch.sh deleted file mode 100644 index 6498fe8..0000000 --- a/-Client_Launch.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -python -m pip install pygame -python -m pip install pygame_gui -python client.py \ No newline at end of file diff --git a/-Server_Launch.sh b/-Server_Launch.sh deleted file mode 100644 index 8252d24..0000000 --- a/-Server_Launch.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -python -m pip install pygame -python -m pip install pygame_gui -python server.py diff --git a/.gitignore b/.gitignore index 05acda6..b0d2be2 100644 --- a/.gitignore +++ b/.gitignore @@ -183,4 +183,5 @@ fabric.properties .idea/caches/build_file_checksums.ser # idea folder, uncomment if you don't need it -# .idea \ No newline at end of file +# .idea +/output/ diff --git a/XML/aircrafts.xml b/Python/XML/aircraft.xml similarity index 100% rename from XML/aircrafts.xml rename to Python/XML/aircraft.xml diff --git a/XML/mapAPS.xml b/Python/XML/carteSecteur.xml similarity index 87% rename from XML/mapAPS.xml rename to Python/XML/carteSecteur.xml index b8cbf92..c2944ed 100644 --- a/XML/mapAPS.xml +++ b/Python/XML/carteSecteur.xml @@ -6,6 +6,43 @@ 245 # plancher de notre secteur 365 # plafond + # liste des secteurs adjacents + + 125.830 + + + 127.840 + + + 128.050 + + + 131.175 + + + 132.740 + + + 119.275 + + + 124.2 + + + 124.080 + + + 134.610 + + + 130.275 + True + + + 127.675 + True + + @@ -162,8 +199,8 @@ - - + + 1109 915 @@ -220,13 +257,14 @@ 1107 1266 - - + + DEPART SO + LSGG LSGG 120 @@ -298,7 +336,7 @@ TRANSIT - SE + E-SE N BURGO @@ -393,6 +431,7 @@ DEPART SO + LIML LIML 20 @@ -455,7 +494,7 @@ JUVEN - 260 + 260 M2 @@ -489,7 +528,7 @@ SEVET - 250 + 250 G2 @@ -517,7 +556,7 @@ SEVET - 250 + 250 G2 @@ -653,6 +692,7 @@ DEPART + LFVB S BST @@ -683,6 +723,7 @@ DEPART + LFVB SE BST @@ -713,6 +754,7 @@ DEPART + LFVB SO BST @@ -745,34 +787,83 @@ - LOWI + EDDT + EDDF + EKCH + EHAM + ESSB + ENGM + EPWA + EGLL EGBB + EGLL EIDW + EGCC + EGGD + EGKK + LFPG + LFPO + LFQQ EGBB + LFPG + LFPO + LFQQ + LFRS + EIDW + EGCC + EGGD + EGKK + BIRK DAAG + DRRN + DTTA + GCTS LPPT + LPPR + LFBD + LFBO + LFBP + LFBT + LFBZ + LEIB + LEMD + LEMG + LEPA + LFKC + LFKJ + DAAG + DRRN + DTTA + + + LGAT LGAV LGIR - + LIMF + LIPZ + - LIML + LOWW + LOWI + LOWS + LHBP diff --git a/XML/simu.xml b/Python/XML/simu.xml similarity index 62% rename from XML/simu.xml rename to Python/XML/simu.xml index 3858b0a..fb9033f 100644 --- a/XML/simu.xml +++ b/Python/XML/simu.xml @@ -2,14 +2,14 @@ 1002 - + FCACA - DA42 - GAI-JUVEN + DH8D + ABISA-BERNI 30000 False - 720 - 1339 + 1400 + 1300 300 diff --git a/Python/__init__.py b/Python/__init__.py new file mode 100644 index 0000000..b111089 --- /dev/null +++ b/Python/__init__.py @@ -0,0 +1,2 @@ + +__package__ = "Python" diff --git a/client.py b/Python/client.py similarity index 72% rename from client.py rename to Python/client.py index df9604c..2c61f36 100644 --- a/client.py +++ b/Python/client.py @@ -1,12 +1,18 @@ -import pygame -import horloge -from network import Network -import server_browser -from player import * -import pygame_gui -import interface -from paquets_avion import * + +# Native import +import time import math +from pathlib import Path +import os + +# fichiers +import Python.horloge as horloge +from Python.network import Network +import Python.server_browser as server_browser +from Python.player import * +import Python.interface as interface +from Python.paquets_avion import * +import Python.outils_radar as outils_radar # recherche de tous les serveurs sur le réseau address = server_browser.serverBrowser() @@ -18,7 +24,8 @@ height = 1000 win = pygame.display.set_mode((width, height)) -manager = pygame_gui.UIManager((width, height), 'theme.json') +path = Path(os.getcwd()) +manager = pygame_gui.UIManager((width, height), path / 'ressources' / 'theme.json') pygame.display.set_caption("Client") temps = pygame.time.get_ticks() @@ -34,10 +41,13 @@ def main(server_ip: str): distance = 10 # menus + conflitBool = False + conflitGen = None menuAvion = None menuATC = None menuValeurs = None flightDataWindow = None + menuRadar = interface.menuRadar() # on se connecte au serveur n = Network(server_ip) @@ -48,6 +58,7 @@ def main(server_ip: str): while packet is None and i < 200: n = Network(server_ip) packet = n.getP() + time.sleep(0.3) i +=1 perfos = packet.perfos @@ -64,6 +75,14 @@ def main(server_ip: str): alidadPos = (0, 0) curseur_alidad = False + # cercles + curseur_cercles = False + cerclePos = None + + # alisep + curseur_aliSep = False + sepDict = {'A': outils_radar.aliSep('A'), 'B': outils_radar.aliSep('B'), 'C': outils_radar.aliSep('C')} + # scroll and zoom zoomDef = 0.5 scrollDef = [width / 4, height/4] @@ -76,7 +95,6 @@ def main(server_ip: str): # vecteurs et type vecteurs = False - affichage_type_avion = False vecteurSetting = 6 # fenêtre nouvel avion @@ -118,6 +136,9 @@ def main(server_ip: str): game = packet.game dictAvions = packet.dictAvions + for sep in sepDict.values(): + sep.calculation(carte) + for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -149,8 +170,41 @@ def main(server_ip: str): avion.dragOffset = calculateEtiquetteOffset(avion.etiquette.container) # on vérifie que l'alidade n'est pas actif - elif event.type == pygame_gui.UI_BUTTON_PRESSED and not curseur_alidad: + elif event.type == pygame_gui.UI_BUTTON_PRESSED and curseur_alidad: + empecherDragging = False + + elif event.type == pygame_gui.UI_BUTTON_PRESSED: empecherDragging = False + + if menuRadar.checkActive(): + action = menuRadar.checkEvent(event) + + if action is not None: + + if type(action) in [list, tuple]: + + if action[0] == 'VecteursToggle': + if vecteurSetting == action[1]: + vecteurs = not vecteurs + else: + vecteurs = True + vecteurSetting = action[1] + + elif action[0] == 'Vecteurs': + vecteurSetting = action[1] + + elif action[0] == 'Sep': + sepDict[action[1]].kill() + curseur_aliSep = action[1] + pygame.mouse.set_cursor(pygame.cursors.diamond) + elif action == 'Alidade': + curseur_alidad = True + pygame.mouse.set_cursor(pygame.cursors.broken_x) + + elif action == 'Cercles': + cerclePos = None + curseur_cercles = True + pygame.mouse.set_cursor(pygame.cursors.broken_x) # on regarde si notre menu pour le pilote est actif if menuAvion is not None: @@ -175,6 +229,19 @@ def main(server_ip: str): if type(action) in [list, tuple]: # si c'est un tuple alors cela correspond à une requête localRequests.append(action) + if conflitGen is not None: + action = conflitGen.checkEvent(event) + + if action: + if type(action) is tuple: + localRequests.append((len(dictAvions), "DelayedAdd", action)) + else: + localRequests.append((len(dictAvions), "Add", action)) + for avion in dictAvionsAff.values(): + avion.conflitSelected = False + conflitBool = False + conflitGen = None + if menuValeurs is not None: # si on valide les modifs, alors la fonction checkEvent retourne les modifs @@ -185,10 +252,21 @@ def main(server_ip: str): elif action in ['HDG', 'DCT']: menuValeurs = interface.menuValeurs(menuValeurs.avion, pygame.mouse.get_pos(), action) + if curseur_aliSep: + for sep in sepDict: + if sep == curseur_aliSep: + for avion in dictAvionsAff.values(): + if avion.checkClicked(event): + if sepDict[sep].linkAvion(avion, carte): + curseur_aliSep = False + pygame.mouse.set_cursor(pygame.cursors.arrow) + else: for avion in dictAvionsAff.values(): # pour chaque avion - action = avion.checkEvent(event, pilote) # on vérifie si l'event est associé avec ses boutons + action = avion.checkEvent(event, pilote, conflitBool) + + # on vérifie si l'event est associé avec ses boutons if type(action) in [list, tuple]: # si c'est un tuple alors cela correspond à une requête localRequests.append(action) @@ -211,6 +289,7 @@ def main(server_ip: str): # on vérifie que newPlane n'est pas None (les valeurs ont été renvoyés) if newPlaneData: + # on crée alors un nouvel avion FL = None PFL = None @@ -230,7 +309,11 @@ def main(server_ip: str): FL=FL, PFL=PFL) - localRequests.append((len(dictAvions), "Add", newPlane)) + if newPlaneData['conflit']: + conflitGen = outils_radar.conflictGenerator(win, newPlane, carte) + conflitBool = True + else: + localRequests.append((len(dictAvions), "Add", newPlane)) elif event.type == pygame_gui.UI_TEXT_ENTRY_CHANGED: nouvelAvionWin.checkFields(event) @@ -262,17 +345,36 @@ def main(server_ip: str): if curseur_alidad: alidad = True alidadPos = pygame.mouse.get_pos() + elif curseur_cercles: + souris = pygame.mouse.get_pos() + cerclePos = ((souris[0] - scroll[0]) / zoom, (souris[1] - scroll[1]) / zoom) - elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 3 and curseur_alidad: + elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 3 and (curseur_alidad or curseur_cercles or curseur_aliSep): alidad = False curseur_alidad = False + curseur_cercles = False + curseur_aliSep = False pygame.mouse.set_cursor(pygame.cursors.arrow) + elif (event.type == pygame.MOUSEBUTTONUP and event.button == 1 and not empecherDragging + and not (curseur_aliSep or curseur_alidad) and mouseDownTime + 150 >= pygame.time.get_ticks()): + if curseur_cercles: + curseur_cercles = False + else: + menuRadar.show() + + elif event.type == pygame.MOUSEBUTTONUP and event.button == 2 and not empecherDragging and conflitGen: + mouse = pygame.mouse.get_pos() + conflitGen.computeSpawn(((mouse[0] - scroll[0]) / zoom, (mouse[1] - scroll[1]) / zoom), carte) + manager.process_events(event) if menuAvion is not None: menuAvion.checkSliders() + if conflitGen is not None: + conflitGen.checkScrollBar(carte) + """Dragging""" if (pygame.mouse.get_pressed()[0] and not empecherDragging and @@ -284,7 +386,7 @@ def main(server_ip: str): drag = [pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1]] else: drag = [pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1]] - if not curseur_alidad: + if not curseur_alidad and not curseur_aliSep and not curseur_cercles: pygame.mouse.set_cursor(pygame.cursors.arrow) """Keys""" @@ -298,38 +400,12 @@ def main(server_ip: str): scroll = scrollDef pressing = True delaiPressage = pygame.time.get_ticks() - if keys[pygame.K_t]: # type avions - affichage_type_avion = not affichage_type_avion - pressing = True - delaiPressage = pygame.time.get_ticks() - if keys[pygame.K_a]: # alidad start - curseur_alidad = True - pygame.mouse.set_cursor(pygame.cursors.broken_x) - pressing = True - delaiPressage = pygame.time.get_ticks() + if keys[pygame.K_f] and flightDataWindow is None: # Flight Data Window flightDataWindow = interface.flightDataWindow() pressing = True delaiPressage = pygame.time.get_ticks() - # commandes vecteurs - if keys[pygame.K_3]: - vecteurSetting = 3 - pressing = True - delaiPressage = pygame.time.get_ticks() - if keys[pygame.K_6]: - vecteurSetting = 6 - pressing = True - delaiPressage = pygame.time.get_ticks() - if keys[pygame.K_9]: - vecteurSetting = 9 - pressing = True - delaiPressage = pygame.time.get_ticks() - if keys[pygame.K_v]: - vecteurs = not vecteurs - pressing = True - delaiPressage = pygame.time.get_ticks() - # commandes pilote if keys[pygame.K_n] and nouvelAvionWin is None and pilote: nouvelAvionWin = interface.nouvelAvionWindow(carte['routes'], perfos) @@ -354,6 +430,7 @@ def main(server_ip: str): if keys[pygame.K_s]: localRequests.append((0, 'Save')) delaiPressage = pygame.time.get_ticks() + elif True not in pygame.key.ScancodeWrapper() and pygame.time.get_ticks() - delaiPressage >= 150: # on vérifie que plus aucune touche n'est pressée et on remet la variable à son état initial pressing = False @@ -379,24 +456,38 @@ def main(server_ip: str): if not nouvelAvionWin.checkAlive(): nouvelAvionWin = None + if menuRadar.checkActive(): + menuRadar.checkMenuHovered() + '''partie affichage''' # on remplit d'abord avec une couleur win.fill((90, 90, 90)) # on dessine les secteurs - for secteur in carte['secteurs']: + for zone in carte['zones']: liste_affichage_secteurs = [] - for point in secteur['contour']: + for point in zone['contour']: pos = positionAffichage(point[0], point[1], zoom, scroll[0], scroll[1]) liste_affichage_secteurs.append((pos[0], pos[1])) - pygame.draw.polygon(win, secteur['couleur'], liste_affichage_secteurs) + pygame.draw.polygon(win, zone['couleur'], liste_affichage_secteurs) # on dessine les routes for segment in carte['segments']['TRANSIT']: pygame.draw.line(win, (150, 150, 150), (segment[0][0]*zoom + scroll[0], segment[0][1]*zoom + scroll[1]), (segment[1][0]*zoom + scroll[0], segment[1][1]*zoom + scroll[1]), 1) + # dessin des cercles concentriques + if cerclePos is not None: + + for i in range(15): + + pygame.draw.circle( + win, (120, 120, 120), + (cerclePos[0] * zoom + scroll[0], cerclePos[1] * zoom + scroll[1]), + 10 * i / mapScale * zoom, 1 + ) + # on dessine les points for nom, point in carte['points'].items(): pygame.draw.polygon(win, (155, 155, 155), ((point[0]*zoom + scroll[0], point[1]*zoom - 2 + scroll[1]), (point[0]*zoom + 2 + scroll[0], point[1]*zoom+2 + scroll[1]), (point[0]*zoom-2 + scroll[0], point[1]*zoom+2 + scroll[1])), 1) @@ -404,12 +495,23 @@ def main(server_ip: str): # win.blit(img, (point[0]*zoom + 10 + scroll[0], point[1]*zoom+10 + scroll[1])) # on affiche les avions - if pilote: - for avion in dictAvionsAff.values(): - avion.draw(win, zoom, scroll, vecteurs, vecteurSetting, carte['points']) - else: + + if conflitGen is not None: + conflitGen.draw(win, zoom, scroll) + color = [10, 10, 10] for avion in dictAvionsAff.values(): - avion.draw(win, zoom, scroll, vecteurs, vecteurSetting, carte['points']) + if color[0] <= 255 - 40: + color[0] += 70 + elif color[1] <= 255 - 40: + color[0] = 255 + color[1] += 70 + elif color[2] <= 255 - 40: + color[1] = 255 + color[2] += 70 + avion.drawEstimatedRoute(carte['points'], conflitGen.temps, color, win, zoom, scroll) + + for avion in dictAvionsAff.values(): + avion.draw(win, zoom, scroll, vecteurs, vecteurSetting, carte['points']) # on affiche les boutons manager.update(time_delta) diff --git a/geometry.py b/Python/geometry.py similarity index 55% rename from geometry.py rename to Python/geometry.py index 16b2819..48a6641 100644 --- a/geometry.py +++ b/Python/geometry.py @@ -1,5 +1,10 @@ + +# Native imports import math + +# Module imports import numpy as np +from scipy.optimize import minimize def calculateHeading(x: int, y: int, xPoint: int, yPoint: int): @@ -20,17 +25,21 @@ def calculateHeading(x: int, y: int, xPoint: int, yPoint: int): return heading -def calculateDistance(x1: int, y1: int, x2: int, y2: int): +def calculateDistance(x1: int | float, y1: int | float, x2: int | float, y2: int | float): return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) -def calculateShortestPoint(pointDroite1: list[float, float], pointDroite2: list[float, float], - point: list[float, float]): +def calculateShortestPoint(pointDroite1: list[float, float] | tuple[float, float], + pointDroite2: list[float, float] | tuple[float, float], + point: list[float, float] | tuple[float, float], + segment=False + ): """ Calcule le point sur une droite où la distance ets la plus courte à un point :param pointDroite1: 1er point pour définir la droite, vecteur2 (x, y) :param pointDroite2: 2em point pour définir la droite, vecteur2 (x, y) :param point: point avec lequel on fait le calcul, vecteur2 (x, y) + :param segment: si on doit considérer comme un objet ou comme une droite :return: retourne le point d'intersection du segment et de la droite (x, y) """ @@ -47,13 +56,40 @@ def calculateShortestPoint(pointDroite1: list[float, float], pointDroite2: list[ left_side = np.array([[-coeffdroite1, 1], [-coeffdroitePerp, 1]]) right_side = np.array([ordonnee1, ordonnee2]) else: # si la droite est verticale alors c'est super simple de trouver les coords du point - return pointDroite1[0], point[1] + + solution = pointDroite1[0], point[1] + + if segment: + + if not pointDroite1[1] <= pointDroite2[1]: # on trie les points pour que le 1 ai le y le plus petit + pointDroite1, pointDroite2 = pointDroite2, pointDroite1 + + if point[1] <= pointDroite1[1]: # on vérifie ensuite si le y est ou non compris dans le segment + solution = pointDroite1 + + elif point[1] >= pointDroite2[1]: + solution = pointDroite2 + + return solution # solve for x and y - return np.linalg.inv(left_side).dot(right_side) + solution = np.linalg.inv(left_side).dot(right_side) + if segment: + if not pointDroite1[0] <= pointDroite2[0]: # on trie les points pour que le 1 ai le x le plus petit + pointDroite1, pointDroite2 = pointDroite2, pointDroite1 -def calculateIntersection(point1Droite, point2Droite, point1Droite2, point2Droite2) -> tuple[float, float]: + if solution[0] <= pointDroite1[0]: # on vérifie ensuite si le x est ou non compris dans le segment + solution = pointDroite1 + + elif solution[0] >= pointDroite2[0]: + solution = pointDroite2 + + return solution + + +def calculateIntersection(point1Droite, point2Droite, + point1Droite2, point2Droite2) -> tuple[float, float]: """ Calcule l'intersection entre deux droites @@ -124,3 +160,67 @@ def calculateAngle(principal, secondaire): return [principal - secondaire, 180 - principal + secondaire] else: return [secondaire - principal + 360, principal - 180 - secondaire] + + +def distanceMinie(pos1: tuple[float, float], vitesse1: float, heading1: float, + pos2: tuple[float, float], vitesse2: float, heading2: float) -> float: + """ + Calcule la position future de deux avions où la distance sera minimale entre les deux. + :param pos1: Position actuelle de l'avion 1 en px + :param vitesse1: Vitesse actuelle de l'avion 1 en px/sec + :param heading1: Heading actuelle de l'avion 1 en radiants + :param pos2: Position actuelle de l'avion 2 en px + :param vitesse2: Vitesse actuelle de l'avion 2 en px/sec + :param heading2: Heading actuelle de l'avion 2 en radiants + :return: Temps dans lequel la distance sera la plus faible + """ + + x0 = 1.0 + + caca = minimize( + distanceMiniEnFduTemps, x0, args=(pos1, vitesse1, heading1, pos2, vitesse2, heading2), + method='Nelder-Mead', tol=1e-4 + ) + return caca.x[0] + + +def distanceMiniEnFduTemps(temps, pos1: tuple[float, float], vitesse1: float, heading1: float, + pos2: tuple[float, float], vitesse2: float, heading2: float): + + return math.sqrt( + ((pos1[0] + vitesse1 * temps * math.cos(heading1)) - (pos2[0] + vitesse2 * temps * math.cos(heading2))) ** 2 + + ((pos1[1] + vitesse1 * temps * math.sin(heading1)) - (pos2[1] + vitesse2 * temps * math.sin(heading2))) ** 2 + ) + + +def findClosestSegment(route: list, position: tuple[float, float], points: dict) -> tuple: + """ + Retourne les 2 points du segment de la route le plus proche de notre position dans l'ordre de la route. + :param route: La route qu'on analyse + :param position: La position qu'on veut comparer + :param points: La carte du jeu + :return: + """ + + start = route[0] + end = route[0] + distance = 99999999 + + for index in range(len(route) - 1): # dans cette boucle, on cherche à quel segment on est le plus proche + point = route[index] + point2 = route[index + 1] + coords1 = (points[point['name']][0], points[point['name']][1]) + coords2 = (points[point2['name']][0], points[point2['name']][1]) + + # d'abord, on trouve le point le plus proche de notre pos sur ce segment + intersection = calculateShortestPoint( + coords1, coords2, position, True) + + disancte = calculateDistance(position[0], position[1], intersection[0], intersection[1]) + + if disancte <= distance: + start = point + end = point2 + distance = disancte + + return start, end diff --git a/horloge.py b/Python/horloge.py similarity index 100% rename from horloge.py rename to Python/horloge.py diff --git a/interface.py b/Python/interface.py similarity index 84% rename from interface.py rename to Python/interface.py index 8c13821..7a8a304 100644 --- a/interface.py +++ b/Python/interface.py @@ -1,7 +1,9 @@ + +# Module imports import pygame import pygame_gui -import horloge -from valeurs_config import * +import Python.horloge as horloge +from Python.valeurs_config import * def selectButtonInList(liste: list, event): @@ -12,13 +14,14 @@ def selectButtonInList(liste: list, event): bouton.unselect() -def scrollListGen(valueList, rect, container, sliderBool=True, sliderDroite=False): +def scrollListGen(valueList, rect, container, sliderBool=True, sliderDroite=False, objectID=None): """Fonction qui construit une liste de boutons, avec une scrollbar width 17 :arg valueList: liste des valeurs en txt ou autre peu importe :arg rect: taille des boutons :arg container: conteneur Pygame_gui dans lequel on met les boutons :parameter sliderBool: Bool, si on veut un slider ou non. True par defaut :parameter sliderDroite: Bool, si on veut le slider à droite ou non + :parameter objectID: Une option de thème optionnelle pour les boutons. :return (slider, listeBoutons)""" valueList = list(valueList) @@ -41,7 +44,7 @@ def scrollListGen(valueList, rect, container, sliderBool=True, sliderDroite=Fals liste = [pygame_gui.elements.UIButton( relative_rect=rect, text=str(valueList[0]), - container=container, anchors=boutonAnchor)] + container=container, anchors=boutonAnchor, object_id=objectID)] for value in valueList[1:]: boutonAnchor.update({'top': 'top', 'top_target': liste[-1]}) @@ -49,7 +52,8 @@ def scrollListGen(valueList, rect, container, sliderBool=True, sliderDroite=Fals relative_rect=rect, text=str(value), container=container, - anchors=boutonAnchor)) + anchors=boutonAnchor, + object_id=objectID)) if sliderDroite: decalage = liste[0].get_abs_rect()[2] @@ -63,7 +67,7 @@ class nouvelAvionWindow: def __init__(self, routes, avions): # le dictionnaire utilisé pour renvoyer les valeurs sélectionnées par nos boutons - self.returnValues = {'indicatif': 'FCACA', 'avion': 'B738', 'arrival': False} + self.returnValues = {'indicatif': 'FCACA', 'avion': 'B738', 'arrival': False, 'conflit': False} # la fenêtre du menu self.window = pygame_gui.elements.UIWindow(pygame.Rect((250, 250), (600, 400))) @@ -77,7 +81,10 @@ def __init__(self, routes, avions): # la liste des types avion self.typeAvionContainer = pygame_gui.elements.UIScrollingContainer(pygame.Rect((0, 34), (150, 200)), - container=self.window, anchors={'left': 'left', 'left_target': self.routeContainer}, allow_scroll_x=False) + container=self.window, + anchors={'left': 'left', + 'left_target': self.routeContainer}, + allow_scroll_x=False) self.typeAvionBoutonListe = scrollListGen( list(avions.keys()), pygame.Rect((0, 0), (125, 17)), self.typeAvionContainer, False)[1] @@ -88,7 +95,8 @@ def __init__(self, routes, avions): relative_rect=pygame.Rect((0, 20), (200, 17)), text='Générateur de conflits', container=self.window, - anchors={'top': 'top', 'top_target': self.typeAvionContainer, 'left': 'left', 'left_target': self.routeContainer}) + anchors={'top': 'top', 'top_target': self.typeAvionContainer, 'left': 'left', + 'left_target': self.routeContainer}) self.arrivalBouton = pygame_gui.elements.UIButton( relative_rect=pygame.Rect((0, 5), (200, 17)), @@ -101,7 +109,8 @@ def __init__(self, routes, avions): relative_rect=pygame.Rect((0, 5), (200, 17)), text='Ok', container=self.window, - anchors={'top': 'top', 'top_target': self.arrivalBouton, 'left': 'left', 'left_target': self.routeContainer}) + anchors={'top': 'top', 'top_target': self.arrivalBouton, 'left': 'left', + 'left_target': self.routeContainer}) self.indicatiflabel = pygame_gui.elements.UILabel( relative_rect=pygame.Rect((0, 0), (200, 17)), @@ -112,12 +121,14 @@ def __init__(self, routes, avions): self.indicatifinput = pygame_gui.elements.UITextEntryBox( relative_rect=pygame.Rect((0, 0), (200, 30)), container=self.window, - anchors={'left': 'left', 'left_target': self.typeAvionContainer, 'top': 'top', 'top_target': self.indicatiflabel}) + anchors={'left': 'left', 'left_target': self.typeAvionContainer, 'top': 'top', + 'top_target': self.indicatiflabel}) self.FLlabel = pygame_gui.elements.UILabel( relative_rect=pygame.Rect((0, 0), (200, 17)), container=self.window, - anchors={'left': 'left', 'left_target': self.typeAvionContainer, 'top': 'top', 'top_target': self.indicatifinput}, + anchors={'left': 'left', 'left_target': self.typeAvionContainer, 'top': 'top', + 'top_target': self.indicatifinput}, text='FL') self.FLinput = pygame_gui.elements.UITextEntryBox( @@ -161,6 +172,13 @@ def checkEvent(self, event): else: self.arrivalBouton.unselect() + elif event.ui_element == self.conflitsBouton: + self.returnValues['conflit'] = not self.returnValues['conflit'] + if not self.conflitsBouton.is_selected: + self.conflitsBouton.select() + else: + self.conflitsBouton.unselect() + elif event.ui_element == self.validerBouton: self.window.kill() return self.returnValues @@ -249,8 +267,9 @@ def __init__(self, avion): container=self.window, anchors={'left': 'left', 'left_target': self.altiContainer}) - tempo = scrollListGen(range(round(avion.papa.speedIAS / 10) * 10 - 50, round(avion.papa.speedIAS / 10) * 10 + 60, 10), - pygame.Rect((0, 0), (75, 17)), self.speedContainer) + tempo = scrollListGen( + range(round(avion.papa.speedIAS / 10) * 10 - 50, round(avion.papa.speedIAS / 10) * 10 + 60, 10), + pygame.Rect((0, 0), (75, 17)), self.speedContainer) self.speedBoutonListe = tempo[1] self.speedSlider = tempo[0] @@ -261,7 +280,7 @@ def __init__(self, avion): self.pointContainer = pygame_gui.elements.UIScrollingContainer( pygame.Rect((0, 0), (100, 200)), container=self.window, - anchors={'left': 'left','left_target': self.speedContainer}) + anchors={'left': 'left', 'left_target': self.speedContainer}) tempo = scrollListGen([point['name'] for point in avion.papa.route['points']], pygame.Rect((0, 0), (75, 17)), @@ -425,7 +444,7 @@ def __init__(self, avion): self.AFL = pygame_gui.elements.UIButton( relative_rect=pygame.Rect((0, 0), (-1, -1)), - text=str(round(avion.papa.altitude/100)), + text=str(round(avion.papa.altitude / 100)), generate_click_events_from=clicks, object_id=pygame_gui.core.ObjectID('@etiquetteBold', 'rose'), anchors={'top': 'top', 'top_target': self.indicatif}, @@ -530,9 +549,10 @@ def update(self, avion): self.DCT.set_text("h") self.XPT.set_text(avion.papa.XPT) + self.nextSector.set_text(avion.papa.nextSector) # alti - self.AFL.set_text(str(round(avion.papa.altitude/100)) + " " + avion.papa.altitudeEvoTxt) + self.AFL.set_text(str(round(avion.papa.altitude / 100)) + " " + avion.papa.altitudeEvoTxt) self.CFL.set_text(str(avion.papa.CFL)[:2]) @@ -618,7 +638,7 @@ def __init__(self, avion, pos): width = 80 height = 120 - x = pos[0] - width/2 + x = pos[0] - width / 2 y = pos[1] - 35 if avion.papa.etatFrequence == 'previousFreq': @@ -760,14 +780,17 @@ def __init__(self, avion, pos: list[float, float], valeur: str): self.headingDCT = None self.listeGauche = None self.listeDroite = None + objectID = None if valeur == 'DCT': # TODO boutons directs en blanc self.liste = [point['name'] for point in self.avion.papa.route['points']] self.listeAff = self.liste[self.liste.index(avion.papa.nextPoint['name']):] + objectID = pygame_gui.core.ObjectID('@menuLabel', 'menuBlanc') elif valeur == 'XPT': # la diff de liste est qu'on ne prend pas en compte les points de notre secteur ici self.liste = [point['name'] for point in self.avion.papa.route['points']] self.listeAff = self.liste[self.liste.index(avion.papa.defaultXPT):] + objectID = pygame_gui.core.ObjectID('@menuLabel', 'menuBlanc') elif valeur == 'C_IAS': self.liste = [*range(0, 60)] @@ -792,11 +815,12 @@ def __init__(self, avion, pos: list[float, float], valeur: str): elif valeur == 'HDG': - cap = round(avion.papa.heading // 5) * 5 + cap = round(avion.papa.heading // 5) * 5 + 5 - self.liste = [*range(0, 365, 5)] + self.liste = [*range(5, 365, 5)] indexDuCap = self.liste.index(cap) - self.listeAff = self.liste[indexDuCap - 3: indexDuCap + 4] + self.listeAff = [self.liste[(indexDuCap - 3 + i) % len(self.liste)] for i in range(7)] + objectID = pygame_gui.core.ObjectID('@menuLabel', 'menuBlanc') self.window = pygame_gui.elements.UIWindow(pygame.Rect((x, y), (width, height)), window_display_title=valeur, @@ -817,9 +841,9 @@ def __init__(self, avion, pos: list[float, float], valeur: str): if valeur == 'C_IAS': self.noeud = pygame_gui.elements.UIButton( - pygame.Rect((0, 0), (width / 2, -1)), - container=self.topContainer, - text="K") + pygame.Rect((0, 0), (width / 2, -1)), + container=self.topContainer, + text="K") self.mach = pygame_gui.elements.UIButton( pygame.Rect((0, 0), (width / 2, -1)), @@ -883,7 +907,7 @@ def __init__(self, avion, pos: list[float, float], valeur: str): self.topContainer.set_dimensions((width, self.headingDCT.get_abs_rect()[3] * 2)) - if self.valeur == 'HDG': # TODO boutons cap abs en blanc + if self.valeur == 'HDG': listeDroite = [] listeGauche = [] @@ -918,7 +942,7 @@ def __init__(self, avion, pos: list[float, float], valeur: str): self.listeGauche = scrollListGen( listeGauche, - pygame.Rect((0, 0), (width / 3, -1)), + pygame.Rect((1, 0), (width / 3, -1)), self.containerHdgGauche, sliderBool=False)[1] @@ -931,17 +955,16 @@ def __init__(self, avion, pos: list[float, float], valeur: str): 'left': 'left', 'left_target': self.containerHdgGauche} ) - tempo = scrollListGen( + self.listeBoutons = scrollListGen( self.listeAff, - pygame.Rect((0, 0), (width / 3, -1)), + pygame.Rect((1, 0), (width / 3, -1)), self.listeContainer, - sliderBool=False) - - self.listeBoutons = tempo[1] + sliderBool=False, + objectID=objectID)[1] self.containerHdgDroite = pygame_gui.elements.UIScrollingContainer( container=self.window, - relative_rect=pygame.Rect((0, 0), (width / 3, height)), + relative_rect=pygame.Rect((1, 0), (width / 3, height)), allow_scroll_x=False, allow_scroll_y=False, anchors={'top': 'top', 'top_target': self.topContainer, @@ -950,7 +973,7 @@ def __init__(self, avion, pos: list[float, float], valeur: str): self.listeDroite = scrollListGen( listeDroite, - pygame.Rect((0, 0), (width / 3, -1)), + pygame.Rect((1, 0), (width / 3, -1)), self.containerHdgDroite, sliderBool=False)[1] else: @@ -964,19 +987,22 @@ def __init__(self, avion, pos: list[float, float], valeur: str): tempo = scrollListGen( self.listeAff, - pygame.Rect((0, 0), (width, -1)), + pygame.Rect((1, 0), (width, -1)), self.listeContainer, - sliderBool=False) + sliderBool=False, + objectID=objectID) self.listeBoutons = tempo[1] if valeur in ['XFL', 'PFL', 'CFL']: # on ramène la fenêtre au bon endroit pour la souris (sur le PFL) - y = y - (self.topContainer.get_abs_rect()[3] + self.listeBoutons[0].get_abs_rect()[3] * 2.5 + self.window.title_bar_height) + y = y - (self.topContainer.get_abs_rect()[3] + self.listeBoutons[0].get_abs_rect()[ + 3] * 2.5 + self.window.title_bar_height) self.window.set_position((x, y)) widthListeContainer = self.listeContainer.get_abs_rect()[2] - self.listeContainer.set_dimensions((widthListeContainer, len(self.listeAff) * self.listeBoutons[0].get_abs_rect()[3])) + self.listeContainer.set_dimensions( + (widthListeContainer, len(self.listeAff) * self.listeBoutons[0].get_abs_rect()[3])) self.window.set_minimum_dimensions((width, width)) height = (self.listeContainer.get_abs_rect()[3] + self.topContainer.rect[3] + @@ -1080,17 +1106,17 @@ def checkScrolled(self, event): if not rect[0] <= mouse[0] <= rect[0] + rect[2] and rect[1] <= mouse[1] <= rect[1] + rect[3]: return True - if event.y >= 0: # on regarde dans quel sens on scroll - indexDebut = self.liste.index(self.listeAff[0]) # on regarde où commence la liste dans l'autre - if indexDebut - 1 >= 0: # cela vérifie qu'on n'est pas en butée de liste - self.listeAff = self.liste[indexDebut - 1: indexDebut - 1 + len(self.listeAff)] - self.scrollUpdate() + indexDebut = self.liste.index(self.listeAff[0]) + + if event.y >= 0: # on regarde dans quel sens on scroll + newIndex = (indexDebut - 1) % len(self.liste) else: - indexDebut = self.liste.index(self.listeAff[0]) # on regarde où commence la liste dans l'autre - if indexDebut + len(self.listeAff) <= len(self.liste) - 1: # cela vérifie qu'on n'est pas en butée de liste - self.listeAff = self.liste[indexDebut + 1: indexDebut + 1 + len(self.listeAff)] - self.scrollUpdate() + newIndex = (indexDebut + 1) % len(self.liste) + + self.listeAff = [self.liste[(newIndex + i) % len(self.liste)] for i in range(len(self.listeAff))] + + self.scrollUpdate() def scrollUpdate(self) -> None: """ @@ -1119,7 +1145,13 @@ def checkUnHovered(self, event) -> None: """ if self.valeur in ['DCT', 'XPT']: if event.ui_element in self.listeBoutons: - self.avion.pointDessinDirect = None + dessin = False + for element in self.listeBoutons: + if element.hovered: + dessin = True + break + if not dessin: + self.avion.pointDessinDirect = None def checkMenuHovered(self) -> None: """ @@ -1224,7 +1256,7 @@ def linkAvion(self, avion, points: list, heure) -> None: self.ligneDeux.set_text(text) text = (avion.papa.provenance + ' ' + avion.papa.destination + ' R' + str(avion.papa.PFL) - + ' ' + str(round(avion.papa.altitude/100)) + ' ' + str(avion.papa.PFL) + + ' ' + str(round(avion.papa.altitude / 100)) + ' ' + str(avion.papa.PFL) + ' ' + avion.papa.EPT + ' ' + avion.papa.XPT + ' X' + str(avion.papa.XFL)) self.ligneTrois.set_text(text) @@ -1248,4 +1280,110 @@ def checkAlive(self): return self.window.alive() +class menuRadar: + + def __init__(self): + + width = 100 + height = 200 + + self.window = pygame_gui.elements.UIWindow( + pygame.Rect((500, 500), (width, height)), + window_display_title="Flight Data Window", + object_id=pygame_gui.core.ObjectID('@menuRadar', 'blanc'), + + ) + ancres = {} + self.listeVecteurs = [] + indice = 0 + + for b in range(3): + + for bb in range(3): + indice += 1 + self.listeVecteurs.append( + pygame_gui.elements.UIButton(pygame.Rect((0, 0), (width / 3, width / 3)), + text=str(indice) + "'", + container=self.window, + anchors=ancres, + object_id=pygame_gui.core.ObjectID('@menuRadar', 'blanc'), + generate_click_events_from=frozenset( + [pygame.BUTTON_LEFT, pygame.BUTTON_RIGHT]) + )) + ancres.update({'left': 'left', 'left_target': self.listeVecteurs[-1]}) + ancres.pop('left') + ancres.pop('left_target') + ancres.update({'top': 'top', 'top_target': self.listeVecteurs[-1]}) + + self.alidade = pygame_gui.elements.UIButton(pygame.Rect((0, 0), (width, width / 3)), + text="ALIDADE", + container=self.window, + anchors={'top': 'top', 'top_target': self.listeVecteurs[-1]}, + object_id=pygame_gui.core.ObjectID('@menuRadar', 'blanc')) + + self.cercles = pygame_gui.elements.UIButton(pygame.Rect((0, 0), (width, width / 3)), + text="@", + container=self.window, + anchors={'top': 'top', 'top_target': self.alidade}, + object_id=pygame_gui.core.ObjectID('@menuRadar', 'blanc')) + + ancres = {'top': 'top', 'top_target': self.cercles} + self.listeSep = [] + for lettre in ['A', 'B', 'C']: + self.listeSep.append( + pygame_gui.elements.UIButton(pygame.Rect((0, 0), (width / 3, width / 3)), + text=lettre, + container=self.window, + anchors=ancres, + object_id=pygame_gui.core.ObjectID('@menuRadar', 'blanc') + )) + ancres.update({'left': 'left', 'left_target': self.listeSep[-1]}) + + self.lastHovered = pygame.time.get_ticks() + self.window.hide() + + def show(self): + + pos = pygame.mouse.get_pos() + rect = self.window.get_abs_rect() + self.window.set_position((pos[0] - rect[2] / 2, pos[1] - rect[3] / 2)) + self.window.show() + + def checkActive(self): + return self.window.visible + + def checkEvent(self, event): + + if event.ui_element in self.listeVecteurs: + if event.mouse_button == 1: + return 'VecteursToggle', int(event.ui_element.text[0]) + + elif event.mouse_button == 3: + return 'Vecteurs', int(event.ui_element.text[0]) + + elif event.ui_element in self.listeSep: + self.window.hide() + return 'Sep', event.ui_element.text + + elif event.ui_element == self.alidade: + self.window.hide() + return 'Alidade' + + elif event.ui_element == self.cercles: + self.window.hide() + return 'Cercles' + + def checkMenuHovered(self) -> None: + """ + Commence le compteur si n'est plus survolé + :return: + """ + rect = self.window.get_abs_rect() + mouse = pygame.mouse.get_pos() + + if rect[0] <= mouse[0] <= rect[0] + rect[2] and rect[1] <= mouse[1] <= rect[1] + rect[3]: + self.lastHovered = pygame.time.get_ticks() + + elif pygame.time.get_ticks() - self.lastHovered > temps_disparition_menus: + self.window.hide() diff --git a/network.py b/Python/network.py similarity index 97% rename from network.py rename to Python/network.py index f36d5cf..25f0a26 100644 --- a/network.py +++ b/Python/network.py @@ -1,3 +1,5 @@ + +# Native imports import socket import pickle diff --git a/Python/outils_radar.py b/Python/outils_radar.py new file mode 100644 index 0000000..f030315 --- /dev/null +++ b/Python/outils_radar.py @@ -0,0 +1,266 @@ + +# Native imports +import math + +# Module imports +import pygame +import pygame_gui + +# Imports fichiers +import Python.geometry as geometry +from Python.valeurs_config import * + + +class aliSep: + + def __init__(self, lettre): + self.lettre = lettre + self.avion1 = None + self.avion2 = None + self.distance = None + self.temps = None + + def linkAvion(self, avion, carte) -> bool: + + """ + Associe un avion. Si un avion est déjà associé alors celà déclenche le calcul de la sep + :param avion: l'avion à associer + :param carte: les données de la carte + :return: si les deux avions ont été associés, renvoies True + """ + + if self.avion2: + return True + + if not self.avion1: + self.avion1 = avion + return False + + self.avion2 = avion + + self.avion1.sep = True + self.avion2.sep = True + + self.calculation(carte) + + return True + + def calculation(self, carte): + + if not self.avion2: + return None + + self.temps = geometry.distanceMinie( + (self.avion1.papa.x, self.avion1.papa.y), self.avion1.papa.speedPx / radarRefresh, + self.avion1.papa.headingRad, + (self.avion2.papa.x, self.avion2.papa.y), self.avion2.papa.speedPx / radarRefresh, + self.avion2.papa.headingRad, + ) + + self.distance = math.sqrt( + ((self.avion1.papa.x + self.avion1.papa.speedPx / radarRefresh * self.temps * math.cos( + self.avion1.papa.headingRad)) - + (self.avion2.papa.x + self.avion2.papa.speedPx / radarRefresh * self.temps * math.cos( + self.avion2.papa.headingRad))) ** 2 + + ((self.avion1.papa.y + self.avion1.papa.speedPx / radarRefresh * self.temps * math.sin( + self.avion1.papa.headingRad)) - + (self.avion2.papa.y + self.avion2.papa.speedPx / radarRefresh * self.temps * math.sin( + self.avion2.papa.headingRad))) ** 2) + + self.avion1.sepSetting.update({self.lettre: [self.temps, self.distance * carte['mapScale']]}) + self.avion2.sepSetting.update({self.lettre: [self.temps, self.distance * carte['mapScale']]}) + + def kill(self): + + if not self.avion2 and self.avion1: + self.avion1 = None + return None + + if not self.avion1: + return None + + self.avion1.sepSetting.pop(self.lettre) + self.avion2.sepSetting.pop(self.lettre) + + if len(self.avion1.sepSetting) == 0: + self.avion1.sep = False + if len(self.avion2.sepSetting) == 0: + self.avion2.sep = False + + self.avion1 = None + self.avion2 = None + + +class conflictGenerator: + + def __init__(self, win, avion, carte): + + size = win.get_size() + width = size[0] / 3 + height = 40 + + self.carte = carte + + self.slider = pygame_gui.elements.UIHorizontalScrollBar( + pygame.Rect((size[0] / 2 - width / 2, 10), (width, height)), + visible_percentage=0.3, + + ) + self.valider = pygame_gui.elements.UIButton( + pygame.Rect((size[0] / 2 + width / 2 + 10, 10), (height, height)), + text='OK' + ) + + self.avion = None + self.temps = 0 + self.maxTemps = 60 * 60 # temps en sec, donc 40 min au max + self.avion = avion + self.x = None + self.y = None + self.spawnDelay = None + self.drawListe = None + + def checkScrollBar(self, carte): + + """Ajuste la valeur de temps en fonction du slider s'il est scrollé""" + + if self.slider.has_moved_recently: + self.temps = self.slider.start_percentage * self.maxTemps + if self.x: + self.increaseTime(self.temps, carte) + + def checkEvent(self, event): + + """ + Vérifies si les boutons sont appuyés et prend les actions nécessaires. + :param event: + :return: + """ + if event.ui_element == self.valider: + return self.kill() + + def computeSpawn(self, pos: list[float, float] | tuple[float, float], carte): + """ + Change la position, ou le delay de spawn de l'avion en fonction de la position voulue et des perfos + :param pos: La position de conflit choisie + :param carte: La carte du jeu + :return: + """ + + points = carte['points'] + route = self.avion.route['points'] + distance = self.temps * self.avion.speedPx / radarRefresh # quelle distance va parcourir l'avion en ce temps + distance_calcule = 0 + + point2 = points[route[0]['name']] + + p = geometry.findClosestSegment(self.avion.route['points'], pos, carte['points'])[0] + + for index in range(len(route[:route.index(p)])): + point1 = points[route[index]['name']] + point2 = points[route[index + 1]['name']] + distance_calcule += geometry.calculateDistance(point1[0], point1[1], point2[0], point2[1]) + + offroadDistance = geometry.calculateDistance(pos[0], pos[1], point2[0], point2[1]) + distance_calcule += offroadDistance + + a = list(range(len(route[:route.index(p) + 1]))) + self.drawListe = [] + + if distance_calcule <= distance: # si on doit parcourir plus que ce qu'on a calculé au spawn + # alors on delay le spawn, temps ici en sec + self.spawnDelay = int((distance - distance_calcule) / self.avion.speedPx * radarRefresh) + self.x = points[route[0]['name']][0] + self.y = points[route[0]['name']][1] + + for index in a: + self.drawListe.append(points[route[index]['name']]) + + else: # si on doit parcourir moins, alors on fait apparaître l'avion plus proche du secteur + self.spawnDelay = None + distanceAparcourir = distance_calcule - distance + index = 0 + found = False + for index in a: + point1 = points[route[index]['name']] + point2 = points[route[index + 1]['name']] + legDistance = geometry.calculateDistance(point1[0], point1[1], point2[0], point2[1]) + + if found: + self.drawListe.append(point1) + + elif legDistance >= distanceAparcourir: # si on doit faire apparaître sur cette branche + ratio = distanceAparcourir / legDistance + self.x = ratio * (point2[0] - point1[0]) + point1[0] + self.y = ratio * (point2[1] - point1[1]) + point1[1] + self.drawListe.append((self.x, self.y)) + self.avion.x = self.x + self.avion.y = self.y + found = True + + else: + distanceAparcourir -= legDistance + + self.drawListe.append(pos) + + def draw(self, win, zoom, scroll) -> None: + if self.x is None: + return None + pygame.draw.circle(win, (255, 255, 0), (self.x * zoom + scroll[0], self.y * zoom + scroll[1]), 3) + for index in range(len(self.drawListe) - 1): + point1 = self.drawListe[index] + point2 = self.drawListe[index + 1] + pygame.draw.line(win, (255, 255, 0), + (point1[0] * zoom + scroll[0], point1[1] * zoom + scroll[1]), + (point2[0] * zoom + scroll[0], point2[1] * zoom + scroll[1])) + + if self.spawnDelay: + font = pygame.font.SysFont('arial', 15) + img = font.render("Délai à l'apparition: " + str(self.spawnDelay) + "s", True, (170, 170, 255)) + win.blit(img, (self.drawListe[0][0] * zoom + scroll[0], self.drawListe[0][1] * zoom + scroll[1])) + + def increaseTime(self, temps, carte): + + """ + Augmente le temps de dessin sans changer le point de spawn + :param carte: La carte du jeu + :param temps: Le nouveau temps de dessin + :return: + """ + points = carte['points'] + self.temps = temps + delay = 0 + if self.spawnDelay: + delay = self.spawnDelay + distance = (self.temps - delay) * self.avion.speedPx / radarRefresh # quelle distance va parcourir l'avion en ce temps + + startPlot = geometry.findClosestSegment(self.avion.route['points'], (self.x, self.y), points)[1] + liste = self.avion.route['points'][self.avion.route['points'].index(startPlot):] + self.drawListe = [(self.x, self.y)] + + point1 = (self.x, self.y) + self.drawListe.append(point1) + for index in range(len(liste)): + point2 = points[liste[index]['name']] + + legDistance = geometry.calculateDistance(point1[0], point1[1], point2[0], point2[1]) + if distance - legDistance <= 0: + ratio = distance / legDistance + x = ratio * (point2[0] - point1[0]) + point1[0] + y = ratio * (point2[1] - point1[1]) + point1[1] + self.drawListe.append(point1) + self.drawListe.append((x, y)) + break + + else: + distance -= legDistance + self.drawListe.append(point2) + point1 = point2 + + def kill(self): + self.valider.kill() + self.slider.kill() + self.avion.findNextPoint(self.carte) + if self.spawnDelay: + return self.spawnDelay, self.avion + return self.avion diff --git a/paquets_avion.py b/Python/paquets_avion.py similarity index 88% rename from paquets_avion.py rename to Python/paquets_avion.py index 43db78d..65c4259 100644 --- a/paquets_avion.py +++ b/Python/paquets_avion.py @@ -1,7 +1,11 @@ -from geometry import * -from valeurs_config import * + +# Native imports import random +# Imports fichiers +from Python.geometry import * +from Python.valeurs_config import * + class Game: def __init__(self, heure): @@ -69,8 +73,15 @@ def __init__(self, gameMap, Id, indicatif, aircraft, perfos, route, arrival, FL= self.callsignFreq = 'Austrian' # TODO ajouter les callsigns - self.provenance = random.choice(gameMap['aeroports'][route['provenance']]) - self.destination = random.choice(gameMap['aeroports'][route['destination']]) + if route['type'] == 'DEPART': + self.provenance = route['provenance'] + else: + self.provenance = random.choice(gameMap['aeroports'][route['provenance']]) + + if self.arrival: + self.destination = route['arrival']['aeroport'] + else: + self.destination = random.choice(gameMap['aeroports'][route['destination']]) # perfo self.turnRate = turnRateDefault @@ -81,12 +92,9 @@ def __init__(self, gameMap, Id, indicatif, aircraft, perfos, route, arrival, FL= self.route = route self.headingMode = False - # TODO changer ça pour pouvoir mettre un avion nimporte ou sur la route - if calculateDistance(self.x, self.y, gameMap['points'][self.route['points'][0]['name']][0], - gameMap['points'][self.route['points'][0]['name']][1]) <= 4 * self.speedPx: - self.nextPoint = self.route['points'][1] - else: - self.nextPoint = self.route['points'][0] + + self.nextPoint = None + self.findNextPoint(gameMap) self.evolution = 0 # taux de variation/radar refresh self.altitudeEvoTxt = '-' @@ -118,14 +126,14 @@ def __init__(self, gameMap, Id, indicatif, aircraft, perfos, route, arrival, FL= self.XFL = 300 for sortie in self.route['sortie']: - print(sortie['min'] < self.PFL < sortie['max']) if sortie['min'] < self.PFL < sortie['max']: self.nextSector = sortie['name'] if not self.nextSector: # si on n'a pas réussi à mettre un secteur suivant, on met un défaut pour pas crash self.nextSector = secteurDefault - self.nextFrequency = '127.675' # TODO faire les fréquences automatiques + self.nextFrequency = gameMap['secteurs'][self.nextSector]['frequence'] + self.etranger = gameMap['secteurs'][self.nextSector]['etranger'] self.CFL = None @@ -153,19 +161,23 @@ def __init__(self, gameMap, Id, indicatif, aircraft, perfos, route, arrival, FL= self.clearedHeading = None self.clearedRate = None - def changeXFL(self) -> None: + def findNextPoint(self, carte): + + self.nextPoint = findClosestSegment(self.route['points'], (self.x, self.y), carte['points'])[1] + + def changeXFL(self, carte) -> None: """ Change le XFL en fonction du PFL. À utliser quand le PFL change :return: """ - print(self.PFL) if self.PFL > 365 and not self.arrival: self.nextSector = "RU" self.XFL = 360 elif not self.arrival: self.XFL = self.PFL + self.changeSortieSecteur(carte) - def changeSortieSecteur(self) -> None: + def changeSortieSecteur(self, carte) -> None: """ Change le secteur de sortie. À utiliser quand le XFL change :return: @@ -175,6 +187,8 @@ def changeSortieSecteur(self) -> None: if sortie['min'] < self.XFL < sortie['max']: self.nextSector = sortie['name'] break + self.etranger = carte['secteurs'][self.nextSector]['etranger'] + self.nextFrequency = carte['secteurs'][self.nextSector]['frequence'] def updateEtatFreq(self, nouvelEtat=None) -> None: """ @@ -188,6 +202,8 @@ def updateEtatFreq(self, nouvelEtat=None) -> None: else: i = liste_etat_freq.index(self.etatFrequence) if i != len(liste_etat_freq) - 1: # on vérifie que ce n'est pas le dernier état fréquence + if self.etranger and self.etatFrequence == 'nextCoord': + i += 1 self.etatFrequence = liste_etat_freq[i + 1] def updateAlti(self): @@ -219,6 +235,12 @@ def updateAlti(self): self.evolution = 0 def move(self, gameMap): + + # frequence update + if self.etatFrequence == 'inFreq': + + if self.calculeEstimate(gameMap['points'], self.XPT) <= 60 * valeurCoord: # si on sort dans moins de 6min + self.updateEtatFreq() # heading update if not self.headingMode: # si l'avion est en direct et pas en cap @@ -233,9 +255,7 @@ def move(self, gameMap): ancien = self.nextPoint # pour la comparaison après self.nextPoint = self.route['points'][self.route['points'].index(self.nextPoint) + 1] if ancien['name'] == self.DCT: # si le point clairé est celui qu'on passe - self.DCT = self.nextPoint['name'] # alors on passe au point suivant aussi - - + self.DCT = self.nextPoint['name'] # alors, on passe au point suivant aussi self.selectedHeading = calculateHeading(self.x, self.y, gameMap['points'][self.nextPoint['name']][0], gameMap['points'][self.nextPoint['name']][1]) @@ -270,7 +290,7 @@ def move(self, gameMap): self.x += self.speedPx * math.cos(self.headingRad) self.y += self.speedPx * math.sin(self.headingRad) - def calculeEstimate(self, points: dict, pointVoulu: str) -> None: + def calculeEstimate(self, points: dict, pointVoulu: str) -> float: """ Calcule le temps à un point sur notre route. Pour avoir l'estimée, il faudra rajouter l'heure courante :param points: dict des points de la carte diff --git a/player.py b/Python/player.py similarity index 82% rename from player.py rename to Python/player.py index 4820b68..5cf8416 100644 --- a/player.py +++ b/Python/player.py @@ -1,9 +1,15 @@ -import pygame + +# Native imports import math + +# Module imports +import pygame import pygame_gui -from valeurs_config import * -import geometry -import interface + +# Imports fichiers +from Python.valeurs_config import * +import Python.geometry as geometry +import Python.interface as interface def positionAffichage(x: int, y: int, zoom: float, scrollX: float, scrollY: float): @@ -55,9 +61,12 @@ def __init__(self, Id: int, papa, zoom: float, scroll: list[float, float]): self.predictionPoint = None # point pour la prédiction de route self.drawRouteBool = False self.locWarning = False + self.sep = False + self.sepSetting = {} # [temps à dessiner, distance minie en nm] self.etiquetteExtended = False self.lastHoveredTime = 0 self.pointDessinDirect = None + self.conflitSelected = False self.drag = False # if true l'etiquette se fait drag self.dragOffset = (0, 0) # le décalage de l'etiquette par rapport au curseur self.startPressTime = 0 @@ -102,6 +111,49 @@ def drawVector(self, color, window, vecteurSetting, zoom): self.affY + plotSize + self.papa.speedPx * 60 / radarRefresh * vecteurSetting * zoom * math.sin( self.papa.headingRad)), 2) + def drawSep(self, window, zoom): + """ + Dessine la sep avec un autre avion + :param window: + :param zoom: + :return: + """ + + sep = (0, 9999999) + + for lettre, sepSetting in self.sepSetting.items(): + if sepSetting[1] <= sep[1] and 0 <= sepSetting[0]: + sep = sepSetting + + sepSetting = sep + + coords = [self.affX + plotSize + self.papa.speedPx / radarRefresh * sepSetting[0] * zoom * math.cos( + self.papa.headingRad), + self.affY + plotSize + self.papa.speedPx / radarRefresh * sepSetting[0] * zoom * math.sin( + self.papa.headingRad) + ] + + if sepSetting[1] <= 6: + color = (255, 0, 0) + elif sepSetting[1] <= 9: + color = (241, 237, 57) + else: + color = (157, 193, 126) + + pygame.draw.line(window, color, (self.affX + plotSize, self.affY + plotSize), coords, 2) + + font = pygame.font.SysFont('arial', 15) + + coords[0] += 2 + for lettre, sepSetting in self.sepSetting.items(): + if 0 <= sepSetting[0]: + img = font.render(lettre + str(round(sepSetting[1], 1)), True, color) + window.blit(img, coords) + coords[1] += 15 + else: + img = font.render(lettre, True, (170, 170, 255)) + window.blit(img, coords) + def draw(self, win, zoom, scroll, vecteurs, vecteurSetting, points): # updates @@ -133,34 +185,39 @@ def draw(self, win, zoom, scroll, vecteurs, vecteurSetting, points): width = 1 # Dessin - if self.visible: - if self.papa.warning: - color = (255, 120, 60) - elif self.locWarning: - color = (100, 200, 100) - elif self.etiquetteExtended: - color = (30, 144, 255) - width = 3 - else: - color = (255, 255, 255) - - pygame.draw.circle(win, color, (self.affX + plotSize, self.affY + plotSize), plotSize, 1) - pygame.draw.circle(win, color, (self.affX + plotSize, self.affY + plotSize), plotSize / 1.5, 1) - - if vecteurs or self.papa.warning or self.locWarning: # si on doit dessiner les vecteurs - self.drawVector(color, win, vecteurSetting, zoom) # on appelle la fonction - - radius = 1 - for plot in self.papa.comete: - affPlot = [plot[0] * zoom + scroll[0], - plot[1] * zoom + scroll[1]] - pygame.draw.circle(win, (255, 255, 255), affPlot, int(round(radius)), 1) - radius += 0.3 - - point = self.findEtiquetteAnchorPoint() - if point is not None: - pygame.draw.line(win, color, (self.affX + plotSize, self.affY + plotSize), - (point[0], point[1]), width) + if not self.visible: + return None + + if self.papa.warning: + color = (255, 120, 60) + elif self.locWarning: + color = (100, 200, 100) + elif self.etiquetteExtended: + color = (30, 144, 255) + width = 3 + else: + color = (255, 255, 255) + + pygame.draw.circle(win, color, (self.affX + plotSize, self.affY + plotSize), plotSize, 1) + pygame.draw.circle(win, color, (self.affX + plotSize, self.affY + plotSize), plotSize / 1.5, 1) + + if self.sep: + self.drawSep(win, zoom) + + elif vecteurs or self.papa.warning or self.locWarning: # si on doit dessiner les vecteurs + self.drawVector(color, win, vecteurSetting, zoom) # on appelle la fonction + + radius = 1 + for plot in self.papa.comete: + affPlot = [plot[0] * zoom + scroll[0], + plot[1] * zoom + scroll[1]] + pygame.draw.circle(win, (255, 255, 255), affPlot, int(round(radius)), 1) + radius += 0.3 + + point = self.findEtiquetteAnchorPoint() + if point is not None: + pygame.draw.line(win, color, (self.affX + plotSize, self.affY + plotSize), + (point[0], point[1]), width) def findEtiquetteAnchorPoint(self) -> tuple[float, float]: """ @@ -176,18 +233,18 @@ def findEtiquetteAnchorPoint(self) -> tuple[float, float]: pointGauche = geometry.calculateIntersection((rect[0], rect[1]), (rect[0], rect[1] + rect[3]), - (self.etiquette.centre[0], self.etiquette.centre[1]), - (self.affX + plotSize, self.affY + plotSize)) + (self.etiquette.centre[0], self.etiquette.centre[1]), + (self.affX + plotSize, self.affY + plotSize)) pointDroite = geometry.calculateIntersection((rect[0] + rect[2], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), - (self.etiquette.centre[0], self.etiquette.centre[1]), - (self.affX + plotSize, self.affY + plotSize)) + (self.etiquette.centre[0], self.etiquette.centre[1]), + (self.affX + plotSize, self.affY + plotSize)) pointBas = geometry.calculateIntersection((rect[0], rect[1] + rect[3]), (rect[0] + rect[2], rect[1] + rect[3]), - (self.etiquette.centre[0], self.etiquette.centre[1]), - (self.affX + plotSize, self.affY + plotSize)) + (self.etiquette.centre[0], self.etiquette.centre[1]), + (self.affX + plotSize, self.affY + plotSize)) if rect[0] <= pointHaut[0] <= rect[0] + rect[2] and self.affY <= self.etiquetteY: return pointHaut @@ -212,25 +269,29 @@ def drawDirect(self, points, point, win, zoom: float, scroll: list[float, float] coord = [points[point][0] * zoom + scroll[0], points[point][1] * zoom + scroll[1]] pygame.draw.line(win, (0, 206, 209), (self.affX + plotSize, self.affY + plotSize), coord) - def drawEstimatedRoute(self, points, temps, win, zoom, scroll): + def drawEstimatedRoute(self, points, temps, color, win, zoom, scroll): """ Dessine la route future de l'avion jusuq'à un certain point défini par une valeur de temps C'est une bonne approximation de la future position de l'avion, à vitesse constante :param points: la liste des points récupérer les coords - :param temps: combien de temps doit faire la route dessinée + :param temps: combien de temps doit faire la route dessinée en sec + :param color: La couleur du tracé :param win: l'écran pygame :param zoom: le niveau de zoom :param scroll: le scroll format [x, y] :return: """ + if not self.conflitSelected: + return None + route = self.papa.route['points'] # on n'a besoin que des noms des points nextPoint = self.papa.nextPoint ratio = 0 route = route[route.index(nextPoint):] # on ne considère que la route devant l'avion pointUn = [self.papa.x, self.papa.y] # on commence à dessiner à partir de l'avion - distance = temps * self.papa.speedPx # on établit la distance de la route avec notre vitesse + distance = temps * self.papa.speedPx / radarRefresh # on établit la distance de la route avec notre vitesse for point in route: pointDeux = [points[point['name']][0], points[point['name']][1]] @@ -246,18 +307,18 @@ def drawEstimatedRoute(self, points, temps, win, zoom, scroll): pointUn[1] + (pointDeux[1] - pointUn[1]) * ratio] # on dessine alors la dernière branche - pygame.draw.line(win, (0, 255, 0), + pygame.draw.line(win, color, (pointUn[0] * zoom + scroll[0], pointUn[1] * zoom + scroll[1]), - (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1])) + (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1]), 2) self.predictionPoint = pointDeux break # on casse la boucle for, pas la peine de faire des calculs pour plus loin, la prédi est finie else: # si le trajet s'arrête après la branche, on dessine la branche en entier - pygame.draw.line(win, (0, 255, 0), + pygame.draw.line(win, color, (pointUn[0] * zoom + scroll[0], pointUn[1] * zoom + scroll[1]), - (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1])) + (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1]), 2) distance -= legDistance # on enlève la distance de la branche parcourue à la distance à parcourir pointUn = pointDeux # on passe au prochain point @@ -271,10 +332,9 @@ def drawRoute(self, points, win, zoom, scroll): :param scroll: le scroll format [x, y] :return: """ - # TODO route qui se dessine correctement même quand on est en direct route = self.papa.route['points'] # on n'a besoin que des noms des points - nextPoint = self.papa.nextPoint + nextPoint = geometry.findClosestSegment(route, (self.papa.x, self.papa.y), points)[1] point1 = points[route[route.index(nextPoint) - 1]['name']][:2] point2 = points[nextPoint['name']][:2] @@ -285,16 +345,30 @@ def drawRoute(self, points, win, zoom, scroll): for point in route: pointDeux = [points[point['name']][0], points[point['name']][1]] - pygame.draw.line(win, (25, 25, 170), + pygame.draw.line(win, (46, 80, 174), (pointUn[0] * zoom + scroll[0], pointUn[1] * zoom + scroll[1]), - (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1]), 2) + (pointDeux[0] * zoom + scroll[0], pointDeux[1] * zoom + scroll[1]), 3) pointUn = pointDeux # on passe au prochain point - def checkEvent(self, event, pilote): + def checkClicked(self, event) -> bool: + """ + Renvoies True si un des boutons (avion ou etiquette) correspond à cet event + :param event: + :return: + """ + if event.ui_element == self.bouton: + return True + if event.ui_element.ui_container == self.etiquette.container: + return True + else: + return False + + def checkEvent(self, event, pilote, conflitBool): """ Vérifie si un bouton associé à l'avion correspond à l'event + :param conflitBool: Si ou non, on est en mode création de conflits :param event: événement à vérifier :param pilote: si l'interface est en mode pilote ou non :return: @@ -302,10 +376,16 @@ def checkEvent(self, event, pilote): # on vérifie que le bouton est bien associé à l'etiquette if event.ui_element.ui_container == self.etiquette.container: + self.startPressTime = 0 if self.drag: self.drag = False return None # comme on était en train de drag, on ne fait aucune action liée au bouton + + if conflitBool: + self.conflitSelected = not self.conflitSelected + return None + if event.mouse_button == 2 and not pilote and event.ui_element is not self.etiquette.DCT: # si c'est un clic milieu, alors on surligne ou non le bouton @@ -322,6 +402,11 @@ def checkEvent(self, event, pilote): return self.Id, 'HighlightBouton', (indexLigne, index) if event.ui_element == self.bouton: + + if conflitBool: + self.conflitSelected = not self.conflitSelected + return None + if event.mouse_button == 2 and not pilote: # clic milieu return self.Id, 'Warning' diff --git a/fonts/LTSuperior-Black.otf b/Python/ressources/fonts/LTSuperior-Black.otf similarity index 100% rename from fonts/LTSuperior-Black.otf rename to Python/ressources/fonts/LTSuperior-Black.otf diff --git a/fonts/LTSuperior-Medium.otf b/Python/ressources/fonts/LTSuperior-Medium.otf similarity index 100% rename from fonts/LTSuperior-Medium.otf rename to Python/ressources/fonts/LTSuperior-Medium.otf diff --git a/fonts/LTSuperior-SemiBold.otf b/Python/ressources/fonts/LTSuperior-SemiBold.otf similarity index 100% rename from fonts/LTSuperior-SemiBold.otf rename to Python/ressources/fonts/LTSuperior-SemiBold.otf diff --git a/theme.json b/Python/ressources/theme.json similarity index 68% rename from theme.json rename to Python/ressources/theme.json index 0040240..8a05e3e 100644 --- a/theme.json +++ b/Python/ressources/theme.json @@ -24,8 +24,8 @@ { "name": "regular", "size": "12", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" } }, "@menu": @@ -38,8 +38,8 @@ "font": { "name": "bold", "size": "12", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" }, "colours": { @@ -51,6 +51,35 @@ "active_border": "#3E3E3E" } }, + "menuBleu": { + "colours": + { + "normal_border": "#808080", + "hovered_border": "#808080", + "disabled_border": "#808080", + "selected_border": "#808080", + "active_border": "#808080" + } + }, + "menuBlanc": { + "colours": + { + "normal_bg": "#C6DADA", + "hovered_bg": "#A3B6C4", + "active_bg": "#C6DADA" + } + }, + "@menuLabel": + { + "misc": { + "border_width": "1", + "text_horiz_alignment": "left", + "text_horiz_alignment_padding": "0", + "text_vert_alignment": "left", + "text_vert_alignment_padding": "2", + "shadow_width": "0" + } + }, "@flightDataWindow": { "misc": { @@ -64,8 +93,8 @@ "font": { "name": "bold", "size": "15", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" }, "colours": { @@ -81,6 +110,27 @@ "active_bg": "#3E3E3E" } }, + "@menuRadar": + { + "misc": { + "border_width": "2", + "shadow_width": "0", + "enable_title_bar": "0" + }, + "colours": + { + "normal_border": "#6B7C91", + "hovered_border": "#6B7C91", + "disabled_border": "#6B7C91", + "selected_border": "#6B7C91", + "active_border": "#6B7C91", + "dark_bg": "#7F94AD", + "normal_text": "#000000", + "hovered_text": "#505050", + "selected_text": "#A0A0A0", + "active_bg":"#5A697A" + } + }, "@etiquette": { "colours": @@ -103,8 +153,8 @@ { "name": "bold", "size": "12", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" } }, "@etiquetteBold": @@ -127,8 +177,8 @@ "name": "bold", "size": "12", "bold": "1", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" } }, "@etiquetteBoldBlue": @@ -151,8 +201,8 @@ "name": "bold", "size": "12", "bold": "1", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" } }, "@etiquetteBlue": @@ -174,8 +224,8 @@ "font": { "name": "bold", "size": "12", - "regular_path": "fonts/LTSuperior-SemiBold.otf", - "bold_path": "fonts/LTSuperior-Black.otf" + "regular_path": "ressources/fonts/LTSuperior-SemiBold.otf", + "bold_path": "ressources/fonts/LTSuperior-Black.otf" } }, "rose": diff --git a/server.py b/Python/server.py similarity index 84% rename from server.py rename to Python/server.py index 2804886..91cb6f8 100644 --- a/server.py +++ b/Python/server.py @@ -1,17 +1,28 @@ + +# Native imports import socket +import math import sys +import pickle from _thread import * from queue import Queue -from network import MCAST_GRP, MCAST_PORT, port -from paquets_avion import * -import pickle import xml.etree.ElementTree as ET from xml.dom import minidom import time import struct import platform +from pathlib import Path + +# Import fichiers +from Python.network import MCAST_GRP, MCAST_PORT, port +from Python.paquets_avion import * + -SIMU = 'simu.xml' +dossierXML = Path("").absolute() / 'XML' + +carte = 'carteSecteur.xml' +aircraftFile = 'aircraft.xml' +simu = 'simu.xml' mode_ecriture = True # On se connecte à internet pour avoir notre adresse IP locale... Oui oui @@ -50,11 +61,11 @@ dictAvion = {} # dict contenant tous les avions requests = [] # liste des requêtes que le serveur doit gérer segments = {} -gameMap = {'points': {}, 'secteurs': [], 'segments': [], 'routes': {}, 'mapScale': 0} +gameMap = {'points': {}, 'zones': [], 'segments': [], 'routes': {}, 'mapScale': 0} # XML map loading -tree = ET.parse('XML/mapAPS.xml') +tree = ET.parse(dossierXML / carte) root = tree.getroot() mapScale = float(root.find('scale').text) # conversion nm-px @@ -73,19 +84,32 @@ gameMap['points'].update({name: (x, y, balise)}) -for secteur in root.find('secteurs'): +for zone in root.find('zones'): contour = [] # liste des points délimitant le contour du secteur, dans l'ordre de lecture xml - for limite in secteur.findall('limite'): + for limite in zone.findall('limite'): x = int(limite.find('x').text) y = int(limite.find('y').text) contour.append((x, y)) - gameMap['secteurs'].append({'couleur': [int(x) for x in secteur.attrib['color'].split(',')], 'contour': contour}) + gameMap['zones'].append({'couleur': [int(x) for x in zone.attrib['color'].split(',')], 'contour': contour}) -listeAeroports = {} +dictSecteurs = {} +for secteur in root.find('secteurs'): + nom = secteur.attrib['name'] + frequence = secteur.find('frequence').text + etranger = False + if secteur.find('etranger') is not None: + if secteur.find('etranger').text == 'True': + etranger = True + + dictSecteurs.update({nom: {'frequence': frequence, 'etranger': etranger}}) + +gameMap.update({'secteurs': dictSecteurs}) + +listeAeroports = {} for direction in root.find('Aeroports'): liste_de_cette_direction = [] @@ -156,8 +180,9 @@ y1 = y2 provenance = 'N' destination = 'S' - - if route.find('provenance') is not None: + if routeType == 'DEPART': + provenance = route.find('AD').text + elif route.find('provenance') is not None: provenance = route.find('provenance').text else: print('[Problème] pas de direction de provenance pour la route', nomRoute) @@ -170,7 +195,7 @@ destination = 'S' if route.find('arrival') is not None: - arrival = {'XFL': int(route.find('arrival').text), 'secteur': route.find('arrival').attrib['secteur']} + arrival = {'XFL': int(route.find('arrival').text), 'secteur': route.find('arrival').attrib['secteur'], 'aeroport': route.find('arrival').attrib['AD']} for sortie in route.findall('sortie'): listeSortie.append({'name': sortie.text, 'min': int(sortie.attrib['min']), 'max': int(sortie.attrib['max'])}) gameMap['routes'].update({nomRoute: {'name': nomRoute, @@ -186,7 +211,7 @@ gameMap.update({'mapScale': mapScale}) aircraftType = {} -tree = ET.parse('XML/aircrafts.xml') +tree = ET.parse(dossierXML / aircraftFile) root = tree.getroot() for aircraft in root: @@ -196,9 +221,10 @@ 'ROC': int(aircraft.find('ROC').text), 'ROD': int(aircraft.find('ROD').text)}}) - +planeId = 0 try: # on essaye de charger une simu, si elle existe - tree = ET.parse('XML/' + SIMU) + + tree = ET.parse(dossierXML / simu) heure = tree.find('heure').text heure = int(heure[0:2]) * 3600 + int(heure[2:]) * 60 @@ -218,7 +244,28 @@ heureSpawn = avion.attrib['heure'] heureSpawn = int(heureSpawn[0:2]) * 3600 + int(heureSpawn[2:]) * 60 - avionSpawnListe.append((heureSpawn, avionDict)) + for route in gameMap['routes']: + if route == avionDict['route']: + spawnRoute = gameMap['routes'][route] + break + if 'altitude' in avionDict: + spawnFL = round(avionDict['altitude'] / 100) + else: + spawnFL = None + avionPack = AvionPacket( + gameMap, + planeId, + avionDict['indicatif'], + avionDict['aircraft'], + aircraftType[avionDict['aircraft']], + spawnRoute, + avionDict['arrival'] == 'True', + FL=spawnFL, + x=avionDict['x'], + y=avionDict['y']) + planeId += 1 + + avionSpawnListe.append((heureSpawn, avionPack)) except: # sinon, on demande juste l'heure de début heure = input('Heure de début de simu, format: hhmm') @@ -294,11 +341,9 @@ def threaded_client(conn, caca): conn.sendall(pickle.dumps(reply)) nombre = 0 - except: - if nombre >= 200: - break - else: - nombre += 1 + + except error: + print(error) print("Lost connection") conn.close() @@ -327,7 +372,6 @@ def threaded_ping_responder(): start_new_thread(threaded_ping_responder, ()) temps = time.time() STCAtriggered = False -planeId = 0 accelerationTemporelle = 1 Running = True while Running: @@ -357,6 +401,28 @@ def threaded_ping_responder(): xEcriture=req[2].x, yEcriture=req[2].y, PFLEcriture=req[2].PFL) + elif req[1] == 'DelayedAdd': + + avionSpawnListe.append((game.heure + req[2][0], req[2][1])) + + if mode_ecriture: + + heures = str(round(game.heure + req[2][0] // 3600)) + if len(heures) == 1: + heures = '0' + heures + minutes = str(round(game.heure + req[2][0] % 3600 // 60)) + if len(minutes) == 1: + minutes = '0' + minutes + + generateAvionXML(avionsXML, + heures + minutes, + req[2][1].indicatif, + req[2][1].aircraft, + req[2][1].route['name'], + req[2][1].altitude, + xEcriture=req[2][1].x, + yEcriture=req[2][1].y, + PFLEcriture=req[2][1].PFL) elif req[1] == 'Remove': dictAvion.pop(req[0]) @@ -364,8 +430,7 @@ def threaded_ping_responder(): dictAvion[req[0]].selectedAlti = req[2] elif req[1] == 'PFL': dictAvion[req[0]].PFL = req[2] - dictAvion[req[0]].changeXFL() - dictAvion[req[0]].changeSortieSecteur() + dictAvion[req[0]].changeXFL(gameMap) elif req[1] == 'CFL': dictAvion[req[0]].CFL = req[2] elif req[1] == 'C_IAS': @@ -380,7 +445,7 @@ def threaded_ping_responder(): dictAvion[req[0]].clearedRate = None elif req[1] == 'XFL': dictAvion[req[0]].XFL = req[2] - dictAvion[req[0]].changeSortieSecteur() + dictAvion[req[0]].changeSortieSecteur(gameMap) elif req[1] == 'XPT': dictAvion[req[0]].XPT = req[2] elif req[1] == 'HDG': @@ -432,25 +497,8 @@ def threaded_ping_responder(): toBeRemovedFromSpawn = [] for spawn in avionSpawnListe: if spawn[0] <= game.heure: - for route in gameMap['routes']: - if route == spawn[1]['route']: - spawnRoute = gameMap['routes'][route] - break - if 'altitude' in spawn[1]: - spawnFL = round(spawn[1]['altitude']/100) - else: - spawnFL = None - dictAvion.update({planeId: AvionPacket( - gameMap, - planeId, - spawn[1]['indicatif'], - spawn[1]['aircraft'], - aircraftType[spawn[1]['aircraft']], - spawnRoute, - spawn[1]['arrival'], - FL=spawnFL)}) toBeRemovedFromSpawn.append(spawn) - planeId += 1 + dictAvion.update({spawn[1].Id: spawn[1]}) for avion in toBeRemovedFromSpawn: avionSpawnListe.remove(avion) @@ -467,6 +515,7 @@ def threaded_ping_responder(): dictAvion.pop(avion) for avion in list(dictAvion.values()): # tous les calculs de distances sont ici effectués en pixel, la conversion se fait avec le mapScale + # TODO remplacer ce STCA avec le nouveau dans server_def STCAtriggered = False predictedPos = [] VspeedOne = avion.evolution diff --git a/server_browser.py b/Python/server_browser.py similarity index 91% rename from server_browser.py rename to Python/server_browser.py index 0ebc7fb..6dd4201 100644 --- a/server_browser.py +++ b/Python/server_browser.py @@ -1,6 +1,9 @@ + +# Native Imports import socket -from network import MCAST_GRP, MCAST_PORT +# Imports fichiers +from Python.network import MCAST_GRP, MCAST_PORT def serverBrowser(): """Lan scan et server browser en une fonction diff --git a/Python/server_def.py b/Python/server_def.py new file mode 100644 index 0000000..88c01fb --- /dev/null +++ b/Python/server_def.py @@ -0,0 +1,41 @@ + +# Native imports +import math + +# Imports fichiers +import Python.geometry as geometry +from Python.valeurs_config import * + + +def STCA(avion1, avion2, carte) -> bool: + """ + Calcule si on doit mettre le SCTA sur deux avions + :param avion1: l'avion paquet + :param avion2: + :param carte: la carte de la map + :return: + """ + + temps = geometry.distanceMinie((avion1.x, avion1.y), avion1.speedPx / radarRefresh, avion1.headingRad, + (avion2.x, avion2.y), avion2.speedPx / radarRefresh, avion2.headingRad) + + if (math.sqrt( + ((avion1.x + avion1.speedPx / radarRefresh * temps * math.cos(avion1.headingRad)) - + (avion2.x + avion2.speedPx / radarRefresh * temps * math.cos(avion2.headingRad))) ** 2 + + ((avion1.y + avion1.speedPx / radarRefresh * temps * math.sin(avion1.headingRad)) - + (avion2.y + avion2.speedPx / radarRefresh * temps * math.sin(avion2.headingRad))) ** 2 + ) <= 5 / carte['mapScale']): # si la distance est infèrieur à 5 nm + + if avion1.evolution == avion2.evolution == 0 and avion2.altitude == avion1.altitude: + return True + + elif avion1.evolution == avion2.evolution == 0: + return False + + elif abs(avion2.altitude + avion2.evolution / radarRefresh * temps + - (avion1.altitude + avion1.evolution / radarRefresh * temps)) <= 2000: + return True + + return False + + diff --git a/valeurs_config.py b/Python/valeurs_config.py similarity index 81% rename from valeurs_config.py rename to Python/valeurs_config.py index fd9ed3e..b6a6cf1 100644 --- a/valeurs_config.py +++ b/Python/valeurs_config.py @@ -6,9 +6,8 @@ acceldefault = 3 # accélération/decelération de kt par refresh turnRateDefault = 10 # turnrate/refresh par défault liste_etat_freq = ['previousFreq', 'previousShoot', 'inFreq', 'nextCoord', 'nextShoot', 'nextFreq'] -secteurBoundaries = [245, 365] # niveau planché et plafond du secteur +valeurCoord = 8 # combien de minute avant la sortie la coord passe plotSize = 5 # taille des plots avions -listeEtrangers = ['G2', 'M2'] # liste des secteurs ajdacents non interopérables dragDelay = 150 # on utilise cette valeur seuil pour déterminer si on doit cliquer sur un bouton ou drag l'etiquette offsettEtiquetteDefault = 30 # de combien les etiquettes sont décalées en px à quand on les dessine la 1ere fois -temps_disparition_menus = 600 # en combien de milli sec les menus disparaissent après ne plus être survolé +temps_disparition_menus = 300 # en combien de milli sec les menus disparaissent après ne plus être survolé diff --git a/requirements.txt b/requirements.txt index ae1e042..fb2aa95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -numpy +numpy~=2.1.1 pygame-ce -pygame_gui -python-i18n \ No newline at end of file +pygame_gui~=0.6.12 +python-i18n +scipy~=1.14.1 \ No newline at end of file