Update the UI to show sim state.

https://github.com/dcs-liberation/dcs_liberation/issues/1704
This commit is contained in:
Dan Albert 2021-10-31 22:25:11 -07:00
parent 87bf3110c8
commit 03430a4df5
12 changed files with 188 additions and 39 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Callable, TYPE_CHECKING from typing import Callable, TYPE_CHECKING
@ -22,6 +23,10 @@ class GameLoop:
self.started = False self.started = False
self.completed = False self.completed = False
@property
def current_time_in_sim(self) -> datetime:
return self.sim.time
def start(self) -> None: def start(self) -> None:
if self.started: if self.started:
raise RuntimeError("Cannot start game loop because it has already started") raise RuntimeError("Cannot start game loop because it has already started")
@ -67,7 +72,5 @@ class GameLoop:
self.pause() self.pause()
logging.info(f"Simulation completed at {self.sim.time}") logging.info(f"Simulation completed at {self.sim.time}")
self.on_complete() self.on_complete()
else:
logging.info(f"Simulation continued at {self.sim.time}")
except SimulationAlreadyCompletedError: except SimulationAlreadyCompletedError:
logging.exception("Attempted to tick already completed sim") logging.exception("Attempted to tick already completed sim")

View File

@ -32,6 +32,7 @@ class MissionSimulation:
self.time = self.game.conditions.start_time self.time = self.game.conditions.start_time
def begin_simulation(self) -> None: def begin_simulation(self) -> None:
self.time = self.game.conditions.start_time
self.aircraft_simulation.begin_simulation() self.aircraft_simulation.begin_simulation()
def tick(self) -> bool: def tick(self) -> bool:

View File

@ -1,14 +1,16 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Callable, Optional, TYPE_CHECKING from typing import Callable, Optional, TYPE_CHECKING
from PySide2.QtCore import QObject, Signal from PySide2.QtCore import QObject, Signal
from game.polldebriefingfilethread import PollDebriefingFileThread
from game.sim.gameloop import GameLoop from game.sim.gameloop import GameLoop
from game.sim.simspeedsetting import SimSpeedSetting from game.sim.simspeedsetting import SimSpeedSetting
from game.polldebriefingfilethread import PollDebriefingFileThread from qt_ui.simupdatethread import SimUpdateThread
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -16,6 +18,7 @@ if TYPE_CHECKING:
class SimController(QObject): class SimController(QObject):
sim_update = Signal()
sim_speed_reset = Signal(SimSpeedSetting) sim_speed_reset = Signal(SimSpeedSetting)
simulation_complete = Signal() simulation_complete = Signal()
@ -24,18 +27,28 @@ class SimController(QObject):
self.game_loop: Optional[GameLoop] = None self.game_loop: Optional[GameLoop] = None
self.recreate_game_loop(game) self.recreate_game_loop(game)
self.started = False self.started = False
self._sim_update_thread = SimUpdateThread(self.sim_update.emit)
self._sim_update_thread.start()
@property @property
def completed(self) -> bool: def completed(self) -> bool:
return self.game_loop.completed return self.game_loop.completed
@property
def current_time_in_sim(self) -> Optional[datetime]:
if self.game_loop is None:
return None
return self.game_loop.current_time_in_sim
def set_game(self, game: Optional[Game]) -> None: def set_game(self, game: Optional[Game]) -> None:
self.recreate_game_loop(game) self.recreate_game_loop(game)
self.sim_speed_reset.emit(SimSpeedSetting.PAUSED) self.sim_speed_reset.emit(SimSpeedSetting.PAUSED)
def recreate_game_loop(self, game: Optional[Game]) -> None: def recreate_game_loop(self, game: Optional[Game]) -> None:
if self.game_loop is not None: if self.game_loop is not None:
self._sim_update_thread.on_sim_pause()
self.game_loop.pause() self.game_loop.pause()
self.game_loop = None
if game is not None: if game is not None:
self.game_loop = GameLoop(game, self.on_simulation_complete) self.game_loop = GameLoop(game, self.on_simulation_complete)
self.started = False self.started = False
@ -48,11 +61,17 @@ class SimController(QObject):
self.game_loop.start() self.game_loop.start()
self.started = True self.started = True
self.game_loop.set_simulation_speed(simulation_speed) self.game_loop.set_simulation_speed(simulation_speed)
if simulation_speed is SimSpeedSetting.PAUSED:
self._sim_update_thread.on_sim_pause()
else:
self._sim_update_thread.on_sim_unpause()
def run_to_first_contact(self) -> None: def run_to_first_contact(self) -> None:
self.game_loop.run_to_first_contact() self.game_loop.run_to_first_contact()
self.sim_update.emit()
def generate_miz(self, output: Path) -> None: def generate_miz(self, output: Path) -> None:
self._sim_update_thread.on_sim_pause()
self.game_loop.pause_and_generate_miz(output) self.game_loop.pause_and_generate_miz(output)
def wait_for_debriefing( def wait_for_debriefing(
@ -65,11 +84,14 @@ class SimController(QObject):
def debrief_current_state( def debrief_current_state(
self, state_path: Path, force_end: bool = False self, state_path: Path, force_end: bool = False
) -> Debriefing: ) -> Debriefing:
self._sim_update_thread.on_sim_pause()
return self.game_loop.pause_and_debrief(state_path, force_end) return self.game_loop.pause_and_debrief(state_path, force_end)
def process_results(self, debriefing: Debriefing) -> None: def process_results(self, debriefing: Debriefing) -> None:
self._sim_update_thread.on_sim_pause()
return self.game_loop.complete_with_results(debriefing) return self.game_loop.complete_with_results(debriefing)
def on_simulation_complete(self) -> None: def on_simulation_complete(self) -> None:
logging.debug("Simulation complete") logging.debug("Simulation complete")
self._sim_update_thread.on_sim_pause()
self.simulation_complete.emit() self.simulation_complete.emit()

45
qt_ui/simupdatethread.py Normal file
View File

@ -0,0 +1,45 @@
from threading import Event, Thread, Timer
from typing import Callable
class SimUpdateThread(Thread):
def __init__(self, update_callback: Callable[[], None]) -> None:
super().__init__()
self.update_callback = update_callback
self.running = False
self.should_shutdown = False
self._interrupt = Event()
self._timer = self._make_timer()
def run(self) -> None:
while True:
self._interrupt.wait()
self._interrupt.clear()
if self.should_shutdown:
return
if self.running:
self.update_callback()
self._timer = self._make_timer()
self._timer.start()
def on_sim_pause(self) -> None:
self._timer.cancel()
self._timer = self._make_timer()
self.running = False
def on_sim_unpause(self) -> None:
if not self.running:
self.running = True
self._timer.start()
def stop(self) -> None:
self.should_shutdown = True
self._interrupt.set()
def on_timer_elapsed(self) -> None:
self._timer = self._make_timer()
self._timer.start()
self._interrupt.set()
def _make_timer(self) -> Timer:
return Timer(1 / 60, lambda: self._interrupt.set())

View File

@ -1,3 +1,5 @@
from datetime import datetime
from PySide2.QtGui import QPixmap from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QFrame, QFrame,
@ -12,6 +14,7 @@ from dcs.weather import CloudPreset, Weather as PydcsWeather
import qt_ui.uiconstants as CONST import qt_ui.uiconstants as CONST
from game.utils import mps from game.utils import mps
from game.weather import Conditions, TimeOfDay from game.weather import Conditions, TimeOfDay
from qt_ui.simcontroller import SimController
class QTimeTurnWidget(QGroupBox): class QTimeTurnWidget(QGroupBox):
@ -19,8 +22,9 @@ class QTimeTurnWidget(QGroupBox):
UI Component to display current turn and time info UI Component to display current turn and time info
""" """
def __init__(self): def __init__(self, sim_controller: SimController) -> None:
super(QTimeTurnWidget, self).__init__("Turn") super(QTimeTurnWidget, self).__init__("Turn")
self.sim_controller = sim_controller
self.setStyleSheet( self.setStyleSheet(
"padding: 0px; margin-left: 5px; margin-right: 0px; margin-top: 1ex; margin-bottom: 5px; border-right: 0px" "padding: 0px; margin-left: 5px; margin-right: 0px; margin-top: 1ex; margin-bottom: 5px; border-right: 0px"
) )
@ -49,17 +53,30 @@ class QTimeTurnWidget(QGroupBox):
self.time_display = QLabel() self.time_display = QLabel()
self.time_column.addWidget(self.time_display) self.time_column.addWidget(self.time_display)
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None: sim_controller.sim_update.connect(self.on_sim_update)
def on_sim_update(self) -> None:
time = self.sim_controller.current_time_in_sim
if time is None:
self.date_display.setText("")
self.time_display.setText("")
else:
self.set_date_and_time(time)
def set_current_turn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display. """Sets the turn information display.
:arg turn Current turn number. :arg turn Current turn number.
:arg conditions Current time and weather conditions. :arg conditions Current time and weather conditions.
""" """
self.daytime_icon.setPixmap(self.icons[conditions.time_of_day]) self.daytime_icon.setPixmap(self.icons[conditions.time_of_day])
self.date_display.setText(conditions.start_time.strftime("%d %b %Y")) self.set_date_and_time(conditions.start_time)
self.time_display.setText(conditions.start_time.strftime("%H:%M:%S Local"))
self.setTitle(f"Turn {turn}") self.setTitle(f"Turn {turn}")
def set_date_and_time(self, time: datetime) -> None:
self.date_display.setText(time.strftime("%d %b %Y"))
self.time_display.setText(time.strftime("%H:%M:%S Local"))
class QWeatherWidget(QGroupBox): class QWeatherWidget(QGroupBox):
""" """
@ -265,7 +282,7 @@ class QConditionsWidget(QFrame):
UI Component to display Turn Number, Day Time & Hour and weather combined. UI Component to display Turn Number, Day Time & Hour and weather combined.
""" """
def __init__(self): def __init__(self, sim_controller: SimController) -> None:
super(QConditionsWidget, self).__init__() super(QConditionsWidget, self).__init__()
self.setProperty("style", "QConditionsWidget") self.setProperty("style", "QConditionsWidget")
@ -275,7 +292,7 @@ class QConditionsWidget(QFrame):
self.layout.setVerticalSpacing(0) self.layout.setVerticalSpacing(0)
self.setLayout(self.layout) self.setLayout(self.layout)
self.time_turn_widget = QTimeTurnWidget() self.time_turn_widget = QTimeTurnWidget(sim_controller)
self.time_turn_widget.setStyleSheet("QGroupBox { margin-right: 0px; }") self.time_turn_widget.setStyleSheet("QGroupBox { margin-right: 0px; }")
self.layout.addWidget(self.time_turn_widget, 0, 0) self.layout.addWidget(self.time_turn_widget, 0, 0)
@ -292,6 +309,6 @@ class QConditionsWidget(QFrame):
:arg turn Current turn number. :arg turn Current turn number.
:arg conditions Current time and weather conditions. :arg conditions Current time and weather conditions.
""" """
self.time_turn_widget.setCurrentTurn(turn, conditions) self.time_turn_widget.set_current_turn(turn, conditions)
self.weather_widget.setCurrentTurn(turn, conditions) self.weather_widget.setCurrentTurn(turn, conditions)
self.weather_widget.show() self.weather_widget.show()

View File

@ -38,7 +38,7 @@ class QTopPanel(QFrame):
self.setMaximumHeight(70) self.setMaximumHeight(70)
self.conditionsWidget = QConditionsWidget() self.conditionsWidget = QConditionsWidget(sim_controller)
self.budgetBox = QBudgetBox(self.game) self.budgetBox = QBudgetBox(self.game)
pass_turn_text = "Pass Turn" pass_turn_text = "Pass Turn"

View File

@ -15,6 +15,7 @@ from PySide2.QtWebEngineWidgets import (
from game import Game from game import Game
from qt_ui.models import GameModel from qt_ui.models import GameModel
from qt_ui.simcontroller import SimController
from qt_ui.widgets.map.mapmodel import MapModel from qt_ui.widgets.map.mapmodel import MapModel
@ -35,11 +36,13 @@ class LoggingWebPage(QWebEnginePage):
class QLiberationMap(QWebEngineView): class QLiberationMap(QWebEngineView):
def __init__(self, game_model: GameModel, parent) -> None: def __init__(
self, game_model: GameModel, sim_controller: SimController, parent
) -> None:
super().__init__(parent) super().__init__(parent)
self.game_model = game_model self.game_model = game_model
self.setMinimumSize(800, 600) self.setMinimumSize(800, 600)
self.map_model = MapModel(game_model) self.map_model = MapModel(game_model, sim_controller)
self.channel = QWebChannel() self.channel = QWebChannel()
self.channel.registerObject("game", self.map_model) self.channel.registerObject("game", self.map_model)

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import logging import logging
from datetime import timedelta from datetime import timedelta
from typing import List, Optional, Tuple, Union, Iterator from typing import Iterator, List, Optional, Tuple, Union
from PySide2.QtCore import Property, QObject, Signal, Slot from PySide2.QtCore import Property, QObject, Signal, Slot
from dcs import Point from dcs import Point
@ -10,40 +10,38 @@ from dcs.unit import Unit
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
from shapely.geometry import ( from shapely.geometry import (
LineString, LineString,
MultiLineString,
MultiPolygon,
Point as ShapelyPoint, Point as ShapelyPoint,
Polygon, Polygon,
MultiPolygon,
MultiLineString,
) )
from game import Game from game import Game
from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.flight import Flight
from game.ato.flightstate import InFlight
from game.ato.flightwaypoint import FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.flightplan import JoinZoneGeometry, HoldZoneGeometry from game.flightplan import HoldZoneGeometry, JoinZoneGeometry
from game.flightplan.ipzonegeometry import IpZoneGeometry
from game.navmesh import NavMesh, NavMeshPoly from game.navmesh import NavMesh, NavMeshPoly
from game.profiling import logged_duration from game.profiling import logged_duration
from game.theater import ( from game.theater import (
ConflictTheater, ConflictTheater,
ControlPoint, ControlPoint,
TheaterGroundObject, ControlPointStatus,
FrontLine, FrontLine,
LatLon, LatLon,
ControlPointStatus, TheaterGroundObject,
) )
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
from game.transfers import MultiGroupTransport, TransportMap from game.transfers import MultiGroupTransport, TransportMap
from game.utils import meters, nautical_miles from game.utils import meters, nautical_miles
from game.ato.airtaaskingorder import AirTaskingOrder from gen.flights.flightplan import CasFlightPlan, FlightPlan, PatrollingFlightPlan
from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.flightwaypoint import FlightWaypoint
from game.ato.flight import Flight
from gen.flights.flightplan import (
FlightPlan,
PatrollingFlightPlan,
CasFlightPlan,
)
from game.flightplan.ipzonegeometry import IpZoneGeometry
from qt_ui.dialogs import Dialog from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel, AtoModel from qt_ui.models import AtoModel, GameModel
from qt_ui.simcontroller import SimController
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
@ -537,6 +535,7 @@ class WaypointJs(QObject):
class FlightJs(QObject): class FlightJs(QObject):
positionChanged = Signal()
flightPlanChanged = Signal() flightPlanChanged = Signal()
blueChanged = Signal() blueChanged = Signal()
selectedChanged = Signal() selectedChanged = Signal()
@ -588,6 +587,13 @@ class FlightJs(QObject):
waypoints.append(waypoint) waypoints.append(waypoint)
return waypoints return waypoints
@Property(list, notify=positionChanged)
def position(self) -> LeafletLatLon:
if isinstance(self.flight.state, InFlight):
ll = self.theater.point_to_ll(self.flight.state.estimate_position())
return [ll.latitude, ll.longitude]
return []
@Property(list, notify=flightPlanChanged) @Property(list, notify=flightPlanChanged)
def flightPlan(self) -> List[WaypointJs]: def flightPlan(self) -> List[WaypointJs]:
return self._waypoints return self._waypoints
@ -1032,7 +1038,7 @@ class MapModel(QObject):
joinZonesChanged = Signal() joinZonesChanged = Signal()
holdZonesChanged = Signal() holdZonesChanged = Signal()
def __init__(self, game_model: GameModel) -> None: def __init__(self, game_model: GameModel, sim_controller: SimController) -> None:
super().__init__() super().__init__()
self.game_model = game_model self.game_model = game_model
self._map_center = [0, 0] self._map_center = [0, 0]
@ -1059,6 +1065,7 @@ class MapModel(QObject):
GameUpdateSignal.get_instance().flight_selection_changed.connect( GameUpdateSignal.get_instance().flight_selection_changed.connect(
self.set_flight_selection self.set_flight_selection
) )
sim_controller.sim_update.connect(self.on_sim_update)
self.reset() self.reset()
def clear(self) -> None: def clear(self) -> None:
@ -1076,6 +1083,10 @@ class MapModel(QObject):
self._ip_zones = IpZonesJs.empty() self._ip_zones = IpZonesJs.empty()
self.cleared.emit() self.cleared.emit()
def on_sim_update(self) -> None:
for flight in self._flights:
flight.positionChanged.emit()
def set_package_selection(self, index: int) -> None: def set_package_selection(self, index: int) -> None:
# Optional[int] isn't a valid type for a Qt signal. None will be converted to # 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. # zero automatically. We use -1 to indicate no selection.

View File

@ -54,7 +54,7 @@ class QLiberationWindow(QMainWindow):
self.sim_controller = SimController(self.game) self.sim_controller = SimController(self.game)
self.ato_panel = QAirTaskingOrderPanel(self.game_model) self.ato_panel = QAirTaskingOrderPanel(self.game_model)
self.info_panel = QInfoPanel(self.game) self.info_panel = QInfoPanel(self.game)
self.liberation_map = QLiberationMap(self.game_model, self) self.liberation_map = QLiberationMap(self.game_model, self.sim_controller, self)
self.setGeometry(300, 100, 270, 100) self.setGeometry(300, 100, 270, 100)
self.updateWindowTitle() self.updateWindowTitle()

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="118" height="128" viewBox="41 26 118 128"><path d="M 155,150 C 155,50 115,30 100,30 85,30 45,50 45,150" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path></svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="118" height="138" viewBox="41 16 118 138"><path d="M 45,150 L45,70 100,20 155,70 155,150" stroke-width="4" stroke="black" fill="rgb(255,128,128)" fill-opacity="1" ></path></svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@ -104,9 +104,31 @@ class TgoIcons {
} }
} }
class AirIcons {
constructor() {
this.icons = {};
for (const player of [true, false]) {
this.icons[player] = this.loadIcon("unspecified", player);
}
}
icon(_category, player, _state) {
return this.icons[player];
}
loadIcon(category, player) {
const color = player ? "blue" : "red";
return new L.Icon({
iconUrl: `../air_assets/${category}_${color}.svg`,
iconSize: [24, 24],
});
}
}
const Icons = Object.freeze({ const Icons = Object.freeze({
ControlPoints: new CpIcons(), ControlPoints: new CpIcons(),
Objectives: new TgoIcons(), Objectives: new TgoIcons(),
AirIcons: new AirIcons(),
}); });
function metersToNauticalMiles(meters) { function metersToNauticalMiles(meters) {
@ -163,6 +185,7 @@ defaultBaseMap.addTo(map);
// Enabled by default, so addTo(map). // Enabled by default, so addTo(map).
const controlPointsLayer = L.layerGroup().addTo(map); const controlPointsLayer = L.layerGroup().addTo(map);
const aircraftLayer = L.layerGroup().addTo(map);
const airDefensesLayer = L.layerGroup().addTo(map); const airDefensesLayer = L.layerGroup().addTo(map);
const factoriesLayer = L.layerGroup().addTo(map); const factoriesLayer = L.layerGroup().addTo(map);
const shipsLayer = L.layerGroup().addTo(map); const shipsLayer = L.layerGroup().addTo(map);
@ -249,8 +272,9 @@ L.control
.groupedLayers( .groupedLayers(
baseLayers, baseLayers,
{ {
"Points of Interest": { "Units and locations": {
"Control points": controlPointsLayer, "Control points": controlPointsLayer,
Aircraft: aircraftLayer,
"Air defenses": airDefensesLayer, "Air defenses": airDefensesLayer,
Factories: factoriesLayer, Factories: factoriesLayer,
Ships: shipsLayer, Ships: shipsLayer,
@ -307,7 +331,7 @@ 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(drawFlightPlans); game.flightsChanged.connect(drawAircraft);
game.threatZonesChanged.connect(drawThreatZones); game.threatZonesChanged.connect(drawThreatZones);
game.navmeshesChanged.connect(drawNavmeshes); game.navmeshesChanged.connect(drawNavmeshes);
game.mapZonesChanged.connect(drawMapZones); game.mapZonesChanged.connect(drawMapZones);
@ -770,9 +794,11 @@ class Flight {
constructor(flight) { constructor(flight) {
this.flight = flight; this.flight = flight;
this.flightPlan = this.flight.flightPlan.map((p) => new Waypoint(p, this)); this.flightPlan = this.flight.flightPlan.map((p) => new Waypoint(p, this));
this.aircraft = null;
this.path = null; this.path = null;
this.commitBoundary = null; this.commitBoundary = null;
this.flight.flightPlanChanged.connect(() => this.draw()); this.flight.positionChanged.connect(() => this.drawAircraftLocation());
this.flight.flightPlanChanged.connect(() => this.drawFlightPlan());
this.flight.commitBoundaryChanged.connect(() => this.drawCommitBoundary()); this.flight.commitBoundaryChanged.connect(() => this.drawCommitBoundary());
} }
@ -808,6 +834,25 @@ class Flight {
} }
} }
draw() {
this.drawAircraftLocation();
this.drawFlightPlan();
this.drawCommitBoundary();
}
drawAircraftLocation() {
if (this.aircraft != null) {
this.aircraft.removeFrom(aircraftLayer);
this.aircraft = null;
}
const position = this.flight.position;
if (position.length > 0) {
this.aircraft = L.marker(position, {
icon: Icons.AirIcons.icon("fighter", this.flight.blue),
}).addTo(aircraftLayer);
}
}
drawCommitBoundary() { drawCommitBoundary() {
if (this.commitBoundary != null) { if (this.commitBoundary != null) {
this.commitBoundary this.commitBoundary
@ -829,7 +874,7 @@ class Flight {
} }
} }
draw() { drawFlightPlan() {
const path = []; const path = [];
this.flightPlan.forEach((waypoint) => { this.flightPlan.forEach((waypoint) => {
if (waypoint.includeInPath()) { if (waypoint.includeInPath()) {
@ -844,11 +889,11 @@ class Flight {
}); });
this.drawPath(path); this.drawPath(path);
this.drawCommitBoundary();
} }
} }
function drawFlightPlans() { function drawAircraft() {
aircraftLayer.clearLayers();
blueFlightPlansLayer.clearLayers(); blueFlightPlansLayer.clearLayers();
redFlightPlansLayer.clearLayers(); redFlightPlansLayer.clearLayers();
selectedFlightPlansLayer.clearLayers(); selectedFlightPlansLayer.clearLayers();
@ -1136,7 +1181,7 @@ function drawInitialMap() {
drawGroundObjects(); drawGroundObjects();
drawSupplyRoutes(); drawSupplyRoutes();
drawFrontLines(); drawFrontLines();
drawFlightPlans(); drawAircraft();
drawThreatZones(); drawThreatZones();
drawNavmeshes(); drawNavmeshes();
drawMapZones(); drawMapZones();