Merge branch 'develop' into frontline

This commit is contained in:
walterroach 2020-11-20 20:32:50 -06:00
commit edd02d9dd6
51 changed files with 411 additions and 303 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -1,4 +1,4 @@
from theater import ControlPoint
from game.theater import ControlPoint
class FrontlineData:

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
"""Only here to keep compatibility for save games generated in version 2.2.0"""

View File

@ -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]]

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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:]

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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("<b>Parking Slots :</b>"), 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)

View File

@ -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)

View File

@ -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()

View File

@ -2,7 +2,7 @@
from PySide2.QtWidgets import QComboBox
from theater import ConflictTheater, MissionTarget
from game.theater import ConflictTheater, MissionTarget
class QFlightTypeComboBox(QComboBox):

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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: <b>${}M</b>"
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("<b>" + db.unit_type_name_2(unit_type) + "</b>")
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("<b>{}</b>".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("<b>{}</b>".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
self.recruitables_types = recruitables_types

View File

@ -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:

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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)
self.setLayout(main_layout)

View File

@ -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):

View File

@ -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):

View File

@ -1,6 +1,6 @@
from PySide2.QtWidgets import QComboBox
from theater import ControlPoint, CombatStance
from game.theater import CombatStance, ControlPoint
class QGroundForcesStrategySelector(QComboBox):

View File

@ -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):

View File

@ -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):

View File

@ -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))

View File

@ -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)

View File

@ -1,2 +0,0 @@
# For save game compatibility. Remove before 2.3.
from game.theater import *

View File

@ -1,2 +0,0 @@
# For save compat. Remove in 2.3.
from game.theater.base import *

View File

@ -1,2 +0,0 @@
# For save compat. Remove in 2.3.
from game.theater.conflicttheater import *

View File

@ -1,2 +0,0 @@
# For save compat. Remove in 2.3.
from game.theater.controlpoint import *

View File

@ -1,3 +0,0 @@
# For save compat. Remove in 2.3.
from game.theater.frontline import *
from game.theater.conflicttheater import FrontLine

View File

@ -1,2 +0,0 @@
# For save compat. Remove in 2.3.
from game.theater.theatergroundobject import *