mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Move FlightJs out of MapModel.
This commit is contained in:
parent
ad0d3412fb
commit
45e76e12b6
@ -17,8 +17,13 @@ class AirTaskingOrder:
|
|||||||
|
|
||||||
def remove_package(self, package: Package) -> None:
|
def remove_package(self, package: Package) -> None:
|
||||||
"""Removes a package from the ATO."""
|
"""Removes a package from the ATO."""
|
||||||
|
# Remove all the flights individually so the database gets updated.
|
||||||
|
for flight in list(package.flights):
|
||||||
|
package.remove_flight(flight)
|
||||||
self.packages.remove(package)
|
self.packages.remove(package)
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""Removes all packages from the ATO."""
|
"""Removes all packages from the ATO."""
|
||||||
self.packages.clear()
|
# Remove all packages individually so the database gets updated.
|
||||||
|
for package in self.packages:
|
||||||
|
self.remove_package(package)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class Navigating(InFlight):
|
|||||||
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
||||||
) -> None:
|
) -> None:
|
||||||
super().on_game_tick(events, time, duration)
|
super().on_game_tick(events, time, duration)
|
||||||
events.update_flight(self.flight, self.estimate_position())
|
events.update_flight_position(self.flight, self.estimate_position())
|
||||||
|
|
||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -129,6 +129,9 @@ class Package:
|
|||||||
"""Removes a flight from the package."""
|
"""Removes a flight from the package."""
|
||||||
self.flights.remove(flight)
|
self.flights.remove(flight)
|
||||||
self._db.remove(flight.id)
|
self._db.remove(flight.id)
|
||||||
|
flight.return_pilots_and_aircraft()
|
||||||
|
if flight.cargo is not None:
|
||||||
|
flight.cargo.transport = None
|
||||||
if not self.flights:
|
if not self.flights:
|
||||||
self.waypoints = None
|
self.waypoints = None
|
||||||
|
|
||||||
|
|||||||
@ -92,5 +92,4 @@ class PackageBuilder:
|
|||||||
"""Returns any planned flights to the inventory."""
|
"""Returns any planned flights to the inventory."""
|
||||||
flights = list(self.package.flights)
|
flights = list(self.package.flights)
|
||||||
for flight in flights:
|
for flight in flights:
|
||||||
flight.return_pilots_and_aircraft()
|
|
||||||
self.package.remove_flight(flight)
|
self.package.remove_flight(flight)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from uuid import UUID
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.server.combat.models import FrozenCombatJs
|
from game.server.combat.models import FrozenCombatJs
|
||||||
|
from game.server.flights.models import FlightJs
|
||||||
from game.server.leaflet import LeafletLatLon
|
from game.server.leaflet import LeafletLatLon
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -14,18 +15,24 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GameUpdateEventsJs(BaseModel):
|
class GameUpdateEventsJs(BaseModel):
|
||||||
updated_flights: dict[UUID, LeafletLatLon]
|
updated_flight_positions: dict[UUID, LeafletLatLon]
|
||||||
new_combats: list[FrozenCombatJs] = []
|
new_combats: list[FrozenCombatJs]
|
||||||
updated_combats: list[FrozenCombatJs] = []
|
updated_combats: list[FrozenCombatJs]
|
||||||
navmesh_updates: set[bool] = set()
|
navmesh_updates: set[bool]
|
||||||
unculled_zones_updated: bool = False
|
unculled_zones_updated: bool
|
||||||
threat_zones_updated: bool = False
|
threat_zones_updated: bool
|
||||||
|
new_flights: list[FlightJs]
|
||||||
|
updated_flights: set[UUID]
|
||||||
|
deleted_flights: set[UUID]
|
||||||
|
selected_flight: UUID | None
|
||||||
|
deselected_flight: bool
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_events(cls, events: GameUpdateEvents, game: Game) -> GameUpdateEventsJs:
|
def from_events(cls, events: GameUpdateEvents, game: Game) -> GameUpdateEventsJs:
|
||||||
return GameUpdateEventsJs(
|
return GameUpdateEventsJs(
|
||||||
updated_flights={
|
updated_flight_positions={
|
||||||
f[0].id: f[1].latlng().as_list() for f in events.updated_flights
|
f[0].id: f[1].latlng().as_list()
|
||||||
|
for f in events.updated_flight_positions
|
||||||
},
|
},
|
||||||
new_combats=[
|
new_combats=[
|
||||||
FrozenCombatJs.for_combat(c, game.theater) for c in events.new_combats
|
FrozenCombatJs.for_combat(c, game.theater) for c in events.new_combats
|
||||||
@ -37,4 +44,9 @@ class GameUpdateEventsJs(BaseModel):
|
|||||||
navmesh_updates=events.navmesh_updates,
|
navmesh_updates=events.navmesh_updates,
|
||||||
unculled_zones_updated=events.unculled_zones_updated,
|
unculled_zones_updated=events.unculled_zones_updated,
|
||||||
threat_zones_updated=events.threat_zones_updated,
|
threat_zones_updated=events.threat_zones_updated,
|
||||||
|
new_flights=[FlightJs.for_flight(f) for f in events.new_flights],
|
||||||
|
updated_flights=events.updated_flights,
|
||||||
|
deleted_flights=events.deleted_flights,
|
||||||
|
selected_flight=events.selected_flight,
|
||||||
|
deselected_flight=events.deselected_flight,
|
||||||
)
|
)
|
||||||
|
|||||||
26
game/server/flights/models.py
Normal file
26
game/server/flights/models.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from dcs.mapping import LatLng
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from game.ato import Flight
|
||||||
|
from game.ato.flightstate import InFlight
|
||||||
|
|
||||||
|
|
||||||
|
class FlightJs(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
blue: bool
|
||||||
|
position: LatLng | None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def for_flight(flight: Flight) -> FlightJs:
|
||||||
|
# Don't provide a location for aircraft that aren't in the air. Later we can
|
||||||
|
# expand the model to include the state data for the UI so that it can make its
|
||||||
|
# own decisions about whether or not to draw the aircraft, but for now we'll
|
||||||
|
# filter here.
|
||||||
|
position = None
|
||||||
|
if isinstance(flight.state, InFlight):
|
||||||
|
position = flight.position().latlng()
|
||||||
|
return FlightJs(id=flight.id, blue=flight.blue, position=position)
|
||||||
@ -4,13 +4,30 @@ from fastapi import APIRouter, Depends
|
|||||||
from shapely.geometry import LineString, Point as ShapelyPoint
|
from shapely.geometry import LineString, Point as ShapelyPoint
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.server import GameContext
|
|
||||||
from game.server.leaflet import LeafletPoly, ShapelyUtil
|
|
||||||
from game.ato.flightplan import CasFlightPlan, PatrollingFlightPlan
|
from game.ato.flightplan import CasFlightPlan, PatrollingFlightPlan
|
||||||
|
from game.server import GameContext
|
||||||
|
from game.server.flights.models import FlightJs
|
||||||
|
from game.server.leaflet import LeafletPoly, ShapelyUtil
|
||||||
|
|
||||||
router: APIRouter = APIRouter(prefix="/flights")
|
router: APIRouter = APIRouter(prefix="/flights")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_flights(game: Game = Depends(GameContext.get)) -> list[FlightJs]:
|
||||||
|
flights = []
|
||||||
|
for coalition in game.coalitions:
|
||||||
|
for package in coalition.ato.packages:
|
||||||
|
for flight in package.flights:
|
||||||
|
flights.append(FlightJs.for_flight(flight))
|
||||||
|
return flights
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{flight_id}")
|
||||||
|
def get_flight(flight_id: UUID, game: Game = Depends(GameContext.get)) -> FlightJs:
|
||||||
|
flight = game.db.flights.get(flight_id)
|
||||||
|
return FlightJs.for_flight(flight)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flight_id}/commit-boundary")
|
@router.get("/{flight_id}/commit-boundary")
|
||||||
def commit_boundary(
|
def commit_boundary(
|
||||||
flight_id: UUID, game: Game = Depends(GameContext.get)
|
flight_id: UUID, game: Game = Depends(GameContext.get)
|
||||||
|
|||||||
@ -1,55 +1,91 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight
|
from game.ato import Flight, Package
|
||||||
from game.sim.combat import FrozenCombat
|
from game.sim.combat import FrozenCombat
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class GameUpdateEvents:
|
class GameUpdateEvents:
|
||||||
def __init__(self) -> None:
|
simulation_complete = False
|
||||||
self.simulation_complete = False
|
new_combats: list[FrozenCombat] = field(default_factory=list)
|
||||||
self.new_combats: list[FrozenCombat] = []
|
updated_combats: list[FrozenCombat] = field(default_factory=list)
|
||||||
self.updated_combats: list[FrozenCombat] = []
|
updated_flight_positions: list[tuple[Flight, Point]] = field(default_factory=list)
|
||||||
self.updated_flights: list[tuple[Flight, Point]] = []
|
navmesh_updates: set[bool] = field(default_factory=set)
|
||||||
self.navmesh_updates: set[bool] = set()
|
unculled_zones_updated: bool = False
|
||||||
self.unculled_zones_updated: bool = False
|
threat_zones_updated: bool = False
|
||||||
self.threat_zones_updated: bool = False
|
new_flights: set[Flight] = field(default_factory=set)
|
||||||
|
updated_flights: set[UUID] = field(default_factory=set)
|
||||||
|
deleted_flights: set[UUID] = field(default_factory=set)
|
||||||
|
selected_flight: UUID | None = None
|
||||||
|
deselected_flight: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def empty(self) -> bool:
|
def empty(self) -> bool:
|
||||||
return not any(
|
return self == GameUpdateEvents()
|
||||||
[
|
|
||||||
self.simulation_complete,
|
|
||||||
self.new_combats,
|
|
||||||
self.updated_combats,
|
|
||||||
self.updated_flights,
|
|
||||||
self.navmesh_updates,
|
|
||||||
self.unculled_zones_updated,
|
|
||||||
self.threat_zones_updated,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def complete_simulation(self) -> None:
|
def complete_simulation(self) -> GameUpdateEvents:
|
||||||
self.simulation_complete = True
|
self.simulation_complete = True
|
||||||
|
return self
|
||||||
|
|
||||||
def new_combat(self, combat: FrozenCombat) -> None:
|
def new_combat(self, combat: FrozenCombat) -> GameUpdateEvents:
|
||||||
self.new_combats.append(combat)
|
self.new_combats.append(combat)
|
||||||
|
return self
|
||||||
|
|
||||||
def update_combat(self, combat: FrozenCombat) -> None:
|
def update_combat(self, combat: FrozenCombat) -> GameUpdateEvents:
|
||||||
self.updated_combats.append(combat)
|
self.updated_combats.append(combat)
|
||||||
|
return self
|
||||||
|
|
||||||
def update_flight(self, flight: Flight, new_position: Point) -> None:
|
def update_flight_position(
|
||||||
self.updated_flights.append((flight, new_position))
|
self, flight: Flight, new_position: Point
|
||||||
|
) -> GameUpdateEvents:
|
||||||
|
self.updated_flight_positions.append((flight, new_position))
|
||||||
|
return self
|
||||||
|
|
||||||
def update_navmesh(self, player: bool) -> None:
|
def update_navmesh(self, player: bool) -> GameUpdateEvents:
|
||||||
self.navmesh_updates.add(player)
|
self.navmesh_updates.add(player)
|
||||||
|
return self
|
||||||
|
|
||||||
def update_unculled_zones(self) -> None:
|
def update_unculled_zones(self) -> GameUpdateEvents:
|
||||||
self.unculled_zones_updated = True
|
self.unculled_zones_updated = True
|
||||||
|
return self
|
||||||
|
|
||||||
def update_threat_zones(self) -> None:
|
def update_threat_zones(self) -> GameUpdateEvents:
|
||||||
self.threat_zones_updated = True
|
self.threat_zones_updated = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def new_flight(self, flight: Flight) -> GameUpdateEvents:
|
||||||
|
self.new_flights.add(flight)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def update_flight(self, flight: Flight) -> GameUpdateEvents:
|
||||||
|
self.updated_flights.add(flight.id)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def update_flights_in_package(self, package: Package) -> GameUpdateEvents:
|
||||||
|
self.updated_flights.update({f.id for f in package.flights})
|
||||||
|
return self
|
||||||
|
|
||||||
|
def delete_flight(self, flight: Flight) -> GameUpdateEvents:
|
||||||
|
self.deleted_flights.add(flight.id)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def delete_flights_in_package(self, package: Package) -> GameUpdateEvents:
|
||||||
|
self.deleted_flights.update({f.id for f in package.flights})
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_flight(self, flight: Flight) -> GameUpdateEvents:
|
||||||
|
self.selected_flight = flight.id
|
||||||
|
self.deselected_flight = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
def deselect_flight(self) -> GameUpdateEvents:
|
||||||
|
self.deselected_flight = True
|
||||||
|
self.selected_flight = None
|
||||||
|
return self
|
||||||
|
|||||||
@ -9,8 +9,8 @@ from typing import Optional, Sequence, TYPE_CHECKING
|
|||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
|
||||||
from game.ato import Flight, FlightType, Package
|
from game.ato import Flight, FlightType, Package
|
||||||
from game.settings import AutoAtoBehavior, Settings
|
|
||||||
from game.ato.flightplan import FlightPlanBuilder
|
from game.ato.flightplan import FlightPlanBuilder
|
||||||
|
from game.settings import AutoAtoBehavior, Settings
|
||||||
from .pilot import Pilot, PilotStatus
|
from .pilot import Pilot, PilotStatus
|
||||||
from ..db.database import Database
|
from ..db.database import Database
|
||||||
from ..utils import meters
|
from ..utils import meters
|
||||||
@ -381,7 +381,6 @@ class Squadron:
|
|||||||
for flight in list(package.flights):
|
for flight in list(package.flights):
|
||||||
if flight.squadron == self and flight.flight_type is FlightType.FERRY:
|
if flight.squadron == self and flight.flight_type is FlightType.FERRY:
|
||||||
package.remove_flight(flight)
|
package.remove_flight(flight)
|
||||||
flight.return_pilots_and_aircraft()
|
|
||||||
if not package.flights:
|
if not package.flights:
|
||||||
self.coalition.ato.remove_package(package)
|
self.coalition.ato.remove_package(package)
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,10 @@ from typing import Generic, Iterator, List, Optional, Sequence, TYPE_CHECKING, T
|
|||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
|
|
||||||
|
from game.ato.ai_flight_planner_db import aircraft_for_task
|
||||||
|
from game.ato.closestairfields import ObjectiveDistanceCache
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.ato.flightplan import FlightPlanBuilder
|
||||||
from game.ato.flighttype import FlightType
|
from game.ato.flighttype import FlightType
|
||||||
from game.ato.package import Package
|
from game.ato.package import Package
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
@ -53,9 +56,6 @@ from game.theater.transitnetwork import (
|
|||||||
TransitNetwork,
|
TransitNetwork,
|
||||||
)
|
)
|
||||||
from game.utils import meters, nautical_miles
|
from game.utils import meters, nautical_miles
|
||||||
from game.ato.ai_flight_planner_db import aircraft_for_task
|
|
||||||
from game.ato.closestairfields import ObjectiveDistanceCache
|
|
||||||
from game.ato.flightplan import FlightPlanBuilder
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -635,7 +635,6 @@ class PendingTransfers:
|
|||||||
flight.package.remove_flight(flight)
|
flight.package.remove_flight(flight)
|
||||||
if not flight.package.flights:
|
if not flight.package.flights:
|
||||||
self.game.ato_for(self.player).remove_package(flight.package)
|
self.game.ato_for(self.player).remove_package(flight.package)
|
||||||
flight.return_pilots_and_aircraft()
|
|
||||||
|
|
||||||
@cancel_transport.register
|
@cancel_transport.register
|
||||||
def _cancel_transport_convoy(
|
def _cancel_transport_convoy(
|
||||||
|
|||||||
@ -169,9 +169,6 @@ class PackageModel(QAbstractListModel):
|
|||||||
"""Removes the given flight from the package."""
|
"""Removes the given flight from the package."""
|
||||||
index = self.package.flights.index(flight)
|
index = self.package.flights.index(flight)
|
||||||
self.beginRemoveRows(QModelIndex(), index, index)
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
if flight.cargo is not None:
|
|
||||||
flight.cargo.transport = None
|
|
||||||
flight.return_pilots_and_aircraft()
|
|
||||||
self.package.remove_flight(flight)
|
self.package.remove_flight(flight)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
self.update_tot()
|
self.update_tot()
|
||||||
@ -253,6 +250,8 @@ class AtoModel(QAbstractListModel):
|
|||||||
"""Adds a package to the ATO."""
|
"""Adds a package to the ATO."""
|
||||||
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||||
self.ato.add_package(package)
|
self.ato.add_package(package)
|
||||||
|
# We do not need to send events for new flights in the package here. Events were
|
||||||
|
# already sent when the flights were added to the in-progress package.
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.client_slots_changed.emit()
|
self.client_slots_changed.emit()
|
||||||
@ -264,14 +263,11 @@ class AtoModel(QAbstractListModel):
|
|||||||
|
|
||||||
def delete_package(self, package: Package) -> None:
|
def delete_package(self, package: Package) -> None:
|
||||||
"""Removes the given package from the ATO."""
|
"""Removes the given package from the ATO."""
|
||||||
|
EventStream.put_nowait(GameUpdateEvents().delete_flights_in_package(package))
|
||||||
self.package_models.release(package)
|
self.package_models.release(package)
|
||||||
index = self.ato.packages.index(package)
|
index = self.ato.packages.index(package)
|
||||||
self.beginRemoveRows(QModelIndex(), index, index)
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
self.ato.remove_package(package)
|
self.ato.remove_package(package)
|
||||||
for flight in package.flights:
|
|
||||||
flight.return_pilots_and_aircraft()
|
|
||||||
if flight.cargo is not None:
|
|
||||||
flight.cargo.transport = None
|
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.client_slots_changed.emit()
|
self.client_slots_changed.emit()
|
||||||
@ -280,9 +276,7 @@ class AtoModel(QAbstractListModel):
|
|||||||
def on_packages_changed(self) -> None:
|
def on_packages_changed(self) -> None:
|
||||||
if self.game is not None:
|
if self.game is not None:
|
||||||
self.game.compute_unculled_zones()
|
self.game.compute_unculled_zones()
|
||||||
events = GameUpdateEvents()
|
EventStream.put_nowait(GameUpdateEvents().update_unculled_zones())
|
||||||
events.update_unculled_zones()
|
|
||||||
EventStream.put_nowait(events)
|
|
||||||
|
|
||||||
def package_at_index(self, index: QModelIndex) -> Package:
|
def package_at_index(self, index: QModelIndex) -> Package:
|
||||||
"""Returns the package at the given index."""
|
"""Returns the package at the given index."""
|
||||||
|
|||||||
@ -27,7 +27,8 @@ from PySide2.QtWidgets import (
|
|||||||
|
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
from game.ato.package import Package
|
from game.ato.package import Package
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from game.server import EventStream
|
||||||
|
from game.sim import GameUpdateEvents
|
||||||
from ..delegates import TwoColumnRowDelegate
|
from ..delegates import TwoColumnRowDelegate
|
||||||
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
||||||
|
|
||||||
@ -128,8 +129,10 @@ class QFlightList(QListView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def delete_flight(self, index: QModelIndex) -> None:
|
def delete_flight(self, index: QModelIndex) -> None:
|
||||||
|
EventStream.put_nowait(
|
||||||
|
GameUpdateEvents().delete_flight(self.package_model.flight_at_index(index))
|
||||||
|
)
|
||||||
self.package_model.delete_flight_at_index(index)
|
self.package_model.delete_flight_at_index(index)
|
||||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
|
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
|
||||||
index = self.indexAt(event.pos())
|
index = self.indexAt(event.pos())
|
||||||
@ -209,13 +212,13 @@ class QFlightPanel(QGroupBox):
|
|||||||
self.delete_button.setEnabled(enabled)
|
self.delete_button.setEnabled(enabled)
|
||||||
self.change_map_flight_selection(index)
|
self.change_map_flight_selection(index)
|
||||||
|
|
||||||
@staticmethod
|
def change_map_flight_selection(self, index: QModelIndex) -> None:
|
||||||
def change_map_flight_selection(index: QModelIndex) -> None:
|
events = GameUpdateEvents()
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
GameUpdateSignal.get_instance().select_flight(None)
|
events.deselect_flight()
|
||||||
return
|
else:
|
||||||
|
events.select_flight(self.package_model.flight_at_index(index))
|
||||||
GameUpdateSignal.get_instance().select_flight(index.row())
|
EventStream.put_nowait(events)
|
||||||
|
|
||||||
def on_edit(self) -> None:
|
def on_edit(self) -> None:
|
||||||
"""Opens the flight edit dialog."""
|
"""Opens the flight edit dialog."""
|
||||||
@ -303,7 +306,6 @@ class QPackageList(QListView):
|
|||||||
|
|
||||||
def delete_package(self, index: QModelIndex) -> None:
|
def delete_package(self, index: QModelIndex) -> None:
|
||||||
self.ato_model.delete_package_at_index(index)
|
self.ato_model.delete_package_at_index(index)
|
||||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
|
||||||
|
|
||||||
def on_new_packages(self, _parent: QModelIndex, first: int, _last: int) -> None:
|
def on_new_packages(self, _parent: QModelIndex, first: int, _last: int) -> None:
|
||||||
# Select the newly created pacakges. This should only ever happen due to
|
# Select the newly created pacakges. This should only ever happen due to
|
||||||
@ -390,14 +392,18 @@ class QPackagePanel(QGroupBox):
|
|||||||
|
|
||||||
def change_map_package_selection(self, index: QModelIndex) -> None:
|
def change_map_package_selection(self, index: QModelIndex) -> None:
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
GameUpdateSignal.get_instance().select_package(None)
|
EventStream.put_nowait(GameUpdateEvents().deselect_flight())
|
||||||
return
|
return
|
||||||
|
|
||||||
package = self.ato_model.get_package_model(index)
|
package = self.ato_model.get_package_model(index)
|
||||||
if package.rowCount() == 0:
|
if package.rowCount() == 0:
|
||||||
GameUpdateSignal.get_instance().select_package(None)
|
EventStream.put_nowait(GameUpdateEvents().deselect_flight())
|
||||||
else:
|
else:
|
||||||
GameUpdateSignal.get_instance().select_package(index.row())
|
EventStream.put_nowait(
|
||||||
|
GameUpdateEvents().select_flight(
|
||||||
|
package.flight_at_index(package.index(0))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def on_edit(self) -> None:
|
def on_edit(self) -> None:
|
||||||
"""Opens the package edit dialog."""
|
"""Opens the package edit dialog."""
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from PySide2.QtCore import Property, QObject, Signal, Slot
|
|
||||||
|
|
||||||
from game.ato import Flight
|
|
||||||
from game.ato.flightstate import InFlight
|
|
||||||
from game.server.leaflet import LeafletLatLon
|
|
||||||
from qt_ui.models import AtoModel
|
|
||||||
|
|
||||||
|
|
||||||
class FlightJs(QObject):
|
|
||||||
idChanged = Signal()
|
|
||||||
positionChanged = Signal()
|
|
||||||
blueChanged = Signal()
|
|
||||||
selectedChanged = Signal()
|
|
||||||
|
|
||||||
def __init__(self, flight: Flight, selected: bool, ato_model: AtoModel) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.flight = flight
|
|
||||||
self._selected = selected
|
|
||||||
self.ato_model = ato_model
|
|
||||||
|
|
||||||
@Property(str, notify=idChanged)
|
|
||||||
def id(self) -> str:
|
|
||||||
return str(self.flight.id)
|
|
||||||
|
|
||||||
@Property(list, notify=positionChanged)
|
|
||||||
def position(self) -> LeafletLatLon:
|
|
||||||
if isinstance(self.flight.state, InFlight):
|
|
||||||
return self.flight.state.estimate_position().latlng().as_list()
|
|
||||||
return []
|
|
||||||
|
|
||||||
@Property(bool, notify=blueChanged)
|
|
||||||
def blue(self) -> bool:
|
|
||||||
return self.flight.departure.captured
|
|
||||||
|
|
||||||
@Property(bool, notify=selectedChanged)
|
|
||||||
def selected(self) -> bool:
|
|
||||||
return self._selected
|
|
||||||
|
|
||||||
@Slot(result=bool)
|
|
||||||
def flightIsInAto(self) -> bool:
|
|
||||||
if self.flight.package not in self.flight.squadron.coalition.ato.packages:
|
|
||||||
return False
|
|
||||||
if self.flight not in self.flight.package.flights:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_selected(self, value: bool) -> None:
|
|
||||||
self._selected = value
|
|
||||||
self.selectedChanged.emit()
|
|
||||||
@ -1,13 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
from typing import List, Optional
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from PySide2.QtCore import Property, QObject, Signal
|
from PySide2.QtCore import Property, QObject, Signal
|
||||||
from dcs.mapping import LatLng
|
from dcs.mapping import LatLng
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.airtaaskingorder import AirTaskingOrder
|
|
||||||
from game.profiling import logged_duration
|
from game.profiling import logged_duration
|
||||||
from game.server.leaflet import LeafletLatLon
|
from game.server.leaflet import LeafletLatLon
|
||||||
from game.server.security import ApiKeyManager
|
from game.server.security import ApiKeyManager
|
||||||
@ -17,7 +15,6 @@ from game.theater import (
|
|||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from .controlpointjs import ControlPointJs
|
from .controlpointjs import ControlPointJs
|
||||||
from .flightjs import FlightJs
|
|
||||||
from .frontlinejs import FrontLineJs
|
from .frontlinejs import FrontLineJs
|
||||||
from .groundobjectjs import GroundObjectJs
|
from .groundobjectjs import GroundObjectJs
|
||||||
from .supplyroutejs import SupplyRouteJs
|
from .supplyroutejs import SupplyRouteJs
|
||||||
@ -47,9 +44,8 @@ class MapModel(QObject):
|
|||||||
controlPointsChanged = Signal()
|
controlPointsChanged = Signal()
|
||||||
groundObjectsChanged = Signal()
|
groundObjectsChanged = Signal()
|
||||||
supplyRoutesChanged = Signal()
|
supplyRoutesChanged = Signal()
|
||||||
flightsChanged = Signal()
|
|
||||||
frontLinesChanged = Signal()
|
frontLinesChanged = Signal()
|
||||||
selectedFlightChanged = Signal(str)
|
mapReset = Signal()
|
||||||
|
|
||||||
def __init__(self, game_model: GameModel) -> None:
|
def __init__(self, game_model: GameModel) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -58,79 +54,18 @@ class MapModel(QObject):
|
|||||||
self._control_points = []
|
self._control_points = []
|
||||||
self._ground_objects = []
|
self._ground_objects = []
|
||||||
self._supply_routes = []
|
self._supply_routes = []
|
||||||
self._flights: dict[tuple[bool, int, int], FlightJs] = {}
|
|
||||||
self._front_lines = []
|
self._front_lines = []
|
||||||
|
|
||||||
self._selected_flight_index: Optional[Tuple[int, int]] = None
|
|
||||||
|
|
||||||
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
|
GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load)
|
||||||
GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos)
|
|
||||||
GameUpdateSignal.get_instance().package_selection_changed.connect(
|
|
||||||
self.set_package_selection
|
|
||||||
)
|
|
||||||
GameUpdateSignal.get_instance().flight_selection_changed.connect(
|
|
||||||
self.set_flight_selection
|
|
||||||
)
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
self._control_points = []
|
self._control_points = []
|
||||||
self._supply_routes = []
|
self._supply_routes = []
|
||||||
self._ground_objects = []
|
self._ground_objects = []
|
||||||
self._flights = {}
|
|
||||||
self._front_lines = []
|
self._front_lines = []
|
||||||
self.cleared.emit()
|
self.cleared.emit()
|
||||||
|
|
||||||
def set_package_selection(self, index: int) -> None:
|
|
||||||
self.deselect_current_flight()
|
|
||||||
# Optional[int] isn't a valid type for a Qt signal. None will be converted to
|
|
||||||
# zero automatically. We use -1 to indicate no selection.
|
|
||||||
if index == -1:
|
|
||||||
self._selected_flight_index = None
|
|
||||||
else:
|
|
||||||
self._selected_flight_index = index, 0
|
|
||||||
self.select_current_flight()
|
|
||||||
|
|
||||||
def set_flight_selection(self, index: int) -> None:
|
|
||||||
self.deselect_current_flight()
|
|
||||||
if self._selected_flight_index is None:
|
|
||||||
if index != -1:
|
|
||||||
# We don't know what order update_package_selection and
|
|
||||||
# update_flight_selection will be called in when the last
|
|
||||||
# package is removed. If no flight is selected, it's not a
|
|
||||||
# problem to also have no package selected.
|
|
||||||
logging.error("Flight was selected with no package selected")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Optional[int] isn't a valid type for a Qt signal. None will be converted to
|
|
||||||
# zero automatically. We use -1 to indicate no selection.
|
|
||||||
if index == -1:
|
|
||||||
self._selected_flight_index = self._selected_flight_index[0], None
|
|
||||||
self._selected_flight_index = self._selected_flight_index[0], index
|
|
||||||
self.select_current_flight()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _selected_flight(self) -> Optional[FlightJs]:
|
|
||||||
if self._selected_flight_index is None:
|
|
||||||
return None
|
|
||||||
package_index, flight_index = self._selected_flight_index
|
|
||||||
blue = True
|
|
||||||
return self._flights.get((blue, package_index, flight_index))
|
|
||||||
|
|
||||||
def deselect_current_flight(self) -> None:
|
|
||||||
flight = self._selected_flight
|
|
||||||
if flight is None:
|
|
||||||
return None
|
|
||||||
flight.set_selected(False)
|
|
||||||
|
|
||||||
def select_current_flight(self):
|
|
||||||
flight = self._selected_flight
|
|
||||||
if flight is None:
|
|
||||||
self.selectedFlightChanged.emit(None)
|
|
||||||
return None
|
|
||||||
flight.set_selected(True)
|
|
||||||
self.selectedFlightChanged.emit(str(flight.flight.id))
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
if self.game_model.game is None:
|
if self.game_model.game is None:
|
||||||
self.clear()
|
self.clear()
|
||||||
@ -139,8 +74,8 @@ class MapModel(QObject):
|
|||||||
self.reset_control_points()
|
self.reset_control_points()
|
||||||
self.reset_ground_objects()
|
self.reset_ground_objects()
|
||||||
self.reset_routes()
|
self.reset_routes()
|
||||||
self.reset_atos()
|
|
||||||
self.reset_front_lines()
|
self.reset_front_lines()
|
||||||
|
self.mapReset.emit()
|
||||||
|
|
||||||
def on_game_load(self, game: Optional[Game]) -> None:
|
def on_game_load(self, game: Optional[Game]) -> None:
|
||||||
if game is not None:
|
if game is not None:
|
||||||
@ -158,29 +93,6 @@ class MapModel(QObject):
|
|||||||
def mapCenter(self) -> LeafletLatLon:
|
def mapCenter(self) -> LeafletLatLon:
|
||||||
return self._map_center.as_list()
|
return self._map_center.as_list()
|
||||||
|
|
||||||
def _flights_in_ato(
|
|
||||||
self, ato: AirTaskingOrder, blue: bool
|
|
||||||
) -> dict[tuple[bool, int, int], FlightJs]:
|
|
||||||
flights = {}
|
|
||||||
for p_idx, package in enumerate(ato.packages):
|
|
||||||
for f_idx, flight in enumerate(package.flights):
|
|
||||||
flights[blue, p_idx, f_idx] = FlightJs(
|
|
||||||
flight,
|
|
||||||
selected=blue and (p_idx, f_idx) == self._selected_flight_index,
|
|
||||||
ato_model=self.game_model.ato_model_for(blue),
|
|
||||||
)
|
|
||||||
return flights
|
|
||||||
|
|
||||||
def reset_atos(self) -> None:
|
|
||||||
self._flights = self._flights_in_ato(
|
|
||||||
self.game.blue.ato, blue=True
|
|
||||||
) | self._flights_in_ato(self.game.red.ato, blue=False)
|
|
||||||
self.flightsChanged.emit()
|
|
||||||
|
|
||||||
@Property(list, notify=flightsChanged)
|
|
||||||
def flights(self) -> list[FlightJs]:
|
|
||||||
return list(self._flights.values())
|
|
||||||
|
|
||||||
def reset_control_points(self) -> None:
|
def reset_control_points(self) -> None:
|
||||||
self._control_points = [
|
self._control_points = [
|
||||||
ControlPointJs(c, self.game_model, self.game.theater)
|
ControlPointJs(c, self.game_model, self.game.theater)
|
||||||
|
|||||||
@ -17,28 +17,12 @@ class GameUpdateSignal(QObject):
|
|||||||
|
|
||||||
game_loaded = Signal(Game)
|
game_loaded = Signal(Game)
|
||||||
|
|
||||||
flight_paths_changed = Signal()
|
|
||||||
package_selection_changed = Signal(int) # -1 indicates no selection.
|
|
||||||
flight_selection_changed = Signal(int) # -1 indicates no selection.
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(GameUpdateSignal, self).__init__()
|
super(GameUpdateSignal, self).__init__()
|
||||||
GameUpdateSignal.instance = self
|
GameUpdateSignal.instance = self
|
||||||
|
|
||||||
self.game_loaded.connect(self.updateGame)
|
self.game_loaded.connect(self.updateGame)
|
||||||
|
|
||||||
def select_package(self, index: Optional[int]) -> None:
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
self.package_selection_changed.emit(-1 if index is None else index)
|
|
||||||
|
|
||||||
def select_flight(self, index: Optional[int]) -> None:
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
self.flight_selection_changed.emit(-1 if index is None else index)
|
|
||||||
|
|
||||||
def redraw_flight_paths(self) -> None:
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
self.flight_paths_changed.emit()
|
|
||||||
|
|
||||||
def updateGame(self, game: Optional[Game]):
|
def updateGame(self, game: Optional[Game]):
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.gameupdated.emit(game)
|
self.gameupdated.emit(game)
|
||||||
|
|||||||
@ -5,9 +5,10 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.server import EventStream
|
||||||
|
from game.sim import GameUpdateEvents
|
||||||
from qt_ui.models import GameModel, PackageModel
|
from qt_ui.models import GameModel, PackageModel
|
||||||
from qt_ui.uiconstants import EVENT_ICONS
|
from qt_ui.uiconstants import EVENT_ICONS
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
|
||||||
from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
|
from qt_ui.windows.mission.flight.QFlightPlanner import QFlightPlanner
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ class QEditFlightDialog(QDialog):
|
|||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
self.game_model = game_model
|
self.game_model = game_model
|
||||||
|
self.flight = flight
|
||||||
|
|
||||||
self.setWindowTitle("Edit flight")
|
self.setWindowTitle("Edit flight")
|
||||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||||
@ -37,5 +39,5 @@ class QEditFlightDialog(QDialog):
|
|||||||
self.finished.connect(self.on_close)
|
self.finished.connect(self.on_close)
|
||||||
|
|
||||||
def on_close(self, _result) -> None:
|
def on_close(self, _result) -> None:
|
||||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
EventStream.put_nowait(GameUpdateEvents().update_flight(self.flight))
|
||||||
self.game_model.ato_model.client_slots_changed.emit()
|
self.game_model.ato_model.client_slots_changed.emit()
|
||||||
|
|||||||
@ -16,14 +16,15 @@ from PySide2.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.ato.flightplan import FlightPlanBuilder, PlanningError
|
||||||
from game.ato.package import Package
|
from game.ato.package import Package
|
||||||
from game.game import Game
|
from game.game import Game
|
||||||
|
from game.server import EventStream
|
||||||
|
from game.sim import GameUpdateEvents
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.ato.flightplan import FlightPlanBuilder, PlanningError
|
|
||||||
from qt_ui.models import AtoModel, GameModel, PackageModel
|
from qt_ui.models import AtoModel, GameModel, PackageModel
|
||||||
from qt_ui.uiconstants import EVENT_ICONS
|
from qt_ui.uiconstants import EVENT_ICONS
|
||||||
from qt_ui.widgets.ato import QFlightList
|
from qt_ui.widgets.ato import QFlightList
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
|
||||||
from qt_ui.windows.mission.flight.QFlightCreator import QFlightCreator
|
from qt_ui.windows.mission.flight.QFlightCreator import QFlightCreator
|
||||||
|
|
||||||
|
|
||||||
@ -141,9 +142,10 @@ class QPackageDialog(QDialog):
|
|||||||
def on_cancel(self) -> None:
|
def on_cancel(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
def on_close(self, _result) -> None:
|
||||||
def on_close(_result) -> None:
|
EventStream.put_nowait(
|
||||||
GameUpdateSignal.get_instance().redraw_flight_paths()
|
GameUpdateEvents().update_flights_in_package(self.package_model.package)
|
||||||
|
)
|
||||||
|
|
||||||
def on_save(self) -> None:
|
def on_save(self) -> None:
|
||||||
self.save_tot()
|
self.save_tot()
|
||||||
@ -183,13 +185,14 @@ class QPackageDialog(QDialog):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
planner.populate_flight_plan(flight)
|
planner.populate_flight_plan(flight)
|
||||||
|
self.package_model.update_tot()
|
||||||
|
EventStream.put_nowait(GameUpdateEvents().new_flight(flight))
|
||||||
except PlanningError as ex:
|
except PlanningError as ex:
|
||||||
self.package_model.delete_flight(flight)
|
self.package_model.delete_flight(flight)
|
||||||
logging.exception("Could not create flight")
|
logging.exception("Could not create flight")
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self, "Could not create flight", str(ex), QMessageBox.Ok
|
self, "Could not create flight", str(ex), QMessageBox.Ok
|
||||||
)
|
)
|
||||||
self.package_model.update_tot()
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.package_changed.emit()
|
self.package_changed.emit()
|
||||||
|
|
||||||
@ -252,7 +255,7 @@ class QNewPackageDialog(QPackageDialog):
|
|||||||
def on_cancel(self) -> None:
|
def on_cancel(self) -> None:
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
for flight in self.package_model.package.flights:
|
for flight in self.package_model.package.flights:
|
||||||
flight.return_pilots_and_aircraft()
|
self.package_model.delete_flight(flight)
|
||||||
|
|
||||||
|
|
||||||
class QEditPackageDialog(QPackageDialog):
|
class QEditPackageDialog(QPackageDialog):
|
||||||
|
|||||||
@ -382,48 +382,61 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
|
|||||||
game.groundObjectsChanged.connect(drawGroundObjects);
|
game.groundObjectsChanged.connect(drawGroundObjects);
|
||||||
game.supplyRoutesChanged.connect(drawSupplyRoutes);
|
game.supplyRoutesChanged.connect(drawSupplyRoutes);
|
||||||
game.frontLinesChanged.connect(drawFrontLines);
|
game.frontLinesChanged.connect(drawFrontLines);
|
||||||
game.flightsChanged.connect(drawAircraft);
|
game.mapReset.connect(drawAircraft);
|
||||||
game.selectedFlightChanged.connect(updateSelectedFlight);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleStreamedEvents(events) {
|
function handleStreamedEvents(events) {
|
||||||
for (const [flightId, position] of Object.entries(events.updated_flights)) {
|
for (const [flightId, position] of Object.entries(
|
||||||
Flight.withId(flightId).drawAircraftLocation(position);
|
events.updated_flight_positions
|
||||||
|
)) {
|
||||||
|
Flight.withId(flightId).updatePosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const combat of events.new_combats) {
|
for (const combat of events.new_combats) {
|
||||||
redrawCombat(combat);
|
redrawCombat(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const combat of events.updated_combats) {
|
for (const combat of events.updated_combats) {
|
||||||
redrawCombat(combat);
|
redrawCombat(combat);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const player of events.navmesh_updates) {
|
for (const player of events.navmesh_updates) {
|
||||||
drawNavmesh(player);
|
drawNavmesh(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events.unculled_zones_updated) {
|
if (events.unculled_zones_updated) {
|
||||||
drawUnculledZones();
|
drawUnculledZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events.threat_zones_updated) {
|
if (events.threat_zones_updated) {
|
||||||
drawThreatZones();
|
drawThreatZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (events.deselected_flight && Flight.selected != null) {
|
||||||
|
Flight.deselectCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events.selected_flight != null) {
|
||||||
|
Flight.select(events.selected_flight);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const flight of events.new_flights) {
|
||||||
|
new Flight(flight).draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const flightId of events.updated_flights) {
|
||||||
|
Flight.withId(flightId).draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const flightId of events.deleted_flights) {
|
||||||
|
Flight.popId(flightId).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recenterMap(center) {
|
function recenterMap(center) {
|
||||||
map.setView(center, 8, { animate: true, duration: 1 });
|
map.setView(center, 8, { animate: true, duration: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedFlight(id) {
|
|
||||||
if (id == null) {
|
|
||||||
holdZones.clearLayers();
|
|
||||||
ipZones.clearLayers();
|
|
||||||
joinZones.clearLayers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHoldZones(id);
|
|
||||||
drawIpZones(id);
|
|
||||||
drawJoinZones(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ControlPoint {
|
class ControlPoint {
|
||||||
constructor(cp) {
|
constructor(cp) {
|
||||||
this.cp = cp;
|
this.cp = cp;
|
||||||
@ -857,15 +870,17 @@ class Waypoint {
|
|||||||
|
|
||||||
class Flight {
|
class Flight {
|
||||||
static registeredFlights = {};
|
static registeredFlights = {};
|
||||||
|
static selected = null;
|
||||||
|
|
||||||
constructor(flight) {
|
constructor(flight) {
|
||||||
this.flight = flight;
|
this.flight = flight;
|
||||||
this.id = flight.id;
|
this.id = flight.id;
|
||||||
|
this.selected = false;
|
||||||
|
self.position = flight.position;
|
||||||
this.aircraft = null;
|
this.aircraft = null;
|
||||||
this.path = null;
|
this.path = null;
|
||||||
this.markers = [];
|
this.markers = [];
|
||||||
this.commitBoundary = null;
|
this.commitBoundary = null;
|
||||||
this.flight.selectedChanged.connect(() => this.draw());
|
|
||||||
Flight.registerFlight(this);
|
Flight.registerFlight(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -877,18 +892,67 @@ class Flight {
|
|||||||
Flight.registeredFlights[flight.id] = flight;
|
Flight.registeredFlights[flight.id] = flight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unregisterFlight(id) {
|
||||||
|
if (Flight.selected != null && Flight.selected.id == id) {
|
||||||
|
Flight.clearSelected();
|
||||||
|
}
|
||||||
|
delete Flight.registeredFlights[id];
|
||||||
|
}
|
||||||
|
|
||||||
static withId(id) {
|
static withId(id) {
|
||||||
return Flight.registeredFlights[id];
|
return Flight.registeredFlights[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static popId(id) {
|
||||||
|
const flight = Flight.withId(id);
|
||||||
|
Flight.unregisterFlight(id);
|
||||||
|
return flight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static clearSelected() {
|
||||||
|
holdZones.clearLayers();
|
||||||
|
ipZones.clearLayers();
|
||||||
|
joinZones.clearLayers();
|
||||||
|
Flight.selected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static deselectCurrent() {
|
||||||
|
const flight = Flight.selected;
|
||||||
|
Flight.clearSelected();
|
||||||
|
if (flight != null) {
|
||||||
|
flight.selected = false;
|
||||||
|
flight.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static select(id) {
|
||||||
|
if (Flight.selected != null && Flight.selected.id == id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flight.deselectCurrent();
|
||||||
|
const flight = Flight.withId(id);
|
||||||
|
Flight.selected = flight;
|
||||||
|
flight.selected = true;
|
||||||
|
flight.draw();
|
||||||
|
drawHoldZones(id);
|
||||||
|
drawIpZones(id);
|
||||||
|
drawJoinZones(id);
|
||||||
|
}
|
||||||
|
|
||||||
shouldMark(waypoint) {
|
shouldMark(waypoint) {
|
||||||
return this.flight.selected && waypoint.shouldMark();
|
return this.selected && waypoint.shouldMark();
|
||||||
}
|
}
|
||||||
|
|
||||||
flightPlanLayer() {
|
flightPlanLayer() {
|
||||||
return this.flight.blue ? blueFlightPlansLayer : redFlightPlansLayer;
|
return this.flight.blue ? blueFlightPlansLayer : redFlightPlansLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePosition(position) {
|
||||||
|
this.position = position;
|
||||||
|
this.drawAircraftLocation();
|
||||||
|
}
|
||||||
|
|
||||||
updatePath(idx, position) {
|
updatePath(idx, position) {
|
||||||
const points = this.path.getLatLngs();
|
const points = this.path.getLatLngs();
|
||||||
points[idx] = position;
|
points[idx] = position;
|
||||||
@ -898,7 +962,7 @@ class Flight {
|
|||||||
drawPath(path) {
|
drawPath(path) {
|
||||||
const color = this.flight.blue ? Colors.Blue : Colors.Red;
|
const color = this.flight.blue ? Colors.Blue : Colors.Red;
|
||||||
const layer = this.flightPlanLayer();
|
const layer = this.flightPlanLayer();
|
||||||
if (this.flight.selected) {
|
if (this.selected) {
|
||||||
this.path = L.polyline(path, {
|
this.path = L.polyline(path, {
|
||||||
color: Colors.Highlight,
|
color: Colors.Highlight,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
@ -919,46 +983,69 @@ class Flight {
|
|||||||
this.drawCommitBoundary();
|
this.drawCommitBoundary();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawAircraftLocation(position = null) {
|
clear() {
|
||||||
|
this.clearAircraftLocation();
|
||||||
|
this.clearFlightPlan();
|
||||||
|
this.clearCommitBoundary();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAircraftLocation() {
|
||||||
if (this.aircraft != null) {
|
if (this.aircraft != null) {
|
||||||
this.aircraft.removeFrom(aircraftLayer);
|
this.aircraft.removeFrom(aircraftLayer);
|
||||||
this.aircraft = null;
|
this.aircraft = null;
|
||||||
}
|
}
|
||||||
if (position == null) {
|
}
|
||||||
position = this.flight.position;
|
|
||||||
}
|
drawAircraftLocation() {
|
||||||
if (position.length > 0) {
|
this.clearAircraftLocation();
|
||||||
this.aircraft = L.marker(position, {
|
if (this.position != null) {
|
||||||
icon: Icons.AirIcons.icon(
|
this.aircraft = L.marker(this.position, {
|
||||||
"fighter",
|
icon: Icons.AirIcons.icon("fighter", this.flight.blue, this.selected),
|
||||||
this.flight.blue,
|
|
||||||
this.flight.selected
|
|
||||||
),
|
|
||||||
}).addTo(aircraftLayer);
|
}).addTo(aircraftLayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCommitBoundary() {
|
clearCommitBoundary() {
|
||||||
if (this.commitBoundary != null) {
|
if (this.commitBoundary != null) {
|
||||||
this.commitBoundary
|
this.commitBoundary
|
||||||
.removeFrom(selectedFlightPlansLayer)
|
.removeFrom(selectedFlightPlansLayer)
|
||||||
.removeFrom(this.flightPlanLayer())
|
.removeFrom(this.flightPlanLayer())
|
||||||
.removeFrom(allFlightPlansLayer);
|
.removeFrom(allFlightPlansLayer);
|
||||||
}
|
}
|
||||||
if (this.flight.selected) {
|
}
|
||||||
getJson(`/flights/${this.flight.id}/commit-boundary`).then((boundary) => {
|
|
||||||
if (boundary) {
|
drawCommitBoundary() {
|
||||||
this.commitBoundary = L.polyline(boundary, {
|
if (!this.selected) {
|
||||||
color: Colors.Highlight,
|
this.clearCommitBoundary();
|
||||||
weight: 1,
|
return;
|
||||||
interactive: false,
|
|
||||||
})
|
|
||||||
.addTo(selectedFlightPlansLayer)
|
|
||||||
.addTo(this.flightPlanLayer())
|
|
||||||
.addTo(allFlightPlansLayer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getJson(`/flights/${this.flight.id}/commit-boundary`).then((boundary) => {
|
||||||
|
// For a selected flight we wait to clear the commit boundary until after
|
||||||
|
// the backend responds. Otherwise if the package is reselected while
|
||||||
|
// waiting we may have the following execution order, with selections A
|
||||||
|
// and B:
|
||||||
|
//
|
||||||
|
// 1. A: clear
|
||||||
|
// 2. A: wait for backend
|
||||||
|
// 3. B: wait for backend
|
||||||
|
// 4. A: Add boundary to map
|
||||||
|
// 5. B: Add boundary to map
|
||||||
|
//
|
||||||
|
// Similarly, we need to recheck that we're still selected before
|
||||||
|
// continuing.
|
||||||
|
this.clearCommitBoundary();
|
||||||
|
if (boundary && this.selected) {
|
||||||
|
this.commitBoundary = L.polyline(boundary, {
|
||||||
|
color: Colors.Highlight,
|
||||||
|
weight: 1,
|
||||||
|
interactive: false,
|
||||||
|
})
|
||||||
|
.addTo(selectedFlightPlansLayer)
|
||||||
|
.addTo(this.flightPlanLayer())
|
||||||
|
.addTo(allFlightPlansLayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFlightPlan() {
|
clearFlightPlan() {
|
||||||
@ -978,39 +1065,23 @@ class Flight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawFlightPlan() {
|
drawFlightPlan() {
|
||||||
this.clearFlightPlan();
|
getJson(`/waypoints/${this.flight.id}`).then((waypoints) => {
|
||||||
this.flight.flightIsInAto().then((inAto) => {
|
this.clearFlightPlan();
|
||||||
if (!inAto) {
|
const path = [];
|
||||||
// HACK: The signal to redraw the ATO following package/flight deletion
|
waypoints.map((raw, idx) => {
|
||||||
// and the signal to change flight/package selection due to UI selection
|
const waypoint = new Waypoint(raw, idx, this);
|
||||||
// change come in an arbitrary order. If redraw signal comes first the
|
if (waypoint.includeInPath()) {
|
||||||
// UI will clear the map and redraw, but then when the UI updates
|
path.push(waypoint.position());
|
||||||
// selection away from the (now deleted) flight/package it calls
|
}
|
||||||
// deselect, which redraws the deleted flight plan in its deselected
|
if (this.shouldMark(waypoint)) {
|
||||||
// state.
|
waypoint.marker
|
||||||
//
|
.addTo(selectedFlightPlansLayer)
|
||||||
// Avoid this by checking that the flight is still in the coalition's
|
.addTo(this.flightPlanLayer())
|
||||||
// ATO before drawing.
|
.addTo(allFlightPlansLayer);
|
||||||
return;
|
this.markers.push(waypoint.marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
getJson(`/waypoints/${this.flight.id}`).then((waypoints) => {
|
|
||||||
const path = [];
|
|
||||||
waypoints.map((raw, idx) => {
|
|
||||||
const waypoint = new Waypoint(raw, idx, this);
|
|
||||||
if (waypoint.includeInPath()) {
|
|
||||||
path.push(waypoint.position());
|
|
||||||
}
|
|
||||||
if (this.shouldMark(waypoint)) {
|
|
||||||
waypoint.marker
|
|
||||||
.addTo(selectedFlightPlansLayer)
|
|
||||||
.addTo(this.flightPlanLayer())
|
|
||||||
.addTo(allFlightPlansLayer);
|
|
||||||
this.markers.push(waypoint.marker);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.drawPath(path);
|
|
||||||
});
|
});
|
||||||
|
this.drawPath(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1022,22 +1093,12 @@ function drawAircraft() {
|
|||||||
redFlightPlansLayer.clearLayers();
|
redFlightPlansLayer.clearLayers();
|
||||||
selectedFlightPlansLayer.clearLayers();
|
selectedFlightPlansLayer.clearLayers();
|
||||||
allFlightPlansLayer.clearLayers();
|
allFlightPlansLayer.clearLayers();
|
||||||
let selected = null;
|
|
||||||
game.flights.forEach((flight) => {
|
getJson("/flights").then((flights) => {
|
||||||
// Draw the selected waypoint last so it's on top. bringToFront only brings
|
for (const flight of flights) {
|
||||||
// it to the front of the *extant* elements, so any flights drawn later will
|
|
||||||
// be drawn on top. We could fight with manual Z-indexes but leaflet does a
|
|
||||||
// lot of that automatically so it'd be error prone.
|
|
||||||
if (flight.selected) {
|
|
||||||
selected = flight;
|
|
||||||
} else {
|
|
||||||
new Flight(flight).draw();
|
new Flight(flight).draw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selected != null) {
|
|
||||||
new Flight(selected).draw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _drawThreatZones(zones, layer, player) {
|
function _drawThreatZones(zones, layer, player) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user