diff --git a/gen/aircraft.py b/gen/aircraft.py index 904b7ece..42d35939 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -120,6 +120,10 @@ class AircraftConflictGenerator: else: group.units[idx].set_client() + # Do not generate player group with late activation. + if group.late_activation: + group.late_activation = False + # Set up F-14 Client to have pre-stored alignement if unit_type is F_14B: group.units[idx].set_property(F_14B.Properties.INSAlignmentStored.id, True) @@ -284,20 +288,45 @@ class AircraftConflictGenerator: groups.append(group) return groups + def _setup_custom_payload(self, flight, group:FlyingGroup): + if flight.use_custom_loadout: + + logging.info("Custom loadout for flight : " + flight.__repr__()) + for p in group.units: + p.pylons.clear() + + for key in flight.loadout.keys(): + if "Pylon" + key in flight.unit_type.__dict__.keys(): + print(flight.loadout) + weapon_dict = flight.unit_type.__dict__["Pylon" + key].__dict__ + if flight.loadout[key] in weapon_dict.keys(): + weapon = weapon_dict[flight.loadout[key]] + group.load_pylon(weapon, int(key)) + else: + logging.warning("Pylon not found ! => Pylon" + key + " on " + str(flight.unit_type)) + def generate_flights(self, cp, country, flight_planner:FlightPlanner): + for flight in flight_planner.interceptor_flights: + group = self.generate_planned_flight(cp, country, flight) + self.setup_group_as_intercept_flight(group, flight) + self._setup_custom_payload(flight, group) + for flight in flight_planner.cap_flights: group = self.generate_planned_flight(cp, country, flight) self.setup_group_as_cap_flight(group, flight) + self._setup_custom_payload(flight, group) for flight in flight_planner.cas_flights: group = self.generate_planned_flight(cp, country, flight) self.setup_group_as_cas_flight(group, flight) + self._setup_custom_payload(flight, group) for flight in flight_planner.sead_flights: group = self.generate_planned_flight(cp, country, flight) self.setup_group_as_sead_flight(group, flight) + self._setup_custom_payload(flight, group) def generate_planned_flight(self, cp, country, flight:Flight): try: @@ -321,10 +350,16 @@ class AircraftConflictGenerator: group.points[0].ETA = flight.scheduled_in * 60 return group + def setup_group_as_intercept_flight(self, group, flight): + group.points[0].ETA = 0 + group.late_activation = True + self._setup_group(group, Intercept, flight.client_count) + + def setup_group_as_cap_flight(self, group, flight): self._setup_group(group, CAP, flight.client_count) for point in flight.points: - group.add_waypoint(Point(point[0],point[1]), point[2]) + group.add_waypoint(Point(point.x,point.y), point.alt) def setup_group_as_cas_flight(self, group, flight): group.task = CAS.name @@ -335,8 +370,8 @@ class AircraftConflictGenerator: group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)) group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree)) - for location in flight.points: - group.add_waypoint(Point(location[0], location[1]), location[2]) + for point in flight.points: + group.add_waypoint(Point(point.x,point.y), point.alt) def setup_group_as_sead_flight(self, group, flight): self._setup_group(group, SEAD, flight.client_count) @@ -347,7 +382,7 @@ class AircraftConflictGenerator: group.points[0].tasks.append(OptROE(OptROE.Values.WeaponFree)) for point in flight.points: - group.add_waypoint(Point(point[0], point[1]), point[2]) + group.add_waypoint(Point(point.x,point.y), point.alt) def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True): assert not escort or len(self.escort_targets) == 0 diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 1f4db09b..4c23fe4f 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -5,8 +5,7 @@ import random from game import db from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE -from gen.flights.flight import Flight, FlightType - +from gen.flights.flight import Flight, FlightType, FlightWaypoint # TODO : Ideally should be based on the aircraft type instead / Availability of fuel STRIKE_MAX_RANGE = 150000 @@ -36,6 +35,7 @@ class FlightPlanner: def __init__(self, from_cp, game): # TODO : have the flight planner depend on a 'stance' setting : [Defensive, Aggresive... etc] and faction doctrine + # TODO : the flight planner should plan package and operations self.from_cp = from_cp self.game = game self.aircraft_inventory = {} # local copy of the airbase inventory @@ -132,7 +132,9 @@ class FlightPlanner: patrolled = [] for ground_object in self.from_cp.ground_objects: if ground_object.group_id not in patrolled and not ground_object.airbase_group: - flight.points.append([ground_object.position.x, ground_object.position.y, patrol_alt]) + point = FlightWaypoint(ground_object.position.x, ground_object.position.y, patrol_alt) + point.description = "Patrol #" + str(len(flight.points)) + flight.points.append(point) patrolled.append(ground_object.group_id) self.cap_flights.append(flight) @@ -170,7 +172,9 @@ class FlightPlanner: location = random.choice(cas_location) flight.targets.append(cas_location) - flight.points.append([location[0], location[1], 1000]) # TODO : Egress / Ingress points + point = FlightWaypoint(location[0], location[1], 1000) + point.description = "PROVIDE CAS" + flight.points.append(point) self.cas_flights.append(flight) self.flights.append(flight) @@ -203,13 +207,16 @@ class FlightPlanner: inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, random.choice([FlightType.SEAD, FlightType.DEAD])) - # Flight path : fly over each ground object (TODO : improve) flight.points = [] flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5) location = self.potential_sead_targets[0][0] self.potential_sead_targets.pop(0) - flight.points.append([location.position.x, location.position.y, 1000]) # TODO : Egress / Ingress points + + point = FlightWaypoint(location.position.x, location.position.y, 1000) + point.description = "SEAD" + point.targets.append(location) + flight.points.append(point) self.cas_flights.append(flight) self.flights.append(flight) diff --git a/gen/flights/flight.py b/gen/flights/flight.py index f540e52e..3f151132 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -1,5 +1,8 @@ -from dcs.unittype import UnitType from enum import Enum +from typing import List + +from dcs.unittype import UnitType + from game import db @@ -26,36 +29,52 @@ class FlightType(Enum): EWAR = 16 -class Flight: +class FlightWaypoint(): - unit_type: UnitType + def __init__(self, x: float, y: float, alt=0): + self.x = x + self.y = y + self.alt = alt + self.name = "" + self.description = "" + self.targets = [] + + +class Flight: + unit_type: UnitType = None from_cp = None - points = [] - type = "" - count = 0 - client_count = 0 + points: List[FlightWaypoint] = [] + flight_type: FlightType = None + count: int = 0 + client_count: int = 0 targets = [] + use_custom_loadout = False + loadout = {} + preset_loadout_name = "" # How long before this flight should take off scheduled_in = 0 - + def __init__(self, unit_type: UnitType, count: int, from_cp, flight_type: FlightType): self.unit_type = unit_type self.count = count self.from_cp = from_cp self.flight_type = flight_type + self.points = [] + self.targets = [] + self.loadout = {} def __repr__(self): - return self.flight_type.name + " | " + str(self.count) + "x" + db.unit_type_name(self.unit_type)\ + return self.flight_type.name + " | " + str(self.count) + "x" + db.unit_type_name(self.unit_type) \ + " in " + str(self.scheduled_in) + " minutes (" + str(len(self.points)) + " wpt)" + # Test if __name__ == '__main__': - from dcs.planes import A_10C - from theater import ControlPoint, Point + from theater import ControlPoint, Point, List - from_cp = ControlPoint(0, "AA", Point(0,0), None, [], 0, 0) + from_cp = ControlPoint(0, "AA", Point(0, 0), None, [], 0, 0) f = Flight(A_10C, 4, from_cp, FlightType.CAS) f.scheduled_in = 50 - print(f) \ No newline at end of file + print(f) diff --git a/qt_ui/main.py b/qt_ui/main.py index e17a0d0d..54802e0b 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -27,10 +27,6 @@ if __name__ == "__main__": logging.info("Using {} as userdata folder".format(persistency.base_path())) app = QApplication(sys.argv) - uiconstants.load_icons() - uiconstants.load_event_icons() - uiconstants.load_aircraft_icons() - uiconstants.load_vehicle_icons() # Splash screen setup pixmap = QPixmap("./resources/ui/splash_screen.png") @@ -38,6 +34,11 @@ if __name__ == "__main__": splash.show() # Once splash screen is up : load resources & setup stuff + uiconstants.load_icons() + uiconstants.load_event_icons() + uiconstants.load_aircraft_icons() + uiconstants.load_vehicle_icons() + persistency.setup(sys.argv[1]) css = "" diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index bd0e3936..479b0589 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -24,7 +24,6 @@ class QTopPanel(QFrame): self.turnCounter = QTurnCounter() self.budgetBox = QBudgetBox() - self.passTurnButton = QPushButton("Pass Turn") self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") @@ -34,6 +33,9 @@ class QTopPanel(QFrame): self.proceedButton.setIcon(CONST.ICONS["PassTurn"]) self.proceedButton.setProperty("style", "btn-primary") self.proceedButton.clicked.connect(self.proceed) + if self.game.turn == 0: + self.proceedButton.setEnabled(False) + self.submenus = QVBoxLayout() self.settings = QPushButton("Settings") @@ -75,6 +77,7 @@ class QTopPanel(QFrame): def passTurn(self): self.game.pass_turn() GameUpdateSignal.get_instance().updateGame(self.game) + self.proceedButton.setEnabled(True) def proceed(self): self.subwindow = QMissionPlanning(self.game) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index a7c1d280..c49cc731 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -125,8 +125,8 @@ class QLiberationMap(QGraphicsView): for flight in planner.flights: scene.addEllipse(pos[0], pos[1], 4, 4) prev_pos = list(pos) - for points in flight.points: - new_pos = self._transform_point(Point(points[0], points[1])) + for point in flight.points: + new_pos = self._transform_point(Point(point.x, point.y)) scene.addLine(prev_pos[0]+2, prev_pos[1]+2, new_pos[0]+2, new_pos[1]+2, flight_path_pen) scene.addEllipse(new_pos[0], new_pos[1], 4, 4, pen, brush) prev_pos = list(new_pos) diff --git a/qt_ui/windows/mission/QChooseAirbase.py b/qt_ui/windows/mission/QChooseAirbase.py index dc1a79d2..c8f02592 100644 --- a/qt_ui/windows/mission/QChooseAirbase.py +++ b/qt_ui/windows/mission/QChooseAirbase.py @@ -26,9 +26,7 @@ class QChooseAirbase(QGroupBox): def _on_airbase_selected(self): selected = self.depart_from.currentText() - print("Airbase changed to : " + selected) self.selected_airbase_changed.emit(selected) - print("Airbase changed to : " + selected) diff --git a/qt_ui/windows/mission/flight/QFlightPlanner.py b/qt_ui/windows/mission/flight/QFlightPlanner.py index 36cf1af5..cdf5c090 100644 --- a/qt_ui/windows/mission/flight/QFlightPlanner.py +++ b/qt_ui/windows/mission/flight/QFlightPlanner.py @@ -12,7 +12,7 @@ class QFlightPlanner(QTabWidget): def __init__(self, flight: Flight, game: Game): super(QFlightPlanner, self).__init__() self.general_settings_tab = QGeneralFlightSettingsTab(flight, game) - self.payload_tab = QFlightPayloadTab(flight) + self.payload_tab = QFlightPayloadTab(flight, game) self.waypoint_tab = QFlightWaypointTab(flight) self.addTab(self.general_settings_tab, "General Flight settings") self.addTab(self.payload_tab, "Payload") diff --git a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py index 2f6f25b5..c6c3b66a 100644 --- a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py +++ b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py @@ -1,16 +1,19 @@ -from PySide2.QtWidgets import QFrame, QGridLayout, QLabel +from PySide2.QtWidgets import QFrame, QGridLayout +from game import Game from gen.flights.flight import Flight +from qt_ui.windows.mission.flight.payload.QLoadoutEditor import QLoadoutEditor class QFlightPayloadTab(QFrame): - def __init__(self, flight: Flight): + def __init__(self, flight: Flight, game: Game): super(QFlightPayloadTab, self).__init__() self.flight = flight + self.payload_editor = QLoadoutEditor(flight, game) self.init_ui() def init_ui(self): layout = QGridLayout() - layout.addWidget(QLabel("Coming in two weeks")) + layout.addWidget(self.payload_editor) self.setLayout(layout) diff --git a/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py new file mode 100644 index 00000000..47711a89 --- /dev/null +++ b/qt_ui/windows/mission/flight/payload/QLoadoutEditor.py @@ -0,0 +1,33 @@ +import inspect + +from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QSpinBox, QGridLayout, QVBoxLayout, QSizePolicy + +from qt_ui.windows.mission.flight.payload.QPylonEditor import QPylonEditor + + +class QLoadoutEditor(QGroupBox): + + def __init__(self, flight, game): + super(QLoadoutEditor, self).__init__("Use custom loadout") + self.flight = flight + self.game = game + self.setCheckable(True) + self.setChecked(flight.use_custom_loadout) + + self.toggled.connect(self.on_toggle) + + layout = QGridLayout() + + pylons = [v for v in self.flight.unit_type.__dict__.values() if inspect.isclass(v) and v.__name__.startswith("Pylon")] + for i, pylon in enumerate(pylons): + label = QLabel("{}".format(pylon.__name__[len("Pylon"):])) + label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)) + layout.addWidget(label, i, 0) + layout.addWidget(QPylonEditor(flight, pylon, i+1), i, 1) + + self.setLayout(layout) + + def on_toggle(self): + self.flight.use_custom_loadout = self.isChecked() + + diff --git a/qt_ui/windows/mission/flight/payload/QPylonEditor.py b/qt_ui/windows/mission/flight/payload/QPylonEditor.py new file mode 100644 index 00000000..30ca6414 --- /dev/null +++ b/qt_ui/windows/mission/flight/payload/QPylonEditor.py @@ -0,0 +1,35 @@ +import logging + +from PySide2.QtWidgets import QWidget, QSpinBox, QComboBox + + +class QPylonEditor(QComboBox): + + def __init__(self, flight, pylon, pylon_number): + super(QPylonEditor, self).__init__() + self.pylon = pylon + self.pylon_number = pylon_number + self.flight = flight + + possible_loadout = [i for i in self.pylon.__dict__.keys() if i[:1] != '_'] + + if not str(self.pylon_number) in self.flight.loadout.keys(): + self.flight.loadout[str(self.pylon_number)] = "" + + self.addItem("None") + for i,k in enumerate(possible_loadout): + self.addItem(str(k)) + if self.flight.loadout[str(self.pylon_number)] == str(k): + self.setCurrentIndex(i + 1) + + self.currentTextChanged.connect(self.on_pylon_change) + + def on_pylon_change(self): + selected = self.currentText() + if selected == "None": + logging.info("Pylon " + str(self.pylon_number) + " emptied") + self.flight.loadout[str(self.pylon_number)] = "" + else: + logging.info("Pylon " + str(self.pylon_number) + " changed to " + selected) + self.flight.loadout[str(self.pylon_number)] = selected + diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointItem.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointItem.py index 9f35e147..b1410fd5 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointItem.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointItem.py @@ -2,11 +2,12 @@ from typing import List from PySide2.QtGui import QStandardItem +from gen.flights.flight import FlightWaypoint + class QWaypointItem(QStandardItem): - def __init__(self, point: List[int]): + def __init__(self, point: FlightWaypoint): super(QWaypointItem, self).__init__() - - self.setText("X: " + str(int(point[0])) + "; Y: " + str(int(point[1])) + "; Alt: " + str(int(point[2])) + "m") + self.setText('{0: <16}'.format(point.description) + " -- [X: " + str(int(point.x)) + "; Y: " + str(int(point.y)) + "; Alt: " + str(int(point.alt)) + "m]") diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py index 5d8dbad9..dff7849d 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py @@ -1,7 +1,7 @@ from PySide2.QtGui import QStandardItemModel from PySide2.QtWidgets import QListView -from gen.flights.flight import Flight +from gen.flights.flight import Flight, FlightWaypoint from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import QWaypointItem @@ -13,6 +13,8 @@ class QFlightWaypointList(QListView): self.setModel(self.model) self.flight = flight - self.model.appendRow(QWaypointItem([flight.from_cp.position.x, flight.from_cp.position.y, 0])) + takeoff = FlightWaypoint(flight.from_cp.position.x, flight.from_cp.position.y, 0) + takeoff.description = "Take Off" + self.model.appendRow(QWaypointItem(takeoff)) for i, point in enumerate(self.flight.points): self.model.appendRow(QWaypointItem(point)) \ No newline at end of file diff --git a/resources/ui/units/aircrafts/F-5E_24.jpg b/resources/ui/units/aircrafts/F-5E-3_24.jpg similarity index 100% rename from resources/ui/units/aircrafts/F-5E_24.jpg rename to resources/ui/units/aircrafts/F-5E-3_24.jpg diff --git a/userdata/persistency.py b/userdata/persistency.py index e416d8d9..818fdced 100644 --- a/userdata/persistency.py +++ b/userdata/persistency.py @@ -1,9 +1,8 @@ import logging -import typing -import pickle import os -import sys +import pickle import shutil +import sys from dcs import installation