Add frozen combat modelling.

This doesn't do anything yet, but sets up the data model handling for
frozen combat. The next step is to show combat in the map view, since
that will be helpful when debugging the step after that one: resolving
frozen combat.

This would benefit from caching the Shapely data for SAM threat zones.
Right now it's generating them once per tick and the stuttering is
visible at max speed.

https://github.com/dcs-liberation/dcs_liberation/issues/1680
This commit is contained in:
Dan Albert
2021-11-07 13:56:10 -08:00
parent ce4628b64f
commit fb10a8d28e
22 changed files with 473 additions and 110 deletions

View File

@@ -11,7 +11,6 @@ from .flightstate import FlightState, Uninitialized
if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType
from game.sim.aircraftengagementzones import AircraftEngagementZones
from game.squadrons import Squadron, Pilot
from game.theater import ControlPoint, MissionTarget
from game.transfers import TransferOrder
@@ -151,10 +150,5 @@ class Flight:
def on_game_tick(self, time: datetime, duration: timedelta) -> None:
self.state.on_game_tick(time, duration)
def check_for_combat(
self, enemy_aircraft_coverage: AircraftEngagementZones
) -> None:
self.state.check_for_combat(enemy_aircraft_coverage)
def should_halt_sim(self) -> bool:
return self.state.should_halt_sim()

View File

@@ -1,6 +1,8 @@
from .completed import Completed
from .flightstate import FlightState
from .incombat import InCombat
from .inflight import InFlight
from .navigating import Navigating
from .startup import StartUp
from .takeoff import Takeoff
from .taxi import Taxi

View File

@@ -9,7 +9,6 @@ from game.ato.starttype import StartType
if TYPE_CHECKING:
from game.ato.flight import Flight
from game.settings import Settings
from game.sim.aircraftengagementzones import AircraftEngagementZones
from game.threatzones import ThreatPoly
@@ -22,10 +21,17 @@ class FlightState(ABC):
def on_game_tick(self, time: datetime, duration: timedelta) -> None:
...
def check_for_combat(
self, enemy_aircraft_coverage: AircraftEngagementZones
) -> None:
pass
@property
def vulnerable_to_intercept(self) -> bool:
return False
@property
def vulnerable_to_sam(self) -> bool:
return False
@property
def will_join_air_combat(self) -> bool:
return False
def should_halt_sim(self) -> bool:
return False

View File

@@ -10,18 +10,18 @@ from .inflight import InFlight
from ..starttype import StartType
if TYPE_CHECKING:
from game.sim.aircraftengagementzones import AircraftEngagementZones
from game.sim.combat import FrozenCombat
class InCombat(InFlight):
def __init__(self, previous_state: InFlight, description: str) -> None:
def __init__(self, previous_state: InFlight, combat: FrozenCombat) -> None:
super().__init__(
previous_state.flight,
previous_state.settings,
previous_state.waypoint_index,
)
self.previous_state = previous_state
self._description = description
self.combat = combat
def estimate_position(self) -> Point:
return self.previous_state.estimate_position()
@@ -35,6 +35,10 @@ class InCombat(InFlight):
def on_game_tick(self, time: datetime, duration: timedelta) -> None:
raise RuntimeError("Cannot simulate combat")
@property
def is_at_ip(self) -> bool:
return False
@property
def is_waiting_for_start(self) -> bool:
return False
@@ -42,10 +46,17 @@ class InCombat(InFlight):
def should_halt_sim(self) -> bool:
return True
def check_for_combat(
self, enemy_aircraft_coverage: AircraftEngagementZones
) -> None:
pass
@property
def vulnerable_to_intercept(self) -> bool:
# Interception results in the interceptor joining the existing combat rather
# than creating a new combat.
return False
@property
def vulnerable_to_sam(self) -> bool:
# SAM contact results in the SAM joining the existing combat rather than
# creating a new combat.
return False
@property
def spawn_type(self) -> StartType:
@@ -53,4 +64,4 @@ class InCombat(InFlight):
@property
def description(self) -> str:
return self._description
return self.combat.describe()

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
import logging
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
@@ -18,7 +17,6 @@ from gen.flights.flightplan import LoiterFlightPlan
if TYPE_CHECKING:
from game.ato.flight import Flight
from game.settings import Settings
from game.sim.aircraftengagementzones import AircraftEngagementZones
class InFlight(FlightState, ABC):
@@ -95,11 +93,8 @@ class InFlight(FlightState, ABC):
if self.elapsed_time > self.total_time_to_next_waypoint:
self.advance_to_next_waypoint()
def check_for_combat(
self, enemy_aircraft_coverage: AircraftEngagementZones
) -> None:
from game.ato.flightstate.incombat import InCombat
@property
def is_at_ip(self) -> bool:
contact_types = {
FlightWaypointType.INGRESS_BAI,
FlightWaypointType.INGRESS_CAS,
@@ -109,30 +104,19 @@ class InFlight(FlightState, ABC):
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
}
return self.current_waypoint.waypoint_type in contact_types
if self.current_waypoint.waypoint_type in contact_types:
logging.info(
f"Interrupting simulation because {self.flight} has reached its "
"ingress point"
)
self.flight.set_state(InCombat(self, "At IP"))
@property
def vulnerable_to_intercept(self) -> bool:
return True
threat_zone = self.flight.squadron.coalition.opponent.threat_zone
if threat_zone.threatened_by_air_defense(self.estimate_position()):
logging.info(
f"Interrupting simulation because {self.flight} has encountered enemy "
"air defenses"
)
self.flight.set_state(InCombat(self, "In combat with enemy air defenses"))
@property
def vulnerable_to_sam(self) -> bool:
return True
if enemy_aircraft_coverage.covers(self.estimate_position()):
logging.info(
f"Interrupting simulation because {self.flight} has encountered enemy "
"air-to-air patrol"
)
self.flight.set_state(
InCombat(self, "In combat with enemy air-to-air patrol")
)
@property
def will_join_air_combat(self) -> bool:
return self.flight.flight_type.is_air_to_air
@property
def is_waiting_for_start(self) -> bool:

View File

@@ -4,12 +4,12 @@ from datetime import timedelta
from typing import Optional, TYPE_CHECKING
from dcs import Point
from shapely.geometry import LineString, Point as ShapelyPoint
from shapely.geometry import LineString
from game.ato import FlightType
from game.ato.flightstate import InFlight
from game.threatzones import ThreatPoly
from game.utils import Distance, Speed
from game.utils import Distance, Speed, dcs_to_shapely_point
from gen.flights.flightplan import PatrollingFlightPlan
if TYPE_CHECKING:
@@ -24,8 +24,8 @@ class RaceTrack(InFlight):
super().__init__(flight, settings, waypoint_index)
self.commit_region = LineString(
[
ShapelyPoint(self.current_waypoint.x, self.current_waypoint.y),
ShapelyPoint(self.next_waypoint.x, self.next_waypoint.y),
dcs_to_shapely_point(self.current_waypoint.position),
dcs_to_shapely_point(self.next_waypoint.position),
]
).buffer(flight.flight_plan.engagement_distance.meters)