Set up bullseye early, create waypoints.

Setting this up as part of the game makes it possible for us to show in
the UI.

https://github.com/dcs-liberation/dcs_liberation/issues/136
This commit is contained in:
Dan Albert 2021-05-20 18:18:13 -07:00
parent 3c8c76f50d
commit a382e74a89
12 changed files with 104 additions and 22 deletions

View File

@ -11,6 +11,7 @@ Saves from 2.5 are not compatible with 3.0.
* **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions. * **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions.
* **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's). * **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's).
* **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups. * **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups.
* **[Flight Planner]** Flight plans now include bullseye waypoints.
* **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows. * **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows.
* **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map. * **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map.
* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present. * **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.

View File

@ -34,6 +34,7 @@ from .procurement import AircraftProcurementRequest, ProcurementAi
from .profiling import logged_duration from .profiling import logged_duration
from .settings import Settings from .settings import Settings
from .theater import ConflictTheater from .theater import ConflictTheater
from .theater.bullseye import Bullseye
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .threatzones import ThreatZones from .threatzones import ThreatZones
from .transfers import PendingTransfers from .transfers import PendingTransfers
@ -130,6 +131,9 @@ class Game:
self.blue_ato = AirTaskingOrder() self.blue_ato = AirTaskingOrder()
self.red_ato = AirTaskingOrder() self.red_ato = AirTaskingOrder()
self.blue_bullseye = Bullseye(Point(0, 0))
self.red_bullseye = Bullseye(Point(0, 0))
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
self.transfers = PendingTransfers(self) self.transfers = PendingTransfers(self)
@ -201,6 +205,11 @@ class Game:
return self.player_faction return self.player_faction
return self.enemy_faction return self.enemy_faction
def bullseye_for(self, player: bool) -> Bullseye:
if player:
return self.blue_bullseye
return self.red_bullseye
def _roll(self, prob, mult): def _roll(self, prob, mult):
if self.settings.version == "dev": if self.settings.version == "dev":
# always generate all events for dev # always generate all events for dev
@ -337,10 +346,17 @@ class Game:
return TurnState.CONTINUE return TurnState.CONTINUE
def set_bullseye(self) -> None:
player_cp, enemy_cp = self.theater.closest_opposing_control_points()
self.blue_bullseye = Bullseye(enemy_cp.position)
self.red_bullseye = Bullseye(player_cp.position)
def initialize_turn(self) -> None: def initialize_turn(self) -> None:
self.events = [] self.events = []
self._generate_events() self._generate_events()
self.set_bullseye()
# Update statistics # Update statistics
self.game_stats.update(self) self.game_stats.update(self)

View File

@ -108,8 +108,12 @@ class Operation:
@classmethod @classmethod
def _setup_mission_coalitions(cls): def _setup_mission_coalitions(cls):
cls.current_mission.coalition["blue"] = Coalition("blue") cls.current_mission.coalition["blue"] = Coalition(
cls.current_mission.coalition["red"] = Coalition("red") "blue", bullseye=cls.game.blue_bullseye.to_pydcs()
)
cls.current_mission.coalition["red"] = Coalition(
"red", bullseye=cls.game.red_bullseye.to_pydcs()
)
p_country = cls.game.player_country p_country = cls.game.player_country
e_country = cls.game.enemy_country e_country = cls.game.enemy_country

26
game/theater/bullseye.py Normal file
View File

@ -0,0 +1,26 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, TYPE_CHECKING
from dcs import Point
from game.theater import LatLon
if TYPE_CHECKING:
from game.theater import ConflictTheater
@dataclass
class Bullseye:
position: Point
@classmethod
def from_pydcs(cls, bulls: Dict[str, float]) -> Bullseye:
return cls(Point(bulls["x"], bulls["y"]))
def to_pydcs(self) -> Dict[str, float]:
return {"x": self.position.x, "y": self.position.y}
def to_lat_lon(self, theater: ConflictTheater) -> LatLon:
return theater.point_to_ll(self.position)

View File

@ -41,6 +41,7 @@ from dcs.unitgroup import (
) )
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from .latlon import LatLon
from ..scenery_group import SceneryGroup from ..scenery_group import SceneryGroup
from pyproj import CRS, Transformer from pyproj import CRS, Transformer
from shapely import geometry, ops from shapely import geometry, ops
@ -582,15 +583,6 @@ class ReferencePoint:
image_coordinates: Point image_coordinates: Point
@dataclass(frozen=True)
class LatLon:
latitude: float
longitude: float
def as_list(self) -> List[float]:
return [self.latitude, self.longitude]
class ConflictTheater: class ConflictTheater:
terrain: Terrain terrain: Terrain

11
game/theater/latlon.py Normal file
View File

@ -0,0 +1,11 @@
from dataclasses import dataclass
from typing import List
@dataclass(frozen=True)
class LatLon:
latitude: float
longitude: float
def as_list(self) -> List[float]:
return [self.latitude, self.longitude]

View File

@ -79,6 +79,7 @@ class FlightWaypointType(Enum):
INGRESS_OCA_AIRCRAFT = 25 INGRESS_OCA_AIRCRAFT = 25
PICKUP = 26 PICKUP = 26
DROP_OFF = 27 DROP_OFF = 27
BULLSEYE = 28
class FlightWaypoint: class FlightWaypoint:

View File

@ -426,6 +426,7 @@ class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -438,6 +439,7 @@ class BarCapFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@dataclass(frozen=True) @dataclass(frozen=True)
@ -446,6 +448,7 @@ class CasFlightPlan(PatrollingFlightPlan):
target: FlightWaypoint target: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -459,6 +462,7 @@ class CasFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
def request_escort_at(self) -> Optional[FlightWaypoint]: def request_escort_at(self) -> Optional[FlightWaypoint]:
return self.patrol_start return self.patrol_start
@ -472,6 +476,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
lead_time: timedelta lead_time: timedelta
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@ -485,6 +490,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_offset(self) -> timedelta: def tot_offset(self) -> timedelta:
@ -523,6 +529,7 @@ class StrikeFlightPlan(FormationFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -537,6 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def package_speed_waypoints(self) -> Set[FlightWaypoint]: def package_speed_waypoints(self) -> Set[FlightWaypoint]:
@ -641,6 +649,7 @@ class SweepFlightPlan(LoiterFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
lead_time: timedelta lead_time: timedelta
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@ -653,6 +662,7 @@ class SweepFlightPlan(LoiterFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_waypoint(self) -> Optional[FlightWaypoint]: def tot_waypoint(self) -> Optional[FlightWaypoint]:
@ -704,6 +714,7 @@ class AwacsFlightPlan(LoiterFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -713,6 +724,7 @@ class AwacsFlightPlan(LoiterFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def mission_start_time(self) -> Optional[timedelta]: def mission_start_time(self) -> Optional[timedelta]:
@ -746,6 +758,7 @@ class AirliftFlightPlan(FlightPlan):
nav_to_home: List[FlightWaypoint] nav_to_home: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -758,6 +771,7 @@ class AirliftFlightPlan(FlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_waypoint(self) -> Optional[FlightWaypoint]: def tot_waypoint(self) -> Optional[FlightWaypoint]:
@ -1053,6 +1067,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
hold=start, hold=start,
hold_duration=timedelta(hours=4), hold_duration=timedelta(hours=4),
) )
@ -1151,6 +1166,7 @@ class FlightPlanBuilder:
patrol_end=end, patrol_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_sweep(self, flight: Flight) -> SweepFlightPlan: def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
@ -1187,6 +1203,7 @@ class FlightPlanBuilder:
sweep_end=end, sweep_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_transport(self, flight: Flight) -> AirliftFlightPlan: def generate_transport(self, flight: Flight) -> AirliftFlightPlan:
@ -1238,6 +1255,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def racetrack_for_objective( def racetrack_for_objective(
@ -1389,6 +1407,7 @@ class FlightPlanBuilder:
patrol_end=end, patrol_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_dead( def generate_dead(
@ -1517,6 +1536,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_cas(self, flight: Flight) -> CasFlightPlan: def generate_cas(self, flight: Flight) -> CasFlightPlan:
@ -1562,6 +1582,7 @@ class FlightPlanBuilder:
patrol_end=builder.egress(egress, location), patrol_end=builder.egress(egress, location),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
@staticmethod @staticmethod
@ -1696,6 +1717,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:

View File

@ -50,6 +50,7 @@ class WaypointBuilder:
self.threat_zones = game.threat_zone_for(not player) self.threat_zones = game.threat_zone_for(not player)
self.navmesh = game.navmesh_for(player) self.navmesh = game.navmesh_for(player)
self.targets = targets self.targets = targets
self._bullseye = game.bullseye_for(player)
@property @property
def is_helo(self) -> bool: def is_helo(self) -> bool:
@ -145,6 +146,19 @@ class WaypointBuilder:
waypoint.only_for_player = True waypoint.only_for_player = True
return waypoint return waypoint
def bullseye(self) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.BULLSEYE,
self._bullseye.position.x,
self._bullseye.position.y,
meters(0),
)
waypoint.pretty_name = "Bullseye"
waypoint.description = "Bullseye"
waypoint.name = "BULLSEYE"
waypoint.only_for_player = True
return waypoint
def hold(self, position: Point) -> FlightWaypoint: def hold(self, position: Point) -> FlightWaypoint:
waypoint = FlightWaypoint( waypoint = FlightWaypoint(
FlightWaypointType.LOITER, FlightWaypointType.LOITER,

View File

@ -209,16 +209,6 @@ class TriggersGenerator:
player_coalition = "blue" player_coalition = "blue"
enemy_coalition = "red" enemy_coalition = "red"
player_cp, enemy_cp = self.game.theater.closest_opposing_control_points()
self.mission.coalition["blue"].bullseye = {
"x": enemy_cp.position.x,
"y": enemy_cp.position.y,
}
self.mission.coalition["red"].bullseye = {
"x": player_cp.position.x,
"y": player_cp.position.y,
}
self._set_skill(player_coalition, enemy_coalition) self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition)
self._gen_markers() self._gen_markers()

View File

@ -375,6 +375,7 @@ class WaypointJs(QObject):
timingChanged = Signal() timingChanged = Signal()
isTakeoffChanged = Signal() isTakeoffChanged = Signal()
isDivertChanged = Signal() isDivertChanged = Signal()
isBullseyeChanged = Signal()
def __init__( def __init__(
self, self,
@ -439,6 +440,10 @@ class WaypointJs(QObject):
def isDivert(self) -> bool: def isDivert(self) -> bool:
return self.waypoint.waypoint_type is FlightWaypointType.DIVERT return self.waypoint.waypoint_type is FlightWaypointType.DIVERT
@Property(bool, notify=isBullseyeChanged)
def isBullseye(self) -> bool:
return self.waypoint.waypoint_type is FlightWaypointType.BULLSEYE
@Slot(list, result=str) @Slot(list, result=str)
def setPosition(self, position: LeafletLatLon) -> str: def setPosition(self, position: LeafletLatLon) -> str:
point = self.theater.ll_to_point(LatLon(*position)) point = self.theater.ll_to_point(LatLon(*position))

View File

@ -570,7 +570,7 @@ class Waypoint {
} }
includeInPath() { includeInPath() {
return !this.waypoint.isDivert; return !this.waypoint.isDivert && !this.waypoint.isBullseye;
} }
} }