diff --git a/game/event/event.py b/game/event/event.py
index 8cc4aea7..ea5f3b80 100644
--- a/game/event/event.py
+++ b/game/event/event.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import logging
import math
-from typing import Dict, List, Optional, Type, TYPE_CHECKING
+from typing import Dict, List, Optional, TYPE_CHECKING, Type
from dcs.mapping import Point
from dcs.task import Task
@@ -11,12 +11,12 @@ from dcs.unittype import UnitType
from game import db, persistency
from game.debriefing import Debriefing
from game.infos.information import Information
-from game.operation.operation import Operation
+from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance
-from theater import ControlPoint
if TYPE_CHECKING:
from ..game import Game
+ from game.operation.operation import Operation
DIFFICULTY_LOG_BASE = 1.1
EVENT_DEPARTURE_MAX_DISTANCE = 340000
diff --git a/game/game.py b/game/game.py
index e9cc42fc..3b29c46c 100644
--- a/game/game.py
+++ b/game/game.py
@@ -160,9 +160,6 @@ class Game:
def _budget_player(self):
self.budget += self.budget_reward_amount
- def awacs_expense_commit(self):
- self.budget -= AWACS_BUDGET_COST
-
def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent:
event = UnitsDeliveryEvent(attacker_name=self.player_name,
defender_name=self.player_name,
@@ -172,10 +169,6 @@ class Game:
self.events.append(event)
return event
- def units_delivery_remove(self, event: Event):
- if event in self.events:
- self.events.remove(event)
-
def initiate_event(self, event: Event):
#assert event in self.events
logging.info("Generating {} (regular)".format(event))
@@ -202,12 +195,6 @@ class Game:
LuaPluginManager.load_settings(self.settings)
ObjectiveDistanceCache.set_theater(self.theater)
- # Save game compatibility.
-
- # TODO: Remove in 2.3.
- if not hasattr(self, "conditions"):
- self.conditions = self.generate_conditions()
-
def pass_turn(self, no_action: bool = False) -> None:
logging.info("Pass turn")
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
@@ -248,6 +235,7 @@ class Game:
self.aircraft_inventory.reset()
for cp in self.theater.controlpoints:
+ cp.pending_unit_deliveries = self.units_delivery_event(cp)
self.aircraft_inventory.set_from_control_point(cp)
# Plan flights & combat for next turn
diff --git a/game/inventory.py b/game/inventory.py
index 89f5afa1..3c92a80f 100644
--- a/game/inventory.py
+++ b/game/inventory.py
@@ -1,11 +1,15 @@
"""Inventory management APIs."""
-from collections import defaultdict
-from typing import Dict, Iterable, Iterator, Set, Tuple
+from __future__ import annotations
-from dcs.unittype import UnitType
+from collections import defaultdict
+from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING
+
+from dcs.unittype import FlyingType
from gen.flights.flight import Flight
-from theater import ControlPoint
+
+if TYPE_CHECKING:
+ from game.theater import ControlPoint
class ControlPointAircraftInventory:
@@ -13,9 +17,9 @@ class ControlPointAircraftInventory:
def __init__(self, control_point: ControlPoint) -> None:
self.control_point = control_point
- self.inventory: Dict[UnitType, int] = defaultdict(int)
+ self.inventory: Dict[FlyingType, int] = defaultdict(int)
- def add_aircraft(self, aircraft: UnitType, count: int) -> None:
+ def add_aircraft(self, aircraft: FlyingType, count: int) -> None:
"""Adds aircraft to the inventory.
Args:
@@ -24,7 +28,7 @@ class ControlPointAircraftInventory:
"""
self.inventory[aircraft] += count
- def remove_aircraft(self, aircraft: UnitType, count: int) -> None:
+ def remove_aircraft(self, aircraft: FlyingType, count: int) -> None:
"""Removes aircraft from the inventory.
Args:
@@ -43,7 +47,7 @@ class ControlPointAircraftInventory:
)
self.inventory[aircraft] -= count
- def available(self, aircraft: UnitType) -> int:
+ def available(self, aircraft: FlyingType) -> int:
"""Returns the number of available aircraft of the given type.
Args:
@@ -55,14 +59,14 @@ class ControlPointAircraftInventory:
return 0
@property
- def types_available(self) -> Iterator[UnitType]:
+ def types_available(self) -> Iterator[FlyingType]:
"""Iterates over all available aircraft types."""
for aircraft, count in self.inventory.items():
if count > 0:
yield aircraft
@property
- def all_aircraft(self) -> Iterator[Tuple[UnitType, int]]:
+ def all_aircraft(self) -> Iterator[Tuple[FlyingType, int]]:
"""Iterates over all available aircraft types, including amounts."""
for aircraft, count in self.inventory.items():
if count > 0:
@@ -102,9 +106,9 @@ class GlobalAircraftInventory:
return self.inventories[control_point]
@property
- def available_types_for_player(self) -> Iterator[UnitType]:
+ def available_types_for_player(self) -> Iterator[FlyingType]:
"""Iterates over all aircraft types available to the player."""
- seen: Set[UnitType] = set()
+ seen: Set[FlyingType] = set()
for control_point, inventory in self.inventories.items():
if control_point.captured:
for aircraft in inventory.types_available:
diff --git a/game/models/frontline_data.py b/game/models/frontline_data.py
index 94947135..586ebd58 100644
--- a/game/models/frontline_data.py
+++ b/game/models/frontline_data.py
@@ -1,4 +1,4 @@
-from theater import ControlPoint
+from game.theater import ControlPoint
class FrontlineData:
diff --git a/game/operation/operation.py b/game/operation/operation.py
index 0ff06ebe..1e01065b 100644
--- a/game/operation/operation.py
+++ b/game/operation/operation.py
@@ -15,6 +15,7 @@ from dcs.triggers import TriggerStart
from dcs.unittype import UnitType
from game.plugins import LuaPluginManager
+from game.theater import ControlPoint
from gen import Conflict, FlightType, VisualGenerator
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
from gen.airfields import AIRFIELD_DATA
@@ -29,7 +30,6 @@ from gen.kneeboard import KneeboardGenerator
from gen.radios import RadioFrequency, RadioRegistry
from gen.tacan import TacanRegistry
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
-from theater import ControlPoint
from .. import db
from ..debriefing import Debriefing
diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py
index 88d622a6..ca21d463 100644
--- a/game/theater/controlpoint.py
+++ b/game/theater/controlpoint.py
@@ -16,6 +16,7 @@ from dcs.ships import (
Type_071_Amphibious_Transport_Dock,
)
from dcs.terrain.terrain import Airport
+from dcs.unittype import FlyingType
from game import db
from gen.ground_forces.combat_stance import CombatStance
@@ -32,6 +33,7 @@ from .theatergroundobject import (
if TYPE_CHECKING:
from game import Game
from gen.flights.flight import FlightType
+ from ..event import UnitsDeliveryEvent
class ControlPointType(Enum):
@@ -157,6 +159,7 @@ class ControlPoint(MissionTarget):
self.cptype = cptype
self.stances: Dict[int, CombatStance] = {}
self.airport = None
+ self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None
@property
def ground_objects(self) -> List[TheaterGroundObject]:
@@ -366,6 +369,13 @@ class ControlPoint(MissionTarget):
# TODO: FlightType.STRIKE
]
+ def can_land(self, aircraft: FlyingType) -> bool:
+ if self.is_carrier and aircraft not in db.CARRIER_CAPABLE:
+ return False
+ if self.is_lha and aircraft not in db.LHA_CAPABLE:
+ return False
+ return True
+
class OffMapSpawn(ControlPoint):
def __init__(self, id: int, name: str, position: Point):
@@ -379,3 +389,7 @@ class OffMapSpawn(ControlPoint):
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from []
+
+ @property
+ def available_aircraft_slots(self) -> int:
+ return 1000
diff --git a/game/theater/frontline.py b/game/theater/frontline.py
deleted file mode 100644
index 3b57f9b6..00000000
--- a/game/theater/frontline.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Only here to keep compatibility for save games generated in version 2.2.0"""
diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py
index ed30750b..c5232a32 100644
--- a/game/theater/start_generator.py
+++ b/game/theater/start_generator.py
@@ -39,10 +39,11 @@ from gen.sam.sam_group_generator import (
generate_anti_air_group,
generate_ewr_group, generate_shorad_group,
)
-from theater import (
+from . import (
ConflictTheater,
ControlPoint,
- ControlPointType, OffMapSpawn,
+ ControlPointType,
+ OffMapSpawn,
)
GroundObjectTemplates = Dict[str, Dict[str, Any]]
diff --git a/game/weather.py b/game/weather.py
index d6775614..e8efd6e7 100644
--- a/game/weather.py
+++ b/game/weather.py
@@ -10,7 +10,7 @@ from typing import Optional
from dcs.weather import Weather as PydcsWeather, Wind
from game.settings import Settings
-from theater import ConflictTheater
+from game.theater import ConflictTheater
class TimeOfDay(Enum):
diff --git a/gen/aircraft.py b/gen/aircraft.py
index 9edc52f4..f3690915 100644
--- a/gen/aircraft.py
+++ b/gen/aircraft.py
@@ -70,6 +70,12 @@ from dcs.unittype import FlyingType, UnitType
from game import db
from game.data.cap_capabilities_db import GUNFIGHTERS
from game.settings import Settings
+from game.theater.controlpoint import (
+ ControlPoint,
+ ControlPointType,
+ OffMapSpawn,
+)
+from game.theater.theatergroundobject import TheaterGroundObject
from game.utils import knots_to_kph, nm_to_meter
from gen.airsupportgen import AirSupport
from gen.ato import AirTaskingOrder, Package
@@ -83,12 +89,6 @@ from gen.flights.flight import (
)
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
from gen.runways import RunwayData
-from theater import TheaterGroundObject
-from game.theater.controlpoint import (
- ControlPoint,
- ControlPointType,
- OffMapSpawn,
-)
from .conflictgen import Conflict
from .flights.flightplan import (
CasFlightPlan,
@@ -695,6 +695,18 @@ class AircraftConflictGenerator:
return StartType.Cold
return StartType.Warm
+ def determine_runway(self, cp: ControlPoint, dynamic_runways) -> RunwayData:
+ fallback = RunwayData(cp.full_name, runway_heading=0, runway_name="")
+ if cp.cptype == ControlPointType.AIRBASE:
+ assigner = RunwayAssigner(self.game.conditions)
+ return assigner.get_preferred_runway(cp.airport)
+ elif cp.is_fleet:
+ return dynamic_runways.get(cp.name, fallback)
+ else:
+ logging.warning(
+ f"Unhandled departure/arrival control point: {cp.cptype}")
+ return fallback
+
def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
package: Package, flight: Flight,
dynamic_runways: Dict[str, RunwayData]) -> None:
@@ -752,19 +764,9 @@ class AircraftConflictGenerator:
channel = self.get_intra_flight_channel(unit_type)
group.set_frequency(channel.mhz)
- # TODO: Support for different departure/arrival airfields.
- cp = flight.from_cp
- fallback_runway = RunwayData(cp.full_name, runway_heading=0,
- runway_name="")
- if cp.cptype == ControlPointType.AIRBASE:
- assigner = RunwayAssigner(self.game.conditions)
- departure_runway = assigner.get_preferred_runway(
- flight.from_cp.airport)
- elif cp.is_fleet:
- departure_runway = dynamic_runways.get(cp.name, fallback_runway)
- else:
- logging.warning(f"Unhandled departure control point: {cp.cptype}")
- departure_runway = fallback_runway
+ divert = None
+ if flight.divert is not None:
+ divert = self.determine_runway(flight.divert, dynamic_runways)
self.flights.append(FlightData(
package=package,
@@ -774,10 +776,9 @@ class AircraftConflictGenerator:
friendly=flight.from_cp.captured,
# Set later.
departure_delay=timedelta(),
- departure=departure_runway,
- arrival=departure_runway,
- # TODO: Support for divert airfields.
- divert=None,
+ departure=self.determine_runway(flight.departure, dynamic_runways),
+ arrival=self.determine_runway(flight.arrival, dynamic_runways),
+ divert=divert,
# Waypoints are added later, after they've had their TOTs set.
waypoints=[],
intra_flight_channel=channel
diff --git a/gen/conflictgen.py b/gen/conflictgen.py
index ecce03e4..2cfe8223 100644
--- a/gen/conflictgen.py
+++ b/gen/conflictgen.py
@@ -5,7 +5,8 @@ from typing import Tuple
from dcs.country import Country
from dcs.mapping import Point
-from theater import ConflictTheater, ControlPoint, FrontLine
+from game.theater.conflicttheater import ConflictTheater, FrontLine
+from game.theater.controlpoint import ControlPoint
AIR_DISTANCE = 40000
diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py
index 7d152efa..182e0455 100644
--- a/gen/flights/ai_flight_planner.py
+++ b/gen/flights/ai_flight_planner.py
@@ -16,11 +16,24 @@ from typing import (
Type,
)
-from dcs.unittype import FlyingType, UnitType
+from dcs.unittype import FlyingType
from game import db
from game.data.radar_db import UNITS_WITH_RADAR
from game.infos.information import Information
+from game.theater import (
+ ControlPoint,
+ FrontLine,
+ MissionTarget,
+ OffMapSpawn,
+ SamGroundObject,
+ TheaterGroundObject,
+)
+# Avoid importing some types that cause circular imports unless type checking.
+from game.theater.theatergroundobject import (
+ EwrGroundObject,
+ NavalGroundObject, VehicleGroupGroundObject,
+)
from game.utils import nm_to_meter
from gen import Conflict
from gen.ato import Package
@@ -46,19 +59,6 @@ from gen.flights.flight import (
)
from gen.flights.flightplan import FlightPlanBuilder
from gen.flights.traveltime import TotEstimator
-from theater import (
- ControlPoint,
- FrontLine,
- MissionTarget,
- OffMapSpawn, TheaterGroundObject,
- SamGroundObject,
-)
-
-# Avoid importing some types that cause circular imports unless type checking.
-from game.theater.theatergroundobject import (
- EwrGroundObject,
- NavalGroundObject, VehicleGroupGroundObject,
-)
if TYPE_CHECKING:
from game import Game
@@ -119,7 +119,7 @@ class AircraftAllocator:
def find_aircraft_for_flight(
self, flight: ProposedFlight
- ) -> Optional[Tuple[ControlPoint, UnitType]]:
+ ) -> Optional[Tuple[ControlPoint, FlyingType]]:
"""Finds aircraft suitable for the given mission.
Searches for aircraft capable of performing the given mission within the
@@ -190,7 +190,7 @@ class AircraftAllocator:
def find_aircraft_of_type(
self, flight: ProposedFlight, types: List[Type[FlyingType]],
- ) -> Optional[Tuple[ControlPoint, UnitType]]:
+ ) -> Optional[Tuple[ControlPoint, FlyingType]]:
airfields_in_range = self.closest_airfields.airfields_within(
flight.max_distance
)
@@ -214,6 +214,8 @@ class PackageBuilder:
global_inventory: GlobalAircraftInventory,
is_player: bool,
start_type: str) -> None:
+ self.closest_airfields = closest_airfields
+ self.is_player = is_player
self.package = Package(location)
self.allocator = AircraftAllocator(closest_airfields, global_inventory,
is_player)
@@ -236,11 +238,28 @@ class PackageBuilder:
start_type = "In Flight"
else:
start_type = self.start_type
- flight = Flight(self.package, aircraft, plan.num_aircraft, airfield,
- plan.task, start_type)
+
+ flight = Flight(self.package, aircraft, plan.num_aircraft, plan.task,
+ start_type, departure=airfield, arrival=airfield,
+ divert=self.find_divert_field(aircraft, airfield))
self.package.add_flight(flight)
return True
+ def find_divert_field(self, aircraft: FlyingType,
+ arrival: ControlPoint) -> Optional[ControlPoint]:
+ divert_limit = nm_to_meter(150)
+ for airfield in self.closest_airfields.airfields_within(divert_limit):
+ if airfield.captured != self.is_player:
+ continue
+ if airfield == arrival:
+ continue
+ if not airfield.can_land(aircraft):
+ continue
+ if isinstance(airfield, OffMapSpawn):
+ continue
+ return airfield
+ return None
+
def build(self) -> Package:
"""Returns the built package."""
return self.package
diff --git a/gen/flights/closestairfields.py b/gen/flights/closestairfields.py
index a6045dde..5bba28db 100644
--- a/gen/flights/closestairfields.py
+++ b/gen/flights/closestairfields.py
@@ -1,7 +1,7 @@
"""Objective adjacency lists."""
from typing import Dict, Iterator, List, Optional
-from theater import ConflictTheater, ControlPoint, MissionTarget
+from game.theater import ConflictTheater, ControlPoint, MissionTarget
class ClosestAirfields:
diff --git a/gen/flights/flight.py b/gen/flights/flight.py
index 2b5e35ea..56d9d04a 100644
--- a/gen/flights/flight.py
+++ b/gen/flights/flight.py
@@ -65,6 +65,7 @@ class FlightWaypointType(Enum):
INGRESS_DEAD = 20
INGRESS_SWEEP = 21
INGRESS_BAI = 22
+ DIVERT = 23
class FlightWaypoint:
@@ -133,12 +134,15 @@ class FlightWaypoint:
class Flight:
def __init__(self, package: Package, unit_type: FlyingType, count: int,
- from_cp: ControlPoint, flight_type: FlightType,
- start_type: str) -> None:
+ flight_type: FlightType, start_type: str,
+ departure: ControlPoint, arrival: ControlPoint,
+ divert: Optional[ControlPoint]) -> None:
self.package = package
self.unit_type = unit_type
self.count = count
- self.from_cp = from_cp
+ self.departure = departure
+ self.arrival = arrival
+ self.divert = divert
self.flight_type = flight_type
# TODO: Replace with FlightPlan.
self.targets: List[MissionTarget] = []
@@ -157,6 +161,10 @@ class Flight:
custom_waypoints=[]
)
+ @property
+ def from_cp(self) -> ControlPoint:
+ return self.departure
+
@property
def points(self) -> List[FlightWaypoint]:
return self.flight_plan.waypoints[1:]
diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py
index 918861e2..d8758e32 100644
--- a/gen/flights/flightplan.py
+++ b/gen/flights/flightplan.py
@@ -7,20 +7,19 @@ generating the waypoints for the mission.
"""
from __future__ import annotations
-import math
-from datetime import timedelta
-from functools import cached_property
import logging
+import math
import random
from dataclasses import dataclass
+from datetime import timedelta
+from functools import cached_property
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
from dcs.mapping import Point
from dcs.unit import Unit
from game.data.doctrine import Doctrine
-from game.utils import nm_to_meter
-from theater import (
+from game.theater import (
ControlPoint,
FrontLine,
MissionTarget,
@@ -28,6 +27,7 @@ from theater import (
TheaterGroundObject,
)
from game.theater.theatergroundobject import EwrGroundObject
+from game.utils import nm_to_meter
from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
from .traveltime import GroundSpeed, TravelTime
@@ -68,6 +68,10 @@ class FlightPlan:
@property
def waypoints(self) -> List[FlightWaypoint]:
"""A list of all waypoints in the flight plan, in order."""
+ return list(self.iter_waypoints())
+
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ """Iterates over all waypoints in the flight plan, in order."""
raise NotImplementedError
@property
@@ -166,8 +170,7 @@ class FlightPlan:
class LoiterFlightPlan(FlightPlan):
hold: FlightWaypoint
- @property
- def waypoints(self) -> List[FlightWaypoint]:
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -193,8 +196,7 @@ class FormationFlightPlan(LoiterFlightPlan):
join: FlightWaypoint
split: FlightWaypoint
- @property
- def waypoints(self) -> List[FlightWaypoint]:
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -295,8 +297,7 @@ class PatrollingFlightPlan(FlightPlan):
return self.patrol_end_time
return None
- @property
- def waypoints(self) -> List[FlightWaypoint]:
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@property
@@ -312,15 +313,17 @@ class PatrollingFlightPlan(FlightPlan):
class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
land: FlightWaypoint
+ divert: Optional[FlightWaypoint]
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return [
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from [
self.takeoff,
self.patrol_start,
self.patrol_end,
self.land,
]
+ if self.divert is not None:
+ yield self.divert
@dataclass(frozen=True)
@@ -328,16 +331,18 @@ class CasFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
target: FlightWaypoint
land: FlightWaypoint
+ divert: Optional[FlightWaypoint]
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return [
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from [
self.takeoff,
self.patrol_start,
self.target,
self.patrol_end,
self.land,
]
+ if self.divert is not None:
+ yield self.divert
def request_escort_at(self) -> Optional[FlightWaypoint]:
return self.patrol_start
@@ -350,16 +355,18 @@ class CasFlightPlan(PatrollingFlightPlan):
class TarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint
land: FlightWaypoint
+ divert: Optional[FlightWaypoint]
lead_time: timedelta
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return [
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from [
self.takeoff,
self.patrol_start,
self.patrol_end,
self.land,
]
+ if self.divert is not None:
+ yield self.divert
@property
def tot_offset(self) -> timedelta:
@@ -386,10 +393,6 @@ class TarCapFlightPlan(PatrollingFlightPlan):
return super().patrol_end_time
-# TODO: Remove when breaking save compat.
-FrontLineCapFlightPlan = TarCapFlightPlan
-
-
@dataclass(frozen=True)
class StrikeFlightPlan(FormationFlightPlan):
takeoff: FlightWaypoint
@@ -400,19 +403,23 @@ class StrikeFlightPlan(FormationFlightPlan):
egress: FlightWaypoint
split: FlightWaypoint
land: FlightWaypoint
+ divert: Optional[FlightWaypoint]
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return [
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from [
self.takeoff,
self.hold,
self.join,
self.ingress
- ] + self.targets + [
+ ]
+ yield from self.targets
+ yield from[
self.egress,
self.split,
self.land,
]
+ if self.divert is not None:
+ yield self.divert
@property
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
@@ -511,17 +518,19 @@ class SweepFlightPlan(LoiterFlightPlan):
sweep_start: FlightWaypoint
sweep_end: FlightWaypoint
land: FlightWaypoint
+ divert: Optional[FlightWaypoint]
lead_time: timedelta
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return [
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from [
self.takeoff,
self.hold,
self.sweep_start,
self.sweep_end,
self.land,
]
+ if self.divert is not None:
+ yield self.divert
@property
def tot_waypoint(self) -> Optional[FlightWaypoint]:
@@ -567,9 +576,8 @@ class SweepFlightPlan(LoiterFlightPlan):
class CustomFlightPlan(FlightPlan):
custom_waypoints: List[FlightWaypoint]
- @property
- def waypoints(self) -> List[FlightWaypoint]:
- return self.custom_waypoints
+ def iter_waypoints(self) -> Iterator[FlightWaypoint]:
+ yield from self.custom_waypoints
@property
def tot_waypoint(self) -> Optional[FlightWaypoint]:
@@ -774,10 +782,11 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
patrol_duration=self.doctrine.cap_duration,
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
patrol_start=start,
patrol_end=end,
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
@@ -800,11 +809,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
lead_time=timedelta(minutes=5),
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
sweep_start=start,
sweep_end=end,
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
def racetrack_for_objective(self,
@@ -900,10 +910,11 @@ class FlightPlanBuilder:
# requests an escort the CAP flight will remain on station for the
# duration of the escorted mission, or until it is winchester/bingo.
patrol_duration=self.doctrine.cap_duration,
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
patrol_start=start,
patrol_end=end,
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
def generate_dead(self, flight: Flight,
@@ -965,14 +976,15 @@ class FlightPlanBuilder:
return StrikeFlightPlan(
package=self.package,
flight=flight,
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=[target],
egress=egress,
split=builder.split(self.package.waypoints.split),
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
def generate_cas(self, flight: Flight) -> CasFlightPlan:
@@ -999,11 +1011,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
patrol_duration=self.doctrine.cas_duration,
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
patrol_start=builder.ingress_cas(ingress, location),
target=builder.cas(center),
patrol_end=builder.egress(egress, location),
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
@staticmethod
@@ -1030,7 +1043,7 @@ class FlightPlanBuilder:
def _hold_point(self, flight: Flight) -> Point:
assert self.package.waypoints is not None
- origin = flight.from_cp.position
+ origin = flight.departure.position
target = self.package.target.position
join = self.package.waypoints.join
origin_to_target = origin.distance_to_point(target)
@@ -1118,14 +1131,15 @@ class FlightPlanBuilder:
return StrikeFlightPlan(
package=self.package,
flight=flight,
- takeoff=builder.takeoff(flight.from_cp),
+ takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=target_waypoints,
egress=builder.egress(self.package.waypoints.egress, location),
split=builder.split(self.package.waypoints.split),
- land=builder.land(flight.from_cp)
+ land=builder.land(flight.arrival),
+ divert=builder.divert(flight.divert)
)
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
@@ -1201,7 +1215,7 @@ class FlightPlanBuilder:
)
for airfield in cache.closest_airfields:
for flight in self.package.flights:
- if flight.from_cp == airfield:
+ if flight.departure == airfield:
return airfield
raise RuntimeError(
"Could not find any airfield assigned to this package"
diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py
index 346a4498..74731929 100644
--- a/gen/flights/waypointbuilder.py
+++ b/gen/flights/waypointbuilder.py
@@ -8,14 +8,13 @@ from dcs.unit import Unit
from dcs.unitgroup import VehicleGroup
from game.data.doctrine import Doctrine
-from game.utils import feet_to_meter
-from game.weather import Conditions
-from theater import (
+from game.theater import (
ControlPoint,
MissionTarget,
OffMapSpawn,
TheaterGroundObject,
)
+from game.weather import Conditions
from .flight import Flight, FlightWaypoint, FlightWaypointType
@@ -104,6 +103,40 @@ class WaypointBuilder:
waypoint.pretty_name = "Land"
return waypoint
+ def divert(self,
+ divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
+ """Create divert waypoint for the given arrival airfield or carrier.
+
+ Args:
+ divert: Divert airfield or carrier.
+ """
+ if divert is None:
+ return None
+
+ position = divert.position
+ if isinstance(divert, OffMapSpawn):
+ if self.is_helo:
+ altitude = 500
+ else:
+ altitude = self.doctrine.rendezvous_altitude
+ altitude_type = "BARO"
+ else:
+ altitude = 0
+ altitude_type = "RADIO"
+
+ waypoint = FlightWaypoint(
+ FlightWaypointType.DIVERT,
+ position.x,
+ position.y,
+ altitude
+ )
+ waypoint.alt_type = altitude_type
+ waypoint.name = "DIVERT"
+ waypoint.description = "Divert"
+ waypoint.pretty_name = "Divert"
+ waypoint.only_for_player = True
+ return waypoint
+
def hold(self, position: Point) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.LOITER,
diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py
index db1deb03..b0f14df4 100644
--- a/gen/ground_forces/ai_ground_planner.py
+++ b/gen/ground_forces/ai_ground_planner.py
@@ -2,12 +2,12 @@ import random
from enum import Enum
from typing import Dict, List
-from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
from dcs.unittype import VehicleType
+from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
import pydcs_extensions.frenchpack.frenchpack as frenchpack
+from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance
-from theater import ControlPoint
TYPE_TANKS = [
Armor.MBT_T_55,
diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py
index edd58e6d..09385c78 100644
--- a/gen/groundobjectsgen.py
+++ b/gen/groundobjectsgen.py
@@ -20,14 +20,14 @@ from dcs.task import (
EPLRS,
OptAlarmState,
)
-from dcs.unit import Ship, Vehicle, Unit
+from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import Group, ShipGroup, StaticGroup
from dcs.unittype import StaticType, UnitType
from game import db
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
from game.db import unit_type_from_name
-from theater import ControlPoint, TheaterGroundObject
+from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
BuildingGroundObject, CarrierGroundObject,
GenericCarrierGroundObject,
diff --git a/gen/runways.py b/gen/runways.py
index 5323c37b..658cc846 100644
--- a/gen/runways.py
+++ b/gen/runways.py
@@ -7,8 +7,8 @@ from typing import Iterator, Optional
from dcs.terrain.terrain import Airport
+from game.theater import ControlPoint, ControlPointType
from game.weather import Conditions
-from theater import ControlPoint, ControlPointType
from .airfields import AIRFIELD_DATA
from .radios import RadioFrequency
from .tacan import TacanChannel
diff --git a/qt_ui/widgets/base/QAirportInformation.py b/qt_ui/widgets/base/QAirportInformation.py
deleted file mode 100644
index 4fc1474c..00000000
--- a/qt_ui/widgets/base/QAirportInformation.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QVBoxLayout, QLCDNumber
-
-from theater import ControlPoint, Airport
-
-
-class QAirportInformation(QGroupBox):
-
- def __init__(self, cp:ControlPoint, airport:Airport):
- super(QAirportInformation, self).__init__(airport.name)
- self.cp = cp
- self.airport = airport
- self.init_ui()
-
- def init_ui(self):
- self.layout = QGridLayout()
-
- # Runway information
- self.runways = QGroupBox("Runways")
- self.runwayLayout = QGridLayout()
- for i, runway in enumerate(self.airport.runways):
-
- # Seems like info is missing in pydcs, even if the attribute is there
- lr = ""
- if runway.leftright == 1:
- lr = "L"
- elif runway.leftright == 2:
- lr = "R"
-
- self.runwayLayout.addWidget(QLabel("Runway " + str(runway.heading) + lr), i, 0)
-
- # Seems like info is missing in pydcs, even if the attribute is there
- if runway.ils:
- self.runwayLayout.addWidget(QLabel("ILS "), i, 1)
- self.runwayLayout.addWidget(QLCDNumber(6, runway.ils), i, 1)
- else:
- self.runwayLayout.addWidget(QLabel("NO ILS"), i, 1)
-
-
- self.runways.setLayout(self.runwayLayout)
- self.layout.addWidget(self.runways, 0, 0)
-
- self.layout.addWidget(QLabel("Parking Slots :"), 1, 0)
- self.layout.addWidget(QLabel(str(len(self.airport.parking_slots))), 1, 1)
-
-
- stretch = QVBoxLayout()
- stretch.addStretch()
-
- self.layout.addLayout(stretch, 2, 0)
- self.setLayout(self.layout)
-
-
diff --git a/qt_ui/widgets/combos/QAircraftTypeSelector.py b/qt_ui/widgets/combos/QAircraftTypeSelector.py
index 1f490e4d..2be6e48c 100644
--- a/qt_ui/widgets/combos/QAircraftTypeSelector.py
+++ b/qt_ui/widgets/combos/QAircraftTypeSelector.py
@@ -3,13 +3,13 @@ from typing import Iterable
from PySide2.QtWidgets import QComboBox
-from dcs.planes import PlaneType
+from dcs.unittype import FlyingType
class QAircraftTypeSelector(QComboBox):
"""Combo box for selecting among the given aircraft types."""
- def __init__(self, aircraft_types: Iterable[PlaneType]) -> None:
+ def __init__(self, aircraft_types: Iterable[FlyingType]) -> None:
super().__init__()
for aircraft in aircraft_types:
self.addItem(f"{aircraft.id}", userData=aircraft)
diff --git a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py
new file mode 100644
index 00000000..c5d89b90
--- /dev/null
+++ b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py
@@ -0,0 +1,40 @@
+"""Combo box for selecting a departure airfield."""
+from typing import Iterable
+
+from PySide2.QtWidgets import QComboBox
+from dcs.unittype import FlyingType
+
+from game import db
+from game.theater.controlpoint import ControlPoint
+
+
+class QArrivalAirfieldSelector(QComboBox):
+ """A combo box for selecting a flight's arrival or divert airfield.
+
+ The combo box will automatically be populated with all airfields the given
+ aircraft type is able to land at.
+ """
+
+ def __init__(self, destinations: Iterable[ControlPoint],
+ aircraft: FlyingType, optional_text: str) -> None:
+ super().__init__()
+ self.destinations = list(destinations)
+ self.aircraft = aircraft
+ self.optional_text = optional_text
+ self.rebuild_selector()
+ self.setCurrentIndex(0)
+
+ def change_aircraft(self, aircraft: FlyingType) -> None:
+ if self.aircraft == aircraft:
+ return
+ self.aircraft = aircraft
+ self.rebuild_selector()
+
+ def rebuild_selector(self) -> None:
+ self.clear()
+ for destination in self.destinations:
+ if destination.can_land(self.aircraft):
+ self.addItem(destination.name, destination)
+ self.model().sort(0)
+ self.insertItem(0, self.optional_text, None)
+ self.update()
diff --git a/qt_ui/widgets/combos/QFlightTypeComboBox.py b/qt_ui/widgets/combos/QFlightTypeComboBox.py
index 6ba9e455..1918dd4d 100644
--- a/qt_ui/widgets/combos/QFlightTypeComboBox.py
+++ b/qt_ui/widgets/combos/QFlightTypeComboBox.py
@@ -2,7 +2,7 @@
from PySide2.QtWidgets import QComboBox
-from theater import ConflictTheater, MissionTarget
+from game.theater import ConflictTheater, MissionTarget
class QFlightTypeComboBox(QComboBox):
diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py
index 14bdbb47..ce1c6301 100644
--- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py
+++ b/qt_ui/widgets/combos/QOriginAirfieldSelector.py
@@ -3,7 +3,7 @@ from typing import Iterable
from PySide2.QtCore import Signal
from PySide2.QtWidgets import QComboBox
-from dcs.planes import PlaneType
+from dcs.unittype import FlyingType
from game.inventory import GlobalAircraftInventory
from game.theater.controlpoint import ControlPoint
@@ -20,7 +20,7 @@ class QOriginAirfieldSelector(QComboBox):
def __init__(self, global_inventory: GlobalAircraftInventory,
origins: Iterable[ControlPoint],
- aircraft: PlaneType) -> None:
+ aircraft: FlyingType) -> None:
super().__init__()
self.global_inventory = global_inventory
self.origins = list(origins)
@@ -28,7 +28,7 @@ class QOriginAirfieldSelector(QComboBox):
self.rebuild_selector()
self.currentIndexChanged.connect(self.index_changed)
- def change_aircraft(self, aircraft: PlaneType) -> None:
+ def change_aircraft(self, aircraft: FlyingType) -> None:
if self.aircraft == aircraft:
return
self.aircraft = aircraft
diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py
index 8af3c3f4..8f40afde 100644
--- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py
+++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py
@@ -1,10 +1,10 @@
from PySide2.QtGui import QStandardItem, QStandardItemModel
from game import Game
+from game.theater import ControlPointType
from gen import BuildingGroundObject, Conflict, FlightWaypointType
from gen.flights.flight import FlightWaypoint
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
-from theater import ControlPointType
class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
diff --git a/qt_ui/widgets/map/QFrontLine.py b/qt_ui/widgets/map/QFrontLine.py
index 1849f5ff..2ca71953 100644
--- a/qt_ui/widgets/map/QFrontLine.py
+++ b/qt_ui/widgets/map/QFrontLine.py
@@ -13,11 +13,11 @@ from PySide2.QtWidgets import (
)
import qt_ui.uiconstants as const
+from game.theater import FrontLine
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.QPackageDialog import QNewPackageDialog
-from theater import FrontLine
class QFrontLine(QGraphicsLineItem):
diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py
index fb5802c3..50fc5fba 100644
--- a/qt_ui/widgets/map/QLiberationMap.py
+++ b/qt_ui/widgets/map/QLiberationMap.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import datetime
import logging
import math
-from typing import Iterable, List, Optional, Tuple, Iterator
+from typing import Iterable, Iterator, List, Optional, Tuple
from PySide2.QtCore import QPointF, Qt
from PySide2.QtGui import (
@@ -27,6 +27,13 @@ from dcs.mapping import point_from_heading
import qt_ui.uiconstants as CONST
from game import Game, db
+from game.theater import ControlPoint
+from game.theater.conflicttheater import FrontLine
+from game.theater.theatergroundobject import (
+ EwrGroundObject,
+ MissileSiteGroundObject,
+ TheaterGroundObject,
+)
from game.utils import meter_to_feet
from game.weather import TimeOfDay
from gen import Conflict
@@ -39,13 +46,7 @@ from qt_ui.widgets.map.QLiberationScene import QLiberationScene
from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
-from theater import ControlPoint
-from game.theater.conflicttheater import FrontLine
-from game.theater.theatergroundobject import (
- EwrGroundObject,
- MissileSiteGroundObject,
- TheaterGroundObject,
-)
+
def binomial(i: int, n: int) -> float:
"""Binomial coefficient"""
@@ -373,6 +374,10 @@ class QLiberationMap(QGraphicsView):
FlightWaypointType.TARGET_SHIP,
)
for idx, point in enumerate(flight.flight_plan.waypoints[1:]):
+ if point.waypoint_type == FlightWaypointType.DIVERT:
+ # Don't clutter the map showing divert points.
+ continue
+
new_pos = self._transform_point(Point(point.x, point.y))
self.draw_flight_path(scene, prev_pos, new_pos, is_player,
selected)
@@ -386,7 +391,6 @@ class QLiberationMap(QGraphicsView):
self.draw_waypoint_info(scene, idx + 1, point, new_pos,
flight.flight_plan)
prev_pos = tuple(new_pos)
- self.draw_flight_path(scene, prev_pos, pos, is_player, selected)
def draw_waypoint(self, scene: QGraphicsScene, position: Tuple[int, int],
player: bool, selected: bool) -> None:
diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py
index 7ef55952..0f88bf7e 100644
--- a/qt_ui/widgets/map/QMapControlPoint.py
+++ b/qt_ui/widgets/map/QMapControlPoint.py
@@ -4,9 +4,9 @@ from PySide2.QtGui import QColor, QPainter
from PySide2.QtWidgets import QAction, QMenu
import qt_ui.uiconstants as const
+from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
-from theater import ControlPoint
from .QMapObject import QMapObject
from ...displayoptions import DisplayOptions
from ...windows.GameUpdateSignal import GameUpdateSignal
diff --git a/qt_ui/widgets/map/QMapGroundObject.py b/qt_ui/widgets/map/QMapGroundObject.py
index a7d857f3..f1d3e542 100644
--- a/qt_ui/widgets/map/QMapGroundObject.py
+++ b/qt_ui/widgets/map/QMapGroundObject.py
@@ -8,8 +8,8 @@ import qt_ui.uiconstants as const
from game import Game
from game.data.building_data import FORTIFICATION_BUILDINGS
from game.db import REWARDS
+from game.theater import ControlPoint, TheaterGroundObject
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
-from theater import ControlPoint, TheaterGroundObject
from .QMapObject import QMapObject
from ...displayoptions import DisplayOptions
diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py
index cfdb8128..f9f7c159 100644
--- a/qt_ui/windows/basemenu/QBaseMenu2.py
+++ b/qt_ui/windows/basemenu/QBaseMenu2.py
@@ -2,12 +2,12 @@ from PySide2.QtCore import Qt
from PySide2.QtGui import QCloseEvent, QPixmap
from PySide2.QtWidgets import QDialog, QGridLayout, QHBoxLayout, QLabel, QWidget
+from game.theater import ControlPoint, ControlPointType
from qt_ui.models import GameModel
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
-from theater import ControlPoint, ControlPointType
class QBaseMenu2(QDialog):
diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py
index fd2f7ae7..1e705372 100644
--- a/qt_ui/windows/basemenu/QBaseMenuTabs.py
+++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py
@@ -1,11 +1,11 @@
-from PySide2.QtWidgets import QFrame, QGridLayout, QLabel, QTabWidget
+from PySide2.QtWidgets import QTabWidget
+from game.theater import ControlPoint, OffMapSpawn
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand
from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ
from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ
from qt_ui.windows.basemenu.intel.QIntelInfo import QIntelInfo
-from theater import ControlPoint, OffMapSpawn
class QBaseMenuTabs(QTabWidget):
diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py
index b41ac68a..5cb26a81 100644
--- a/qt_ui/windows/basemenu/QRecruitBehaviour.py
+++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py
@@ -1,3 +1,5 @@
+import logging
+
from PySide2.QtWidgets import (
QGroupBox,
QHBoxLayout,
@@ -6,17 +8,17 @@ from PySide2.QtWidgets import (
QSizePolicy,
QSpacerItem,
)
-import logging
from dcs.unittype import UnitType
-from theater import db
-
+from game import db
+from game.event import UnitsDeliveryEvent
+from game.theater import ControlPoint
+from qt_ui.models import GameModel
class QRecruitBehaviour:
- game = None
- cp = None
- deliveryEvent = None
+ game_model: GameModel
+ cp: ControlPoint
existing_units_labels = None
bought_amount_labels = None
maximum_units = -1
@@ -24,12 +26,16 @@ class QRecruitBehaviour:
BUDGET_FORMAT = "Available Budget: ${}M"
def __init__(self) -> None:
- self.deliveryEvent = None
self.bought_amount_labels = {}
self.existing_units_labels = {}
self.recruitable_types = []
self.update_available_budget()
+ @property
+ def pending_deliveries(self) -> UnitsDeliveryEvent:
+ assert self.cp.pending_unit_deliveries
+ return self.cp.pending_unit_deliveries
+
@property
def budget(self) -> int:
return self.game_model.game.budget
@@ -47,7 +53,7 @@ class QRecruitBehaviour:
exist.setLayout(existLayout)
existing_units = self.cp.base.total_units_of_type(unit_type)
- scheduled_units = self.deliveryEvent.units.get(unit_type, 0)
+ scheduled_units = self.pending_deliveries.units.get(unit_type, 0)
unitName = QLabel("" + db.unit_type_name_2(unit_type) + "")
unitName.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
@@ -103,7 +109,7 @@ class QRecruitBehaviour:
def _update_count_label(self, unit_type: UnitType):
self.bought_amount_labels[unit_type].setText("{}".format(
- unit_type in self.deliveryEvent.units and "{}".format(self.deliveryEvent.units[unit_type]) or "0"
+ unit_type in self.pending_deliveries.units and "{}".format(self.pending_deliveries.units[unit_type]) or "0"
))
self.existing_units_labels[unit_type].setText("{}".format(
@@ -129,7 +135,7 @@ class QRecruitBehaviour:
price = db.PRICES[unit_type]
if self.budget >= price:
- self.deliveryEvent.deliver({unit_type: 1})
+ self.pending_deliveries.deliver({unit_type: 1})
self.budget -= price
else:
# TODO : display modal warning
@@ -138,12 +144,12 @@ class QRecruitBehaviour:
self.update_available_budget()
def sell(self, unit_type):
- if self.deliveryEvent.units.get(unit_type, 0) > 0:
+ if self.pending_deliveries.units.get(unit_type, 0) > 0:
price = db.PRICES[unit_type]
self.budget += price
- self.deliveryEvent.units[unit_type] = self.deliveryEvent.units[unit_type] - 1
- if self.deliveryEvent.units[unit_type] == 0:
- del self.deliveryEvent.units[unit_type]
+ self.pending_deliveries.units[unit_type] = self.pending_deliveries.units[unit_type] - 1
+ if self.pending_deliveries.units[unit_type] == 0:
+ del self.pending_deliveries.units[unit_type]
elif self.cp.base.total_units_of_type(unit_type) > 0:
price = db.PRICES[unit_type]
self.budget += price
@@ -154,20 +160,14 @@ class QRecruitBehaviour:
@property
def total_units(self):
-
total = 0
for unit_type in self.recruitables_types:
total += self.cp.base.total_units(unit_type)
- print(unit_type, total, self.cp.base.total_units(unit_type))
- print("--------------------------------")
- if self.deliveryEvent:
- for unit_bought in self.deliveryEvent.units:
+ if self.pending_deliveries:
+ for unit_bought in self.pending_deliveries.units:
if db.unit_task(unit_bought) in self.recruitables_types:
- total += self.deliveryEvent.units[unit_bought]
- print(unit_bought, total, self.deliveryEvent.units[unit_bought])
-
- print("=============================")
+ total += self.pending_deliveries.units[unit_bought]
return total
@@ -181,4 +181,4 @@ class QRecruitBehaviour:
"""
Set the maximum number of units that can be bought
"""
- self.recruitables_types = recruitables_types
\ No newline at end of file
+ self.recruitables_types = recruitables_types
diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
index a01aaaa9..3e8fef9f 100644
--- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
+++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
@@ -11,13 +11,14 @@ from PySide2.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from dcs.task import CAP, CAS
from dcs.unittype import UnitType
-from game.event.event import UnitsDeliveryEvent
+from game import db
+from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.uiconstants import ICONS
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
-from theater import CAP, CAS, ControlPoint, db
class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
@@ -25,17 +26,10 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
QFrame.__init__(self)
self.cp = cp
self.game_model = game_model
- self.deliveryEvent: Optional[UnitsDeliveryEvent] = None
self.bought_amount_labels = {}
self.existing_units_labels = {}
- for event in self.game_model.game.events:
- if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp:
- self.deliveryEvent = event
- if not self.deliveryEvent:
- self.deliveryEvent = self.game_model.game.units_delivery_event(self.cp)
-
# Determine maximum number of aircrafts that can be bought
self.set_maximum_units(self.cp.available_aircraft_slots)
self.set_recruitable_types([CAP, CAS])
@@ -92,7 +86,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
def sell(self, unit_type: UnitType):
# Don't need to remove aircraft from the inventory if we're canceling
# orders.
- if self.deliveryEvent.units.get(unit_type, 0) <= 0:
+ if self.pending_deliveries.units.get(unit_type, 0) <= 0:
global_inventory = self.game_model.game.aircraft_inventory
inventory = global_inventory.for_control_point(self.cp)
try:
diff --git a/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py
index 9965115a..97c804bf 100644
--- a/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py
+++ b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py
@@ -1,10 +1,10 @@
from PySide2.QtWidgets import QFrame, QGridLayout, QGroupBox, QVBoxLayout
+from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.airfield.QAircraftRecruitmentMenu import \
QAircraftRecruitmentMenu
from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView
-from theater import ControlPoint
class QAirfieldCommand(QFrame):
diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py b/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py
index 350cf5e8..6d46b35b 100644
--- a/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py
+++ b/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py
@@ -1,10 +1,16 @@
from PySide2.QtCore import Qt
-from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QPushButton, QVBoxLayout
+from PySide2.QtWidgets import (
+ QGridLayout,
+ QGroupBox,
+ QLabel,
+ QPushButton,
+ QVBoxLayout,
+)
+from game.theater import ControlPoint, TheaterGroundObject
from qt_ui.dialogs import Dialog
from qt_ui.uiconstants import VEHICLES_ICONS
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
-from theater import ControlPoint, TheaterGroundObject
class QBaseDefenseGroupInfo(QGroupBox):
diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py
index 5ad1f6c9..75a45eb0 100644
--- a/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py
+++ b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py
@@ -1,7 +1,9 @@
from PySide2.QtWidgets import QFrame, QGridLayout
+
from game import Game
-from qt_ui.windows.basemenu.base_defenses.QBaseInformation import QBaseInformation
-from theater import ControlPoint
+from game.theater import ControlPoint
+from qt_ui.windows.basemenu.base_defenses.QBaseInformation import \
+ QBaseInformation
class QBaseDefensesHQ(QFrame):
diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py b/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py
index f5325887..50ec2f81 100644
--- a/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py
+++ b/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py
@@ -1,10 +1,15 @@
from PySide2.QtGui import Qt
-from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QVBoxLayout, QFrame, QWidget, QScrollArea
+from PySide2.QtWidgets import (
+ QFrame,
+ QGridLayout,
+ QScrollArea,
+ QVBoxLayout,
+ QWidget,
+)
-from game import db
-from qt_ui.uiconstants import AIRCRAFT_ICONS, VEHICLES_ICONS
-from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import QBaseDefenseGroupInfo
-from theater import ControlPoint, Airport
+from game.theater import Airport, ControlPoint
+from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import \
+ QBaseDefenseGroupInfo
class QBaseInformation(QFrame):
diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
index ec1cabf6..c359eaaf 100644
--- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
+++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
@@ -6,11 +6,12 @@ from PySide2.QtWidgets import (
QVBoxLayout,
QWidget,
)
+from dcs.task import PinpointStrike
-from game.event import UnitsDeliveryEvent
+from game import db
+from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
-from theater import ControlPoint, PinpointStrike, db
class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
@@ -23,12 +24,6 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
self.bought_amount_labels = {}
self.existing_units_labels = {}
- for event in self.game_model.game.events:
- if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp:
- self.deliveryEvent = event
- if not self.deliveryEvent:
- self.deliveryEvent = self.game_model.game.units_delivery_event(self.cp)
-
self.init_ui()
def init_ui(self):
@@ -61,4 +56,4 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
scroll.setWidgetResizable(True)
scroll.setWidget(scroll_content)
main_layout.addWidget(scroll)
- self.setLayout(main_layout)
\ No newline at end of file
+ self.setLayout(main_layout)
diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py
index bb18594f..39cba843 100644
--- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py
+++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py
@@ -1,11 +1,11 @@
from PySide2.QtWidgets import QFrame, QGridLayout
+from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.ground_forces.QArmorRecruitmentMenu import \
QArmorRecruitmentMenu
from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategy import \
QGroundForcesStrategy
-from theater import ControlPoint
class QGroundForcesHQ(QFrame):
diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py
index 0b7b4db6..3aee8c50 100644
--- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py
+++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py
@@ -1,8 +1,9 @@
-from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout
+from PySide2.QtWidgets import QGroupBox, QLabel, QVBoxLayout
from game import Game
-from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector
-from theater import ControlPoint
+from game.theater import ControlPoint
+from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import \
+ QGroundForcesStrategySelector
class QGroundForcesStrategy(QGroupBox):
diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py
index 09c3fa5b..4acd8731 100644
--- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py
+++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py
@@ -1,6 +1,6 @@
from PySide2.QtWidgets import QComboBox
-from theater import ControlPoint, CombatStance
+from game.theater import CombatStance, ControlPoint
class QGroundForcesStrategySelector(QComboBox):
diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py
index bc7cb13b..e422ef3a 100644
--- a/qt_ui/windows/basemenu/intel/QIntelInfo.py
+++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py
@@ -1,11 +1,14 @@
+from PySide2.QtWidgets import (
+ QFrame,
+ QGridLayout,
+ QGroupBox,
+ QLabel,
+ QVBoxLayout,
+)
+from dcs.task import CAP, CAS, Embarking, PinpointStrike
-
-from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout, QFrame, QGridLayout
-from dcs.task import Embarking, CAS, PinpointStrike, CAP
-
-from game import Game
-from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector
-from theater import ControlPoint, db
+from game import Game, db
+from game.theater import ControlPoint
class QIntelInfo(QFrame):
diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py
index a1fda851..abbf5c8c 100644
--- a/qt_ui/windows/groundobject/QGroundObjectMenu.py
+++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py
@@ -2,20 +2,31 @@ import logging
from PySide2 import QtCore
from PySide2.QtGui import Qt
-from PySide2.QtWidgets import QHBoxLayout, QDialog, QGridLayout, QLabel, QGroupBox, QVBoxLayout, QPushButton, \
- QComboBox, QSpinBox, QMessageBox
+from PySide2.QtWidgets import (
+ QComboBox,
+ QDialog,
+ QGridLayout,
+ QGroupBox,
+ QHBoxLayout,
+ QLabel,
+ QMessageBox,
+ QPushButton,
+ QSpinBox,
+ QVBoxLayout,
+)
from dcs import Point
from game import Game, db
from game.data.building_data import FORTIFICATION_BUILDINGS
-from game.db import PRICES, REWARDS, unit_type_of, PinpointStrike
-from gen.defenses.armor_group_generator import generate_armor_group_of_type_and_size
+from game.db import PRICES, PinpointStrike, REWARDS, unit_type_of
+from game.theater import ControlPoint, TheaterGroundObject
+from gen.defenses.armor_group_generator import \
+ generate_armor_group_of_type_and_size
from gen.sam.sam_group_generator import get_faction_possible_sams_generator
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.QBudgetBox import QBudgetBox
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.groundobject.QBuildingInfo import QBuildingInfo
-from theater import ControlPoint, TheaterGroundObject
class QGroundObjectMenu(QDialog):
diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py
index 80fbf219..0e0bf773 100644
--- a/qt_ui/windows/mission/flight/QFlightCreator.py
+++ b/qt_ui/windows/mission/flight/QFlightCreator.py
@@ -10,15 +10,17 @@ from PySide2.QtWidgets import (
from dcs.planes import PlaneType
from game import Game
+from game.theater import ControlPoint, OffMapSpawn
from gen.ato import Package
from gen.flights.flight import Flight
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.widgets.QFlightSizeSpinner import QFlightSizeSpinner
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.combos.QAircraftTypeSelector import QAircraftTypeSelector
+from qt_ui.widgets.combos.QArrivalAirfieldSelector import \
+ QArrivalAirfieldSelector
from qt_ui.widgets.combos.QFlightTypeComboBox import QFlightTypeComboBox
from qt_ui.widgets.combos.QOriginAirfieldSelector import QOriginAirfieldSelector
-from theater import ControlPoint, OffMapSpawn
class QFlightCreator(QDialog):
@@ -49,16 +51,30 @@ class QFlightCreator(QDialog):
self.on_aircraft_changed)
layout.addLayout(QLabeledWidget("Aircraft:", self.aircraft_selector))
- self.airfield_selector = QOriginAirfieldSelector(
+ self.departure = QOriginAirfieldSelector(
self.game.aircraft_inventory,
[cp for cp in game.theater.controlpoints if cp.captured],
self.aircraft_selector.currentData()
)
- self.airfield_selector.availability_changed.connect(self.update_max_size)
- layout.addLayout(QLabeledWidget("Airfield:", self.airfield_selector))
+ self.departure.availability_changed.connect(self.update_max_size)
+ layout.addLayout(QLabeledWidget("Departure:", self.departure))
+
+ self.arrival = QArrivalAirfieldSelector(
+ [cp for cp in game.theater.controlpoints if cp.captured],
+ self.aircraft_selector.currentData(),
+ "Same as departure"
+ )
+ layout.addLayout(QLabeledWidget("Arrival:", self.arrival))
+
+ self.divert = QArrivalAirfieldSelector(
+ [cp for cp in game.theater.controlpoints if cp.captured],
+ self.aircraft_selector.currentData(),
+ "None"
+ )
+ layout.addLayout(QLabeledWidget("Divert:", self.divert))
self.flight_size_spinner = QFlightSizeSpinner()
- self.update_max_size(self.airfield_selector.available)
+ self.update_max_size(self.departure.available)
layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner))
self.client_slots_spinner = QFlightSizeSpinner(
@@ -82,10 +98,16 @@ class QFlightCreator(QDialog):
def verify_form(self) -> Optional[str]:
aircraft: PlaneType = self.aircraft_selector.currentData()
- origin: ControlPoint = self.airfield_selector.currentData()
+ origin: ControlPoint = self.departure.currentData()
+ arrival: ControlPoint = self.arrival.currentData()
+ divert: ControlPoint = self.divert.currentData()
size: int = self.flight_size_spinner.value()
if not origin.captured:
return f"{origin.name} is not owned by your coalition."
+ if arrival is not None and not arrival.captured:
+ return f"{arrival.name} is not owned by your coalition."
+ if divert is not None and not divert.captured:
+ return f"{divert.name} is not owned by your coalition."
available = origin.base.aircraft.get(aircraft, 0)
if not available:
return f"{origin.name} has no {aircraft.id} available."
@@ -104,16 +126,22 @@ class QFlightCreator(QDialog):
task = self.task_selector.currentData()
aircraft = self.aircraft_selector.currentData()
- origin = self.airfield_selector.currentData()
+ origin = self.departure.currentData()
+ arrival = self.arrival.currentData()
+ divert = self.divert.currentData()
size = self.flight_size_spinner.value()
+ if arrival is None:
+ arrival = origin
+
if isinstance(origin, OffMapSpawn):
start_type = "In Flight"
elif self.game.settings.perf_ai_parking_start:
start_type = "Cold"
else:
start_type = "Warm"
- flight = Flight(self.package, aircraft, size, origin, task, start_type)
+ flight = Flight(self.package, aircraft, size, task, start_type, origin,
+ arrival, divert)
flight.client_count = self.client_slots_spinner.value()
# noinspection PyUnresolvedReferences
@@ -122,7 +150,9 @@ class QFlightCreator(QDialog):
def on_aircraft_changed(self, index: int) -> None:
new_aircraft = self.aircraft_selector.itemData(index)
- self.airfield_selector.change_aircraft(new_aircraft)
+ self.departure.change_aircraft(new_aircraft)
+ self.arrival.change_aircraft(new_aircraft)
+ self.divert.change_aircraft(new_aircraft)
def update_max_size(self, available: int) -> None:
self.flight_size_spinner.setMaximum(min(available, 4))
diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py
index 09839224..86ce0461 100644
--- a/qt_ui/windows/newgame/QCampaignList.py
+++ b/qt_ui/windows/newgame/QCampaignList.py
@@ -12,7 +12,7 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QListView
import qt_ui.uiconstants as CONST
-from theater import ConflictTheater
+from game.theater import ConflictTheater
@dataclass(frozen=True)
diff --git a/theater/__init__.py b/theater/__init__.py
deleted file mode 100644
index f6b256d8..00000000
--- a/theater/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# For save game compatibility. Remove before 2.3.
-from game.theater import *
diff --git a/theater/base.py b/theater/base.py
deleted file mode 100644
index fc28c91b..00000000
--- a/theater/base.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# For save compat. Remove in 2.3.
-from game.theater.base import *
diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py
deleted file mode 100644
index e1566178..00000000
--- a/theater/conflicttheater.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# For save compat. Remove in 2.3.
-from game.theater.conflicttheater import *
diff --git a/theater/controlpoint.py b/theater/controlpoint.py
deleted file mode 100644
index 90a6b164..00000000
--- a/theater/controlpoint.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# For save compat. Remove in 2.3.
-from game.theater.controlpoint import *
diff --git a/theater/frontline.py b/theater/frontline.py
deleted file mode 100644
index 5ddb5706..00000000
--- a/theater/frontline.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# For save compat. Remove in 2.3.
-from game.theater.frontline import *
-from game.theater.conflicttheater import FrontLine
\ No newline at end of file
diff --git a/theater/theatergroundobject.py b/theater/theatergroundobject.py
deleted file mode 100644
index 3c77455d..00000000
--- a/theater/theatergroundobject.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# For save compat. Remove in 2.3.
-from game.theater.theatergroundobject import *