mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Draw frozen combat on the map.
Very basic display. Draws the engagement footprint for air-to-air combat, a line from the flight to the target for IP, and lines from SAMs to their target for air defense. https://github.com/dcs-liberation/dcs_liberation/issues/1680
This commit is contained in:
parent
fb10a8d28e
commit
515efd0598
@ -18,14 +18,16 @@ from game.ato.flightstate import (
|
|||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
from gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
from .combat import CombatInitiator, FrozenCombat
|
from .combat import CombatInitiator, FrozenCombat
|
||||||
|
from .gameupdatecallbacks import GameUpdateCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
class AircraftSimulation:
|
class AircraftSimulation:
|
||||||
def __init__(self, game: Game) -> None:
|
def __init__(self, game: Game, callbacks: GameUpdateCallbacks) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
|
self.callbacks = callbacks
|
||||||
self.combats: list[FrozenCombat] = []
|
self.combats: list[FrozenCombat] = []
|
||||||
|
|
||||||
def begin_simulation(self) -> None:
|
def begin_simulation(self) -> None:
|
||||||
@ -38,7 +40,7 @@ class AircraftSimulation:
|
|||||||
|
|
||||||
# Finish updating all flights before checking for combat so that the new
|
# Finish updating all flights before checking for combat so that the new
|
||||||
# positions are used.
|
# positions are used.
|
||||||
CombatInitiator(self.game, self.combats).update_active_combats()
|
CombatInitiator(self.game, self.combats, self.callbacks).update_active_combats()
|
||||||
|
|
||||||
# After updating all combat states, check for halts.
|
# After updating all combat states, check for halts.
|
||||||
for flight in self.iter_flights():
|
for flight in self.iter_flights():
|
||||||
|
|||||||
@ -12,6 +12,7 @@ from .atip import AtIp
|
|||||||
from .defendingsam import DefendingSam
|
from .defendingsam import DefendingSam
|
||||||
from .joinablecombat import JoinableCombat
|
from .joinablecombat import JoinableCombat
|
||||||
from .samengagementzones import SamEngagementZones
|
from .samengagementzones import SamEngagementZones
|
||||||
|
from ..gameupdatecallbacks import GameUpdateCallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -20,9 +21,12 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class CombatInitiator:
|
class CombatInitiator:
|
||||||
def __init__(self, game: Game, combats: list[FrozenCombat]) -> None:
|
def __init__(
|
||||||
|
self, game: Game, combats: list[FrozenCombat], callbacks: GameUpdateCallbacks
|
||||||
|
) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.combats = combats
|
self.combats = combats
|
||||||
|
self.callbacks = callbacks
|
||||||
|
|
||||||
def update_active_combats(self) -> None:
|
def update_active_combats(self) -> None:
|
||||||
blue_a2a = AircraftEngagementZones.from_ato(self.game.blue.ato)
|
blue_a2a = AircraftEngagementZones.from_ato(self.game.blue.ato)
|
||||||
@ -60,6 +64,7 @@ class CombatInitiator:
|
|||||||
logging.info(f"{flight} is joining existing combat {joined}")
|
logging.info(f"{flight} is joining existing combat {joined}")
|
||||||
joined.join(flight)
|
joined.join(flight)
|
||||||
own_a2a.remove_flight(flight)
|
own_a2a.remove_flight(flight)
|
||||||
|
self.callbacks.on_combat_changed(joined)
|
||||||
elif (combat := self.check_flight_for_new_combat(flight, a2a, sam)) is not None:
|
elif (combat := self.check_flight_for_new_combat(flight, a2a, sam)) is not None:
|
||||||
logging.info(f"Interrupting simulation because {combat.because()}")
|
logging.info(f"Interrupting simulation because {combat.because()}")
|
||||||
combat.update_flight_states()
|
combat.update_flight_states()
|
||||||
@ -70,6 +75,7 @@ class CombatInitiator:
|
|||||||
a2a.update_for_combat(combat)
|
a2a.update_for_combat(combat)
|
||||||
own_a2a.update_for_combat(combat)
|
own_a2a.update_for_combat(combat)
|
||||||
self.combats.append(combat)
|
self.combats.append(combat)
|
||||||
|
self.callbacks.on_add_combat(combat)
|
||||||
|
|
||||||
def check_flight_for_joined_combat(
|
def check_flight_for_joined_combat(
|
||||||
self, flight: Flight
|
self, flight: Flight
|
||||||
|
|||||||
@ -3,9 +3,10 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .gamelooptimer import GameLoopTimer
|
from .gamelooptimer import GameLoopTimer
|
||||||
|
from .gameupdatecallbacks import GameUpdateCallbacks
|
||||||
from .missionsimulation import MissionSimulation, SimulationAlreadyCompletedError
|
from .missionsimulation import MissionSimulation, SimulationAlreadyCompletedError
|
||||||
from .simspeedsetting import SimSpeedSetting
|
from .simspeedsetting import SimSpeedSetting
|
||||||
|
|
||||||
@ -15,11 +16,11 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GameLoop:
|
class GameLoop:
|
||||||
def __init__(self, game: Game, on_complete: Callable[[], None]) -> None:
|
def __init__(self, game: Game, callbacks: GameUpdateCallbacks) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.on_complete = on_complete
|
self.callbacks = callbacks
|
||||||
self.timer = GameLoopTimer(self.tick)
|
self.timer = GameLoopTimer(self.tick)
|
||||||
self.sim = MissionSimulation(self.game)
|
self.sim = MissionSimulation(self.game, self.callbacks)
|
||||||
self.started = False
|
self.started = False
|
||||||
self.completed = False
|
self.completed = False
|
||||||
|
|
||||||
@ -75,6 +76,6 @@ class GameLoop:
|
|||||||
if self.completed:
|
if self.completed:
|
||||||
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.callbacks.on_simulation_complete()
|
||||||
except SimulationAlreadyCompletedError:
|
except SimulationAlreadyCompletedError:
|
||||||
logging.exception("Attempted to tick already completed sim")
|
logging.exception("Attempted to tick already completed sim")
|
||||||
|
|||||||
17
game/sim/gameupdatecallbacks.py
Normal file
17
game/sim/gameupdatecallbacks.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.sim.combat import FrozenCombat
|
||||||
|
|
||||||
|
|
||||||
|
# Ought to be frozen but mypy can't handle that:
|
||||||
|
# https://github.com/python/mypy/issues/5485
|
||||||
|
@dataclass
|
||||||
|
class GameUpdateCallbacks:
|
||||||
|
on_simulation_complete: Callable[[], None]
|
||||||
|
on_add_combat: Callable[[FrozenCombat], None]
|
||||||
|
on_combat_changed: Callable[[FrozenCombat], None]
|
||||||
@ -9,6 +9,7 @@ from game.debriefing import Debriefing
|
|||||||
from game.missiongenerator import MissionGenerator
|
from game.missiongenerator import MissionGenerator
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from .aircraftsimulation import AircraftSimulation
|
from .aircraftsimulation import AircraftSimulation
|
||||||
|
from .gameupdatecallbacks import GameUpdateCallbacks
|
||||||
from .missionresultsprocessor import MissionResultsProcessor
|
from .missionresultsprocessor import MissionResultsProcessor
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -24,10 +25,10 @@ class SimulationAlreadyCompletedError(RuntimeError):
|
|||||||
|
|
||||||
|
|
||||||
class MissionSimulation:
|
class MissionSimulation:
|
||||||
def __init__(self, game: Game) -> None:
|
def __init__(self, game: Game, callbacks: GameUpdateCallbacks) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.unit_map: Optional[UnitMap] = None
|
self.unit_map: Optional[UnitMap] = None
|
||||||
self.aircraft_simulation = AircraftSimulation(self.game)
|
self.aircraft_simulation = AircraftSimulation(self.game, callbacks)
|
||||||
self.completed = False
|
self.completed = False
|
||||||
self.time = self.game.conditions.start_time
|
self.time = self.game.conditions.start_time
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,9 @@ 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.polldebriefingfilethread import PollDebriefingFileThread
|
||||||
|
from game.sim.combat import FrozenCombat
|
||||||
from game.sim.gameloop import GameLoop
|
from game.sim.gameloop import GameLoop
|
||||||
|
from game.sim.gameupdatecallbacks import GameUpdateCallbacks
|
||||||
from game.sim.simspeedsetting import SimSpeedSetting
|
from game.sim.simspeedsetting import SimSpeedSetting
|
||||||
from qt_ui.simupdatethread import SimUpdateThread
|
from qt_ui.simupdatethread import SimUpdateThread
|
||||||
|
|
||||||
@ -21,6 +23,8 @@ class SimController(QObject):
|
|||||||
sim_update = Signal()
|
sim_update = Signal()
|
||||||
sim_speed_reset = Signal(SimSpeedSetting)
|
sim_speed_reset = Signal(SimSpeedSetting)
|
||||||
simulation_complete = Signal()
|
simulation_complete = Signal()
|
||||||
|
on_add_combat = Signal(FrozenCombat)
|
||||||
|
on_combat_changed = Signal(FrozenCombat)
|
||||||
|
|
||||||
def __init__(self, game: Optional[Game]) -> None:
|
def __init__(self, game: Optional[Game]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -59,7 +63,14 @@ class SimController(QObject):
|
|||||||
self.game_loop.pause()
|
self.game_loop.pause()
|
||||||
self.game_loop = None
|
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,
|
||||||
|
GameUpdateCallbacks(
|
||||||
|
self.on_simulation_complete,
|
||||||
|
self.on_add_combat.emit,
|
||||||
|
self.on_combat_changed.emit,
|
||||||
|
),
|
||||||
|
)
|
||||||
self.started = False
|
self.started = False
|
||||||
|
|
||||||
def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None:
|
def set_simulation_speed(self, simulation_speed: SimSpeedSetting) -> None:
|
||||||
|
|||||||
27
qt_ui/widgets/map/model/aircombatjs.py
Normal file
27
qt_ui/widgets/map/model/aircombatjs.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from PySide2.QtCore import Property, QObject, Signal
|
||||||
|
|
||||||
|
from game.sim.combat.aircombat import AirCombat
|
||||||
|
from game.theater import ConflictTheater
|
||||||
|
from .leaflet import LeafletPoly
|
||||||
|
from .shapelyutil import ShapelyUtil
|
||||||
|
|
||||||
|
|
||||||
|
class AirCombatJs(QObject):
|
||||||
|
footprintChanged = Signal()
|
||||||
|
|
||||||
|
def __init__(self, combat: AirCombat, theater: ConflictTheater) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.combat = combat
|
||||||
|
self.theater = theater
|
||||||
|
self._footprint = self._make_footprint()
|
||||||
|
|
||||||
|
@Property(type=list, notify=footprintChanged)
|
||||||
|
def footprint(self) -> list[LeafletPoly]:
|
||||||
|
return self._footprint
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
self._footprint = self._make_footprint()
|
||||||
|
self.footprintChanged.emit()
|
||||||
|
|
||||||
|
def _make_footprint(self) -> list[LeafletPoly]:
|
||||||
|
return ShapelyUtil.polys_to_leaflet(self.combat.footprint, self.theater)
|
||||||
28
qt_ui/widgets/map/model/ipcombatjs.py
Normal file
28
qt_ui/widgets/map/model/ipcombatjs.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from PySide2.QtCore import Property, QObject, Signal
|
||||||
|
|
||||||
|
from game.sim.combat.atip import AtIp
|
||||||
|
from qt_ui.models import GameModel
|
||||||
|
from .flightjs import FlightJs
|
||||||
|
|
||||||
|
|
||||||
|
class IpCombatJs(QObject):
|
||||||
|
flightChanged = Signal()
|
||||||
|
|
||||||
|
def __init__(self, combat: AtIp, game_model: GameModel) -> None:
|
||||||
|
super().__init__()
|
||||||
|
assert game_model.game is not None
|
||||||
|
self.combat = combat
|
||||||
|
self.theater = game_model.game.theater
|
||||||
|
self._flight = FlightJs(
|
||||||
|
combat.flight,
|
||||||
|
selected=False,
|
||||||
|
theater=game_model.game.theater,
|
||||||
|
ato_model=game_model.ato_model_for(combat.flight.squadron.player),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Property(FlightJs, notify=flightChanged)
|
||||||
|
def flight(self) -> FlightJs:
|
||||||
|
return self._flight
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
pass
|
||||||
@ -9,22 +9,29 @@ from dcs import Point
|
|||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.airtaaskingorder import AirTaskingOrder
|
from game.ato.airtaaskingorder import AirTaskingOrder
|
||||||
from game.profiling import logged_duration
|
from game.profiling import logged_duration
|
||||||
|
from game.sim.combat import FrozenCombat
|
||||||
|
from game.sim.combat.aircombat import AirCombat
|
||||||
|
from game.sim.combat.atip import AtIp
|
||||||
|
from game.sim.combat.defendingsam import DefendingSam
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
ConflictTheater,
|
ConflictTheater,
|
||||||
)
|
)
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
from qt_ui.simcontroller import SimController
|
from qt_ui.simcontroller import SimController
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
|
from .aircombatjs import AirCombatJs
|
||||||
from .controlpointjs import ControlPointJs
|
from .controlpointjs import ControlPointJs
|
||||||
from .flightjs import FlightJs
|
from .flightjs import FlightJs
|
||||||
from .frontlinejs import FrontLineJs
|
from .frontlinejs import FrontLineJs
|
||||||
from .groundobjectjs import GroundObjectJs
|
from .groundobjectjs import GroundObjectJs
|
||||||
from .holdzonesjs import HoldZonesJs
|
from .holdzonesjs import HoldZonesJs
|
||||||
|
from .ipcombatjs import IpCombatJs
|
||||||
from .ipzonesjs import IpZonesJs
|
from .ipzonesjs import IpZonesJs
|
||||||
from .joinzonesjs import JoinZonesJs
|
from .joinzonesjs import JoinZonesJs
|
||||||
from .leaflet import LeafletLatLon
|
from .leaflet import LeafletLatLon
|
||||||
from .mapzonesjs import MapZonesJs
|
from .mapzonesjs import MapZonesJs
|
||||||
from .navmeshjs import NavMeshJs
|
from .navmeshjs import NavMeshJs
|
||||||
|
from .samcombatjs import SamCombatJs
|
||||||
from .supplyroutejs import SupplyRouteJs
|
from .supplyroutejs import SupplyRouteJs
|
||||||
from .threatzonecontainerjs import ThreatZoneContainerJs
|
from .threatzonecontainerjs import ThreatZoneContainerJs
|
||||||
from .threatzonesjs import ThreatZonesJs
|
from .threatzonesjs import ThreatZonesJs
|
||||||
@ -63,6 +70,9 @@ class MapModel(QObject):
|
|||||||
ipZonesChanged = Signal()
|
ipZonesChanged = Signal()
|
||||||
joinZonesChanged = Signal()
|
joinZonesChanged = Signal()
|
||||||
holdZonesChanged = Signal()
|
holdZonesChanged = Signal()
|
||||||
|
airCombatsChanged = Signal()
|
||||||
|
samCombatsChanged = Signal()
|
||||||
|
ipCombatsChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, game_model: GameModel, sim_controller: SimController) -> None:
|
def __init__(self, game_model: GameModel, sim_controller: SimController) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -83,6 +93,10 @@ class MapModel(QObject):
|
|||||||
self._join_zones = JoinZonesJs.empty()
|
self._join_zones = JoinZonesJs.empty()
|
||||||
self._hold_zones = HoldZonesJs.empty()
|
self._hold_zones = HoldZonesJs.empty()
|
||||||
self._selected_flight_index: Optional[Tuple[int, int]] = None
|
self._selected_flight_index: Optional[Tuple[int, int]] = None
|
||||||
|
self._air_combats = []
|
||||||
|
self._sam_combats = []
|
||||||
|
self._ip_combats = []
|
||||||
|
|
||||||
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().flight_paths_changed.connect(self.reset_atos)
|
||||||
GameUpdateSignal.get_instance().package_selection_changed.connect(
|
GameUpdateSignal.get_instance().package_selection_changed.connect(
|
||||||
@ -92,6 +106,8 @@ class MapModel(QObject):
|
|||||||
self.set_flight_selection
|
self.set_flight_selection
|
||||||
)
|
)
|
||||||
sim_controller.sim_update.connect(self.on_sim_update)
|
sim_controller.sim_update.connect(self.on_sim_update)
|
||||||
|
sim_controller.on_add_combat.connect(self.on_add_combat)
|
||||||
|
sim_controller.on_combat_changed.connect(self.on_combat_changed)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
@ -107,6 +123,9 @@ class MapModel(QObject):
|
|||||||
self._map_zones = MapZonesJs([], [], [])
|
self._map_zones = MapZonesJs([], [], [])
|
||||||
self._unculled_zones = []
|
self._unculled_zones = []
|
||||||
self._ip_zones = IpZonesJs.empty()
|
self._ip_zones = IpZonesJs.empty()
|
||||||
|
self._air_combats = []
|
||||||
|
self._sam_combats = []
|
||||||
|
self._ip_combats = []
|
||||||
self.cleared.emit()
|
self.cleared.emit()
|
||||||
|
|
||||||
def on_sim_update(self) -> None:
|
def on_sim_update(self) -> None:
|
||||||
@ -180,6 +199,7 @@ class MapModel(QObject):
|
|||||||
self.reset_navmeshes()
|
self.reset_navmeshes()
|
||||||
self.reset_map_zones()
|
self.reset_map_zones()
|
||||||
self.reset_unculled_zones()
|
self.reset_unculled_zones()
|
||||||
|
self.reset_combats()
|
||||||
|
|
||||||
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:
|
||||||
@ -367,6 +387,79 @@ class MapModel(QObject):
|
|||||||
def holdZones(self) -> HoldZonesJs:
|
def holdZones(self) -> HoldZonesJs:
|
||||||
return self._hold_zones
|
return self._hold_zones
|
||||||
|
|
||||||
|
def reset_combats(self) -> None:
|
||||||
|
self._air_combats = []
|
||||||
|
self._sam_combats = []
|
||||||
|
self._ip_combats = []
|
||||||
|
self.airCombatsChanged.emit()
|
||||||
|
self.samCombatsChanged.emit()
|
||||||
|
self.ipCombatsChanged.emit()
|
||||||
|
|
||||||
|
def on_add_combat(self, combat: FrozenCombat) -> None:
|
||||||
|
if isinstance(combat, AirCombat):
|
||||||
|
self.add_air_combat(combat)
|
||||||
|
elif isinstance(combat, DefendingSam):
|
||||||
|
self.add_sam_combat(combat)
|
||||||
|
elif isinstance(combat, AtIp):
|
||||||
|
self.add_ip_combat(combat)
|
||||||
|
else:
|
||||||
|
logging.error(f"Unhandled FrozenCombat type: {combat.__class__}")
|
||||||
|
|
||||||
|
def add_air_combat(self, combat: AirCombat) -> None:
|
||||||
|
self._air_combats.append(AirCombatJs(combat, self.game.theater))
|
||||||
|
self.airCombatsChanged.emit()
|
||||||
|
|
||||||
|
def add_sam_combat(self, combat: DefendingSam) -> None:
|
||||||
|
self._sam_combats.append(SamCombatJs(combat, self.game_model))
|
||||||
|
self.samCombatsChanged.emit()
|
||||||
|
|
||||||
|
def add_ip_combat(self, combat: AtIp) -> None:
|
||||||
|
self._ip_combats.append(IpCombatJs(combat, self.game_model))
|
||||||
|
self.ipCombatsChanged.emit()
|
||||||
|
|
||||||
|
def on_combat_changed(self, combat: FrozenCombat) -> None:
|
||||||
|
if isinstance(combat, AirCombat):
|
||||||
|
self.refresh_air_combat(combat)
|
||||||
|
elif isinstance(combat, DefendingSam):
|
||||||
|
self.refresh_sam_combat(combat)
|
||||||
|
elif isinstance(combat, AtIp):
|
||||||
|
self.refresh_ip_combat(combat)
|
||||||
|
else:
|
||||||
|
logging.error(f"Unhandled FrozenCombat type: {combat.__class__}")
|
||||||
|
|
||||||
|
def refresh_air_combat(self, combat: AirCombat) -> None:
|
||||||
|
for js in self._air_combats:
|
||||||
|
if js.combat == combat:
|
||||||
|
js.refresh()
|
||||||
|
return
|
||||||
|
logging.error(f"Could not find existing combat model to update for {combat}")
|
||||||
|
|
||||||
|
def refresh_sam_combat(self, combat: DefendingSam) -> None:
|
||||||
|
for js in self._sam_combats:
|
||||||
|
if js.combat == combat:
|
||||||
|
js.refresh()
|
||||||
|
return
|
||||||
|
logging.error(f"Could not find existing combat model to update for {combat}")
|
||||||
|
|
||||||
|
def refresh_ip_combat(self, combat: AtIp) -> None:
|
||||||
|
for js in self._ip_combats:
|
||||||
|
if js.combat == combat:
|
||||||
|
js.refresh()
|
||||||
|
return
|
||||||
|
logging.error(f"Could not find existing combat model to update for {combat}")
|
||||||
|
|
||||||
|
@Property(list, notify=airCombatsChanged)
|
||||||
|
def airCombats(self) -> list[AirCombatJs]:
|
||||||
|
return self._air_combats
|
||||||
|
|
||||||
|
@Property(list, notify=samCombatsChanged)
|
||||||
|
def samCombats(self) -> list[SamCombatJs]:
|
||||||
|
return self._sam_combats
|
||||||
|
|
||||||
|
@Property(list, notify=ipCombatsChanged)
|
||||||
|
def ipCombats(self) -> list[IpCombatJs]:
|
||||||
|
return self._ip_combats
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def game(self) -> Game:
|
def game(self) -> Game:
|
||||||
if self.game_model.game is None:
|
if self.game_model.game is None:
|
||||||
|
|||||||
37
qt_ui/widgets/map/model/samcombatjs.py
Normal file
37
qt_ui/widgets/map/model/samcombatjs.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from PySide2.QtCore import Property, QObject, Signal
|
||||||
|
|
||||||
|
from game.sim.combat.defendingsam import DefendingSam
|
||||||
|
from qt_ui.models import GameModel
|
||||||
|
from .flightjs import FlightJs
|
||||||
|
from .groundobjectjs import GroundObjectJs
|
||||||
|
|
||||||
|
|
||||||
|
class SamCombatJs(QObject):
|
||||||
|
flightChanged = Signal()
|
||||||
|
airDefensesChanged = Signal()
|
||||||
|
|
||||||
|
def __init__(self, combat: DefendingSam, game_model: GameModel) -> None:
|
||||||
|
super().__init__()
|
||||||
|
assert game_model.game is not None
|
||||||
|
self.combat = combat
|
||||||
|
self.theater = game_model.game.theater
|
||||||
|
self._flight = FlightJs(
|
||||||
|
combat.flight,
|
||||||
|
selected=False,
|
||||||
|
theater=game_model.game.theater,
|
||||||
|
ato_model=game_model.ato_model_for(combat.flight.squadron.player),
|
||||||
|
)
|
||||||
|
self._air_defenses = [
|
||||||
|
GroundObjectJs(tgo, game_model.game) for tgo in self.combat.air_defenses
|
||||||
|
]
|
||||||
|
|
||||||
|
@Property(FlightJs, notify=flightChanged)
|
||||||
|
def flight(self) -> FlightJs:
|
||||||
|
return self._flight
|
||||||
|
|
||||||
|
@Property(list, notify=airDefensesChanged)
|
||||||
|
def airDefenses(self) -> list[GroundObjectJs]:
|
||||||
|
return self._air_defenses
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
pass
|
||||||
@ -206,6 +206,7 @@ const supplyRoutesLayer = L.layerGroup().addTo(map);
|
|||||||
const frontLinesLayer = L.layerGroup().addTo(map);
|
const frontLinesLayer = L.layerGroup().addTo(map);
|
||||||
const redSamThreatLayer = L.layerGroup().addTo(map);
|
const redSamThreatLayer = L.layerGroup().addTo(map);
|
||||||
const blueFlightPlansLayer = L.layerGroup().addTo(map);
|
const blueFlightPlansLayer = L.layerGroup().addTo(map);
|
||||||
|
const combatLayer = L.layerGroup().addTo(map);
|
||||||
|
|
||||||
// Added to map by the user via layer controls.
|
// Added to map by the user via layer controls.
|
||||||
const blueSamThreatLayer = L.layerGroup();
|
const blueSamThreatLayer = L.layerGroup();
|
||||||
@ -287,6 +288,7 @@ L.control
|
|||||||
"Units and locations": {
|
"Units and locations": {
|
||||||
"Control points": controlPointsLayer,
|
"Control points": controlPointsLayer,
|
||||||
Aircraft: aircraftLayer,
|
Aircraft: aircraftLayer,
|
||||||
|
"Active combat": combatLayer,
|
||||||
"Air defenses": airDefensesLayer,
|
"Air defenses": airDefensesLayer,
|
||||||
Factories: factoriesLayer,
|
Factories: factoriesLayer,
|
||||||
Ships: shipsLayer,
|
Ships: shipsLayer,
|
||||||
@ -351,6 +353,9 @@ new QWebChannel(qt.webChannelTransport, function (channel) {
|
|||||||
game.ipZonesChanged.connect(drawIpZones);
|
game.ipZonesChanged.connect(drawIpZones);
|
||||||
game.joinZonesChanged.connect(drawJoinZones);
|
game.joinZonesChanged.connect(drawJoinZones);
|
||||||
game.holdZonesChanged.connect(drawHoldZones);
|
game.holdZonesChanged.connect(drawHoldZones);
|
||||||
|
game.airCombatsChanged.connect(drawCombat);
|
||||||
|
game.samCombatsChanged.connect(drawCombat);
|
||||||
|
game.ipCombatsChanged.connect(drawCombat);
|
||||||
});
|
});
|
||||||
|
|
||||||
function recenterMap(center) {
|
function recenterMap(center) {
|
||||||
@ -1226,6 +1231,34 @@ function drawHoldZones() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawCombat() {
|
||||||
|
combatLayer.clearLayers();
|
||||||
|
|
||||||
|
for (const airCombat of game.airCombats) {
|
||||||
|
L.polygon(airCombat.footprint, {
|
||||||
|
color: Colors.Red,
|
||||||
|
interactive: false,
|
||||||
|
fillOpacity: 0.2,
|
||||||
|
}).addTo(combatLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const samCombat of game.samCombats) {
|
||||||
|
for (const airDefense of samCombat.airDefenses) {
|
||||||
|
L.polyline([samCombat.flight.position, airDefense.position], {
|
||||||
|
color: Colors.Red,
|
||||||
|
interactive: false,
|
||||||
|
}).addTo(combatLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ipCombat of game.ipCombats) {
|
||||||
|
L.polyline([ipCombat.flight.position, ipCombat.flight.target], {
|
||||||
|
color: Colors.Red,
|
||||||
|
interactive: false,
|
||||||
|
}).addTo(combatLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function drawInitialMap() {
|
function drawInitialMap() {
|
||||||
recenterMap(game.mapCenter);
|
recenterMap(game.mapCenter);
|
||||||
drawControlPoints();
|
drawControlPoints();
|
||||||
@ -1240,6 +1273,7 @@ function drawInitialMap() {
|
|||||||
drawIpZones();
|
drawIpZones();
|
||||||
drawJoinZones();
|
drawJoinZones();
|
||||||
drawHoldZones();
|
drawHoldZones();
|
||||||
|
drawCombat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllLayers() {
|
function clearAllLayers() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user