From 9121cf7ecb10d815e37b063af80351eaa62e26e4 Mon Sep 17 00:00:00 2001 From: Khopa Date: Mon, 2 Aug 2021 23:51:56 +0200 Subject: [PATCH 01/41] Fixed icons not appearing in UI for Mi-24V, Tu-95 and Tu-142 --- .../aircrafts/icons/{Mi-24_24.jpg => Mi-24V_24.jpg} | Bin .../icons/{Tu-142M_24.jpg => Tu-142_24.jpg} | Bin .../icons/{Tu-95_24.jpg => Tu-95MS_24.jpg} | Bin 3 files changed, 0 insertions(+), 0 deletions(-) rename resources/ui/units/aircrafts/icons/{Mi-24_24.jpg => Mi-24V_24.jpg} (100%) rename resources/ui/units/aircrafts/icons/{Tu-142M_24.jpg => Tu-142_24.jpg} (100%) rename resources/ui/units/aircrafts/icons/{Tu-95_24.jpg => Tu-95MS_24.jpg} (100%) diff --git a/resources/ui/units/aircrafts/icons/Mi-24_24.jpg b/resources/ui/units/aircrafts/icons/Mi-24V_24.jpg similarity index 100% rename from resources/ui/units/aircrafts/icons/Mi-24_24.jpg rename to resources/ui/units/aircrafts/icons/Mi-24V_24.jpg diff --git a/resources/ui/units/aircrafts/icons/Tu-142M_24.jpg b/resources/ui/units/aircrafts/icons/Tu-142_24.jpg similarity index 100% rename from resources/ui/units/aircrafts/icons/Tu-142M_24.jpg rename to resources/ui/units/aircrafts/icons/Tu-142_24.jpg diff --git a/resources/ui/units/aircrafts/icons/Tu-95_24.jpg b/resources/ui/units/aircrafts/icons/Tu-95MS_24.jpg similarity index 100% rename from resources/ui/units/aircrafts/icons/Tu-95_24.jpg rename to resources/ui/units/aircrafts/icons/Tu-95MS_24.jpg From 30801dff9f9561058e021a0e2e467f79ad10690c Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Tue, 3 Aug 2021 12:22:55 +0200 Subject: [PATCH 02/41] Use more sensible patrol speeds for CAP, and fix is_helo (#1492) * Use more sensible patrol speeds for CAP, and fix is_helo --- game/dcs/aircrafttype.py | 46 ++++++++++++++++++++++++++++++++-- gen/aircraft.py | 16 ++++-------- gen/flights/flight.py | 4 +-- gen/flights/flightplan.py | 39 ++++++++++++++++++++++++---- gen/flights/waypointbuilder.py | 3 ++- 5 files changed, 87 insertions(+), 21 deletions(-) diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 39e3b06d..1ce958ca 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -29,7 +29,15 @@ from game.radio.channels import ( ViggenRadioChannelAllocator, NoOpChannelAllocator, ) -from game.utils import Distance, Speed, feet, kph, knots, nautical_miles +from game.utils import ( + Distance, + SPEED_OF_SOUND_AT_SEA_LEVEL, + Speed, + feet, + kph, + knots, + nautical_miles, +) if TYPE_CHECKING: from gen.aircraft import FlightData @@ -182,7 +190,7 @@ class AircraftType(UnitType[Type[FlyingType]]): @property def preferred_patrol_altitude(self) -> Distance: - if self.patrol_altitude: + if self.patrol_altitude is not None: return self.patrol_altitude else: # Estimate based on max speed. @@ -208,6 +216,40 @@ class AircraftType(UnitType[Type[FlyingType]]): min(altitude_for_highest_speed, rounded_altitude), ) + def preferred_patrol_speed(self, altitude: Distance) -> Speed: + """Preferred true airspeed when patrolling""" + if self.patrol_speed is not None: + return self.patrol_speed + else: + # Estimate based on max speed. + max_speed = self.max_speed + if max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 1.6: + # Fast airplanes, should manage pretty high patrol speed + return ( + Speed.from_mach(0.85, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.7, altitude) + ) + elif max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 1.2: + # Medium-fast like F/A-18C + return ( + Speed.from_mach(0.8, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.65, altitude) + ) + elif max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL * 0.7: + # Semi-fast like airliners or similar + return ( + Speed.from_mach(0.5, altitude) + if altitude.feet > 20000 + else Speed.from_mach(0.4, altitude) + ) + else: + # Slow like warbirds or helicopters + # Use whichever is slowest - mach 0.35 or 70% of max speed + logging.debug(f"{self.name} max_speed * 0.7 is {max_speed * 0.7}") + return min(Speed.from_mach(0.35, altitude), max_speed * 0.7) + def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency: from gen.radios import ChannelInUseError, kHz diff --git a/gen/aircraft.py b/gen/aircraft.py index 42a5ccac..d42e52f2 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1775,17 +1775,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder): ) ) - # TODO: Set orbit speeds for all race tracks and remove this special case. - if isinstance(flight_plan, RefuelingFlightPlan): - orbit = OrbitAction( - altitude=waypoint.alt, - pattern=OrbitAction.OrbitPattern.RaceTrack, - speed=int(flight_plan.patrol_speed.kph), - ) - else: - orbit = OrbitAction( - altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.RaceTrack - ) + orbit = OrbitAction( + altitude=waypoint.alt, + pattern=OrbitAction.OrbitPattern.RaceTrack, + speed=int(flight_plan.patrol_speed.kph), + ) racetrack = ControlledTask(orbit) self.set_waypoint_tot(waypoint, flight_plan.patrol_start_time) diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 5c3241f7..9bb0c9bc 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -141,8 +141,8 @@ class FlightWaypoint: waypoint_type: The waypoint type. x: X coordinate of the waypoint. y: Y coordinate of the waypoint. - alt: Altitude of the waypoint. By default this is AGL, but it can be - changed to MSL by setting alt_type to "RADIO". + alt: Altitude of the waypoint. By default this is MSL, but it can be + changed to AGL by setting alt_type to "RADIO" """ self.waypoint_type = waypoint_type self.x = x diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 463be24d..408eb5fd 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -411,6 +411,9 @@ class PatrollingFlightPlan(FlightPlan): #: Maximum time to remain on station. patrol_duration: timedelta + #: Racetrack speed TAS. + patrol_speed: Speed + #: The engagement range of any Search Then Engage task, or the radius of a #: Search Then Engage in Zone task. Any enemies of the appropriate type for #: this mission within this range of the flight's current position (or the @@ -779,9 +782,6 @@ class RefuelingFlightPlan(PatrollingFlightPlan): divert: Optional[FlightWaypoint] bullseye: FlightWaypoint - #: Racetrack speed. - patrol_speed: Speed - def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff yield from self.nav_to @@ -1124,6 +1124,11 @@ class FlightPlanBuilder: min(self.doctrine.max_patrol_altitude, randomized_alt), ) + patrol_speed = flight.unit_type.preferred_patrol_speed(patrol_alt) + logging.debug( + f"BARCAP patrol speed for {flight.unit_type.name} at {patrol_alt.feet}ft: {patrol_speed.knots} KTAS" + ) + builder = WaypointBuilder(flight, self.coalition) start, end = builder.race_track(start_pos, end_pos, patrol_alt) @@ -1131,6 +1136,7 @@ class FlightPlanBuilder: package=self.package, flight=flight, patrol_duration=self.doctrine.cap_duration, + patrol_speed=patrol_speed, engagement_distance=self.doctrine.cap_engagement_range, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path( @@ -1362,6 +1368,10 @@ class FlightPlanBuilder: self.doctrine.min_patrol_altitude, min(self.doctrine.max_patrol_altitude, randomized_alt), ) + patrol_speed = flight.unit_type.preferred_patrol_speed(patrol_alt) + logging.debug( + f"TARCAP patrol speed for {flight.unit_type.name} at {patrol_alt.feet}ft: {patrol_speed.knots} KTAS" + ) # Create points builder = WaypointBuilder(flight, self.coalition) @@ -1383,6 +1393,7 @@ 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, + patrol_speed=patrol_speed, engagement_distance=self.doctrine.cap_engagement_range, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path(flight.departure.position, orbit0p, patrol_alt), @@ -1546,16 +1557,33 @@ class FlightPlanBuilder: builder = WaypointBuilder(flight, self.coalition) + # 2021-08-02: patrol_speed will currently have no effect because + # CAS doesn't use OrbitAction. But all PatrollingFlightPlan are expected + # to have patrol_speed + is_helo = flight.unit_type.dcs_unit_type.helicopter + ingress_egress_altitude = ( + self.doctrine.ingress_altitude if not is_helo else meters(50) + ) + patrol_speed = flight.unit_type.preferred_patrol_speed(ingress_egress_altitude) + use_agl_ingress_egress = is_helo + return CasFlightPlan( package=self.package, flight=flight, patrol_duration=self.doctrine.cas_duration, + patrol_speed=patrol_speed, takeoff=builder.takeoff(flight.departure), nav_to=builder.nav_path( - flight.departure.position, ingress, self.doctrine.ingress_altitude + flight.departure.position, + ingress, + ingress_egress_altitude, + use_agl_ingress_egress, ), nav_from=builder.nav_path( - egress, flight.arrival.position, self.doctrine.ingress_altitude + egress, + flight.arrival.position, + ingress_egress_altitude, + use_agl_ingress_egress, ), patrol_start=builder.ingress( FlightWaypointType.INGRESS_CAS, ingress, location @@ -1608,6 +1636,7 @@ class FlightPlanBuilder: else: altitude = feet(21000) + # TODO: Could use flight.unit_type.preferred_patrol_speed(altitude) instead. if tanker_type.patrol_speed is not None: speed = tanker_type.patrol_speed else: diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 05ca1d93..37e23bb4 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -1,4 +1,5 @@ from __future__ import annotations +import logging import random from dataclasses import dataclass @@ -55,7 +56,7 @@ class WaypointBuilder: @property def is_helo(self) -> bool: - return getattr(self.flight.unit_type, "helicopter", False) + return self.flight.unit_type.dcs_unit_type.helicopter def takeoff(self, departure: ControlPoint) -> FlightWaypoint: """Create takeoff waypoint for the given arrival airfield or carrier. From 8608b73009bbb752c9d7e9b7cf8977bdd4591d37 Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Mon, 2 Aug 2021 16:24:43 +0900 Subject: [PATCH 03/41] Wrap lines for NotesPage --- gen/kneeboard.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/gen/kneeboard.py b/gen/kneeboard.py index a2be2ef6..aa9cfb94 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -65,7 +65,8 @@ class KneeboardPageWriter: else: self.foreground_fill = (15, 15, 15) self.background_fill = (255, 252, 252) - self.image = Image.new("RGB", (768, 1024), self.background_fill) + self.image_size = (768, 1024) + self.image = Image.new("RGB", self.image_size, self.background_fill) # These font sizes create a relatively full page for current sorties. If # we start generating more complicated flight plans, or start including # more information in the comm ladder (the latter of which we should @@ -84,6 +85,7 @@ class KneeboardPageWriter: "resources/fonts/Inconsolata.otf", 20, layout_engine=ImageFont.LAYOUT_BASIC ) self.draw = ImageDraw.Draw(self.image) + self.page_margin = page_margin self.x = page_margin self.y = page_margin self.line_spacing = line_spacing @@ -97,12 +99,21 @@ class KneeboardPageWriter: text: str, font: Optional[ImageFont.FreeTypeFont] = None, fill: Optional[Tuple[int, int, int]] = None, + wrap: bool = False, ) -> None: if font is None: font = self.content_font if fill is None: fill = self.foreground_fill + if wrap: + text = "\n".join( + self.wrap_line_with_font( + line, self.image_size[0] - self.page_margin - self.x, font + ) + for line in text.splitlines() + ) + self.draw.text(self.position, text, font=font, fill=fill) width, height = self.draw.textsize(text, font=font) self.y += height + self.line_spacing @@ -146,6 +157,24 @@ class KneeboardPageWriter: output = combo return "".join(segments + [output]).strip() + @staticmethod + def wrap_line_with_font( + inputstr: str, max_width: int, font: ImageFont.FreeTypeFont + ) -> str: + if font.getsize(inputstr)[0] <= max_width: + return inputstr + tokens = inputstr.split(" ") + output = "" + segments = [] + for token in tokens: + combo = output + " " + token + if font.getsize(combo)[0] > max_width: + segments.append(output + "\n") + output = token + else: + output = combo + return "".join(segments + [output]).strip() + class KneeboardPage: """Base class for all kneeboard pages.""" @@ -631,7 +660,7 @@ class NotesPage(KneeboardPage): def write(self, path: Path) -> None: writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard) writer.title(f"Notes") - writer.text(self.notes) + writer.text(self.notes, wrap=True) writer.write(path) From c58ecd96f0dc46763fcf5ea699f5303b45b5dec0 Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Wed, 4 Aug 2021 23:44:48 +0900 Subject: [PATCH 04/41] Make Radio dataclass store list of ranges --- gen/radios.py | 102 +++++++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 38 deletions(-) diff --git a/gen/radios.py b/gen/radios.py index ced4ac9c..ae464622 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -2,7 +2,7 @@ import itertools import logging from dataclasses import dataclass -from typing import Dict, Iterator, List, Set +from typing import Dict, FrozenSet, Iterator, List, Reversible, Set, Tuple @dataclass(frozen=True) @@ -45,14 +45,8 @@ def kHz(num: int) -> RadioFrequency: @dataclass(frozen=True) -class Radio: - """A radio. - - Defines the minimum (inclusive) and maximum (exclusive) range of the radio. - """ - - #: The name of the radio. - name: str +class RadioRange: + """Defines the minimum (inclusive) and maximum (exclusive) range of the radio.""" #: The minimum (inclusive) frequency tunable by this radio. minimum: RadioFrequency @@ -63,19 +57,51 @@ class Radio: #: The spacing between adjacent frequencies. step: RadioFrequency - def __str__(self) -> str: - return self.name + #: Specific frequencies to exclude. (e.g. Guard channels) + excludes: FrozenSet[RadioFrequency] = frozenset() def range(self) -> Iterator[RadioFrequency]: """Returns an iterator over the usable frequencies of this radio.""" return ( RadioFrequency(x) for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz) + if RadioFrequency(x) not in self.excludes ) @property def last_channel(self) -> RadioFrequency: - return RadioFrequency(self.maximum.hertz - self.step.hertz) + return next( + RadioFrequency(x) + for x in reversed( + range(self.minimum.hertz, self.maximum.hertz, self.step.hertz) + ) + if RadioFrequency(x) not in self.excludes + ) + + +@dataclass(frozen=True) +class Radio: + """A radio. + + Defines ranges of usable frequencies of the radio. + """ + + #: The name of the radio. + name: str + + #: List of usable frequency range of this radio. + ranges: Tuple[RadioRange, ...] + + def __str__(self) -> str: + return self.name + + def range(self) -> Iterator[RadioFrequency]: + """Returns an iterator over the usable frequencies of this radio.""" + return itertools.chain.from_iterable(rng.range() for rng in self.ranges) + + @property + def last_channel(self) -> RadioFrequency: + return self.ranges[-1].last_channel class ChannelInUseError(RuntimeError): @@ -88,53 +114,53 @@ class ChannelInUseError(RuntimeError): # TODO: Figure out appropriate steps for each radio. These are just guesses. #: List of all known radios used by aircraft in the game. RADIOS: List[Radio] = [ - Radio("AN/ARC-164", MHz(225), MHz(400), step=MHz(1)), - Radio("AN/ARC-186(V) AM", MHz(116), MHz(152), step=MHz(1)), - Radio("AN/ARC-186(V) FM", MHz(30), MHz(76), step=MHz(1)), + Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), + Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), step=MHz(1)),)), + Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), step=MHz(1)),)), # The AN/ARC-210 can also use [30, 88) and [108, 118), but the current # implementation can't implement the gap and the radio can't transmit on the # latter. There's still plenty of channels between 118 MHz and 400 MHz, so # not worth worrying about. - Radio("AN/ARC-210", MHz(118), MHz(400), step=MHz(1)), - Radio("AN/ARC-222", MHz(116), MHz(174), step=MHz(1)), - Radio("SCR-522", MHz(100), MHz(156), step=MHz(1)), - Radio("A.R.I. 1063", MHz(100), MHz(156), step=MHz(1)), - Radio("BC-1206", kHz(200), kHz(400), step=kHz(10)), + Radio("AN/ARC-210", (RadioRange(MHz(118), MHz(400), step=MHz(1)),)), + Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(174), step=MHz(1)),)), + Radio("SCR-522", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), + Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), + Radio("BC-1206", (RadioRange(kHz(200), kHz(400), step=kHz(10)),)), # Note: The M2000C V/UHF can operate in both ranges, but has a gap between # 150 MHz and 225 MHz. We can't allocate in that gap, and the current # system doesn't model gaps, so just pretend it ends at 150 MHz for now. We # can model gaps later if needed. - Radio("TRT ERA 7000 V/UHF", MHz(118), MHz(150), step=MHz(1)), - Radio("TRT ERA 7200 UHF", MHz(225), MHz(400), step=MHz(1)), + Radio("TRT ERA 7000 V/UHF", (RadioRange(MHz(118), MHz(150), step=MHz(1)),)), + Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # Tomcat radios # # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio - Radio("AN/ARC-159", MHz(225), MHz(400), step=MHz(1)), + Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # AN/ARC-182 can also operate from 30 MHz to 88 MHz, as well as from 225 MHz # to 400 MHz range, but we can't model gaps with the current implementation. # https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio - Radio("AN/ARC-182", MHz(108), MHz(174), step=MHz(1)), + Radio("AN/ARC-182", (RadioRange(MHz(108), MHz(174), step=MHz(1)),)), # Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps. - Radio("FR 22", MHz(225), MHz(400), step=kHz(50)), + Radio("FR 22", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)), # P-51 / P-47 Radio # 4 preset channels (A/B/C/D) - Radio("SCR522", MHz(100), MHz(156), step=kHz(25)), - Radio("R&S M3AR VHF", MHz(120), MHz(174), step=MHz(1)), - Radio("R&S M3AR UHF", MHz(225), MHz(400), step=MHz(1)), + Radio("SCR522", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)), + Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), step=MHz(1)),)), + Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), # MiG-15bis - Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)), + Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), step=kHz(25)),)), # MiG-19P - Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)), + Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), step=MHz(1)),)), # MiG-21bis - Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)), + Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), step=MHz(1)),)), # Ka-50 # Note: Also capable of 100MHz-150MHz, but we can't model gaps. - Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)), - Radio("R-828", MHz(20), MHz(60), step=kHz(25)), + Radio("R-800L1", (RadioRange(MHz(220), MHz(400), step=kHz(25)),)), + Radio("R-828", (RadioRange(MHz(20), MHz(60), step=kHz(25)),)), # UH-1H - Radio("AN/ARC-51BX", MHz(225), MHz(400), step=kHz(50)), - Radio("AN/ARC-131", MHz(30), MHz(76), step=kHz(50)), - Radio("AN/ARC-134", MHz(116), MHz(150), step=kHz(25)), - Radio("R&S Series 6000", MHz(100), MHz(156), step=kHz(25)), + Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)), + Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), step=kHz(50)),)), + Radio("AN/ARC-134", (RadioRange(MHz(116), MHz(150), step=kHz(25)),)), + Radio("R&S Series 6000", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)), ] @@ -175,7 +201,7 @@ class RadioRegistry: # Not a real radio, but useful for allocating a channel usable for # inter-flight communications. - BLUFOR_UHF = Radio("BLUFOR UHF", MHz(225), MHz(400), step=MHz(1)) + BLUFOR_UHF = Radio("BLUFOR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)) def __init__(self) -> None: self.allocated_channels: Set[RadioFrequency] = set() From aaa932f725a7b8ca0e6b9b1e65b0c1bbd867eefa Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Wed, 4 Aug 2021 23:54:16 +0900 Subject: [PATCH 05/41] Update freq list for ARC-210 --- gen/radios.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/gen/radios.py b/gen/radios.py index ae464622..6cffb372 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -117,11 +117,16 @@ RADIOS: List[Radio] = [ Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)), Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), step=MHz(1)),)), Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), step=MHz(1)),)), - # The AN/ARC-210 can also use [30, 88) and [108, 118), but the current - # implementation can't implement the gap and the radio can't transmit on the - # latter. There's still plenty of channels between 118 MHz and 400 MHz, so - # not worth worrying about. - Radio("AN/ARC-210", (RadioRange(MHz(118), MHz(400), step=MHz(1)),)), + Radio( + "AN/ARC-210", + ( + RadioRange(MHz(225), MHz(400), MHz(1), frozenset((MHz(243),))), + RadioRange(MHz(136), MHz(155), MHz(1)), + RadioRange(MHz(156), MHz(174), MHz(1)), + RadioRange(MHz(118), MHz(136), MHz(1)), + RadioRange(MHz(30), MHz(88), MHz(1)), + ), + ), Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(174), step=MHz(1)),)), Radio("SCR-522", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)), From ff571db494bdd6eec6fdb47d83e119f5365731cb Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 4 Aug 2021 23:33:22 -0700 Subject: [PATCH 06/41] Update Syria Full and Humble Helper campaigns. https://github.com/dcs-liberation/dcs_liberation/issues/1494 https://github.com/dcs-liberation/dcs_liberation/issues/1497 --- resources/campaigns/humble_helper.json | 2 +- resources/campaigns/humble_helper.miz | Bin 21801 -> 22471 bytes resources/campaigns/syria_full_map.json | 2 +- resources/campaigns/syria_full_map.miz | Bin 82386 -> 82455 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/campaigns/humble_helper.json b/resources/campaigns/humble_helper.json index e89a183e..37cfaed9 100644 --- a/resources/campaigns/humble_helper.json +++ b/resources/campaigns/humble_helper.json @@ -7,5 +7,5 @@ "description": "

In this scenario, you start in Israel in an high intensity conflict with Syria, backed by a Russian Expeditiary Force. Your goal is to take the heavily fortified city of Damascus, as fast as you can. The longer you wait, the more resources the enemy can pump into the defense of the city or even might try to take chunks of Israel. ATTENTION: CAMPAIGN INVERTING IS NOT YET IMPLEMENTED!!! Feedback: @Headiii in the DCSLiberation Discord

", "miz": "humble_helper.miz", "performance": 1, - "version": "7.0" + "version": "8.0" } diff --git a/resources/campaigns/humble_helper.miz b/resources/campaigns/humble_helper.miz index 8053703a1262f7b694a74f500c99f1c6250c93ed..1c71133ecfe4369f807b6446cf4d20cc6c0c4bf0 100644 GIT binary patch literal 22471 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 QfpfUGCC)0ExFG=GKjD68k^lez literal 21801 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 diff --git a/resources/campaigns/syria_full_map.json b/resources/campaigns/syria_full_map.json index f0393d24..d33b26d9 100644 --- a/resources/campaigns/syria_full_map.json +++ b/resources/campaigns/syria_full_map.json @@ -7,5 +7,5 @@ "description": "

A long campaign across the syria map, strap in.

Each turn after planning missions, enable culling to ensure correct culling behaviour.

", "miz": "syria_full_map.miz", "performance": 3, - "version": "7.0" + "version": "8.0" } \ No newline at end of file diff --git a/resources/campaigns/syria_full_map.miz b/resources/campaigns/syria_full_map.miz index f36953aba355472172651e31b1fe5ce56ef20a9e..fd9aad70e1af229830b4895a10914dd3ad4ad49f 100644 GIT binary patch literal 82455 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 Rr2_`^H3DTN7t{p?_FvYv^CbWP literal 82386 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�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�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 From 8488a5ec1ae1e8c5370a04a5074d7d2b345ca150 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Thu, 5 Aug 2021 13:52:33 +0200 Subject: [PATCH 07/41] Use AGL altitude for helo hold (#1499) --- gen/flights/waypointbuilder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 37e23bb4..15adde01 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -168,6 +168,8 @@ class WaypointBuilder: position.y, meters(500) if self.is_helo else self.doctrine.rendezvous_altitude, ) + if self.is_helo: + waypoint.alt_type = "RADIO" waypoint.pretty_name = "Hold" waypoint.description = "Wait until push time" waypoint.name = "HOLD" From 9792c17c698e656b30abda5bdf8403cabca1475a Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Thu, 5 Aug 2021 13:54:42 +0200 Subject: [PATCH 08/41] Make arid theaters more likely to have clouds, and tweak others sliiightly (#1501) --- game/theater/seasonalconditions/caucasus.py | 4 ++-- game/theater/seasonalconditions/nevada.py | 4 ++-- game/theater/seasonalconditions/normandy.py | 4 ++-- game/theater/seasonalconditions/persiangulf.py | 16 ++++++++-------- game/theater/seasonalconditions/syria.py | 16 ++++++++-------- game/theater/seasonalconditions/thechannel.py | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/game/theater/seasonalconditions/caucasus.py b/game/theater/seasonalconditions/caucasus.py index e605a543..2fe87861 100644 --- a/game/theater/seasonalconditions/caucasus.py +++ b/game/theater/seasonalconditions/caucasus.py @@ -23,8 +23,8 @@ CONDITIONS = SeasonalConditions( Season.Summer: WeatherTypeChances( thunderstorm=1, raining=10, - cloudy=30, - clear_skies=60, + cloudy=35, + clear_skies=55, ), Season.Fall: WeatherTypeChances( thunderstorm=1, diff --git a/game/theater/seasonalconditions/nevada.py b/game/theater/seasonalconditions/nevada.py index 352ca456..36b7934d 100644 --- a/game/theater/seasonalconditions/nevada.py +++ b/game/theater/seasonalconditions/nevada.py @@ -23,8 +23,8 @@ CONDITIONS = SeasonalConditions( Season.Summer: WeatherTypeChances( thunderstorm=1, raining=5, - cloudy=25, - clear_skies=70, + cloudy=30, + clear_skies=65, ), Season.Fall: WeatherTypeChances( thunderstorm=1, diff --git a/game/theater/seasonalconditions/normandy.py b/game/theater/seasonalconditions/normandy.py index 109c781f..a0f86b6d 100644 --- a/game/theater/seasonalconditions/normandy.py +++ b/game/theater/seasonalconditions/normandy.py @@ -23,8 +23,8 @@ CONDITIONS = SeasonalConditions( Season.Summer: WeatherTypeChances( thunderstorm=1, raining=10, - cloudy=30, - clear_skies=60, + cloudy=35, + clear_skies=55, ), Season.Fall: WeatherTypeChances( thunderstorm=1, diff --git a/game/theater/seasonalconditions/persiangulf.py b/game/theater/seasonalconditions/persiangulf.py index 467168ab..923b887d 100644 --- a/game/theater/seasonalconditions/persiangulf.py +++ b/game/theater/seasonalconditions/persiangulf.py @@ -12,26 +12,26 @@ CONDITIONS = SeasonalConditions( # Winter there is some rain in PG (Dubai) thunderstorm=1, raining=15, - cloudy=35, - clear_skies=50, + cloudy=40, + clear_skies=45, ), Season.Spring: WeatherTypeChances( thunderstorm=1, raining=2, - cloudy=18, - clear_skies=80, + cloudy=28, + clear_skies=70, ), Season.Summer: WeatherTypeChances( thunderstorm=1, raining=1, - cloudy=8, - clear_skies=90, + cloudy=18, + clear_skies=80, ), Season.Fall: WeatherTypeChances( thunderstorm=1, raining=2, - cloudy=18, - clear_skies=80, + cloudy=28, + clear_skies=70, ), }, ) diff --git a/game/theater/seasonalconditions/syria.py b/game/theater/seasonalconditions/syria.py index 0a6c7ec1..d405abc8 100644 --- a/game/theater/seasonalconditions/syria.py +++ b/game/theater/seasonalconditions/syria.py @@ -11,8 +11,8 @@ CONDITIONS = SeasonalConditions( Season.Winter: WeatherTypeChances( thunderstorm=1, raining=25, - cloudy=25, - clear_skies=50, + cloudy=35, + clear_skies=40, ), Season.Spring: WeatherTypeChances( thunderstorm=1, @@ -22,15 +22,15 @@ CONDITIONS = SeasonalConditions( ), Season.Summer: WeatherTypeChances( thunderstorm=1, - raining=3, - cloudy=20, - clear_skies=77, + raining=5, + cloudy=30, + clear_skies=65, ), Season.Fall: WeatherTypeChances( thunderstorm=1, - raining=10, - cloudy=30, - clear_skies=60, + raining=15, + cloudy=35, + clear_skies=50, ), }, ) diff --git a/game/theater/seasonalconditions/thechannel.py b/game/theater/seasonalconditions/thechannel.py index 109c781f..a0f86b6d 100644 --- a/game/theater/seasonalconditions/thechannel.py +++ b/game/theater/seasonalconditions/thechannel.py @@ -23,8 +23,8 @@ CONDITIONS = SeasonalConditions( Season.Summer: WeatherTypeChances( thunderstorm=1, raining=10, - cloudy=30, - clear_skies=60, + cloudy=35, + clear_skies=55, ), Season.Fall: WeatherTypeChances( thunderstorm=1, From b67fd16081e78caf3a093d68bb9887c697c78842 Mon Sep 17 00:00:00 2001 From: RndName Date: Wed, 4 Aug 2021 21:07:11 +0200 Subject: [PATCH 09/41] tweak the airlift procurement - only buy airlift capable aircraft if there is one friendly cp without a factory which can only be reached via airlift - prevent that an airlift procurement gets fulfilled at a different cp than the requesting one. this ensures that the cp also has a factory to produce ground units which can then be transported - fixes an infinite buy loop if the fulfilling cp has no factory and the requesting cp has no space for airlift - have always 2 reserve transport planes at the biggest CP --- changelog.md | 1 + game/transfers.py | 53 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index f1486a61..8022fc43 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI. * **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points. * **[Campaign AI]** Auto-planning mission range limits are now specified per-aircraft. On average this means that longer range missions will now be plannable. The limit only accounts for the direct distance to the target, not the path taken. +* **[Campaign AI]** Transport aircraft will now be bought only if necessary at control points which can produce ground units and are capable to operate transport aircraft. * **[Campaign AI]** Aircraft will now only be automatically purchased or assigned at appropriate bases. Naval aircraft will default to only operating from carriers, Harriers will default to LHAs and shore bases, helicopters will operate from anywhere. This can be customized per-squadron. * **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet). * **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons. diff --git a/game/transfers.py b/game/transfers.py index ffb879e4..643bcacb 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -722,9 +722,39 @@ class PendingTransfers: ): self.order_airlift_assets_at(control_point) - @staticmethod - def desired_airlift_capacity(control_point: ControlPoint) -> int: - return 4 if control_point.has_factory else 0 + def desired_airlift_capacity(self, control_point: ControlPoint) -> int: + + if control_point.has_factory: + is_major_hub = control_point.total_aircraft_parking > 0 + # Check if there is a CP which is only reachable via Airlift + transit_network = self.network_for(control_point) + for cp in self.game.theater.control_points_for(self.player): + # check if the CP has no factory, is reachable from the current + # position and can only be reached with airlift connections + if ( + cp.can_deploy_ground_units + and not cp.has_factory + and transit_network.has_link(control_point, cp) + and not any( + link_type + for link, link_type in transit_network.nodes[cp].items() + if not link_type == TransitConnection.Airlift + ) + ): + return 4 + + if ( + is_major_hub + and cp.has_factory + and cp.total_aircraft_parking > control_point.total_aircraft_parking + ): + is_major_hub = False + + if is_major_hub: + # If the current CP is a major hub keep always 2 planes on reserve + return 2 + + return 0 def current_airlift_capacity(self, control_point: ControlPoint) -> int: inventory = self.game.aircraft_inventory.for_control_point(control_point) @@ -739,9 +769,16 @@ class PendingTransfers: ) def order_airlift_assets_at(self, control_point: ControlPoint) -> None: - gap = self.desired_airlift_capacity( - control_point - ) - self.current_airlift_capacity(control_point) + unclaimed_parking = control_point.unclaimed_parking(self.game) + # Buy a maximum of unclaimed_parking only to prevent that aircraft procurement + # take place at another base + gap = min( + [ + self.desired_airlift_capacity(control_point) + - self.current_airlift_capacity(control_point), + unclaimed_parking, + ] + ) if gap <= 0: return @@ -751,6 +788,10 @@ class PendingTransfers: # aesthetic. gap += 1 + if gap > unclaimed_parking: + # Prevent to buy more aircraft than possible + return + self.game.coalition_for(self.player).add_procurement_request( AircraftProcurementRequest(control_point, FlightType.TRANSPORT, gap) ) From 07f8a203ea6366efe9746a773723810de13683d5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 5 Aug 2021 19:32:52 -0700 Subject: [PATCH 10/41] Remove abandoned campaigns. --- resources/campaigns/Operation_Atilla.json | 11 ----------- resources/campaigns/Operation_Atilla.miz | Bin 113912 -> 0 bytes .../campaigns/Russian_Intervention_2015.json | 11 ----------- .../campaigns/Russian_Intervention_2015.miz | Bin 62497 -> 0 bytes resources/campaigns/guam.json | 11 ----------- resources/campaigns/guam.miz | Bin 16612 -> 0 bytes 6 files changed, 33 deletions(-) delete mode 100644 resources/campaigns/Operation_Atilla.json delete mode 100644 resources/campaigns/Operation_Atilla.miz delete mode 100644 resources/campaigns/Russian_Intervention_2015.json delete mode 100644 resources/campaigns/Russian_Intervention_2015.miz delete mode 100644 resources/campaigns/guam.json delete mode 100644 resources/campaigns/guam.miz diff --git a/resources/campaigns/Operation_Atilla.json b/resources/campaigns/Operation_Atilla.json deleted file mode 100644 index 313f178e..00000000 --- a/resources/campaigns/Operation_Atilla.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Syria - Operation Atilla", - "theater": "Syria", - "authors": "Malakhit", - "recommended_player_faction": "Turkey 2005", - "recommended_enemy_faction": "Greece 2005", - "description": "

This is based on the Turkish invasion of Cyprus, and the Greek defense of the island. You must make sure to keep your beachhead at all costs, otherwise reclaiming it will be tricky. It is recommended to reduce the per-turn income rate for both factions in this scenario due to the large oil depots both sides have - setting it to around 15-20% is probably reasonable.

", - "version": "7.0", - "miz": "Operation_Atilla.miz", - "performance": 2 -} \ No newline at end of file diff --git a/resources/campaigns/Operation_Atilla.miz b/resources/campaigns/Operation_Atilla.miz deleted file mode 100644 index e68aa41849791ceda980159a76388eca0015f4f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113912 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 b@|_EE(o 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 A?*IS* diff --git a/resources/campaigns/Russian_Intervention_2015.json b/resources/campaigns/Russian_Intervention_2015.json deleted file mode 100644 index fd25fd3c..00000000 --- a/resources/campaigns/Russian_Intervention_2015.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Syria - Russian Intervention 2015", - "theater": "Syria", - "authors": "Malakhit", - "recommended_player_faction": "Russia 2010", - "recommended_enemy_faction": "Insurgents (Hard)", - "description": "

This short campaign is loosely based on the 2015 Russian military intervention in Syria. It should be perfect for COIN operations, especially with the Hind. Not designed for campaign inversion.

", - "version": "7.0", - "miz": "Russian_Intervention_2015.miz", - "performance": 1 -} \ No newline at end of file diff --git a/resources/campaigns/Russian_Intervention_2015.miz b/resources/campaigns/Russian_Intervention_2015.miz deleted file mode 100644 index e1797983a0afb78772d475f00174644b0f5965cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62497 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

cK?^LOH4~<_qT`SiPUDuA7tt0LpM#$YOX9 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πjD91Xb$K6-7d@>5>n`g?0L7^KJ=ev(MFBn62%* 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 USA, repel a Chinese invasion of Guam Island.

", - "miz": "guam.miz", - "performance": 1, - "version": "7.0" -} diff --git a/resources/campaigns/guam.miz b/resources/campaigns/guam.miz deleted file mode 100644 index b40ec2463a0cba476314ef09f005556d2e995167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16612 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 Z?-o~({sQ^;0^}!*K3m25^AZTi{{iC%4h8@K From 71559154a821573199f6a7fef17bafea2e43ffd9 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Fri, 6 Aug 2021 18:01:24 +0200 Subject: [PATCH 11/41] Cherry-pick cap/helo changelog from 4.x (#1512) * Changelog updates for 4.x. Regarding patrol speeds and helo fix. * Update changelog for 4.x BAI missions are actually planned at low altitude. The problem remaining is that they have join/hold/split waypoints, which makes the flight times _incredibly_ long for these slow movers. --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 8022fc43..6be34a99 100644 --- a/changelog.md +++ b/changelog.md @@ -30,6 +30,7 @@ Saves from 4.0.0 are compatible with 4.1.0. * **[Campaign AI]** Adjustments to aircraft selection priorities for most mission types. * **[Engine]** Support for DCS 2.7.4.9632 and newer, including the Marianas map, F-16 JSOWs, NASAMS, and Tin Shield EWR. * **[Flight Planning]** CAP patrol altitudes are now set per-aircraft. By default the altitude will be set based on the aircraft's maximum speed. +* **[Flight Planning]** CAP patrol speeds are now set per-aircraft to be more suitable/sensible. By default the speed will be set based on the aircraft's maximum speed. * **[Mission Generation]** Improvements for better support of the Skynet Plugin and long range SAMs are now acting as EWR * **[Mission Generation]** SAM sites are now headed towards the center of the conflict * **[Mods]** Support for latest version of Gripen mod. In-progress campaigns may need to re-plan Gripen flights to pick up updated loadouts. @@ -49,6 +50,7 @@ Saves from 4.0.0 are compatible with 4.1.0. * **[Data]** Fixed Introduction dates for targeting pods (ATFLIR and LITENING were both a few years too early). * **[Data]** Removed SA-10 from Syria 2011 faction. * **[Economy]** EWRs can now be bought and sold for the correct price and can no longer be used to generate money +* **[Flight Planning]** Helicopters are now correctly identified, and will fly ingress/CAS/BAI/egress and similar at low altitude. * **[Flight Planning]** Fixed potential issue with angles > 360° or < 0° being generated when summing two angles. * **[Mission Generation]** The lua data for other plugins is now generated correctly * **[Mission Generation]** Fixed problem with opfor planning missions against sold ground objects like SAMs From fdbc3c55c7a882a2e3b0a5c36219479e07664399 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Fri, 6 Aug 2021 18:17:46 +0200 Subject: [PATCH 12/41] Add weather changes to changelog (#1513) Co-authored-by: Magnus Wolffelt --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 6be34a99..bc23f7b6 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,8 @@ Saves from 4.x are not compatible with 5.0. ## Features/Improvements + +* **[Campaign]** Weather! Theaters now experience weather that is more realistic for the region and its current season. For example, Persian Gulf will have very hot, sunny summers and Marianas will experience lots of rain during fall. These changes affect pressure, temperature, clouds and precipitation. Additionally, temperature will drop during the night, by an amount that is somewhat realistic for the region. * **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated. * **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts. * **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions. @@ -13,6 +15,7 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign AI]** Transport aircraft will now be bought only if necessary at control points which can produce ground units and are capable to operate transport aircraft. * **[Campaign AI]** Aircraft will now only be automatically purchased or assigned at appropriate bases. Naval aircraft will default to only operating from carriers, Harriers will default to LHAs and shore bases, helicopters will operate from anywhere. This can be customized per-squadron. * **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet). +* **[Kneeboard]** QNH (pressure MSL) and temperature have been added to the kneeboard. * **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons. ## Fixes From 85ccbf34c79cefc3b4f9cb9b4f340e8e9b51bb69 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 6 Aug 2021 19:24:07 -0700 Subject: [PATCH 13/41] Add fuel esimtations for the Viper. --- resources/units/aircraft/F-16C_50.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/resources/units/aircraft/F-16C_50.yaml b/resources/units/aircraft/F-16C_50.yaml index adefe831..441acf94 100644 --- a/resources/units/aircraft/F-16C_50.yaml +++ b/resources/units/aircraft/F-16C_50.yaml @@ -28,6 +28,16 @@ origin: USA price: 22 role: Multirole Fighter max_range: 200 +fuel: + # Parking 44 to RWY 06L at Anderson AFB. + taxi: 200 + # AB takeoff to 350/0.75, reduce to MIL and maintain 350/0.75 to 25k ft. + climb_ppm: 28.33 + # 0.85 mach for 100NM. + cruise_ppm: 12 + # MIL for 100NM. Occasional AB use. + combat_ppm: 26 + min_safe: 1000 variants: F-16CM Fighting Falcon (Block 50): {} F-2A: {} From 72d83e2fe40234423ceb719714108c8b88736fb9 Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Sat, 7 Aug 2021 04:33:49 +0900 Subject: [PATCH 14/41] Reorganize setting fields to match UI --- game/settings.py | 68 +++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/game/settings.py b/game/settings.py index e76e0816..77251972 100644 --- a/game/settings.py +++ b/game/settings.py @@ -16,78 +16,80 @@ class AutoAtoBehavior(Enum): @dataclass class Settings: + version: Optional[str] = None # Difficulty settings + # AI Difficulty player_skill: str = "Good" enemy_skill: str = "Average" - ai_pilot_levelling: bool = True enemy_vehicle_skill: str = "Average" - map_coalition_visibility: ForcedOptions.Views = ForcedOptions.Views.All - labels: str = "Full" - only_player_takeoff: bool = True # Legacy parameter do not use - night_disabled: bool = False - external_views_allowed: bool = True - supercarrier: bool = False - generate_marks: bool = True - manpads: bool = True - version: Optional[str] = None player_income_multiplier: float = 1.0 enemy_income_multiplier: float = 1.0 + invulnerable_player_pilots: bool = True + # Mission Difficulty + night_disabled: bool = False + manpads: bool = True + # Mission Restrictions + labels: str = "Full" + map_coalition_visibility: ForcedOptions.Views = ForcedOptions.Views.All + external_views_allowed: bool = True + # Campaign management + # General + restrict_weapons_by_date: bool = False + disable_legacy_aewc: bool = True + disable_legacy_tanker: bool = True + # Pilots and Squadrons + ai_pilot_levelling: bool = True #: Feature flag for squadron limits. enable_squadron_pilot_limits: bool = False - #: The maximum number of pilots a squadron can have at one time. Changing this after #: the campaign has started will have no immediate effect; pilots already in the #: squadron will not be removed if the limit is lowered and pilots will not be #: immediately created if the limit is raised. squadron_pilot_limit: int = 12 - #: The number of pilots a squadron can replace per turn. squadron_replenishment_rate: int = 4 - - default_start_type: str = "Cold" - - # Mission specific - desired_player_mission_duration: timedelta = timedelta(minutes=60) - - # Campaign management + # HQ Automation automate_runway_repair: bool = False automate_front_line_reinforcements: bool = False automate_aircraft_reinforcements: bool = False - automate_front_line_stance: bool = True - restrict_weapons_by_date: bool = False - disable_legacy_aewc: bool = True - disable_legacy_tanker: bool = True - generate_dark_kneeboard: bool = False - invulnerable_player_pilots: bool = True auto_ato_behavior: AutoAtoBehavior = AutoAtoBehavior.Default auto_ato_player_missions_asap: bool = True + automate_front_line_stance: bool = True + reserves_procurement_target: int = 10 - # Performance oriented - perf_red_alert_state: bool = True + # Mission Generator + # Gameplay + supercarrier: bool = False + generate_marks: bool = True + generate_dark_kneeboard: bool = False + never_delay_player_flights: bool = False + default_start_type: str = "Cold" + # Mission specific + desired_player_mission_duration: timedelta = timedelta(minutes=60) + # Performance perf_smoke_gen: bool = True perf_smoke_spacing = 1600 + perf_red_alert_state: bool = True perf_artillery: bool = True perf_moving_units: bool = True perf_infantry: bool = True perf_destroyed_units: bool = True - reserves_procurement_target: int = 10 - # Performance culling perf_culling: bool = False perf_culling_distance: int = 100 perf_do_not_cull_carrier = True - # LUA Plugins system - plugins: Dict[str, bool] = field(default_factory=dict) - # Cheating show_red_ato: bool = False enable_frontline_cheats: bool = False enable_base_capture_cheat: bool = False - never_delay_player_flights: bool = False + # LUA Plugins system + plugins: Dict[str, bool] = field(default_factory=dict) + + only_player_takeoff: bool = True # Legacy parameter do not use @staticmethod def plugin_settings_key(identifier: str) -> str: From 74e6226d13dd18b4abf3ac66792462bd90fb1f46 Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Sat, 7 Aug 2021 04:35:31 +0900 Subject: [PATCH 15/41] Fix typo --- qt_ui/windows/settings/QSettingsWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 188963d7..dd274062 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -230,7 +230,7 @@ class PilotSettingsBox(QGroupBox): ) enable_squadron_pilot_limits_label = QLabel( - "Enable per-squadron pilot limtits (WIP)" + "Enable per-squadron pilot limits (WIP)" ) enable_squadron_pilot_limits_label.setToolTip(enable_squadron_pilot_limits_info) enable_squadron_pilot_limits = QCheckBox() From def5454e5f27dc60a562f8061ec64c9ddabe4109 Mon Sep 17 00:00:00 2001 From: Kangwook Lee Date: Sat, 7 Aug 2021 04:43:43 +0900 Subject: [PATCH 16/41] Add battle damage assessment option --- game/settings.py | 1 + gen/forcedoptionsgen.py | 6 ++++++ qt_ui/windows/settings/QSettingsWindow.py | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/game/settings.py b/game/settings.py index 77251972..3bb557bf 100644 --- a/game/settings.py +++ b/game/settings.py @@ -33,6 +33,7 @@ class Settings: labels: str = "Full" map_coalition_visibility: ForcedOptions.Views = ForcedOptions.Views.All external_views_allowed: bool = True + battle_damage_assessment: Optional[bool] = None # Campaign management # General diff --git a/gen/forcedoptionsgen.py b/gen/forcedoptionsgen.py index e4025d48..0e5be7bc 100644 --- a/gen/forcedoptionsgen.py +++ b/gen/forcedoptionsgen.py @@ -43,8 +43,14 @@ class ForcedOptionsGenerator: if blue.unrestricted_satnav or red.unrestricted_satnav: self.mission.forced_options.unrestricted_satnav = True + def _set_battle_damage_assessment(self) -> None: + self.mission.forced_options.battle_damage_assessment = ( + self.game.settings.battle_damage_assessment + ) + def generate(self) -> None: self._set_options_view() self._set_external_views() self._set_labels() self._set_unrestricted_satnav() + self._set_battle_damage_assessment() diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index dd274062..8cd86618 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -517,6 +517,18 @@ class QSettingsWindow(QDialog): self.ext_views.setChecked(self.game.settings.external_views_allowed) self.ext_views.toggled.connect(self.applySettings) + self.battleDamageAssessment = QComboBox() + self.battleDamageAssessment.addItem("Player preference", None) + self.battleDamageAssessment.addItem("Enforced on", True) + self.battleDamageAssessment.addItem("Enforced off", False) + if self.game.settings.battle_damage_assessment is None: + self.battleDamageAssessment.setCurrentIndex(0) + elif self.game.settings.battle_damage_assessment is True: + self.battleDamageAssessment.setCurrentIndex(1) + else: + self.battleDamageAssessment.setCurrentIndex(2) + self.battleDamageAssessment.currentIndexChanged.connect(self.applySettings) + def set_invulnerable_player_pilots(checked: bool) -> None: self.game.settings.invulnerable_player_pilots = checked @@ -568,6 +580,14 @@ class QSettingsWindow(QDialog): ) self.missionRestrictionsLayout.addWidget(QLabel("Allow external views"), 2, 0) self.missionRestrictionsLayout.addWidget(self.ext_views, 2, 1, Qt.AlignRight) + + self.missionRestrictionsLayout.addWidget( + QLabel("Battle damage assessment"), 3, 0 + ) + self.missionRestrictionsLayout.addWidget( + self.battleDamageAssessment, 3, 1, Qt.AlignRight + ) + self.missionRestrictionsSettings.setLayout(self.missionRestrictionsLayout) self.difficultyLayout.addWidget(self.missionRestrictionsSettings) @@ -909,6 +929,9 @@ class QSettingsWindow(QDialog): self.mapVisibiitySelection.currentData() ) self.game.settings.external_views_allowed = self.ext_views.isChecked() + self.game.settings.battle_damage_assessment = ( + self.battleDamageAssessment.currentData() + ) self.game.settings.generate_marks = self.generate_marks.isChecked() self.game.settings.never_delay_player_flights = ( self.never_delay_players.isChecked() From 91daabc9d28ae49e9759f2967e1ae5f10c9f65e6 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 7 Aug 2021 14:04:32 -0700 Subject: [PATCH 17/41] Fixup auto-assignable tasks when limits change. The air wing config was fixing the main `mission_types` field, but the `auto_assignable_mission_types` property had already been set. Update that field whenever the `mission_types` are changed. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1515 --- game/squadrons.py | 6 +++++- qt_ui/windows/AirWingConfigurationDialog.py | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/game/squadrons.py b/game/squadrons.py index 5777102f..53bf6989 100644 --- a/game/squadrons.py +++ b/game/squadrons.py @@ -4,7 +4,7 @@ import dataclasses import itertools import logging import random -from collections import defaultdict +from collections import defaultdict, Iterable from dataclasses import dataclass, field from enum import unique, Enum from pathlib import Path @@ -146,6 +146,10 @@ class Squadron: def pilot_limits_enabled(self) -> bool: return self.settings.enable_squadron_pilot_limits + def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None: + self.mission_types = tuple(mission_types) + self.auto_assignable_mission_types.intersection_update(self.mission_types) + def claim_new_pilot_if_allowed(self) -> Optional[Pilot]: if self.pilot_limits_enabled: return None diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index 85539d06..ac6000f6 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -158,7 +158,9 @@ class SquadronConfigurationBox(QGroupBox): self.squadron.pilot_pool = [ Pilot(n, player=True) for n in player_names ] + self.squadron.pilot_pool - self.squadron.mission_types = tuple(self.allowed_missions.allowed_mission_types) + self.squadron.set_allowed_mission_types( + self.allowed_missions.allowed_mission_types + ) return self.squadron From b31c09c4ff72b15009b69876e1581b1cfd651d7c Mon Sep 17 00:00:00 2001 From: RndName Date: Sun, 8 Aug 2021 13:16:39 +0200 Subject: [PATCH 18/41] Fix AAA Flak generator using wrong index - Fixes #1519 as the Opel Blitz unit generator was using the index without incrementing it --- changelog.md | 1 + gen/sam/aaa_flak.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index bc23f7b6..85bb4d12 100644 --- a/changelog.md +++ b/changelog.md @@ -61,6 +61,7 @@ Saves from 4.0.0 are compatible with 4.1.0. * **[Mission Generation]** Prevent the creation of a transfer order with 0 units for a rare situtation when a point was captured. * **[Mission Generation]** Planned transfers which will be impossible after a base capture will no longer prevent the mission result submit. * **[Mission Generation]** Fix occasional KeyError preventing mission generation when all units of the same type in a convoy were killed. +* **[Mission Generation]** Fix for AAA Flak generator using Opel Blitz preventing the mission from being generated because duplicate unit names were used. * **[Mission Generation]** Fixed a potential bug with laser code generation where it would generate invalid codes. * **[UI]** Statistics window tick marks are now always integers. * **[UI]** Statistics window now shows the correct info for the turn diff --git a/gen/sam/aaa_flak.py b/gen/sam/aaa_flak.py index 0e27a8d2..72f6fb2e 100644 --- a/gen/sam/aaa_flak.py +++ b/gen/sam/aaa_flak.py @@ -82,8 +82,10 @@ class FlakGenerator(AirDefenseGroupGenerator): ) # Some Opel Blitz trucks + index = 0 for i in range(int(max(1, 2))): for j in range(int(max(1, 2))): + index += 1 self.add_unit( Unarmed.Blitz_36_6700A, "BLITZ#" + str(index), From c80293d9e05c1f13f368165183507c6671503e1d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 8 Aug 2021 12:58:04 -0700 Subject: [PATCH 19/41] Add a bug template for mod support requests. --- .github/ISSUE_TEMPLATE/mod_support.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/mod_support.md diff --git a/.github/ISSUE_TEMPLATE/mod_support.md b/.github/ISSUE_TEMPLATE/mod_support.md new file mode 100644 index 00000000..be6b644c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mod_support.md @@ -0,0 +1,12 @@ +--- +name: Mod support request +about: Request Liberation support for new mods, or updates to existing mods +title: Add/update +labels: mod support +assignees: '' + +--- + +* Mod name: +* Mod URL: +* Update or new mod? \ No newline at end of file From 42d09292b73b2689b7723a25830fa256fcf87436 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Tue, 10 Aug 2021 11:50:24 +0200 Subject: [PATCH 20/41] Update changelog with Marianas fix for 4.1.1 --- changelog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changelog.md b/changelog.md index 85bb4d12..82c97754 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,14 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign]** Naval control points will no longer claim ground objectives during campaign generation and prevent them from spawning. +# 4.1.1 + +Saves from 4.1.0 are compatible with 4.1.1. + +## Fixes + +* **[Campaign]** Fixed broken support for Mariana Islands map. + # 4.1.0 Saves from 4.0.0 are compatible with 4.1.0. From b3dedbdf7506783fcbdc3d3a1d3e4b23445fcd5b Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Tue, 10 Aug 2021 13:20:08 +0200 Subject: [PATCH 21/41] Update changelog with sam site heading fix --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 82c97754..3ff29356 100644 --- a/changelog.md +++ b/changelog.md @@ -29,6 +29,7 @@ Saves from 4.1.0 are compatible with 4.1.1. ## Fixes * **[Campaign]** Fixed broken support for Mariana Islands map. +* **[Mission Generation]** Fix SAM sites pointing towards the center of the conflict. # 4.1.0 From 37491ceffbf3dea7ffa25eff43b636076c1224c5 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Tue, 10 Aug 2021 13:38:35 +0200 Subject: [PATCH 22/41] Stop using Su-34 for CAP missions, update changelog --- changelog.md | 1 + gen/flights/ai_flight_planner_db.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 3ff29356..1a2da917 100644 --- a/changelog.md +++ b/changelog.md @@ -30,6 +30,7 @@ Saves from 4.1.0 are compatible with 4.1.1. * **[Campaign]** Fixed broken support for Mariana Islands map. * **[Mission Generation]** Fix SAM sites pointing towards the center of the conflict. +* **[Flight Planning]** No longer using Su-34 for CAP missions. # 4.1.0 diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index 59e49406..4b868bef 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -129,7 +129,6 @@ CAP_CAPABLE = [ F_14B, F_14A_135_GR, Su_33, - Su_34, J_11A, Su_30, Su_27, From 6c7b62b8b19a317f2901bf3b31531c5e0061f47c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 10 Aug 2021 17:58:47 -0700 Subject: [PATCH 23/41] Move MizCampaignLoader out of conflicttheater.py. Also removes the useless `size` and `importance` fields from `ControlPoint`. --- game/theater/conflicttheater.py | 491 +----------------- game/theater/controlpoint.py | 27 +- game/theater/mizcampaignloader.py | 466 +++++++++++++++++ ...bu_dhabi.json => battle_of_abu_dhabi.yaml} | 0 4 files changed, 469 insertions(+), 515 deletions(-) create mode 100644 game/theater/mizcampaignloader.py rename resources/campaigns/{battle_of_abu_dhabi.json => battle_of_abu_dhabi.yaml} (100%) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index bdae60e6..c87d8c77 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -1,27 +1,11 @@ from __future__ import annotations -import itertools import math from dataclasses import dataclass -from functools import cached_property from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING -from dcs import Mission -from dcs.countries import ( - CombinedJointTaskForcesBlue, - CombinedJointTaskForcesRed, -) -from dcs.country import Country from dcs.mapping import Point -from dcs.planes import F_15C -from dcs.ships import ( - HandyWind, - Stennis, - USS_Arleigh_Burke_IIa, - LHA_Tarawa, -) -from dcs.statics import Fortification, Warehouse from dcs.terrain import ( caucasus, nevada, @@ -31,496 +15,25 @@ from dcs.terrain import ( thechannel, marianaislands, ) -from dcs.terrain.terrain import Airport, Terrain -from dcs.unitgroup import ( - ShipGroup, - StaticGroup, - VehicleGroup, - PlaneGroup, -) -from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed +from dcs.terrain.terrain import Terrain from pyproj import CRS, Transformer from shapely import geometry, ops from .controlpoint import ( - Airfield, - Carrier, ControlPoint, - Fob, - Lha, MissionTarget, - OffMapSpawn, ) +from .mizcampaignloader import MizCampaignLoader from .seasonalconditions import SeasonalConditions from .frontline import FrontLine from .landmap import Landmap, load_landmap, poly_contains from .latlon import LatLon from .projections import TransverseMercator -from ..point_with_heading import PointWithHeading -from ..positioned import Positioned from ..profiling import logged_duration -from ..scenery_group import SceneryGroup -from ..utils import Distance, Heading, meters if TYPE_CHECKING: from . import TheaterGroundObject -SIZE_TINY = 150 -SIZE_SMALL = 600 -SIZE_REGULAR = 1000 -SIZE_BIG = 2000 -SIZE_LARGE = 3000 - -IMPORTANCE_LOW = 1 -IMPORTANCE_MEDIUM = 1.2 -IMPORTANCE_HIGH = 1.4 - - -class MizCampaignLoader: - BLUE_COUNTRY = CombinedJointTaskForcesBlue() - RED_COUNTRY = CombinedJointTaskForcesRed() - - OFF_MAP_UNIT_TYPE = F_15C.id - - CV_UNIT_TYPE = Stennis.id - LHA_UNIT_TYPE = LHA_Tarawa.id - FRONT_LINE_UNIT_TYPE = Armor.M_113.id - SHIPPING_LANE_UNIT_TYPE = HandyWind.id - - FOB_UNIT_TYPE = Unarmed.SKP_11.id - FARP_HELIPAD = "SINGLE_HELIPAD" - - OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id - SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id - MISSILE_SITE_UNIT_TYPE = MissilesSS.Scud_B.id - COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.Hy_launcher.id - - # Multiple options for air defenses so campaign designers can more accurately see - # the coverage of their IADS for the expected type. - LONG_RANGE_SAM_UNIT_TYPES = { - AirDefence.Patriot_ln.id, - AirDefence.S_300PS_5P85C_ln.id, - AirDefence.S_300PS_5P85D_ln.id, - } - - MEDIUM_RANGE_SAM_UNIT_TYPES = { - AirDefence.Hawk_ln.id, - AirDefence.S_75M_Volhov.id, - AirDefence._5p73_s_125_ln.id, - } - - SHORT_RANGE_SAM_UNIT_TYPES = { - AirDefence.M1097_Avenger.id, - AirDefence.Rapier_fsa_launcher.id, - AirDefence._2S6_Tunguska.id, - AirDefence.Strela_1_9P31.id, - } - - AAA_UNIT_TYPES = { - AirDefence.Flak18.id, - AirDefence.Vulcan.id, - AirDefence.ZSU_23_4_Shilka.id, - } - - EWR_UNIT_TYPE = AirDefence._1L13_EWR.id - - ARMOR_GROUP_UNIT_TYPE = Armor.M_1_Abrams.id - - FACTORY_UNIT_TYPE = Fortification.Workshop_A.id - - AMMUNITION_DEPOT_UNIT_TYPE = Warehouse._Ammunition_depot.id - - STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id - - def __init__(self, miz: Path, theater: ConflictTheater) -> None: - self.theater = theater - self.mission = Mission() - with logged_duration("Loading miz"): - self.mission.load_file(str(miz)) - self.control_point_id = itertools.count(1000) - - # If there are no red carriers there usually aren't red units. Make sure - # both countries are initialized so we don't have to deal with None. - if self.mission.country(self.BLUE_COUNTRY.name) is None: - self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) - if self.mission.country(self.RED_COUNTRY.name) is None: - self.mission.coalition["red"].add_country(self.RED_COUNTRY) - - @staticmethod - def control_point_from_airport(airport: Airport) -> ControlPoint: - - # The wiki says this is a legacy property and to just use regular. - size = SIZE_REGULAR - - # The importance is taken from the periodicity of the airport's - # warehouse divided by 10. 30 is the default, and out of range (valid - # values are between 1.0 and 1.4). If it is used, pick the default - # importance. - if airport.periodicity == 30: - importance = IMPORTANCE_MEDIUM - else: - importance = airport.periodicity / 10 - - cp = Airfield(airport, size, importance) - cp.captured = airport.is_blue() - - # Use the unlimited aircraft option to determine if an airfield should - # be owned by the player when the campaign is "inverted". - cp.captured_invert = airport.unlimited_aircrafts - - return cp - - def country(self, blue: bool) -> Country: - country = self.mission.country( - self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name - ) - # Should be guaranteed because we initialized them. - assert country - return country - - @property - def blue(self) -> Country: - return self.country(blue=True) - - @property - def red(self) -> Country: - return self.country(blue=False) - - def off_map_spawns(self, blue: bool) -> Iterator[PlaneGroup]: - for group in self.country(blue).plane_group: - if group.units[0].type == self.OFF_MAP_UNIT_TYPE: - yield group - - def carriers(self, blue: bool) -> Iterator[ShipGroup]: - for group in self.country(blue).ship_group: - if group.units[0].type == self.CV_UNIT_TYPE: - yield group - - def lhas(self, blue: bool) -> Iterator[ShipGroup]: - for group in self.country(blue).ship_group: - if group.units[0].type == self.LHA_UNIT_TYPE: - yield group - - def fobs(self, blue: bool) -> Iterator[VehicleGroup]: - for group in self.country(blue).vehicle_group: - if group.units[0].type == self.FOB_UNIT_TYPE: - yield group - - @property - def ships(self) -> Iterator[ShipGroup]: - for group in self.red.ship_group: - if group.units[0].type == self.SHIP_UNIT_TYPE: - yield group - - @property - def offshore_strike_targets(self) -> Iterator[StaticGroup]: - for group in self.red.static_group: - if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: - yield group - - @property - def missile_sites(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE: - yield group - - @property - def coastal_defenses(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE: - yield group - - @property - def long_range_sams(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type in self.LONG_RANGE_SAM_UNIT_TYPES: - yield group - - @property - def medium_range_sams(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type in self.MEDIUM_RANGE_SAM_UNIT_TYPES: - yield group - - @property - def short_range_sams(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type in self.SHORT_RANGE_SAM_UNIT_TYPES: - yield group - - @property - def aaa(self) -> Iterator[VehicleGroup]: - for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): - if group.units[0].type in self.AAA_UNIT_TYPES: - yield group - - @property - def ewrs(self) -> Iterator[VehicleGroup]: - for group in self.red.vehicle_group: - if group.units[0].type in self.EWR_UNIT_TYPE: - yield group - - @property - def armor_groups(self) -> Iterator[VehicleGroup]: - for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): - if group.units[0].type in self.ARMOR_GROUP_UNIT_TYPE: - yield group - - @property - def helipads(self) -> Iterator[StaticGroup]: - for group in self.blue.static_group: - if group.units[0].type == self.FARP_HELIPAD: - yield group - - @property - def factories(self) -> Iterator[StaticGroup]: - for group in self.blue.static_group: - if group.units[0].type in self.FACTORY_UNIT_TYPE: - yield group - - @property - def ammunition_depots(self) -> Iterator[StaticGroup]: - for group in itertools.chain(self.blue.static_group, self.red.static_group): - if group.units[0].type in self.AMMUNITION_DEPOT_UNIT_TYPE: - yield group - - @property - def strike_targets(self) -> Iterator[StaticGroup]: - for group in itertools.chain(self.blue.static_group, self.red.static_group): - if group.units[0].type in self.STRIKE_TARGET_UNIT_TYPE: - yield group - - @property - def scenery(self) -> List[SceneryGroup]: - return SceneryGroup.from_trigger_zones(self.mission.triggers._zones) - - @cached_property - def control_points(self) -> Dict[int, ControlPoint]: - control_points = {} - for airport in self.mission.terrain.airport_list(): - if airport.is_blue() or airport.is_red(): - control_point = self.control_point_from_airport(airport) - control_points[control_point.id] = control_point - - for blue in (False, True): - for group in self.off_map_spawns(blue): - control_point = OffMapSpawn( - next(self.control_point_id), str(group.name), group.position - ) - control_point.captured = blue - control_point.captured_invert = group.late_activation - control_points[control_point.id] = control_point - for ship in self.carriers(blue): - # TODO: Name the carrier. - control_point = Carrier( - "carrier", ship.position, next(self.control_point_id) - ) - control_point.captured = blue - control_point.captured_invert = ship.late_activation - control_points[control_point.id] = control_point - for ship in self.lhas(blue): - # TODO: Name the LHA.db - control_point = Lha("lha", ship.position, next(self.control_point_id)) - control_point.captured = blue - control_point.captured_invert = ship.late_activation - control_points[control_point.id] = control_point - for fob in self.fobs(blue): - control_point = Fob( - str(fob.name), fob.position, next(self.control_point_id) - ) - control_point.captured = blue - control_point.captured_invert = fob.late_activation - control_points[control_point.id] = control_point - - return control_points - - @property - def front_line_path_groups(self) -> Iterator[VehicleGroup]: - for group in self.country(blue=True).vehicle_group: - if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: - yield group - - @property - def shipping_lane_groups(self) -> Iterator[ShipGroup]: - for group in self.country(blue=True).ship_group: - if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: - yield group - - def add_supply_routes(self) -> None: - for group in self.front_line_path_groups: - # The unit will have its first waypoint at the source CP and the final - # waypoint at the destination CP. Each waypoint defines the path of the - # cargo ship. - waypoints = [p.position for p in group.points] - origin = self.theater.closest_control_point(waypoints[0]) - if origin is None: - raise RuntimeError( - f"No control point near the first waypoint of {group.name}" - ) - destination = self.theater.closest_control_point(waypoints[-1]) - if destination is None: - raise RuntimeError( - f"No control point near the final waypoint of {group.name}" - ) - - self.control_points[origin.id].create_convoy_route(destination, waypoints) - self.control_points[destination.id].create_convoy_route( - origin, list(reversed(waypoints)) - ) - - def add_shipping_lanes(self) -> None: - for group in self.shipping_lane_groups: - # The unit will have its first waypoint at the source CP and the final - # waypoint at the destination CP. Each waypoint defines the path of the - # cargo ship. - waypoints = [p.position for p in group.points] - origin = self.theater.closest_control_point(waypoints[0]) - if origin is None: - raise RuntimeError( - f"No control point near the first waypoint of {group.name}" - ) - destination = self.theater.closest_control_point(waypoints[-1]) - if destination is None: - raise RuntimeError( - f"No control point near the final waypoint of {group.name}" - ) - - self.control_points[origin.id].create_shipping_lane(destination, waypoints) - self.control_points[destination.id].create_shipping_lane( - origin, list(reversed(waypoints)) - ) - - def objective_info( - self, near: Positioned, allow_naval: bool = False - ) -> Tuple[ControlPoint, Distance]: - closest = self.theater.closest_control_point(near.position, allow_naval) - distance = meters(closest.position.distance_to_point(near.position)) - return closest, distance - - def add_preset_locations(self) -> None: - for static in self.offshore_strike_targets: - closest, distance = self.objective_info(static) - closest.preset_locations.offshore_strike_locations.append( - PointWithHeading.from_point( - static.position, Heading.from_degrees(static.units[0].heading) - ) - ) - - for ship in self.ships: - closest, distance = self.objective_info(ship, allow_naval=True) - closest.preset_locations.ships.append( - PointWithHeading.from_point( - ship.position, Heading.from_degrees(ship.units[0].heading) - ) - ) - - for group in self.missile_sites: - closest, distance = self.objective_info(group) - closest.preset_locations.missile_sites.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.coastal_defenses: - closest, distance = self.objective_info(group) - closest.preset_locations.coastal_defenses.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.long_range_sams: - closest, distance = self.objective_info(group) - closest.preset_locations.long_range_sams.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.medium_range_sams: - closest, distance = self.objective_info(group) - closest.preset_locations.medium_range_sams.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.short_range_sams: - closest, distance = self.objective_info(group) - closest.preset_locations.short_range_sams.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.aaa: - closest, distance = self.objective_info(group) - closest.preset_locations.aaa.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.ewrs: - closest, distance = self.objective_info(group) - closest.preset_locations.ewrs.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for group in self.armor_groups: - closest, distance = self.objective_info(group) - closest.preset_locations.armor_groups.append( - PointWithHeading.from_point( - group.position, Heading.from_degrees(group.units[0].heading) - ) - ) - - for static in self.helipads: - closest, distance = self.objective_info(static) - closest.helipads.append( - PointWithHeading.from_point( - static.position, Heading.from_degrees(static.units[0].heading) - ) - ) - - for static in self.factories: - closest, distance = self.objective_info(static) - closest.preset_locations.factories.append( - PointWithHeading.from_point( - static.position, Heading.from_degrees(static.units[0].heading) - ) - ) - - for static in self.ammunition_depots: - closest, distance = self.objective_info(static) - closest.preset_locations.ammunition_depots.append( - PointWithHeading.from_point( - static.position, Heading.from_degrees(static.units[0].heading) - ) - ) - - for static in self.strike_targets: - closest, distance = self.objective_info(static) - closest.preset_locations.strike_locations.append( - PointWithHeading.from_point( - static.position, Heading.from_degrees(static.units[0].heading) - ) - ) - - for scenery_group in self.scenery: - closest, distance = self.objective_info(scenery_group) - closest.preset_locations.scenery.append(scenery_group) - - def populate_theater(self) -> None: - for control_point in self.control_points.values(): - self.theater.add_controlpoint(control_point) - self.add_preset_locations() - self.add_supply_routes() - self.add_shipping_lanes() - @dataclass class ReferencePoint: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 12786d32..f0c5286b 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -293,8 +293,6 @@ class ControlPoint(MissionTarget, ABC): name: str, position: Point, at: db.StartingPosition, - size: int, - importance: float, has_frontline: bool = True, cptype: ControlPointType = ControlPointType.AIRBASE, ) -> None: @@ -307,9 +305,6 @@ class ControlPoint(MissionTarget, ABC): self.preset_locations = PresetLocations() self.helipads: List[PointWithHeading] = [] - # TODO: Should be Airbase specific. - self.size = size - self.importance = importance self.captured = False self.captured_invert = False # TODO: Should be Airbase specific. @@ -791,16 +786,12 @@ class ControlPoint(MissionTarget, ABC): class Airfield(ControlPoint): - def __init__( - self, airport: Airport, size: int, importance: float, has_frontline: bool = True - ) -> None: + def __init__(self, airport: Airport, has_frontline: bool = True) -> None: super().__init__( airport.id, airport.name, airport.position, airport, - size, - importance, has_frontline, cptype=ControlPointType.AIRBASE, ) @@ -966,15 +957,11 @@ class NavalControlPoint(ControlPoint, ABC): class Carrier(NavalControlPoint): def __init__(self, name: str, at: Point, cp_id: int): - import game.theater.conflicttheater - super().__init__( cp_id, name, at, at, - game.theater.conflicttheater.SIZE_SMALL, - 1, has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP, ) @@ -1010,15 +997,11 @@ class Carrier(NavalControlPoint): class Lha(NavalControlPoint): def __init__(self, name: str, at: Point, cp_id: int): - import game.theater.conflicttheater - super().__init__( cp_id, name, at, at, - game.theater.conflicttheater.SIZE_SMALL, - 1, has_frontline=False, cptype=ControlPointType.LHA_GROUP, ) @@ -1047,15 +1030,11 @@ class OffMapSpawn(ControlPoint): return True def __init__(self, cp_id: int, name: str, position: Point): - from . import IMPORTANCE_MEDIUM, SIZE_REGULAR - super().__init__( cp_id, name, position, at=position, - size=SIZE_REGULAR, - importance=IMPORTANCE_MEDIUM, has_frontline=False, cptype=ControlPointType.OFF_MAP, ) @@ -1104,15 +1083,11 @@ class OffMapSpawn(ControlPoint): class Fob(ControlPoint): def __init__(self, name: str, at: Point, cp_id: int): - import game.theater.conflicttheater - super().__init__( cp_id, name, at, at, - game.theater.conflicttheater.SIZE_SMALL, - 1, has_frontline=True, cptype=ControlPointType.FOB, ) diff --git a/game/theater/mizcampaignloader.py b/game/theater/mizcampaignloader.py new file mode 100644 index 00000000..a2565029 --- /dev/null +++ b/game/theater/mizcampaignloader.py @@ -0,0 +1,466 @@ +from __future__ import annotations + +import itertools +from functools import cached_property +from pathlib import Path +from typing import Iterator, List, Dict, Tuple, TYPE_CHECKING + +from dcs import Mission +from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed +from dcs.country import Country +from dcs.planes import F_15C +from dcs.ships import Stennis, LHA_Tarawa, HandyWind, USS_Arleigh_Burke_IIa +from dcs.statics import Fortification, Warehouse +from dcs.terrain import Airport +from dcs.unitgroup import PlaneGroup, ShipGroup, VehicleGroup, StaticGroup +from dcs.vehicles import Armor, Unarmed, MissilesSS, AirDefence + +from game.point_with_heading import PointWithHeading +from game.positioned import Positioned +from game.profiling import logged_duration +from game.scenery_group import SceneryGroup +from game.utils import Distance, meters, Heading +from .controlpoint import ( + Airfield, + Carrier, + ControlPoint, + Fob, + Lha, + OffMapSpawn, +) + +if TYPE_CHECKING: + from .conflicttheater import ConflictTheater + + +class MizCampaignLoader: + BLUE_COUNTRY = CombinedJointTaskForcesBlue() + RED_COUNTRY = CombinedJointTaskForcesRed() + + OFF_MAP_UNIT_TYPE = F_15C.id + + CV_UNIT_TYPE = Stennis.id + LHA_UNIT_TYPE = LHA_Tarawa.id + FRONT_LINE_UNIT_TYPE = Armor.M_113.id + SHIPPING_LANE_UNIT_TYPE = HandyWind.id + + FOB_UNIT_TYPE = Unarmed.SKP_11.id + FARP_HELIPAD = "SINGLE_HELIPAD" + + OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id + SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id + MISSILE_SITE_UNIT_TYPE = MissilesSS.Scud_B.id + COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.Hy_launcher.id + + # Multiple options for air defenses so campaign designers can more accurately see + # the coverage of their IADS for the expected type. + LONG_RANGE_SAM_UNIT_TYPES = { + AirDefence.Patriot_ln.id, + AirDefence.S_300PS_5P85C_ln.id, + AirDefence.S_300PS_5P85D_ln.id, + } + + MEDIUM_RANGE_SAM_UNIT_TYPES = { + AirDefence.Hawk_ln.id, + AirDefence.S_75M_Volhov.id, + AirDefence._5p73_s_125_ln.id, + } + + SHORT_RANGE_SAM_UNIT_TYPES = { + AirDefence.M1097_Avenger.id, + AirDefence.Rapier_fsa_launcher.id, + AirDefence._2S6_Tunguska.id, + AirDefence.Strela_1_9P31.id, + } + + AAA_UNIT_TYPES = { + AirDefence.Flak18.id, + AirDefence.Vulcan.id, + AirDefence.ZSU_23_4_Shilka.id, + } + + EWR_UNIT_TYPE = AirDefence._1L13_EWR.id + + ARMOR_GROUP_UNIT_TYPE = Armor.M_1_Abrams.id + + FACTORY_UNIT_TYPE = Fortification.Workshop_A.id + + AMMUNITION_DEPOT_UNIT_TYPE = Warehouse._Ammunition_depot.id + + STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id + + def __init__(self, miz: Path, theater: ConflictTheater) -> None: + self.theater = theater + self.mission = Mission() + with logged_duration("Loading miz"): + self.mission.load_file(str(miz)) + self.control_point_id = itertools.count(1000) + + # If there are no red carriers there usually aren't red units. Make sure + # both countries are initialized so we don't have to deal with None. + if self.mission.country(self.BLUE_COUNTRY.name) is None: + self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) + if self.mission.country(self.RED_COUNTRY.name) is None: + self.mission.coalition["red"].add_country(self.RED_COUNTRY) + + @staticmethod + def control_point_from_airport(airport: Airport) -> ControlPoint: + cp = Airfield(airport) + cp.captured = airport.is_blue() + + # Use the unlimited aircraft option to determine if an airfield should + # be owned by the player when the campaign is "inverted". + cp.captured_invert = airport.unlimited_aircrafts + + return cp + + def country(self, blue: bool) -> Country: + country = self.mission.country( + self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name + ) + # Should be guaranteed because we initialized them. + assert country + return country + + @property + def blue(self) -> Country: + return self.country(blue=True) + + @property + def red(self) -> Country: + return self.country(blue=False) + + def off_map_spawns(self, blue: bool) -> Iterator[PlaneGroup]: + for group in self.country(blue).plane_group: + if group.units[0].type == self.OFF_MAP_UNIT_TYPE: + yield group + + def carriers(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.CV_UNIT_TYPE: + yield group + + def lhas(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.LHA_UNIT_TYPE: + yield group + + def fobs(self, blue: bool) -> Iterator[VehicleGroup]: + for group in self.country(blue).vehicle_group: + if group.units[0].type == self.FOB_UNIT_TYPE: + yield group + + @property + def ships(self) -> Iterator[ShipGroup]: + for group in self.red.ship_group: + if group.units[0].type == self.SHIP_UNIT_TYPE: + yield group + + @property + def offshore_strike_targets(self) -> Iterator[StaticGroup]: + for group in self.red.static_group: + if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: + yield group + + @property + def missile_sites(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE: + yield group + + @property + def coastal_defenses(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE: + yield group + + @property + def long_range_sams(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type in self.LONG_RANGE_SAM_UNIT_TYPES: + yield group + + @property + def medium_range_sams(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type in self.MEDIUM_RANGE_SAM_UNIT_TYPES: + yield group + + @property + def short_range_sams(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type in self.SHORT_RANGE_SAM_UNIT_TYPES: + yield group + + @property + def aaa(self) -> Iterator[VehicleGroup]: + for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): + if group.units[0].type in self.AAA_UNIT_TYPES: + yield group + + @property + def ewrs(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type in self.EWR_UNIT_TYPE: + yield group + + @property + def armor_groups(self) -> Iterator[VehicleGroup]: + for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group): + if group.units[0].type in self.ARMOR_GROUP_UNIT_TYPE: + yield group + + @property + def helipads(self) -> Iterator[StaticGroup]: + for group in self.blue.static_group: + if group.units[0].type == self.FARP_HELIPAD: + yield group + + @property + def factories(self) -> Iterator[StaticGroup]: + for group in self.blue.static_group: + if group.units[0].type in self.FACTORY_UNIT_TYPE: + yield group + + @property + def ammunition_depots(self) -> Iterator[StaticGroup]: + for group in itertools.chain(self.blue.static_group, self.red.static_group): + if group.units[0].type in self.AMMUNITION_DEPOT_UNIT_TYPE: + yield group + + @property + def strike_targets(self) -> Iterator[StaticGroup]: + for group in itertools.chain(self.blue.static_group, self.red.static_group): + if group.units[0].type in self.STRIKE_TARGET_UNIT_TYPE: + yield group + + @property + def scenery(self) -> List[SceneryGroup]: + return SceneryGroup.from_trigger_zones(self.mission.triggers._zones) + + @cached_property + def control_points(self) -> Dict[int, ControlPoint]: + control_points = {} + for airport in self.mission.terrain.airport_list(): + if airport.is_blue() or airport.is_red(): + control_point = self.control_point_from_airport(airport) + control_points[control_point.id] = control_point + + for blue in (False, True): + for group in self.off_map_spawns(blue): + control_point = OffMapSpawn( + next(self.control_point_id), str(group.name), group.position + ) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point + for ship in self.carriers(blue): + # TODO: Name the carrier. + control_point = Carrier( + "carrier", ship.position, next(self.control_point_id) + ) + control_point.captured = blue + control_point.captured_invert = ship.late_activation + control_points[control_point.id] = control_point + for ship in self.lhas(blue): + # TODO: Name the LHA.db + control_point = Lha("lha", ship.position, next(self.control_point_id)) + control_point.captured = blue + control_point.captured_invert = ship.late_activation + control_points[control_point.id] = control_point + for fob in self.fobs(blue): + control_point = Fob( + str(fob.name), fob.position, next(self.control_point_id) + ) + control_point.captured = blue + control_point.captured_invert = fob.late_activation + control_points[control_point.id] = control_point + + return control_points + + @property + def front_line_path_groups(self) -> Iterator[VehicleGroup]: + for group in self.country(blue=True).vehicle_group: + if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: + yield group + + @property + def shipping_lane_groups(self) -> Iterator[ShipGroup]: + for group in self.country(blue=True).ship_group: + if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: + yield group + + def add_supply_routes(self) -> None: + for group in self.front_line_path_groups: + # The unit will have its first waypoint at the source CP and the final + # waypoint at the destination CP. Each waypoint defines the path of the + # cargo ship. + waypoints = [p.position for p in group.points] + origin = self.theater.closest_control_point(waypoints[0]) + if origin is None: + raise RuntimeError( + f"No control point near the first waypoint of {group.name}" + ) + destination = self.theater.closest_control_point(waypoints[-1]) + if destination is None: + raise RuntimeError( + f"No control point near the final waypoint of {group.name}" + ) + + self.control_points[origin.id].create_convoy_route(destination, waypoints) + self.control_points[destination.id].create_convoy_route( + origin, list(reversed(waypoints)) + ) + + def add_shipping_lanes(self) -> None: + for group in self.shipping_lane_groups: + # The unit will have its first waypoint at the source CP and the final + # waypoint at the destination CP. Each waypoint defines the path of the + # cargo ship. + waypoints = [p.position for p in group.points] + origin = self.theater.closest_control_point(waypoints[0]) + if origin is None: + raise RuntimeError( + f"No control point near the first waypoint of {group.name}" + ) + destination = self.theater.closest_control_point(waypoints[-1]) + if destination is None: + raise RuntimeError( + f"No control point near the final waypoint of {group.name}" + ) + + self.control_points[origin.id].create_shipping_lane(destination, waypoints) + self.control_points[destination.id].create_shipping_lane( + origin, list(reversed(waypoints)) + ) + + def objective_info( + self, near: Positioned, allow_naval: bool = False + ) -> Tuple[ControlPoint, Distance]: + closest = self.theater.closest_control_point(near.position, allow_naval) + distance = meters(closest.position.distance_to_point(near.position)) + return closest, distance + + def add_preset_locations(self) -> None: + for static in self.offshore_strike_targets: + closest, distance = self.objective_info(static) + closest.preset_locations.offshore_strike_locations.append( + PointWithHeading.from_point( + static.position, Heading.from_degrees(static.units[0].heading) + ) + ) + + for ship in self.ships: + closest, distance = self.objective_info(ship, allow_naval=True) + closest.preset_locations.ships.append( + PointWithHeading.from_point( + ship.position, Heading.from_degrees(ship.units[0].heading) + ) + ) + + for group in self.missile_sites: + closest, distance = self.objective_info(group) + closest.preset_locations.missile_sites.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.coastal_defenses: + closest, distance = self.objective_info(group) + closest.preset_locations.coastal_defenses.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.long_range_sams: + closest, distance = self.objective_info(group) + closest.preset_locations.long_range_sams.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.medium_range_sams: + closest, distance = self.objective_info(group) + closest.preset_locations.medium_range_sams.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.short_range_sams: + closest, distance = self.objective_info(group) + closest.preset_locations.short_range_sams.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.aaa: + closest, distance = self.objective_info(group) + closest.preset_locations.aaa.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.ewrs: + closest, distance = self.objective_info(group) + closest.preset_locations.ewrs.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for group in self.armor_groups: + closest, distance = self.objective_info(group) + closest.preset_locations.armor_groups.append( + PointWithHeading.from_point( + group.position, Heading.from_degrees(group.units[0].heading) + ) + ) + + for static in self.helipads: + closest, distance = self.objective_info(static) + closest.helipads.append( + PointWithHeading.from_point( + static.position, Heading.from_degrees(static.units[0].heading) + ) + ) + + for static in self.factories: + closest, distance = self.objective_info(static) + closest.preset_locations.factories.append( + PointWithHeading.from_point( + static.position, Heading.from_degrees(static.units[0].heading) + ) + ) + + for static in self.ammunition_depots: + closest, distance = self.objective_info(static) + closest.preset_locations.ammunition_depots.append( + PointWithHeading.from_point( + static.position, Heading.from_degrees(static.units[0].heading) + ) + ) + + for static in self.strike_targets: + closest, distance = self.objective_info(static) + closest.preset_locations.strike_locations.append( + PointWithHeading.from_point( + static.position, Heading.from_degrees(static.units[0].heading) + ) + ) + + for scenery_group in self.scenery: + closest, distance = self.objective_info(scenery_group) + closest.preset_locations.scenery.append(scenery_group) + + def populate_theater(self) -> None: + for control_point in self.control_points.values(): + self.theater.add_controlpoint(control_point) + self.add_preset_locations() + self.add_supply_routes() + self.add_shipping_lanes() diff --git a/resources/campaigns/battle_of_abu_dhabi.json b/resources/campaigns/battle_of_abu_dhabi.yaml similarity index 100% rename from resources/campaigns/battle_of_abu_dhabi.json rename to resources/campaigns/battle_of_abu_dhabi.yaml From 88d52003b31f8e67fb3ff9e5b554289802af97db Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 10 Aug 2021 18:21:52 -0700 Subject: [PATCH 24/41] Allow using yaml for campaign definitions. JSON continues to be supported as well. No additional work needed here, just needed to allow both. --- game/theater/conflicttheater.py | 2 +- qt_ui/main.py | 2 +- qt_ui/windows/newgame/QCampaignList.py | 12 +++++++---- resources/campaigns/battle_of_abu_dhabi.yaml | 21 ++++++++++---------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index c87d8c77..7f9fee11 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -244,7 +244,7 @@ class ConflictTheater: raise KeyError(f"Cannot find ControlPoint with ID {id}") @staticmethod - def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater: + def from_file_data(directory: Path, data: Dict[str, Any]) -> ConflictTheater: theaters = { "Caucasus": CaucasusTheater, "Nevada": NevadaTheater, diff --git a/qt_ui/main.py b/qt_ui/main.py index 70d4dd5b..09cfd011 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -231,7 +231,7 @@ def create_game( # for loadouts) without saving the generated campaign and reloading it the normal # way. inject_custom_payloads(Path(persistency.base_path())) - campaign = Campaign.from_json(campaign_path) + campaign = Campaign.from_file(campaign_path) generator = GameGenerator( FACTIONS[blue], FACTIONS[red], diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 1d74e64a..8d2c1fd2 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Any, Dict, List, Union, Tuple import packaging.version +import yaml from PySide2 import QtGui from PySide2.QtCore import QItemSelectionModel, QModelIndex, Qt from PySide2.QtGui import QStandardItem, QStandardItemModel @@ -41,9 +42,12 @@ class Campaign: path: Path @classmethod - def from_json(cls, path: Path) -> Campaign: + def from_file(cls, path: Path) -> Campaign: with path.open() as campaign_file: - data = json.load(campaign_file) + if path.suffix == ".yaml": + data = yaml.safe_load(campaign_file) + else: + data = json.load(campaign_file) sanitized_theater = data["theater"].replace(" ", "") version_field = data.get("version", "0") @@ -68,7 +72,7 @@ class Campaign: ) def load_theater(self) -> ConflictTheater: - return ConflictTheater.from_json(self.path.parent, self.data) + return ConflictTheater.from_file_data(self.path.parent, self.data) @property def is_out_of_date(self) -> bool: @@ -105,7 +109,7 @@ def load_campaigns() -> List[Campaign]: for path in campaign_dir.glob("*.json"): try: logging.debug(f"Loading campaign from {path}...") - campaign = Campaign.from_json(path) + campaign = Campaign.from_file(path) campaigns.append(campaign) except RuntimeError: logging.exception(f"Unable to load campaign from {path}") diff --git a/resources/campaigns/battle_of_abu_dhabi.yaml b/resources/campaigns/battle_of_abu_dhabi.yaml index 5d6c25ca..ebd2d5d4 100644 --- a/resources/campaigns/battle_of_abu_dhabi.yaml +++ b/resources/campaigns/battle_of_abu_dhabi.yaml @@ -1,11 +1,10 @@ -{ - "name": "Persian Gulf - Battle of Abu Dhabi", - "theater": "Persian Gulf", - "authors": "Colonel Panic", - "recommended_player_faction": "Iran 2015", - "recommended_enemy_faction": "United Arab Emirates 2015", - "description": "

You have managed to establish a foothold near Ras Al Khaima. Continue pushing south.

", - "miz": "battle_of_abu_dhabi.miz", - "performance": 2, - "version": "8.0" -} \ No newline at end of file +--- +name: Persian Gulf - Battle of Abu Dhabi +theater: Persian Gulf +authors: Colonel Panic +recommended_player_faction: Iran 2015 +recommended_enemy_faction: United Arab Emirates 2015 +description:

You have managed to establish a foothold near Ras Al Khaima. Continue pushing south.

+miz: battle_of_abu_dhabi.miz +performance: 2 +version": "8.0" \ No newline at end of file From bc5ffdec8e30829d0b3e244f73831cad9373896c Mon Sep 17 00:00:00 2001 From: RndName Date: Wed, 11 Aug 2021 20:47:58 +0200 Subject: [PATCH 25/41] EWR heading towards conflict implements #1530 --- changelog.md | 1 + gen/sam/ewrs.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 1a2da917..acf402c7 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign AI]** Auto-planning mission range limits are now specified per-aircraft. On average this means that longer range missions will now be plannable. The limit only accounts for the direct distance to the target, not the path taken. * **[Campaign AI]** Transport aircraft will now be bought only if necessary at control points which can produce ground units and are capable to operate transport aircraft. * **[Campaign AI]** Aircraft will now only be automatically purchased or assigned at appropriate bases. Naval aircraft will default to only operating from carriers, Harriers will default to LHAs and shore bases, helicopters will operate from anywhere. This can be customized per-squadron. +* **[Mission Generation]** EWRs are now also headed towards the center of the conflict * **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet). * **[Kneeboard]** QNH (pressure MSL) and temperature have been added to the kneeboard. * **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons. diff --git a/gen/sam/ewrs.py b/gen/sam/ewrs.py index 2ffc93da..854337b6 100644 --- a/gen/sam/ewrs.py +++ b/gen/sam/ewrs.py @@ -16,7 +16,11 @@ class EwrGenerator(VehicleGroupGenerator[EwrGroundObject]): def generate(self) -> None: self.add_unit( - self.unit_type, "EWR", self.position.x, self.position.y, self.heading + self.unit_type, + "EWR", + self.position.x, + self.position.y, + self.heading_to_conflict(), ) From ee8e8d4a9a93953a3aee6ccee0cfd5de175e565a Mon Sep 17 00:00:00 2001 From: RndName Date: Thu, 12 Aug 2021 14:58:17 +0200 Subject: [PATCH 26/41] Fix selling of units not visible allow pending_deliveries to become negative again but still delete the delivery when the amount of a specific unit_type comes to exactly 0 to prevent emtpy group sizes --- changelog.md | 1 + game/unitdelivery.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index acf402c7..7a105b53 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,7 @@ Saves from 4.x are not compatible with 5.0. ## Fixes * **[Campaign]** Naval control points will no longer claim ground objectives during campaign generation and prevent them from spawning. +* **[UI]** Selling of Units is now visible again in the UI dialog and shows the correct amount of sold units # 4.1.1 diff --git a/game/unitdelivery.py b/game/unitdelivery.py index cf1af512..9bba6130 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -40,9 +40,8 @@ class PendingUnitDeliveries: def sell(self, units: dict[UnitType[Any], int]) -> None: for k, v in units.items(): - if self.units[k] > v: - self.units[k] -= v - else: + self.units[k] -= v + if self.units[k] == 0: del self.units[k] def refund_all(self, coalition: Coalition) -> None: From adeebbc422f89b8392e68ac8ee7161ed63fbc7e0 Mon Sep 17 00:00:00 2001 From: RndName Date: Thu, 12 Aug 2021 18:51:28 +0200 Subject: [PATCH 27/41] Enable sell button according to available aircraft also added a tooltip which gives the user the hint that maybe the aircraft is assigned to mission and therefore the button is disabled --- changelog.md | 1 + qt_ui/windows/basemenu/QRecruitBehaviour.py | 18 +++++++ .../airfield/QAircraftRecruitmentMenu.py | 48 ++++++++++++------- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/changelog.md b/changelog.md index 7a105b53..d138bf04 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ Saves from 4.x are not compatible with 5.0. * **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet). * **[Kneeboard]** QNH (pressure MSL) and temperature have been added to the kneeboard. * **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons. +* **[UI]** Sell Button for aircraft will be disabled if there are no units available to be sold or all are already assigned to a mission ## Fixes diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 77b0258b..21684682 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -80,7 +80,13 @@ class PurchaseGroup(QGroupBox): def update_state(self) -> None: self.buy_button.setEnabled(self.recruiter.enable_purchase(self.unit_type)) + self.buy_button.setToolTip( + self.recruiter.purchase_tooltip(self.buy_button.isEnabled()) + ) self.sell_button.setEnabled(self.recruiter.enable_sale(self.unit_type)) + self.sell_button.setToolTip( + self.recruiter.sell_tooltip(self.sell_button.isEnabled()) + ) self.amount_bought.setText(f"{self.pending_units}") @@ -223,6 +229,18 @@ class QRecruitBehaviour: def enable_sale(self, unit_type: UnitType) -> bool: return True + def purchase_tooltip(self, is_enabled: bool) -> str: + if is_enabled: + return "Buy unit. Use Shift or Ctrl key to buy multiple units at once." + else: + return "Unit can not be bought." + + def sell_tooltip(self, is_enabled: bool) -> str: + if is_enabled: + return "Sell unit. Use Shift or Ctrl key to buy multiple units at once." + else: + return "Unit can not be sold." + def info(self, unit_type: UnitType) -> None: self.info_window = QUnitInfoWindow(self.game_model.game, unit_type) self.info_window.show() diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index e435fd75..88ddbffd 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -85,9 +85,13 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): return True def enable_sale(self, unit_type: AircraftType) -> bool: - if not self.cp.can_operate(unit_type): - return False - return True + return self.can_be_sold(unit_type) + + def sell_tooltip(self, is_enabled: bool) -> str: + if is_enabled: + return "Sell unit. Use Shift or Ctrl key to sell multiple units at once." + else: + return "Can not be sold because either no aircraft are available or are already assigned to a mission." def buy(self, unit_type: AircraftType) -> bool: if self.maximum_units > 0: @@ -112,24 +116,32 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.hangar_status.update_label() return True + def can_be_sold(self, unit_type: AircraftType) -> bool: + inventory = self.game_model.game.aircraft_inventory.for_control_point(self.cp) + pending_deliveries = self.pending_deliveries.units.get(unit_type, 0) + return self.cp.can_operate(unit_type) and ( + pending_deliveries > 0 or inventory.available(unit_type) > 0 + ) + def sell(self, unit_type: AircraftType) -> bool: # Don't need to remove aircraft from the inventory if we're canceling # orders. - 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: - inventory.remove_aircraft(unit_type, 1) - except ValueError: - QMessageBox.critical( - self, - "Could not sell aircraft", - f"Attempted to sell one {unit_type} at {self.cp.name} " - "but none are available. Are all aircraft currently " - "assigned to a mission?", - QMessageBox.Ok, - ) - return False + if not self.can_be_sold(unit_type): + QMessageBox.critical( + self, + "Could not sell aircraft", + f"Attempted to sell one {unit_type} at {self.cp.name} " + "but none are available. Are all aircraft currently " + "assigned to a mission?", + QMessageBox.Ok, + ) + return False + + inventory = self.game_model.game.aircraft_inventory.for_control_point(self.cp) + pending_deliveries = self.pending_deliveries.units.get(unit_type, 0) + if pending_deliveries <= 0 < inventory.available(unit_type): + inventory.remove_aircraft(unit_type, 1) + super().sell(unit_type) self.hangar_status.update_label() From c80d0e537800b21135c1da9dc3004f5606995553 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Sat, 14 Aug 2021 00:54:48 +0200 Subject: [PATCH 28/41] Raise helo AGL to 60 m for ingress, egress, cap, escort (#1542) --- gen/flights/waypointbuilder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 15adde01..3c5851fd 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -213,7 +213,7 @@ class WaypointBuilder: ingress_type, position.x, position.y, - meters(50) if self.is_helo else self.doctrine.ingress_altitude, + meters(60) if self.is_helo else self.doctrine.ingress_altitude, ) if self.is_helo: waypoint.alt_type = "RADIO" @@ -228,7 +228,7 @@ class WaypointBuilder: FlightWaypointType.EGRESS, position.x, position.y, - meters(50) if self.is_helo else self.doctrine.ingress_altitude, + meters(60) if self.is_helo else self.doctrine.ingress_altitude, ) if self.is_helo: waypoint.alt_type = "RADIO" @@ -312,7 +312,7 @@ class WaypointBuilder: FlightWaypointType.CAS, position.x, position.y, - meters(50) if self.is_helo else meters(1000), + meters(60) if self.is_helo else meters(1000), ) waypoint.alt_type = "RADIO" waypoint.description = "Provide CAS" @@ -448,7 +448,7 @@ class WaypointBuilder: FlightWaypointType.TARGET_GROUP_LOC, target.position.x, target.position.y, - meters(50) if self.is_helo else self.doctrine.ingress_altitude, + meters(60) if self.is_helo else self.doctrine.ingress_altitude, ) if self.is_helo: waypoint.alt_type = "RADIO" From b5b0d82a1a6f59e55f59bdb83f4174fd9656c480 Mon Sep 17 00:00:00 2001 From: RndName Date: Thu, 12 Aug 2021 23:32:09 +0200 Subject: [PATCH 29/41] Open all files with utf-8 encoding - will not be used for binary read/writes (rb,wb)! - prevents a bug where units with special characters in the unit name can not be tracked anymore as there will be a name mismatch due to wrong encoding --- changelog.md | 1 + game/dcs/aircrafttype.py | 2 +- game/dcs/groundunittype.py | 2 +- game/debriefing.py | 2 +- game/operation/operation.py | 2 +- game/version.py | 2 +- qt_ui/main.py | 3 ++- qt_ui/windows/QWaitingForMissionResultWindow.py | 2 +- qt_ui/windows/newgame/QCampaignList.py | 2 +- 9 files changed, 10 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index d138bf04..8cb857c9 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ Saves from 4.x are not compatible with 5.0. ## Fixes * **[Campaign]** Naval control points will no longer claim ground objectives during campaign generation and prevent them from spawning. +* **[Mission Generation]** Mission results and other files will now be opened with enforced utf-8 encoding to prevent an issue where destroyed ground units were untracked because of special characters in their names. * **[UI]** Selling of Units is now visible again in the UI dialog and shows the correct amount of sold units # 4.1.1 diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 1ce958ca..b54d9bc0 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -325,7 +325,7 @@ class AircraftType(UnitType[Type[FlyingType]]): logging.warning(f"No data for {aircraft.id}; it will not be available") return - with data_path.open() as data_file: + with data_path.open(encoding="utf-8") as data_file: data = yaml.safe_load(data_file) try: diff --git a/game/dcs/groundunittype.py b/game/dcs/groundunittype.py index c22d8a21..db93aa49 100644 --- a/game/dcs/groundunittype.py +++ b/game/dcs/groundunittype.py @@ -67,7 +67,7 @@ class GroundUnitType(UnitType[Type[VehicleType]]): logging.warning(f"No data for {vehicle.id}; it will not be available") return - with data_path.open() as data_file: + with data_path.open(encoding="utf-8") as data_file: data = yaml.safe_load(data_file) try: diff --git a/game/debriefing.py b/game/debriefing.py index b2a155ad..e756f043 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -386,7 +386,7 @@ class PollDebriefingFileThread(threading.Thread): os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified ): - with open("state.json", "r") as json_file: + with open("state.json", "r", encoding="utf-8") as json_file: json_data = json.load(json_file) debriefing = Debriefing(json_data, self.game, self.unit_map) self.callback(debriefing) diff --git a/game/operation/operation.py b/game/operation/operation.py index 3c070ba9..87b440d6 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -65,7 +65,7 @@ class Operation: @classmethod def prepare(cls, game: Game) -> None: - with open("resources/default_options.lua", "r") as f: + with open("resources/default_options.lua", "r", encoding="utf-8") as f: options_dict = loads(f.read())["options"] cls._set_mission(Mission(game.theater.terrain)) cls.game = game diff --git a/game/version.py b/game/version.py index 87d8a841..d89d875d 100644 --- a/game/version.py +++ b/game/version.py @@ -12,7 +12,7 @@ def _build_version_string() -> str: ] build_number_path = Path("resources/buildnumber") if build_number_path.exists(): - with build_number_path.open("r") as build_number_file: + with build_number_path.open("r", encoding="utf-8") as build_number_file: components.append(build_number_file.readline()) if not Path("resources/final").exists(): diff --git a/qt_ui/main.py b/qt_ui/main.py index 09cfd011..67609d11 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -64,7 +64,8 @@ def run_ui(game: Optional[Game]) -> None: # init the theme and load the stylesheet based on the theme index liberation_theme.init() with open( - "./resources/stylesheets/" + liberation_theme.get_theme_css_file() + "./resources/stylesheets/" + liberation_theme.get_theme_css_file(), + encoding="utf-8", ) as stylesheet: logging.info("Loading stylesheet: %s", liberation_theme.get_theme_css_file()) app.setStyleSheet(stylesheet.read()) diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index f4ce44fb..0361fcaa 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -228,7 +228,7 @@ class QWaitingForMissionResultWindow(QDialog): ) print(file) try: - with open(file[0], "r") as json_file: + with open(file[0], "r", encoding="utf-8") as json_file: json_data = json.load(json_file) json_data["mission_ended"] = True debriefing = Debriefing(json_data, self.game, self.unit_map) diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 8d2c1fd2..1fae35bc 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -43,7 +43,7 @@ class Campaign: @classmethod def from_file(cls, path: Path) -> Campaign: - with path.open() as campaign_file: + with path.open(encoding="utf-8") as campaign_file: if path.suffix == ".yaml": data = yaml.safe_load(campaign_file) else: From 103675e5bb175614d828c45d1dfd17d189978afd Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Sat, 14 Aug 2021 21:45:23 +0200 Subject: [PATCH 30/41] Make some paths cross-platform compatible (#1543) * Make some paths cross-platform compatible * Fix lint error for Path --- game/theater/conflicttheater.py | 14 +++++++------- game/theater/landmap.py | 3 ++- qt_ui/windows/newgame/QCampaignList.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 7f9fee11..dc2d3e53 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -292,7 +292,7 @@ class CaucasusTheater(ConflictTheater): ReferencePoint(caucasus.Batumi.position, Point(1307, 1205)), ) - landmap = load_landmap("resources\\caulandmap.p") + landmap = load_landmap(Path("resources/caulandmap.p")) daytime_map = { "dawn": (6, 9), "day": (9, 18), @@ -320,7 +320,7 @@ class PersianGulfTheater(ConflictTheater): ReferencePoint(persiangulf.Jiroft.position, Point(1692, 1343)), ReferencePoint(persiangulf.Liwa_AFB.position, Point(358, 3238)), ) - landmap = load_landmap("resources\\gulflandmap.p") + landmap = load_landmap(Path("resources/gulflandmap.p")) daytime_map = { "dawn": (6, 8), "day": (8, 16), @@ -348,7 +348,7 @@ class NevadaTheater(ConflictTheater): ReferencePoint(nevada.Mina_Airport_3Q0.position, Point(252, 295)), ReferencePoint(nevada.Laughlin_Airport.position, Point(844, 909)), ) - landmap = load_landmap("resources\\nevlandmap.p") + landmap = load_landmap(Path("resources/nevlandmap.p")) daytime_map = { "dawn": (4, 6), "day": (6, 17), @@ -376,7 +376,7 @@ class NormandyTheater(ConflictTheater): ReferencePoint(normandy.Needs_Oar_Point.position, Point(515, 329)), ReferencePoint(normandy.Evreux.position, Point(2029, 1709)), ) - landmap = load_landmap("resources\\normandylandmap.p") + landmap = load_landmap(Path("resources/normandylandmap.p")) daytime_map = { "dawn": (6, 8), "day": (10, 17), @@ -404,7 +404,7 @@ class TheChannelTheater(ConflictTheater): ReferencePoint(thechannel.Abbeville_Drucat.position, Point(2005, 2390)), ReferencePoint(thechannel.Detling.position, Point(706, 382)), ) - landmap = load_landmap("resources\\channellandmap.p") + landmap = load_landmap(Path("resources/channellandmap.p")) daytime_map = { "dawn": (6, 8), "day": (10, 17), @@ -432,7 +432,7 @@ class SyriaTheater(ConflictTheater): ReferencePoint(syria.Eyn_Shemer.position, Point(564, 1289)), ReferencePoint(syria.Tabqa.position, Point(1329, 491)), ) - landmap = load_landmap("resources\\syrialandmap.p") + landmap = load_landmap(Path("resources/syrialandmap.p")) daytime_map = { "dawn": (6, 8), "day": (8, 16), @@ -457,7 +457,7 @@ class MarianaIslandsTheater(ConflictTheater): terrain = marianaislands.MarianaIslands() overview_image = "marianaislands.gif" - landmap = load_landmap("resources\\marianaislandslandmap.p") + landmap = load_landmap(Path("resources/marianaislandslandmap.p")) daytime_map = { "dawn": (6, 8), "day": (8, 16), diff --git a/game/theater/landmap.py b/game/theater/landmap.py index 2cc3867c..8192b057 100644 --- a/game/theater/landmap.py +++ b/game/theater/landmap.py @@ -3,6 +3,7 @@ import pickle from functools import cached_property from typing import Optional, Tuple, Union import logging +from pathlib import Path from shapely import geometry from shapely.geometry import MultiPolygon, Polygon @@ -27,7 +28,7 @@ class Landmap: return self.inclusion_zones - self.exclusion_zones - self.sea_zones -def load_landmap(filename: str) -> Optional[Landmap]: +def load_landmap(filename: Path) -> Optional[Landmap]: try: with open(filename, "rb") as f: return pickle.load(f) diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 1fae35bc..1832baa4 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -104,7 +104,7 @@ class Campaign: def load_campaigns() -> List[Campaign]: - campaign_dir = Path("resources\\campaigns") + campaign_dir = Path("resources/campaigns") campaigns = [] for path in campaign_dir.glob("*.json"): try: From d2e22ef8bfd5083888852ec0dd8d64443cae7256 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 12:11:25 -0700 Subject: [PATCH 31/41] More campaign loader cleanup. --- game/campaignloader/__init__.py | 1 + game/campaignloader/campaign.py | 148 ++++++++++++++++++ .../mizcampaignloader.py | 4 +- game/theater/conflicttheater.py | 28 +--- game/theater/start_generator.py | 11 -- qt_ui/main.py | 2 +- qt_ui/windows/newgame/QCampaignList.py | 112 +------------ qt_ui/windows/newgame/QNewGameWizard.py | 14 +- 8 files changed, 164 insertions(+), 156 deletions(-) create mode 100644 game/campaignloader/__init__.py create mode 100644 game/campaignloader/campaign.py rename game/{theater => campaignloader}/mizcampaignloader.py (99%) diff --git a/game/campaignloader/__init__.py b/game/campaignloader/__init__.py new file mode 100644 index 00000000..ed7204dc --- /dev/null +++ b/game/campaignloader/__init__.py @@ -0,0 +1 @@ +from .campaign import Campaign diff --git a/game/campaignloader/campaign.py b/game/campaignloader/campaign.py new file mode 100644 index 00000000..ab13e61c --- /dev/null +++ b/game/campaignloader/campaign.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import json +import logging +from collections import Iterator +from dataclasses import dataclass +from pathlib import Path +from typing import Tuple, Dict, Any + +from packaging.version import Version +import yaml + +from game.campaignloader.mizcampaignloader import MizCampaignLoader +from game.profiling import logged_duration +from game.theater import ( + ConflictTheater, + CaucasusTheater, + NevadaTheater, + PersianGulfTheater, + NormandyTheater, + TheChannelTheater, + SyriaTheater, + MarianaIslandsTheater, +) +from game.version import CAMPAIGN_FORMAT_VERSION + + +PERF_FRIENDLY = 0 +PERF_MEDIUM = 1 +PERF_HARD = 2 +PERF_NASA = 3 + + +@dataclass(frozen=True) +class Campaign: + name: str + icon_name: str + authors: str + description: str + + #: The revision of the campaign format the campaign was built for. We do not attempt + #: to migrate old campaigns, but this is used to show a warning in the UI when + #: selecting a campaign that is not up to date. + version: Tuple[int, int] + + recommended_player_faction: str + recommended_enemy_faction: str + performance: int + data: Dict[str, Any] + path: Path + + @classmethod + def from_file(cls, path: Path) -> Campaign: + with path.open() as campaign_file: + if path.suffix == ".yaml": + data = yaml.safe_load(campaign_file) + else: + data = json.load(campaign_file) + + sanitized_theater = data["theater"].replace(" ", "") + version_field = data.get("version", "0") + try: + version = Version(version_field) + except TypeError: + logging.warning( + f"Non-string campaign version in {path}. Parse may be incorrect." + ) + version = Version(str(version_field)) + return cls( + data["name"], + f"Terrain_{sanitized_theater}", + data.get("authors", "???"), + data.get("description", ""), + (version.major, version.minor), + data.get("recommended_player_faction", "USA 2005"), + data.get("recommended_enemy_faction", "Russia 1990"), + data.get("performance", 0), + data, + path, + ) + + def load_theater(self) -> ConflictTheater: + theaters = { + "Caucasus": CaucasusTheater, + "Nevada": NevadaTheater, + "Persian Gulf": PersianGulfTheater, + "Normandy": NormandyTheater, + "The Channel": TheChannelTheater, + "Syria": SyriaTheater, + "MarianaIslands": MarianaIslandsTheater, + } + theater = theaters[self.data["theater"]] + t = theater() + + try: + miz = self.data["miz"] + except KeyError as ex: + raise RuntimeError( + "Old format (non-miz) campaigns are no longer supported." + ) from ex + + with logged_duration("Importing miz data"): + MizCampaignLoader(self.path.parent / miz, t).populate_theater() + return t + + @property + def is_out_of_date(self) -> bool: + """Returns True if this campaign is not up to date with the latest format. + + This is more permissive than is_from_future, which is sensitive to minor version + bumps (the old game definitely doesn't support the minor features added in the + new version, and the campaign may require them. However, the minor version only + indicates *optional* new features, so we do not need to mark out of date + campaigns as incompatible if they are within the same major version. + """ + return self.version[0] < CAMPAIGN_FORMAT_VERSION[0] + + @property + def is_from_future(self) -> bool: + """Returns True if this campaign is newer than the supported format.""" + return self.version > CAMPAIGN_FORMAT_VERSION + + @property + def is_compatible(self) -> bool: + """Returns True is this campaign was built for this version of the game.""" + if self.version == (0, 0): + return False + if self.is_out_of_date: + return False + if self.is_from_future: + return False + return True + + @staticmethod + def iter_campaign_defs() -> Iterator[Path]: + campaign_dir = Path("resources/campaigns") + yield from campaign_dir.glob("*.json") + yield from campaign_dir.glob("*.yaml") + + @classmethod + def load_each(cls) -> Iterator[Campaign]: + for path in cls.iter_campaign_defs(): + try: + logging.debug(f"Loading campaign from {path}...") + campaign = Campaign.from_file(path) + yield campaign + except RuntimeError: + logging.exception(f"Unable to load campaign from {path}") diff --git a/game/theater/mizcampaignloader.py b/game/campaignloader/mizcampaignloader.py similarity index 99% rename from game/theater/mizcampaignloader.py rename to game/campaignloader/mizcampaignloader.py index a2565029..ab99e265 100644 --- a/game/theater/mizcampaignloader.py +++ b/game/campaignloader/mizcampaignloader.py @@ -20,7 +20,7 @@ from game.positioned import Positioned from game.profiling import logged_duration from game.scenery_group import SceneryGroup from game.utils import Distance, meters, Heading -from .controlpoint import ( +from game.theater.controlpoint import ( Airfield, Carrier, ControlPoint, @@ -30,7 +30,7 @@ from .controlpoint import ( ) if TYPE_CHECKING: - from .conflicttheater import ConflictTheater + from game.theater.conflicttheater import ConflictTheater class MizCampaignLoader: diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index dc2d3e53..4ac622cf 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -23,13 +23,11 @@ from .controlpoint import ( ControlPoint, MissionTarget, ) -from .mizcampaignloader import MizCampaignLoader -from .seasonalconditions import SeasonalConditions from .frontline import FrontLine from .landmap import Landmap, load_landmap, poly_contains from .latlon import LatLon from .projections import TransverseMercator -from ..profiling import logged_duration +from .seasonalconditions import SeasonalConditions if TYPE_CHECKING: from . import TheaterGroundObject @@ -243,30 +241,6 @@ class ConflictTheater: return i raise KeyError(f"Cannot find ControlPoint with ID {id}") - @staticmethod - def from_file_data(directory: Path, data: Dict[str, Any]) -> ConflictTheater: - theaters = { - "Caucasus": CaucasusTheater, - "Nevada": NevadaTheater, - "Persian Gulf": PersianGulfTheater, - "Normandy": NormandyTheater, - "The Channel": TheChannelTheater, - "Syria": SyriaTheater, - "MarianaIslands": MarianaIslandsTheater, - } - theater = theaters[data["theater"]] - t = theater() - - miz = data.get("miz", None) - if miz is None: - raise RuntimeError( - "Old format (non-miz) campaigns are no longer supported." - ) - - with logged_duration("Importing miz data"): - MizCampaignLoader(directory / miz, t).populate_theater() - return t - @property def seasonal_conditions(self) -> SeasonalConditions: raise NotImplementedError diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 61cc25af..4ad78ec3 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -54,17 +54,6 @@ from ..settings import Settings GroundObjectTemplates = Dict[str, Dict[str, Any]] -UNIT_VARIETY = 6 -UNIT_AMOUNT_FACTOR = 16 -UNIT_COUNT_IMPORTANCE_LOG = 1.3 - -COUNT_BY_TASK = { - PinpointStrike: 12, - CAP: 8, - CAS: 4, - AirDefence: 1, -} - @dataclass(frozen=True) class GeneratorSettings: diff --git a/qt_ui/main.py b/qt_ui/main.py index 67609d11..e7aad522 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -27,7 +27,7 @@ from qt_ui import ( ) from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.QLiberationWindow import QLiberationWindow -from qt_ui.windows.newgame.QCampaignList import Campaign +from game.campaignloader.campaign import Campaign from qt_ui.windows.newgame.QNewGameWizard import DEFAULT_BUDGET from qt_ui.windows.preferences.QLiberationFirstStartWindow import ( QLiberationFirstStartWindow, diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 1832baa4..a5fcf305 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -1,120 +1,14 @@ from __future__ import annotations -import json -import logging -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Dict, List, Union, Tuple +from typing import Optional -import packaging.version -import yaml from PySide2 import QtGui from PySide2.QtCore import QItemSelectionModel, QModelIndex, Qt from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtWidgets import QAbstractItemView, QListView import qt_ui.uiconstants as CONST -from game.theater import ConflictTheater -from game.version import CAMPAIGN_FORMAT_VERSION - -PERF_FRIENDLY = 0 -PERF_MEDIUM = 1 -PERF_HARD = 2 -PERF_NASA = 3 - - -@dataclass(frozen=True) -class Campaign: - name: str - icon_name: str - authors: str - description: str - - #: The revision of the campaign format the campaign was built for. We do not attempt - #: to migrate old campaigns, but this is used to show a warning in the UI when - #: selecting a campaign that is not up to date. - version: Tuple[int, int] - - recommended_player_faction: str - recommended_enemy_faction: str - performance: Union[PERF_FRIENDLY, PERF_MEDIUM, PERF_HARD, PERF_NASA] - data: Dict[str, Any] - path: Path - - @classmethod - def from_file(cls, path: Path) -> Campaign: - with path.open(encoding="utf-8") as campaign_file: - if path.suffix == ".yaml": - data = yaml.safe_load(campaign_file) - else: - data = json.load(campaign_file) - - sanitized_theater = data["theater"].replace(" ", "") - version_field = data.get("version", "0") - try: - version = packaging.version.parse(version_field) - except TypeError: - logging.warning( - f"Non-string campaign version in {path}. Parse may be incorrect." - ) - version = packaging.version.parse(str(version_field)) - return cls( - data["name"], - f"Terrain_{sanitized_theater}", - data.get("authors", "???"), - data.get("description", ""), - (version.major, version.minor), - data.get("recommended_player_faction", "USA 2005"), - data.get("recommended_enemy_faction", "Russia 1990"), - data.get("performance", 0), - data, - path, - ) - - def load_theater(self) -> ConflictTheater: - return ConflictTheater.from_file_data(self.path.parent, self.data) - - @property - def is_out_of_date(self) -> bool: - """Returns True if this campaign is not up to date with the latest format. - - This is more permissive than is_from_future, which is sensitive to minor version - bumps (the old game definitely doesn't support the minor features added in the - new version, and the campaign may require them. However, the minor version only - indicates *optional* new features, so we do not need to mark out of date - campaigns as incompatible if they are within the same major version. - """ - return self.version[0] < CAMPAIGN_FORMAT_VERSION[0] - - @property - def is_from_future(self) -> bool: - """Returns True if this campaign is newer than the supported format.""" - return self.version > CAMPAIGN_FORMAT_VERSION - - @property - def is_compatible(self) -> bool: - """Returns True is this campaign was built for this version of the game.""" - if not self.version: - return False - if self.is_out_of_date: - return False - if self.is_from_future: - return False - return True - - -def load_campaigns() -> List[Campaign]: - campaign_dir = Path("resources/campaigns") - campaigns = [] - for path in campaign_dir.glob("*.json"): - try: - logging.debug(f"Loading campaign from {path}...") - campaign = Campaign.from_file(path) - campaigns.append(campaign) - except RuntimeError: - logging.exception(f"Unable to load campaign from {path}") - - return sorted(campaigns, key=lambda x: x.name) +from game.campaignloader.campaign import Campaign class QCampaignItem(QStandardItem): @@ -144,7 +38,7 @@ class QCampaignList(QListView): self.setup_content(show_incompatible) @property - def selected_campaign(self) -> Campaign: + def selected_campaign(self) -> Optional[Campaign]: return self.currentIndex().data(QCampaignList.CampaignRole) def setup_content(self, show_incompatible: bool) -> None: diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index b29a4806..0bbd0715 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -10,17 +10,14 @@ from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QLabel, QCheckBox from jinja2 import Environment, FileSystemLoader, select_autoescape from game import db +from game.campaignloader.campaign import Campaign from game.settings import Settings from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings from game.factions.faction import Faction from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs, CurrencySpinner from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog -from qt_ui.windows.newgame.QCampaignList import ( - Campaign, - QCampaignList, - load_campaigns, -) +from qt_ui.windows.newgame.QCampaignList import QCampaignList jinja_env = Environment( loader=FileSystemLoader("resources/ui/templates"), @@ -41,7 +38,7 @@ class NewGameWizard(QtWidgets.QWizard): def __init__(self, parent=None): super(NewGameWizard, self).__init__(parent) - self.campaigns = load_campaigns() + self.campaigns = list(sorted(Campaign.load_each(), key=lambda x: x.name)) self.faction_selection_page = FactionSelection() self.addPage(IntroPage()) @@ -369,6 +366,11 @@ class TheaterConfiguration(QtWidgets.QWizardPage): ) campaign = campaignList.selected_campaign self.setField("selectedCampaign", campaign) + if campaign is None: + self.campaignMapDescription.setText("No campaign selected") + self.performanceText.setText("No campaign selected") + return + self.campaignMapDescription.setText(template.render({"campaign": campaign})) self.faction_selection.setDefaultFactions(campaign) self.performanceText.setText( From 8e1b33bc5102db5ebcbe996348263b51c90fdd85 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 19:35:09 -0700 Subject: [PATCH 32/41] Add range info for some Russian aircraft. --- resources/units/aircraft/MiG-25PD.yaml | 4 +++- resources/units/aircraft/MiG-25RBT.yaml | 4 +++- resources/units/aircraft/MiG-31.yaml | 4 +++- resources/units/aircraft/Su-25.yaml | 4 +++- resources/units/aircraft/Su-25T.yaml | 4 +++- resources/units/aircraft/Su-27.yaml | 4 +++- resources/units/aircraft/Su-30.yaml | 4 +++- resources/units/aircraft/Su-33.yaml | 4 +++- resources/units/aircraft/Su-34.yaml | 4 +++- resources/units/aircraft/Tu-160.yaml | 4 +++- 10 files changed, 30 insertions(+), 10 deletions(-) diff --git a/resources/units/aircraft/MiG-25PD.yaml b/resources/units/aircraft/MiG-25PD.yaml index ed5b0e43..85c2afd5 100644 --- a/resources/units/aircraft/MiG-25PD.yaml +++ b/resources/units/aircraft/MiG-25PD.yaml @@ -1,4 +1,5 @@ -description: "The Mikoyan-Gurevich MiG-25 (Russian: \u041C\u0438\u043A\u043E\u044F\ +description: + "The Mikoyan-Gurevich MiG-25 (Russian: \u041C\u0438\u043A\u043E\u044F\ \u043D \u0438 \u0413\u0443\u0440\u0435\u0432\u0438\u0447 \u041C\u0438\u0413-25;\ \ NATO reporting name: Foxbat) is a supersonic interceptor and reconnaissance aircraft\ \ that was among the fastest military aircraft to enter service. It was designed\ @@ -24,5 +25,6 @@ manufacturer: Mikoyan-Gurevich origin: USSR/Russia price: 17 role: Interceptor +max_range: 500 variants: MiG-25PD Foxbat-E: {} diff --git a/resources/units/aircraft/MiG-25RBT.yaml b/resources/units/aircraft/MiG-25RBT.yaml index 99d65253..15ffc5ca 100644 --- a/resources/units/aircraft/MiG-25RBT.yaml +++ b/resources/units/aircraft/MiG-25RBT.yaml @@ -1,4 +1,5 @@ -description: "The Mikoyan-Gurevich MiG-25 (Russian: \u041C\u0438\u043A\u043E\u044F\ +description: + "The Mikoyan-Gurevich MiG-25 (Russian: \u041C\u0438\u043A\u043E\u044F\ \u043D \u0438 \u0413\u0443\u0440\u0435\u0432\u0438\u0447 \u041C\u0438\u0413-25;\ \ NATO reporting name: Foxbat) is a supersonic interceptor and reconnaissance aircraft\ \ that was among the fastest military aircraft to enter service. It was designed\ @@ -24,5 +25,6 @@ manufacturer: Mikoyan-Gurevich origin: USSR/Russia price: 17 role: Strike Fighter +max_range: 500 variants: MiG-25RBT Foxbat-B: {} diff --git a/resources/units/aircraft/MiG-31.yaml b/resources/units/aircraft/MiG-31.yaml index 753404fd..f38d54dc 100644 --- a/resources/units/aircraft/MiG-31.yaml +++ b/resources/units/aircraft/MiG-31.yaml @@ -1,4 +1,5 @@ -description: "The Mikoyan MiG-31 (Russian: \u041C\u0438\u043A\u043E\u044F\u043D \u041C\ +description: + "The Mikoyan MiG-31 (Russian: \u041C\u0438\u043A\u043E\u044F\u043D \u041C\ \u0438\u0413-31; NATO reporting name: Foxhound) is a supersonic interceptor aircraft\ \ that was developed for use by the Soviet Air Forces. The aircraft was designed\ \ by the Mikoyan design bureau as a replacement for the earlier MiG-25 \"Foxbat\"\ @@ -14,5 +15,6 @@ manufacturer: Mikoyan origin: USSR/Russia price: 24 role: Interceptor +max_range: 800 variants: MiG-31 Foxhound: {} diff --git a/resources/units/aircraft/Su-25.yaml b/resources/units/aircraft/Su-25.yaml index 7287262f..8c425bc3 100644 --- a/resources/units/aircraft/Su-25.yaml +++ b/resources/units/aircraft/Su-25.yaml @@ -1,5 +1,6 @@ always_keeps_gun: true -description: The Su-25 'Grach' (Rook), NATO callsigned 'Frogfoot', is a dedicated +description: + The Su-25 'Grach' (Rook), NATO callsigned 'Frogfoot', is a dedicated strike attack aircraft designed for the close air support and anti-tank roles. The Su-25 has seen combat in several conflicts during its more than 30 years in service. The Su-25 combines excellent pilot protection and high speed compared to most dedicated @@ -10,5 +11,6 @@ manufacturer: Sukhoi origin: USSR/Russia price: 11 role: Close Air Support/Attack +max_range: 200 variants: Su-25 Frogfoot: {} diff --git a/resources/units/aircraft/Su-25T.yaml b/resources/units/aircraft/Su-25T.yaml index b4523739..0059c2ca 100644 --- a/resources/units/aircraft/Su-25T.yaml +++ b/resources/units/aircraft/Su-25T.yaml @@ -1,5 +1,6 @@ always_keeps_gun: true -description: The Su-25 'Grach' (Rook), NATO callsigned 'Frogfoot', is a dedicated +description: + The Su-25 'Grach' (Rook), NATO callsigned 'Frogfoot', is a dedicated strike attack aircraft designed for the close air support and anti-tank roles. The Su-25 has seen combat in several conflicts during its more than 30 years in service. The Su-25 combines excellent pilot protection and high speed compared to most dedicated @@ -10,5 +11,6 @@ manufacturer: Sukhoi origin: USSR/Russia price: 18 role: Close Air Support/Attack +max_range: 200 variants: Su-25T Frogfoot: {} diff --git a/resources/units/aircraft/Su-27.yaml b/resources/units/aircraft/Su-27.yaml index cadef3cc..da9f3c87 100644 --- a/resources/units/aircraft/Su-27.yaml +++ b/resources/units/aircraft/Su-27.yaml @@ -1,4 +1,5 @@ -description: The Su-27, NATO codename Flanker, is one of the pillars of modern-day +description: + The Su-27, NATO codename Flanker, is one of the pillars of modern-day Russian combat aviation. Built to counter the American F-15 Eagle, the Flanker is a twin-engine, supersonic, highly manoeuvrable air superiority fighter. The Flanker is equally capable of engaging targets well beyond visual range as it is in a dogfight @@ -13,5 +14,6 @@ manufacturer: Sukhoi origin: USSR/Russia price: 18 role: Air-Superiority Fighter +max_range: 300 variants: Su-27 Flanker-B: {} diff --git a/resources/units/aircraft/Su-30.yaml b/resources/units/aircraft/Su-30.yaml index ad93e33a..d7922a31 100644 --- a/resources/units/aircraft/Su-30.yaml +++ b/resources/units/aircraft/Su-30.yaml @@ -1,4 +1,5 @@ -description: "The Sukhoi Su-30 (Russian: \u0421\u0443\u0445\u043E\u0439 \u0421\u0443\ +description: + "The Sukhoi Su-30 (Russian: \u0421\u0443\u0445\u043E\u0439 \u0421\u0443\ -30; NATO reporting name: Flanker-C/G/H) is a twin-engine, two-seat supermaneuverable\ \ fighter aircraft developed in the Soviet Union by Russia's Sukhoi Aviation Corporation.\ \ It is a multirole fighter for all-weather, air-to-air and air-to-surface deep\ @@ -8,6 +9,7 @@ manufacturer: Sukhoi origin: USSR/Russia price: 23 role: Multirole Fighter +max_range: 300 variants: Su-30 Flanker-C: {} Su-30MKK Flanker-G: diff --git a/resources/units/aircraft/Su-33.yaml b/resources/units/aircraft/Su-33.yaml index 63a5f29f..1b6568da 100644 --- a/resources/units/aircraft/Su-33.yaml +++ b/resources/units/aircraft/Su-33.yaml @@ -1,5 +1,6 @@ carrier_capable: true -description: 'The Su-33 has been the backbone of Russian aircraft carrier aviation +description: + 'The Su-33 has been the backbone of Russian aircraft carrier aviation since the late 1990s and is an all-weather fighter capable of engaging both air and surface targets. Based on the powerful Su-27 "Flanker", the Su-33 is a navalized version suited for operations aboard the Admiral Kuznetsov aircraft carrier. Changes @@ -20,6 +21,7 @@ manufacturer: Sukhoi origin: USSR/Russia price: 22 role: Carrier-based Multirole Fighter +max_range: 300 variants: J-15 Flanker X-2: introduced: 2013 diff --git a/resources/units/aircraft/Su-34.yaml b/resources/units/aircraft/Su-34.yaml index 37d5a4e1..bc971598 100644 --- a/resources/units/aircraft/Su-34.yaml +++ b/resources/units/aircraft/Su-34.yaml @@ -1,4 +1,5 @@ -description: "The Sukhoi Su-34 (Russian: \u0421\u0443\u0445\u043E\u0439 \u0421\u0443\ +description: + "The Sukhoi Su-34 (Russian: \u0421\u0443\u0445\u043E\u0439 \u0421\u0443\ -34; NATO reporting name: Fullback) is a Soviet-origin Russian twin-engine, twin-seat,\ \ all-weather supersonic medium-range fighter-bomber/strike aircraft. It first flew\ \ in 1990, intended for the Soviet Air Forces, and it entered service in 2014 with\ @@ -16,5 +17,6 @@ manufacturer: Sukhoi origin: USSR/Russia price: 26 role: Fighter-Bomber/Strike Fighter +max_range: 300 variants: Su-34 Fullback: {} diff --git a/resources/units/aircraft/Tu-160.yaml b/resources/units/aircraft/Tu-160.yaml index fb5b877a..fcd58b36 100644 --- a/resources/units/aircraft/Tu-160.yaml +++ b/resources/units/aircraft/Tu-160.yaml @@ -1,4 +1,5 @@ -description: "The Tupolev Tu-160 (Russian: \u0422\u0443\u043F\u043E\u043B\u0435\u0432\ +description: + "The Tupolev Tu-160 (Russian: \u0422\u0443\u043F\u043E\u043B\u0435\u0432\ \ \u0422\u0443-160 \u0411\u0435\u043B\u044B\u0439 \u043B\u0435\u0431\u0435\u0434\ \u044C, romanized: Belyj Lebe\u010F, lit.\u2009'White Swan'; NATO reporting name:\ \ Blackjack) is a supersonic, variable-sweep wing heavy strategic bomber designed\ @@ -12,5 +13,6 @@ manufacturer: Tupolev origin: USSR/Russia price: 45 role: Supersonic Strategic Bomber +max_range: 2000 variants: Tu-160 Blackjack: {} From 51e056a765eced3ea18391b55623021f46cad251 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 10 Aug 2021 18:44:45 -0700 Subject: [PATCH 33/41] Convert Black Sea to yaml. --- resources/campaigns/black_sea.json | 9 --------- resources/campaigns/black_sea.yaml | 8 ++++++++ 2 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 resources/campaigns/black_sea.json create mode 100644 resources/campaigns/black_sea.yaml diff --git a/resources/campaigns/black_sea.json b/resources/campaigns/black_sea.json deleted file mode 100644 index 94cc5e02..00000000 --- a/resources/campaigns/black_sea.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Caucasus - Black Sea", - "theater": "Caucasus", - "authors": "Colonel Panic", - "description": "

A medium sized theater with bases along the coast of the Black Sea.

", - "miz": "black_sea.miz", - "performance": 2, - "version": "8.0" -} \ No newline at end of file diff --git a/resources/campaigns/black_sea.yaml b/resources/campaigns/black_sea.yaml new file mode 100644 index 00000000..d015ac36 --- /dev/null +++ b/resources/campaigns/black_sea.yaml @@ -0,0 +1,8 @@ +--- +name: Caucasus - Black Sea +theater: Caucasus +authors: Colonel Panic +description:

A medium sized theater with bases along the coast of the Black Sea.

+miz: black_sea.miz +performance: 2, +version: "8.0" \ No newline at end of file From 90ad1f4a613aa2a15284cff093e14626677ff24f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 7 Aug 2021 21:51:18 -0700 Subject: [PATCH 34/41] Change squadrons to operate out of a single base. https://github.com/dcs-liberation/dcs_liberation/issues/1145 Currently this is fixed at the start of the campaign. The squadron locations are defined by the campaign file. Follow up work: * Track aircraft ownership per-squadron rather than per-airbase. * UI for relocating squadrons. * Ferry missions for squadrons that are relocating. * Auto-relocation (probably only for retreat handling). Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1138 --- changelog.md | 5 +- game/campaignloader/__init__.py | 1 + game/campaignloader/campaign.py | 11 +- game/campaignloader/campaignairwingconfig.py | 68 +++ .../campaignloader/defaultsquadronassigner.py | 142 +++++ game/campaignloader/mizcampaignloader.py | 8 +- game/campaignloader/squadrondefgenerator.py | 82 +++ game/coalition.py | 21 +- game/commander/aircraftallocator.py | 5 +- game/commander/packagebuilder.py | 4 +- game/commander/packagefulfiller.py | 2 +- game/commander/tasks/packageplanningtask.py | 3 +- game/data/doctrine.py | 28 - game/dcs/aircrafttype.py | 4 +- game/event/event.py | 2 +- game/game.py | 14 +- game/inventory.py | 2 +- game/operation/operation.py | 5 +- game/procurement.py | 35 +- game/radio/channels.py | 3 +- game/squadrons.py | 507 ------------------ game/squadrons/__init__.py | 3 + game/squadrons/airwing.py | 74 +++ game/squadrons/operatingbases.py | 33 ++ game/squadrons/pilot.py | 51 ++ game/squadrons/squadron.py | 262 +++++++++ game/squadrons/squadrondef.py | 99 ++++ game/squadrons/squadrondefloader.py | 68 +++ game/theater/conflicttheater.py | 6 + game/theater/controlpoint.py | 3 + game/theater/start_generator.py | 6 +- game/transfers.py | 4 +- game/version.py | 7 +- game/weather.py | 8 - gen/__init__.py | 13 - gen/aircraft.py | 2 +- gen/airsupport.py | 3 +- gen/airsupportgen.py | 3 +- gen/flights/flight.py | 37 +- gen/naming.py | 8 +- qt_ui/main.py | 4 +- qt_ui/models.py | 2 +- .../QPredefinedWaypointSelectionComboBox.py | 6 +- qt_ui/windows/AirWingConfigurationDialog.py | 2 +- qt_ui/windows/AirWingDialog.py | 10 +- qt_ui/windows/SquadronDialog.py | 1 - .../airfield/QAircraftRecruitmentMenu.py | 18 +- .../windows/mission/flight/QFlightCreator.py | 2 +- .../mission/flight/SquadronSelector.py | 2 +- .../flight/settings/QFlightSlotEditor.py | 2 +- qt_ui/windows/newgame/QNewGameWizard.py | 4 +- 51 files changed, 1043 insertions(+), 652 deletions(-) create mode 100644 game/campaignloader/campaignairwingconfig.py create mode 100644 game/campaignloader/defaultsquadronassigner.py create mode 100644 game/campaignloader/squadrondefgenerator.py delete mode 100644 game/squadrons.py create mode 100644 game/squadrons/__init__.py create mode 100644 game/squadrons/airwing.py create mode 100644 game/squadrons/operatingbases.py create mode 100644 game/squadrons/pilot.py create mode 100644 game/squadrons/squadron.py create mode 100644 game/squadrons/squadrondef.py create mode 100644 game/squadrons/squadrondefloader.py diff --git a/changelog.md b/changelog.md index 8cb857c9..e1f3b9e0 100644 --- a/changelog.md +++ b/changelog.md @@ -4,10 +4,10 @@ Saves from 4.x are not compatible with 5.0. ## Features/Improvements - * **[Campaign]** Weather! Theaters now experience weather that is more realistic for the region and its current season. For example, Persian Gulf will have very hot, sunny summers and Marianas will experience lots of rain during fall. These changes affect pressure, temperature, clouds and precipitation. Additionally, temperature will drop during the night, by an amount that is somewhat realistic for the region. * **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated. * **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts. +* **[Campaign]** (WIP) Squadrons now have a home base and will not operate out of other bases. See https://github.com/dcs-liberation/dcs_liberation/issues/1145 for status. * **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions. * **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI. * **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points. @@ -15,9 +15,10 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign AI]** Transport aircraft will now be bought only if necessary at control points which can produce ground units and are capable to operate transport aircraft. * **[Campaign AI]** Aircraft will now only be automatically purchased or assigned at appropriate bases. Naval aircraft will default to only operating from carriers, Harriers will default to LHAs and shore bases, helicopters will operate from anywhere. This can be customized per-squadron. * **[Mission Generation]** EWRs are now also headed towards the center of the conflict +* **[Modding]** Campaigns now specify the squadrons that are present in the campaign, their roles, and their starting bases. Players can customize this at game start but the campaign will choose the defaults. * **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet). * **[Kneeboard]** QNH (pressure MSL) and temperature have been added to the kneeboard. -* **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable or rename squadrons. +* **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable, relocate, or rename squadrons. * **[UI]** Sell Button for aircraft will be disabled if there are no units available to be sold or all are already assigned to a mission ## Fixes diff --git a/game/campaignloader/__init__.py b/game/campaignloader/__init__.py index ed7204dc..937a39b2 100644 --- a/game/campaignloader/__init__.py +++ b/game/campaignloader/__init__.py @@ -1 +1,2 @@ from .campaign import Campaign +from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig diff --git a/game/campaignloader/campaign.py b/game/campaignloader/campaign.py index ab13e61c..29cc45b7 100644 --- a/game/campaignloader/campaign.py +++ b/game/campaignloader/campaign.py @@ -10,7 +10,6 @@ from typing import Tuple, Dict, Any from packaging.version import Version import yaml -from game.campaignloader.mizcampaignloader import MizCampaignLoader from game.profiling import logged_duration from game.theater import ( ConflictTheater, @@ -23,6 +22,8 @@ from game.theater import ( MarianaIslandsTheater, ) from game.version import CAMPAIGN_FORMAT_VERSION +from .campaignairwingconfig import CampaignAirWingConfig +from .mizcampaignloader import MizCampaignLoader PERF_FRIENDLY = 0 @@ -103,6 +104,14 @@ class Campaign: MizCampaignLoader(self.path.parent / miz, t).populate_theater() return t + def load_air_wing_config(self, theater: ConflictTheater) -> CampaignAirWingConfig: + try: + squadron_data = self.data["squadrons"] + except KeyError: + logging.warning(f"Campaign {self.name} does not define any squadrons") + return CampaignAirWingConfig({}) + return CampaignAirWingConfig.from_campaign_data(squadron_data, theater) + @property def is_out_of_date(self) -> bool: """Returns True if this campaign is not up to date with the latest format. diff --git a/game/campaignloader/campaignairwingconfig.py b/game/campaignloader/campaignairwingconfig.py new file mode 100644 index 00000000..3c7acae4 --- /dev/null +++ b/game/campaignloader/campaignairwingconfig.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import logging +from collections import defaultdict +from dataclasses import dataclass +from typing import Any, TYPE_CHECKING, Union + +from gen.flights.flight import FlightType +from game.theater.controlpoint import ControlPoint + +if TYPE_CHECKING: + from game.theater import ConflictTheater + + +@dataclass(frozen=True) +class SquadronConfig: + primary: FlightType + secondary: list[FlightType] + aircraft: list[str] + + @property + def auto_assignable(self) -> set[FlightType]: + return set(self.secondary) | {self.primary} + + @classmethod + def from_data(cls, data: dict[str, Any]) -> SquadronConfig: + secondary_raw = data.get("secondary") + if secondary_raw is None: + secondary = [] + elif isinstance(secondary_raw, str): + secondary = cls.expand_secondary_alias(secondary_raw) + else: + secondary = [FlightType(s) for s in secondary_raw] + + return SquadronConfig( + FlightType(data["primary"]), secondary, data.get("aircraft", []) + ) + + @staticmethod + def expand_secondary_alias(alias: str) -> list[FlightType]: + if alias == "any": + return list(FlightType) + elif alias == "air-to-air": + return [t for t in FlightType if t.is_air_to_air] + elif alias == "air-to-ground": + return [t for t in FlightType if t.is_air_to_ground] + raise KeyError(f"Unknown secondary mission type: {alias}") + + +@dataclass(frozen=True) +class CampaignAirWingConfig: + by_location: dict[ControlPoint, list[SquadronConfig]] + + @classmethod + def from_campaign_data( + cls, data: dict[Union[str, int], Any], theater: ConflictTheater + ) -> CampaignAirWingConfig: + by_location: dict[ControlPoint, list[SquadronConfig]] = defaultdict(list) + for base_id, squadron_configs in data.items(): + if isinstance(base_id, int): + base = theater.find_control_point_by_id(base_id) + else: + base = theater.control_point_named(base_id) + + for squadron_data in squadron_configs: + by_location[base].append(SquadronConfig.from_data(squadron_data)) + + return CampaignAirWingConfig(by_location) diff --git a/game/campaignloader/defaultsquadronassigner.py b/game/campaignloader/defaultsquadronassigner.py new file mode 100644 index 00000000..c529738a --- /dev/null +++ b/game/campaignloader/defaultsquadronassigner.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +import logging +from typing import Optional, TYPE_CHECKING + +from game.squadrons import Squadron +from game.squadrons.squadrondef import SquadronDef +from game.squadrons.squadrondefloader import SquadronDefLoader +from gen.flights.flight import FlightType +from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig +from .squadrondefgenerator import SquadronDefGenerator +from ..dcs.aircrafttype import AircraftType +from ..theater import ControlPoint + +if TYPE_CHECKING: + from game import Game + from game.coalition import Coalition + + +class DefaultSquadronAssigner: + def __init__( + self, config: CampaignAirWingConfig, game: Game, coalition: Coalition + ) -> None: + self.config = config + self.game = game + self.coalition = coalition + self.air_wing = coalition.air_wing + self.squadron_defs = SquadronDefLoader(game, coalition).load() + self.squadron_def_generator = SquadronDefGenerator(self.coalition) + + def claim_squadron_def(self, squadron: SquadronDef) -> None: + try: + self.squadron_defs[squadron.aircraft].remove(squadron) + except ValueError: + pass + + def assign(self) -> None: + for control_point, squadron_configs in self.config.by_location.items(): + if not control_point.is_friendly(self.coalition.player): + continue + for squadron_config in squadron_configs: + squadron_def = self.find_squadron_for(squadron_config, control_point) + if squadron_def is None: + logging.info( + f"{self.coalition.faction.name} has no aircraft compatible " + f"with {squadron_config.primary} at {control_point}" + ) + continue + + self.claim_squadron_def(squadron_def) + squadron = Squadron.create_from( + squadron_def, control_point, self.coalition, self.game + ) + squadron.set_auto_assignable_mission_types( + squadron_config.auto_assignable + ) + self.air_wing.add_squadron(squadron) + + def find_squadron_for( + self, config: SquadronConfig, control_point: ControlPoint + ) -> Optional[SquadronDef]: + for preferred_aircraft in config.aircraft: + squadron_def = self.find_preferred_squadron( + preferred_aircraft, config.primary, control_point + ) + if squadron_def is not None: + return squadron_def + + # If we didn't find any of the preferred types we should use any squadron + # compatible with the primary task. + squadron_def = self.find_squadron_for_task(config.primary, control_point) + if squadron_def is not None: + return squadron_def + + # If we can't find any squadron matching the requirement, we should + # create one. + return self.squadron_def_generator.generate_for_task( + config.primary, control_point + ) + + def find_preferred_squadron( + self, preferred_aircraft: str, task: FlightType, control_point: ControlPoint + ) -> Optional[SquadronDef]: + # Attempt to find a squadron with the name in the request. + squadron_def = self.find_squadron_by_name( + preferred_aircraft, task, control_point + ) + if squadron_def is not None: + return squadron_def + + # If the name didn't match a squadron available to this coalition, try to find + # an aircraft with the matching name that meets the requirements. + try: + aircraft = AircraftType.named(preferred_aircraft) + except KeyError: + # No aircraft with this name. + return None + + if aircraft not in self.coalition.faction.aircrafts: + return None + + squadron_def = self.find_squadron_for_airframe(aircraft, task, control_point) + if squadron_def is not None: + return squadron_def + + # No premade squadron available for this aircraft that meets the requirements, + # so generate one if possible. + return self.squadron_def_generator.generate_for_aircraft(aircraft) + + @staticmethod + def squadron_compatible_with( + squadron: SquadronDef, task: FlightType, control_point: ControlPoint + ) -> bool: + return squadron.operates_from(control_point) and task in squadron.mission_types + + def find_squadron_for_airframe( + self, aircraft: AircraftType, task: FlightType, control_point: ControlPoint + ) -> Optional[SquadronDef]: + for squadron in self.squadron_defs[aircraft]: + if self.squadron_compatible_with(squadron, task, control_point): + return squadron + return None + + def find_squadron_by_name( + self, name: str, task: FlightType, control_point: ControlPoint + ) -> Optional[SquadronDef]: + for squadrons in self.squadron_defs.values(): + for squadron in squadrons: + if squadron.name == name and self.squadron_compatible_with( + squadron, task, control_point + ): + return squadron + return None + + def find_squadron_for_task( + self, task: FlightType, control_point: ControlPoint + ) -> Optional[SquadronDef]: + for squadrons in self.squadron_defs.values(): + for squadron in squadrons: + if self.squadron_compatible_with(squadron, task, control_point): + return squadron + return None diff --git a/game/campaignloader/mizcampaignloader.py b/game/campaignloader/mizcampaignloader.py index ab99e265..d217250f 100644 --- a/game/campaignloader/mizcampaignloader.py +++ b/game/campaignloader/mizcampaignloader.py @@ -255,16 +255,16 @@ class MizCampaignLoader: control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for ship in self.carriers(blue): - # TODO: Name the carrier. control_point = Carrier( - "carrier", ship.position, next(self.control_point_id) + ship.name, ship.position, next(self.control_point_id) ) control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for ship in self.lhas(blue): - # TODO: Name the LHA.db - control_point = Lha("lha", ship.position, next(self.control_point_id)) + control_point = Lha( + ship.name, ship.position, next(self.control_point_id) + ) control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point diff --git a/game/campaignloader/squadrondefgenerator.py b/game/campaignloader/squadrondefgenerator.py new file mode 100644 index 00000000..f8b90a1a --- /dev/null +++ b/game/campaignloader/squadrondefgenerator.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import itertools +import random +from typing import TYPE_CHECKING, Optional + +from game.dcs.aircrafttype import AircraftType +from game.squadrons.operatingbases import OperatingBases +from game.squadrons.squadrondef import SquadronDef +from game.theater import ControlPoint +from gen.flights.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft +from gen.flights.flight import FlightType + +if TYPE_CHECKING: + from game.coalition import Coalition + + +class SquadronDefGenerator: + def __init__(self, coalition: Coalition) -> None: + self.coalition = coalition + self.count = itertools.count(1) + self.used_nicknames: set[str] = set() + + def generate_for_task( + self, task: FlightType, control_point: ControlPoint + ) -> Optional[SquadronDef]: + aircraft_choice: Optional[AircraftType] = None + for aircraft in aircraft_for_task(task): + if aircraft not in self.coalition.faction.aircrafts: + continue + if not control_point.can_operate(aircraft): + continue + aircraft_choice = aircraft + # 50/50 chance to keep looking for an aircraft that isn't as far up the + # priority list to maintain some unit variety. + if random.choice([True, False]): + break + + if aircraft_choice is None: + return None + return self.generate_for_aircraft(aircraft_choice) + + def generate_for_aircraft(self, aircraft: AircraftType) -> SquadronDef: + return SquadronDef( + name=f"Squadron {next(self.count):03}", + nickname=self.random_nickname(), + country=self.coalition.country_name, + role="Flying Squadron", + aircraft=aircraft, + livery=None, + mission_types=tuple(tasks_for_aircraft(aircraft)), + operating_bases=OperatingBases.default_for_aircraft(aircraft), + pilot_pool=[], + ) + + @staticmethod + def _make_random_nickname() -> str: + from gen.naming import ANIMALS + + animal = random.choice(ANIMALS) + adjective = random.choice( + ( + None, + "Red", + "Blue", + "Green", + "Golden", + "Black", + "Fighting", + "Flying", + ) + ) + if adjective is None: + return animal.title() + return f"{adjective} {animal}".title() + + def random_nickname(self) -> str: + while True: + nickname = self._make_random_nickname() + if nickname not in self.used_nicknames: + self.used_nicknames.add(nickname) + return nickname diff --git a/game/coalition.py b/game/coalition.py index b6e681f9..9804d553 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -5,6 +5,8 @@ from typing import TYPE_CHECKING, Any, Optional from dcs import Point from faker import Faker +from game.campaignloader import CampaignAirWingConfig +from game.campaignloader.defaultsquadronassigner import DefaultSquadronAssigner from game.commander import TheaterCommander from game.commander.missionscheduler import MissionScheduler from game.income import Income @@ -12,7 +14,7 @@ from game.inventory import GlobalAircraftInventory from game.navmesh import NavMesh from game.orderedset import OrderedSet from game.profiling import logged_duration, MultiEventTracer -from game.savecompat import has_save_compat_for +from game.squadrons import AirWing from game.threatzones import ThreatZones from game.transfers import PendingTransfers @@ -21,10 +23,9 @@ if TYPE_CHECKING: from game.data.doctrine import Doctrine from game.factions.faction import Faction from game.procurement import AircraftProcurementRequest, ProcurementAi -from game.squadrons import AirWing from game.theater.bullseye import Bullseye from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder -from gen import AirTaskingOrder +from gen.ato import AirTaskingOrder class Coalition: @@ -40,7 +41,7 @@ class Coalition: self.procurement_requests: OrderedSet[AircraftProcurementRequest] = OrderedSet() self.bullseye = Bullseye(Point(0, 0)) self.faker = Faker(self.faction.locales) - self.air_wing = AirWing(game, self) + self.air_wing = AirWing(game) self.transfers = PendingTransfers(game, player) # Late initialized because the two coalitions in the game are mutually @@ -100,14 +101,7 @@ class Coalition: del state["faker"] return state - @has_save_compat_for(5) def __setstate__(self, state: dict[str, Any]) -> None: - # Begin save compat - old_procurement_requests = state["procurement_requests"] - if isinstance(old_procurement_requests, list): - state["procurement_requests"] = OrderedSet(old_procurement_requests) - # End save compat - self.__dict__.update(state) # Regenerate any state that was not persisted. self.on_load() @@ -120,6 +114,11 @@ class Coalition: raise RuntimeError("Double-initialization of Coalition.opponent") self._opponent = opponent + def configure_default_air_wing( + self, air_wing_config: CampaignAirWingConfig + ) -> None: + DefaultSquadronAssigner(air_wing_config, self.game, self).assign() + def adjust_budget(self, amount: float) -> None: self.budget += amount diff --git a/game/commander/aircraftallocator.py b/game/commander/aircraftallocator.py index a50dbd22..b8bfa812 100644 --- a/game/commander/aircraftallocator.py +++ b/game/commander/aircraftallocator.py @@ -2,7 +2,8 @@ from typing import Optional, Tuple from game.commander.missionproposals import ProposedFlight from game.inventory import GlobalAircraftInventory -from game.squadrons import AirWing, Squadron +from game.squadrons.squadron import Squadron +from game.squadrons.airwing import AirWing from game.theater import ControlPoint, MissionTarget from game.utils import meters from gen.flights.ai_flight_planner_db import aircraft_for_task @@ -67,7 +68,7 @@ class AircraftAllocator: # Valid location with enough aircraft available. Find a squadron to fit # the role. squadrons = self.air_wing.auto_assignable_for_task_with_type( - aircraft, task + aircraft, task, airfield ) for squadron in squadrons: if squadron.operates_from(airfield) and squadron.can_provide_pilots( diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index da96a8e2..f39c0e07 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -3,10 +3,10 @@ from typing import Optional from game.commander.missionproposals import ProposedFlight from game.dcs.aircrafttype import AircraftType from game.inventory import GlobalAircraftInventory -from game.squadrons import AirWing +from game.squadrons.airwing import AirWing from game.theater import MissionTarget, OffMapSpawn, ControlPoint from game.utils import nautical_miles -from gen import Package +from gen.ato import Package from game.commander.aircraftallocator import AircraftAllocator from gen.flights.closestairfields import ClosestAirfields from gen.flights.flight import Flight diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index 83dbcf76..87842a9e 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -13,8 +13,8 @@ from game.settings import Settings from game.squadrons import AirWing from game.theater import ConflictTheater from game.threatzones import ThreatZones -from gen import AirTaskingOrder, Package from game.commander.packagebuilder import PackageBuilder +from gen.ato import AirTaskingOrder, Package from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flight import FlightType from gen.flights.flightplan import FlightPlanBuilder diff --git a/game/commander/tasks/packageplanningtask.py b/game/commander/tasks/packageplanningtask.py index cf75eb1b..65a7ffd8 100644 --- a/game/commander/tasks/packageplanningtask.py +++ b/game/commander/tasks/packageplanningtask.py @@ -11,12 +11,11 @@ from game.commander.missionproposals import ProposedFlight, EscortType, Proposed from game.commander.packagefulfiller import PackageFulfiller from game.commander.tasks.theatercommandertask import TheaterCommanderTask from game.commander.theaterstate import TheaterState -from game.data.doctrine import Doctrine from game.settings import AutoAtoBehavior from game.theater import MissionTarget from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject from game.utils import Distance, meters -from gen import Package +from gen.ato import Package from gen.flights.flight import FlightType if TYPE_CHECKING: diff --git a/game/data/doctrine.py b/game/data/doctrine.py index 7ef7c59a..0466d31d 100644 --- a/game/data/doctrine.py +++ b/game/data/doctrine.py @@ -1,9 +1,7 @@ from dataclasses import dataclass from datetime import timedelta -from typing import Any from game.data.groundunitclass import GroundUnitClass -from game.savecompat import has_save_compat_for from game.utils import Distance, feet, nautical_miles @@ -79,32 +77,6 @@ class Doctrine: ground_unit_procurement_ratios: GroundUnitProcurementRatios - @has_save_compat_for(5) - def __setstate__(self, state: dict[str, Any]) -> None: - if "max_ingress_distance" not in state: - try: - state["max_ingress_distance"] = state["ingress_distance"] - del state["ingress_distance"] - except KeyError: - state["max_ingress_distance"] = state["ingress_egress_distance"] - del state["ingress_egress_distance"] - - max_ip: Distance = state["max_ingress_distance"] - if "min_ingress_distance" not in state: - if max_ip < nautical_miles(10): - min_ip = nautical_miles(5) - else: - min_ip = nautical_miles(10) - state["min_ingress_distance"] = min_ip - - self.__dict__.update(state) - - -class MissionPlannerMaxRanges: - @has_save_compat_for(5) - def __init__(self) -> None: - pass - MODERN_DOCTRINE = Doctrine( cap=True, diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index b54d9bc0..618f08d8 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -41,8 +41,8 @@ from game.utils import ( if TYPE_CHECKING: from gen.aircraft import FlightData - from gen import AirSupport, RadioFrequency, RadioRegistry - from gen.radios import Radio + from gen.airsupport import AirSupport + from gen.radios import Radio, RadioFrequency, RadioRegistry @dataclass(frozen=True) diff --git a/game/event/event.py b/game/event/event.py index 757b9be1..caf68a38 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -11,7 +11,7 @@ from game.debriefing import AirLosses, Debriefing from game.infos.information import Information from game.operation.operation import Operation from game.theater import ControlPoint -from gen import AirTaskingOrder +from gen.ato import AirTaskingOrder from gen.ground_forces.combat_stance import CombatStance from ..dcs.groundunittype import GroundUnitType from ..unitmap import UnitMap diff --git a/game/game.py b/game/game.py index 1b9d1c7f..8401b6ad 100644 --- a/game/game.py +++ b/game/game.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import itertools import logging import math from collections import Iterator from datetime import date, datetime, timedelta from enum import Enum -from typing import Any, List, Type, Union, cast +from typing import Any, List, Type, Union, cast, TYPE_CHECKING from dcs.mapping import Point from dcs.task import CAP, CAS, PinpointStrike @@ -21,6 +23,7 @@ from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flight import FlightType from gen.ground_forces.ai_ground_planner import GroundPlanner from . import persistency +from .campaignloader import CampaignAirWingConfig from .coalition import Coalition from .debriefing import Debriefing from .event.event import Event @@ -28,10 +31,8 @@ from .event.frontlineattack import FrontlineAttackEvent from .factions.faction import Faction from .infos.information import Information from .navmesh import NavMesh -from .procurement import AircraftProcurementRequest from .profiling import logged_duration from .settings import Settings -from .squadrons import AirWing from .theater import ConflictTheater, ControlPoint from .theater.bullseye import Bullseye from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder @@ -39,6 +40,9 @@ from .threatzones import ThreatZones from .unitmap import UnitMap from .weather import Conditions, TimeOfDay +if TYPE_CHECKING: + from .squadrons import AirWing + COMMISION_UNIT_VARIETY = 4 COMMISION_LIMITS_SCALE = 1.5 COMMISION_LIMITS_FACTORS = { @@ -85,6 +89,7 @@ class Game: player_faction: Faction, enemy_faction: Faction, theater: ConflictTheater, + air_wing_config: CampaignAirWingConfig, start_date: datetime, settings: Settings, player_budget: float, @@ -119,6 +124,9 @@ class Game: self.blue.set_opponent(self.red) self.red.set_opponent(self.blue) + self.blue.configure_default_air_wing(air_wing_config) + self.red.configure_default_air_wing(air_wing_config) + self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) self.on_load(game_still_initializing=True) diff --git a/game/inventory.py b/game/inventory.py index f7f0dbe1..e4d40789 100644 --- a/game/inventory.py +++ b/game/inventory.py @@ -5,10 +5,10 @@ from collections import defaultdict, Iterator, Iterable from typing import TYPE_CHECKING from game.dcs.aircrafttype import AircraftType -from gen.flights.flight import Flight if TYPE_CHECKING: from game.theater import ControlPoint + from gen.flights.flight import Flight class ControlPointAircraftInventory: diff --git a/game/operation/operation.py b/game/operation/operation.py index 87b440d6..59952fdf 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -16,16 +16,18 @@ from dcs.triggers import TriggerStart from game.plugins import LuaPluginManager from game.theater.theatergroundobject import TheaterGroundObject -from gen import Conflict, FlightType, VisualGenerator, AirSupport from gen.aircraft import AircraftConflictGenerator, FlightData from gen.airfields import AIRFIELD_DATA +from gen.airsupport import AirSupport from gen.airsupportgen import AirSupportConflictGenerator from gen.armor import GroundConflictGenerator from gen.beacons import load_beacons_for_terrain from gen.briefinggen import BriefingGenerator, MissionInfoGenerator from gen.cargoshipgen import CargoShipGenerator +from gen.conflictgen import Conflict from gen.convoygen import ConvoyGenerator from gen.environmentgen import EnvironmentGenerator +from gen.flights.flight import FlightType from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.groundobjectsgen import GroundObjectsGenerator from gen.kneeboard import KneeboardGenerator @@ -34,6 +36,7 @@ from gen.naming import namegen from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator +from gen.visualgen import VisualGenerator from .. import db from ..theater import Airfield, FrontLine from ..unitmap import UnitMap diff --git a/game/procurement.py b/game/procurement.py index 950e19d0..0690cf39 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -2,7 +2,7 @@ from __future__ import annotations import math import random -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple from game import db @@ -10,6 +10,7 @@ from game.data.groundunitclass import GroundUnitClass from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.factions.faction import Faction +from game.squadrons import Squadron from game.theater import ControlPoint, MissionTarget from game.utils import meters from gen.flights.ai_flight_planner_db import aircraft_for_task @@ -209,32 +210,38 @@ class ProcurementAi: return GroundUnitClass.Tank return worst_balanced + @staticmethod + def _compatible_squadron_at( + aircraft: AircraftType, airbase: ControlPoint, task: FlightType, count: int + ) -> Optional[Squadron]: + for squadron in airbase.squadrons: + if squadron.aircraft != aircraft: + continue + if not squadron.can_auto_assign(task): + continue + if not squadron.can_provide_pilots(count): + continue + return squadron + return None + def affordable_aircraft_for( self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float ) -> Optional[AircraftType]: best_choice: Optional[AircraftType] = None for unit in aircraft_for_task(request.task_capability): - if unit not in self.faction.aircrafts: - continue if unit.price * request.number > budget: continue - if not airbase.can_operate(unit): + + squadron = self._compatible_squadron_at( + unit, airbase, request.task_capability, request.number + ) + if squadron is None: continue distance_to_target = meters(request.near.distance_to(airbase)) if distance_to_target > unit.max_mission_range: continue - for squadron in self.air_wing.squadrons_for(unit): - if ( - squadron.operates_from(airbase) - and request.task_capability - in squadron.auto_assignable_mission_types - ): - break - else: - continue - # Affordable, compatible, and we have a squadron capable of the task. To # keep some variety, skip with a 50/50 chance. Might be a good idea to have # the chance to skip based on the price compared to the rest of the choices. diff --git a/game/radio/channels.py b/game/radio/channels.py index 4fbf7e23..214596fe 100644 --- a/game/radio/channels.py +++ b/game/radio/channels.py @@ -4,7 +4,8 @@ from dataclasses import dataclass from typing import Optional, Any, TYPE_CHECKING if TYPE_CHECKING: - from gen import FlightData, AirSupport + from gen.aircraft import FlightData + from gen.airsupport import AirSupport class RadioChannelAllocator: diff --git a/game/squadrons.py b/game/squadrons.py deleted file mode 100644 index 53bf6989..00000000 --- a/game/squadrons.py +++ /dev/null @@ -1,507 +0,0 @@ -from __future__ import annotations - -import dataclasses -import itertools -import logging -import random -from collections import defaultdict, Iterable -from dataclasses import dataclass, field -from enum import unique, Enum -from pathlib import Path -from typing import ( - Tuple, - TYPE_CHECKING, - Optional, - Iterator, - Sequence, - Any, -) - -import yaml -from faker import Faker - -from game.dcs.aircrafttype import AircraftType -from game.settings import AutoAtoBehavior, Settings - -if TYPE_CHECKING: - from game import Game - from game.coalition import Coalition - from gen.flights.flight import FlightType - from game.theater import ControlPoint - - -@dataclass -class PilotRecord: - missions_flown: int = field(default=0) - - -@unique -class PilotStatus(Enum): - Active = "Active" - OnLeave = "On leave" - Dead = "Dead" - - -@dataclass -class Pilot: - name: str - player: bool = field(default=False) - status: PilotStatus = field(default=PilotStatus.Active) - record: PilotRecord = field(default_factory=PilotRecord) - - @property - def alive(self) -> bool: - return self.status is not PilotStatus.Dead - - @property - def on_leave(self) -> bool: - return self.status is PilotStatus.OnLeave - - def send_on_leave(self) -> None: - if self.status is not PilotStatus.Active: - raise RuntimeError("Only active pilots may be sent on leave") - self.status = PilotStatus.OnLeave - - def return_from_leave(self) -> None: - if self.status is not PilotStatus.OnLeave: - raise RuntimeError("Only pilots on leave may be returned from leave") - self.status = PilotStatus.Active - - def kill(self) -> None: - self.status = PilotStatus.Dead - - @classmethod - def random(cls, faker: Faker) -> Pilot: - return Pilot(faker.name()) - - -@dataclass(frozen=True) -class OperatingBases: - shore: bool - carrier: bool - lha: bool - - @classmethod - def default_for_aircraft(cls, aircraft: AircraftType) -> OperatingBases: - if aircraft.dcs_unit_type.helicopter: - # Helicopters operate from anywhere by default. - return OperatingBases(shore=True, carrier=True, lha=True) - if aircraft.lha_capable: - # Marine aircraft operate from LHAs and the shore by default. - return OperatingBases(shore=True, carrier=False, lha=True) - if aircraft.carrier_capable: - # Carrier aircraft operate from carriers by default. - return OperatingBases(shore=False, carrier=True, lha=False) - # And the rest are only capable of shore operation. - return OperatingBases(shore=True, carrier=False, lha=False) - - @classmethod - def from_yaml(cls, aircraft: AircraftType, data: dict[str, bool]) -> OperatingBases: - return dataclasses.replace( - OperatingBases.default_for_aircraft(aircraft), **data - ) - - -@dataclass -class Squadron: - name: str - nickname: Optional[str] - country: str - role: str - aircraft: AircraftType - livery: Optional[str] - mission_types: tuple[FlightType, ...] - operating_bases: OperatingBases - - #: The pool of pilots that have not yet been assigned to the squadron. This only - #: happens when a preset squadron defines more preset pilots than the squadron limit - #: allows. This pool will be consumed before random pilots are generated. - pilot_pool: list[Pilot] - - current_roster: list[Pilot] = field(default_factory=list, init=False, hash=False) - available_pilots: list[Pilot] = field( - default_factory=list, init=False, hash=False, compare=False - ) - - auto_assignable_mission_types: set[FlightType] = field( - init=False, hash=False, compare=False - ) - - coalition: Coalition = field(hash=False, compare=False) - settings: Settings = field(hash=False, compare=False) - - def __post_init__(self) -> None: - self.auto_assignable_mission_types = set(self.mission_types) - - def __str__(self) -> str: - if self.nickname is None: - return self.name - return f'{self.name} "{self.nickname}"' - - @property - def player(self) -> bool: - return self.coalition.player - - @property - def pilot_limits_enabled(self) -> bool: - return self.settings.enable_squadron_pilot_limits - - def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None: - self.mission_types = tuple(mission_types) - self.auto_assignable_mission_types.intersection_update(self.mission_types) - - def claim_new_pilot_if_allowed(self) -> Optional[Pilot]: - if self.pilot_limits_enabled: - return None - self._recruit_pilots(1) - return self.available_pilots.pop() - - def claim_available_pilot(self) -> Optional[Pilot]: - if not self.available_pilots: - return self.claim_new_pilot_if_allowed() - - # For opfor, so player/AI option is irrelevant. - if not self.player: - return self.available_pilots.pop() - - preference = self.settings.auto_ato_behavior - - # No preference, so the first pilot is fine. - if preference is AutoAtoBehavior.Default: - return self.available_pilots.pop() - - prefer_players = preference is AutoAtoBehavior.Prefer - for pilot in self.available_pilots: - if pilot.player == prefer_players: - self.available_pilots.remove(pilot) - return pilot - - # No pilot was found that matched the user's preference. - # - # If they chose to *never* assign players and only players remain in the pool, - # we cannot fill the slot with the available pilots. - # - # If they only *prefer* players and we're out of players, just return an AI - # pilot. - if not prefer_players: - return self.claim_new_pilot_if_allowed() - return self.available_pilots.pop() - - def claim_pilot(self, pilot: Pilot) -> None: - if pilot not in self.available_pilots: - raise ValueError( - f"Cannot assign {pilot} to {self} because they are not available" - ) - self.available_pilots.remove(pilot) - - def return_pilot(self, pilot: Pilot) -> None: - self.available_pilots.append(pilot) - - def return_pilots(self, pilots: Sequence[Pilot]) -> None: - # Return in reverse so that returning two pilots and then getting two more - # results in the same ordering. This happens commonly when resetting rosters in - # the UI, when we clear the roster because the UI is updating, then end up - # repopulating the same size flight from the same squadron. - self.available_pilots.extend(reversed(pilots)) - - def _recruit_pilots(self, count: int) -> None: - new_pilots = self.pilot_pool[:count] - self.pilot_pool = self.pilot_pool[count:] - count -= len(new_pilots) - new_pilots.extend([Pilot(self.faker.name()) for _ in range(count)]) - self.current_roster.extend(new_pilots) - self.available_pilots.extend(new_pilots) - - def populate_for_turn_0(self) -> None: - if any(p.status is not PilotStatus.Active for p in self.pilot_pool): - raise ValueError("Squadrons can only be created with active pilots.") - self._recruit_pilots(self.settings.squadron_pilot_limit) - - def replenish_lost_pilots(self) -> None: - if not self.pilot_limits_enabled: - return - - replenish_count = min( - self.settings.squadron_replenishment_rate, - self._number_of_unfilled_pilot_slots, - ) - if replenish_count > 0: - self._recruit_pilots(replenish_count) - - def return_all_pilots(self) -> None: - self.available_pilots = list(self.active_pilots) - - @staticmethod - def send_on_leave(pilot: Pilot) -> None: - pilot.send_on_leave() - - def return_from_leave(self, pilot: Pilot) -> None: - if not self.has_unfilled_pilot_slots: - raise RuntimeError( - f"Cannot return {pilot} from leave because {self} is full" - ) - pilot.return_from_leave() - - @property - def faker(self) -> Faker: - return self.coalition.faker - - def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]: - return [p for p in self.current_roster if p.status == status] - - def _pilots_without_status(self, status: PilotStatus) -> list[Pilot]: - return [p for p in self.current_roster if p.status != status] - - @property - def active_pilots(self) -> list[Pilot]: - return self._pilots_with_status(PilotStatus.Active) - - @property - def pilots_on_leave(self) -> list[Pilot]: - return self._pilots_with_status(PilotStatus.OnLeave) - - @property - def number_of_pilots_including_inactive(self) -> int: - return len(self.current_roster) - - @property - def _number_of_unfilled_pilot_slots(self) -> int: - return self.settings.squadron_pilot_limit - len(self.active_pilots) - - @property - def number_of_available_pilots(self) -> int: - return len(self.available_pilots) - - def can_provide_pilots(self, count: int) -> bool: - return not self.pilot_limits_enabled or self.number_of_available_pilots >= count - - @property - def has_available_pilots(self) -> bool: - return not self.pilot_limits_enabled or bool(self.available_pilots) - - @property - def has_unfilled_pilot_slots(self) -> bool: - return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0 - - def can_auto_assign(self, task: FlightType) -> bool: - return task in self.auto_assignable_mission_types - - def operates_from(self, control_point: ControlPoint) -> bool: - if control_point.is_carrier: - return self.operating_bases.carrier - elif control_point.is_lha: - return self.operating_bases.lha - else: - return self.operating_bases.shore - - def pilot_at_index(self, index: int) -> Pilot: - return self.current_roster[index] - - @classmethod - def from_yaml(cls, path: Path, game: Game, coalition: Coalition) -> Squadron: - from gen.flights.ai_flight_planner_db import tasks_for_aircraft - from gen.flights.flight import FlightType - - with path.open(encoding="utf8") as squadron_file: - data = yaml.safe_load(squadron_file) - - name = data["aircraft"] - try: - unit_type = AircraftType.named(name) - except KeyError as ex: - raise KeyError(f"Could not find any aircraft named {name}") from ex - - pilots = [Pilot(n, player=False) for n in data.get("pilots", [])] - pilots.extend([Pilot(n, player=True) for n in data.get("players", [])]) - - mission_types = [FlightType.from_name(n) for n in data["mission_types"]] - tasks = tasks_for_aircraft(unit_type) - for mission_type in list(mission_types): - if mission_type not in tasks: - logging.error( - f"Squadron has mission type {mission_type} but {unit_type} is not " - f"capable of that task: {path}" - ) - mission_types.remove(mission_type) - - return Squadron( - name=data["name"], - nickname=data.get("nickname"), - country=data["country"], - role=data["role"], - aircraft=unit_type, - livery=data.get("livery"), - mission_types=tuple(mission_types), - operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})), - pilot_pool=pilots, - coalition=coalition, - settings=game.settings, - ) - - def __setstate__(self, state: dict[str, Any]) -> None: - # TODO: Remove save compat. - if "auto_assignable_mission_types" not in state: - state["auto_assignable_mission_types"] = set(state["mission_types"]) - self.__dict__.update(state) - - -class SquadronLoader: - def __init__(self, game: Game, coalition: Coalition) -> None: - self.game = game - self.coalition = coalition - - @staticmethod - def squadron_directories() -> Iterator[Path]: - from game import persistency - - yield Path(persistency.base_path()) / "Liberation/Squadrons" - yield Path("resources/squadrons") - - def load(self) -> dict[AircraftType, list[Squadron]]: - squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list) - country = self.coalition.country_name - faction = self.coalition.faction - any_country = country.startswith("Combined Joint Task Forces ") - for directory in self.squadron_directories(): - for path, squadron in self.load_squadrons_from(directory): - if not any_country and squadron.country != country: - logging.debug( - "Not using squadron for non-matching country (is " - f"{squadron.country}, need {country}: {path}" - ) - continue - if squadron.aircraft not in faction.aircrafts: - logging.debug( - f"Not using squadron because {faction.name} cannot use " - f"{squadron.aircraft}: {path}" - ) - continue - logging.debug( - f"Found {squadron.name} {squadron.aircraft} {squadron.role} " - f"compatible with {faction.name}" - ) - squadrons[squadron.aircraft].append(squadron) - # Convert away from defaultdict because defaultdict doesn't unpickle so we don't - # want it in the save state. - return dict(squadrons) - - def load_squadrons_from(self, directory: Path) -> Iterator[Tuple[Path, Squadron]]: - logging.debug(f"Looking for factions in {directory}") - # First directory level is the aircraft type so that historical squadrons that - # have flown multiple airframes can be defined as many times as needed. The main - # load() method is responsible for filtering out squadrons that aren't - # compatible with the faction. - for squadron_path in directory.glob("*/*.yaml"): - try: - yield squadron_path, Squadron.from_yaml( - squadron_path, self.game, self.coalition - ) - except Exception as ex: - raise RuntimeError( - f"Failed to load squadron defined by {squadron_path}" - ) from ex - - -class AirWing: - def __init__(self, game: Game, coalition: Coalition) -> None: - from gen.flights.ai_flight_planner_db import tasks_for_aircraft - - self.game = game - self.squadrons = SquadronLoader(game, coalition).load() - - count = itertools.count(1) - for aircraft in coalition.faction.aircrafts: - if aircraft in self.squadrons: - continue - self.squadrons[aircraft] = [ - Squadron( - name=f"Squadron {next(count):03}", - nickname=self.random_nickname(), - country=coalition.country_name, - role="Flying Squadron", - aircraft=aircraft, - livery=None, - mission_types=tuple(tasks_for_aircraft(aircraft)), - operating_bases=OperatingBases.default_for_aircraft(aircraft), - pilot_pool=[], - coalition=coalition, - settings=game.settings, - ) - ] - - def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]: - return self.squadrons[aircraft] - - def can_auto_plan(self, task: FlightType) -> bool: - try: - next(self.auto_assignable_for_task(task)) - return True - except StopIteration: - return False - - def auto_assignable_for_task(self, task: FlightType) -> Iterator[Squadron]: - for squadron in self.iter_squadrons(): - if squadron.can_auto_assign(task): - yield squadron - - def auto_assignable_for_task_with_type( - self, aircraft: AircraftType, task: FlightType - ) -> Iterator[Squadron]: - for squadron in self.squadrons_for(aircraft): - if squadron.can_auto_assign(task) and squadron.has_available_pilots: - yield squadron - - def squadron_for(self, aircraft: AircraftType) -> Squadron: - return self.squadrons_for(aircraft)[0] - - def iter_squadrons(self) -> Iterator[Squadron]: - return itertools.chain.from_iterable(self.squadrons.values()) - - def squadron_at_index(self, index: int) -> Squadron: - return list(self.iter_squadrons())[index] - - def populate_for_turn_0(self) -> None: - for squadron in self.iter_squadrons(): - squadron.populate_for_turn_0() - - def replenish(self) -> None: - for squadron in self.iter_squadrons(): - squadron.replenish_lost_pilots() - - def reset(self) -> None: - for squadron in self.iter_squadrons(): - squadron.return_all_pilots() - - @property - def size(self) -> int: - return sum(len(s) for s in self.squadrons.values()) - - @staticmethod - def _make_random_nickname() -> str: - from gen.naming import ANIMALS - - animal = random.choice(ANIMALS) - adjective = random.choice( - ( - None, - "Red", - "Blue", - "Green", - "Golden", - "Black", - "Fighting", - "Flying", - ) - ) - if adjective is None: - return animal.title() - return f"{adjective} {animal}".title() - - def random_nickname(self) -> str: - while True: - nickname = self._make_random_nickname() - for squadron in self.iter_squadrons(): - if squadron.nickname == nickname: - break - else: - return nickname diff --git a/game/squadrons/__init__.py b/game/squadrons/__init__.py new file mode 100644 index 00000000..99fdd47a --- /dev/null +++ b/game/squadrons/__init__.py @@ -0,0 +1,3 @@ +from .airwing import AirWing +from .pilot import Pilot +from .squadron import Squadron diff --git a/game/squadrons/airwing.py b/game/squadrons/airwing.py new file mode 100644 index 00000000..a710f918 --- /dev/null +++ b/game/squadrons/airwing.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import itertools +from collections import defaultdict +from typing import Sequence, Iterator, TYPE_CHECKING + +from game.dcs.aircrafttype import AircraftType +from gen.flights.flight import FlightType +from .squadron import Squadron +from ..theater import ControlPoint + +if TYPE_CHECKING: + from game import Game + + +class AirWing: + def __init__(self, game: Game) -> None: + self.game = game + self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list) + + def add_squadron(self, squadron: Squadron) -> None: + squadron.location.squadrons.append(squadron) + self.squadrons[squadron.aircraft].append(squadron) + + def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]: + return self.squadrons[aircraft] + + def can_auto_plan(self, task: FlightType) -> bool: + try: + next(self.auto_assignable_for_task(task)) + return True + except StopIteration: + return False + + def auto_assignable_for_task(self, task: FlightType) -> Iterator[Squadron]: + for squadron in self.iter_squadrons(): + if squadron.can_auto_assign(task): + yield squadron + + def auto_assignable_for_task_with_type( + self, aircraft: AircraftType, task: FlightType, base: ControlPoint + ) -> Iterator[Squadron]: + for squadron in self.squadrons_for(aircraft): + if ( + squadron.location == base + and squadron.can_auto_assign(task) + and squadron.has_available_pilots + ): + yield squadron + + def squadron_for(self, aircraft: AircraftType) -> Squadron: + return self.squadrons_for(aircraft)[0] + + def iter_squadrons(self) -> Iterator[Squadron]: + return itertools.chain.from_iterable(self.squadrons.values()) + + def squadron_at_index(self, index: int) -> Squadron: + return list(self.iter_squadrons())[index] + + def populate_for_turn_0(self) -> None: + for squadron in self.iter_squadrons(): + squadron.populate_for_turn_0() + + def replenish(self) -> None: + for squadron in self.iter_squadrons(): + squadron.replenish_lost_pilots() + + def reset(self) -> None: + for squadron in self.iter_squadrons(): + squadron.return_all_pilots() + + @property + def size(self) -> int: + return sum(len(s) for s in self.squadrons.values()) diff --git a/game/squadrons/operatingbases.py b/game/squadrons/operatingbases.py new file mode 100644 index 00000000..181e3867 --- /dev/null +++ b/game/squadrons/operatingbases.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import dataclasses +from dataclasses import dataclass + +from game.dcs.aircrafttype import AircraftType + + +@dataclass(frozen=True) +class OperatingBases: + shore: bool + carrier: bool + lha: bool + + @classmethod + def default_for_aircraft(cls, aircraft: AircraftType) -> OperatingBases: + if aircraft.dcs_unit_type.helicopter: + # Helicopters operate from anywhere by default. + return OperatingBases(shore=True, carrier=True, lha=True) + if aircraft.lha_capable: + # Marine aircraft operate from LHAs and the shore by default. + return OperatingBases(shore=True, carrier=False, lha=True) + if aircraft.carrier_capable: + # Carrier aircraft operate from carriers by default. + return OperatingBases(shore=False, carrier=True, lha=False) + # And the rest are only capable of shore operation. + return OperatingBases(shore=True, carrier=False, lha=False) + + @classmethod + def from_yaml(cls, aircraft: AircraftType, data: dict[str, bool]) -> OperatingBases: + return dataclasses.replace( + OperatingBases.default_for_aircraft(aircraft), **data + ) diff --git a/game/squadrons/pilot.py b/game/squadrons/pilot.py new file mode 100644 index 00000000..dd3bac27 --- /dev/null +++ b/game/squadrons/pilot.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import unique, Enum + +from faker import Faker + + +@dataclass +class PilotRecord: + missions_flown: int = field(default=0) + + +@unique +class PilotStatus(Enum): + Active = "Active" + OnLeave = "On leave" + Dead = "Dead" + + +@dataclass +class Pilot: + name: str + player: bool = field(default=False) + status: PilotStatus = field(default=PilotStatus.Active) + record: PilotRecord = field(default_factory=PilotRecord) + + @property + def alive(self) -> bool: + return self.status is not PilotStatus.Dead + + @property + def on_leave(self) -> bool: + return self.status is PilotStatus.OnLeave + + def send_on_leave(self) -> None: + if self.status is not PilotStatus.Active: + raise RuntimeError("Only active pilots may be sent on leave") + self.status = PilotStatus.OnLeave + + def return_from_leave(self) -> None: + if self.status is not PilotStatus.OnLeave: + raise RuntimeError("Only pilots on leave may be returned from leave") + self.status = PilotStatus.Active + + def kill(self) -> None: + self.status = PilotStatus.Dead + + @classmethod + def random(cls, faker: Faker) -> Pilot: + return Pilot(faker.name()) diff --git a/game/squadrons/squadron.py b/game/squadrons/squadron.py new file mode 100644 index 00000000..369514c5 --- /dev/null +++ b/game/squadrons/squadron.py @@ -0,0 +1,262 @@ +from __future__ import annotations + +import logging +from collections import Iterable +from dataclasses import dataclass, field +from typing import ( + TYPE_CHECKING, + Optional, + Sequence, +) + +from faker import Faker + +from game.dcs.aircrafttype import AircraftType +from game.settings import AutoAtoBehavior, Settings +from game.squadrons.operatingbases import OperatingBases +from game.squadrons.pilot import Pilot, PilotStatus +from game.squadrons.squadrondef import SquadronDef + +if TYPE_CHECKING: + from game import Game + from game.coalition import Coalition + from gen.flights.flight import FlightType + from game.theater import ControlPoint + + +@dataclass +class Squadron: + name: str + nickname: Optional[str] + country: str + role: str + aircraft: AircraftType + livery: Optional[str] + mission_types: tuple[FlightType, ...] + operating_bases: OperatingBases + + #: The pool of pilots that have not yet been assigned to the squadron. This only + #: happens when a preset squadron defines more preset pilots than the squadron limit + #: allows. This pool will be consumed before random pilots are generated. + pilot_pool: list[Pilot] + + current_roster: list[Pilot] = field(default_factory=list, init=False, hash=False) + available_pilots: list[Pilot] = field( + default_factory=list, init=False, hash=False, compare=False + ) + + auto_assignable_mission_types: set[FlightType] = field( + init=False, hash=False, compare=False + ) + + coalition: Coalition = field(hash=False, compare=False) + settings: Settings = field(hash=False, compare=False) + + location: ControlPoint + + def __post_init__(self) -> None: + self.auto_assignable_mission_types = set(self.mission_types) + + def __str__(self) -> str: + if self.nickname is None: + return self.name + return f'{self.name} "{self.nickname}"' + + @property + def player(self) -> bool: + return self.coalition.player + + def assign_to_base(self, base: ControlPoint) -> None: + self.location.squadrons.remove(self) + self.location = base + self.location.squadrons.append(self) + logging.debug(f"Assigned {self} to {base}") + + @property + def pilot_limits_enabled(self) -> bool: + return self.settings.enable_squadron_pilot_limits + + def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None: + self.mission_types = tuple(mission_types) + self.auto_assignable_mission_types.intersection_update(self.mission_types) + + def set_auto_assignable_mission_types( + self, mission_types: Iterable[FlightType] + ) -> None: + self.auto_assignable_mission_types = set(self.mission_types).intersection( + mission_types + ) + + def claim_new_pilot_if_allowed(self) -> Optional[Pilot]: + if self.pilot_limits_enabled: + return None + self._recruit_pilots(1) + return self.available_pilots.pop() + + def claim_available_pilot(self) -> Optional[Pilot]: + if not self.available_pilots: + return self.claim_new_pilot_if_allowed() + + # For opfor, so player/AI option is irrelevant. + if not self.player: + return self.available_pilots.pop() + + preference = self.settings.auto_ato_behavior + + # No preference, so the first pilot is fine. + if preference is AutoAtoBehavior.Default: + return self.available_pilots.pop() + + prefer_players = preference is AutoAtoBehavior.Prefer + for pilot in self.available_pilots: + if pilot.player == prefer_players: + self.available_pilots.remove(pilot) + return pilot + + # No pilot was found that matched the user's preference. + # + # If they chose to *never* assign players and only players remain in the pool, + # we cannot fill the slot with the available pilots. + # + # If they only *prefer* players and we're out of players, just return an AI + # pilot. + if not prefer_players: + return self.claim_new_pilot_if_allowed() + return self.available_pilots.pop() + + def claim_pilot(self, pilot: Pilot) -> None: + if pilot not in self.available_pilots: + raise ValueError( + f"Cannot assign {pilot} to {self} because they are not available" + ) + self.available_pilots.remove(pilot) + + def return_pilot(self, pilot: Pilot) -> None: + self.available_pilots.append(pilot) + + def return_pilots(self, pilots: Sequence[Pilot]) -> None: + # Return in reverse so that returning two pilots and then getting two more + # results in the same ordering. This happens commonly when resetting rosters in + # the UI, when we clear the roster because the UI is updating, then end up + # repopulating the same size flight from the same squadron. + self.available_pilots.extend(reversed(pilots)) + + def _recruit_pilots(self, count: int) -> None: + new_pilots = self.pilot_pool[:count] + self.pilot_pool = self.pilot_pool[count:] + count -= len(new_pilots) + new_pilots.extend([Pilot(self.faker.name()) for _ in range(count)]) + self.current_roster.extend(new_pilots) + self.available_pilots.extend(new_pilots) + + def populate_for_turn_0(self) -> None: + if any(p.status is not PilotStatus.Active for p in self.pilot_pool): + raise ValueError("Squadrons can only be created with active pilots.") + self._recruit_pilots(self.settings.squadron_pilot_limit) + + def replenish_lost_pilots(self) -> None: + if not self.pilot_limits_enabled: + return + + replenish_count = min( + self.settings.squadron_replenishment_rate, + self._number_of_unfilled_pilot_slots, + ) + if replenish_count > 0: + self._recruit_pilots(replenish_count) + + def return_all_pilots(self) -> None: + self.available_pilots = list(self.active_pilots) + + @staticmethod + def send_on_leave(pilot: Pilot) -> None: + pilot.send_on_leave() + + def return_from_leave(self, pilot: Pilot) -> None: + if not self.has_unfilled_pilot_slots: + raise RuntimeError( + f"Cannot return {pilot} from leave because {self} is full" + ) + pilot.return_from_leave() + + @property + def faker(self) -> Faker: + return self.coalition.faker + + def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]: + return [p for p in self.current_roster if p.status == status] + + def _pilots_without_status(self, status: PilotStatus) -> list[Pilot]: + return [p for p in self.current_roster if p.status != status] + + @property + def max_size(self) -> int: + return self.settings.squadron_pilot_limit + + @property + def active_pilots(self) -> list[Pilot]: + return self._pilots_with_status(PilotStatus.Active) + + @property + def pilots_on_leave(self) -> list[Pilot]: + return self._pilots_with_status(PilotStatus.OnLeave) + + @property + def number_of_pilots_including_inactive(self) -> int: + return len(self.current_roster) + + @property + def _number_of_unfilled_pilot_slots(self) -> int: + return self.max_size - len(self.active_pilots) + + @property + def number_of_available_pilots(self) -> int: + return len(self.available_pilots) + + def can_provide_pilots(self, count: int) -> bool: + return not self.pilot_limits_enabled or self.number_of_available_pilots >= count + + @property + def has_available_pilots(self) -> bool: + return not self.pilot_limits_enabled or bool(self.available_pilots) + + @property + def has_unfilled_pilot_slots(self) -> bool: + return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0 + + def can_auto_assign(self, task: FlightType) -> bool: + return task in self.auto_assignable_mission_types + + def operates_from(self, control_point: ControlPoint) -> bool: + if control_point.is_carrier: + return self.operating_bases.carrier + elif control_point.is_lha: + return self.operating_bases.lha + else: + return self.operating_bases.shore + + def pilot_at_index(self, index: int) -> Pilot: + return self.current_roster[index] + + @classmethod + def create_from( + cls, + squadron_def: SquadronDef, + base: ControlPoint, + coalition: Coalition, + game: Game, + ) -> Squadron: + return Squadron( + squadron_def.name, + squadron_def.nickname, + squadron_def.country, + squadron_def.role, + squadron_def.aircraft, + squadron_def.livery, + squadron_def.mission_types, + squadron_def.operating_bases, + squadron_def.pilot_pool, + coalition, + game.settings, + base, + ) diff --git a/game/squadrons/squadrondef.py b/game/squadrons/squadrondef.py new file mode 100644 index 00000000..72176b34 --- /dev/null +++ b/game/squadrons/squadrondef.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import logging +from collections import Iterable +from dataclasses import dataclass, field +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Optional, +) + +import yaml + +from game.dcs.aircrafttype import AircraftType +from game.squadrons.operatingbases import OperatingBases +from game.squadrons.pilot import Pilot + +if TYPE_CHECKING: + from gen.flights.flight import FlightType + from game.theater import ControlPoint + + +@dataclass +class SquadronDef: + name: str + nickname: Optional[str] + country: str + role: str + aircraft: AircraftType + livery: Optional[str] + mission_types: tuple[FlightType, ...] + operating_bases: OperatingBases + pilot_pool: list[Pilot] + + auto_assignable_mission_types: set[FlightType] = field( + init=False, hash=False, compare=False + ) + + def __post_init__(self) -> None: + self.auto_assignable_mission_types = set(self.mission_types) + + def __str__(self) -> str: + if self.nickname is None: + return self.name + return f'{self.name} "{self.nickname}"' + + def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None: + self.mission_types = tuple(mission_types) + self.auto_assignable_mission_types.intersection_update(self.mission_types) + + def can_auto_assign(self, task: FlightType) -> bool: + return task in self.auto_assignable_mission_types + + def operates_from(self, control_point: ControlPoint) -> bool: + if control_point.is_carrier: + return self.operating_bases.carrier + elif control_point.is_lha: + return self.operating_bases.lha + else: + return self.operating_bases.shore + + @classmethod + def from_yaml(cls, path: Path) -> SquadronDef: + from gen.flights.ai_flight_planner_db import tasks_for_aircraft + from gen.flights.flight import FlightType + + with path.open(encoding="utf8") as squadron_file: + data = yaml.safe_load(squadron_file) + + name = data["aircraft"] + try: + unit_type = AircraftType.named(name) + except KeyError as ex: + raise KeyError(f"Could not find any aircraft named {name}") from ex + + pilots = [Pilot(n, player=False) for n in data.get("pilots", [])] + pilots.extend([Pilot(n, player=True) for n in data.get("players", [])]) + + mission_types = [FlightType.from_name(n) for n in data["mission_types"]] + tasks = tasks_for_aircraft(unit_type) + for mission_type in list(mission_types): + if mission_type not in tasks: + logging.error( + f"Squadron has mission type {mission_type} but {unit_type} is not " + f"capable of that task: {path}" + ) + mission_types.remove(mission_type) + + return SquadronDef( + name=data["name"], + nickname=data.get("nickname"), + country=data["country"], + role=data["role"], + aircraft=unit_type, + livery=data.get("livery"), + mission_types=tuple(mission_types), + operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})), + pilot_pool=pilots, + ) diff --git a/game/squadrons/squadrondefloader.py b/game/squadrons/squadrondefloader.py new file mode 100644 index 00000000..5af93576 --- /dev/null +++ b/game/squadrons/squadrondefloader.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import logging +from collections import defaultdict +from pathlib import Path +from typing import Iterator, Tuple, TYPE_CHECKING + +from game.dcs.aircrafttype import AircraftType +from .squadrondef import SquadronDef + +if TYPE_CHECKING: + from game import Game + from game.coalition import Coalition + + +class SquadronDefLoader: + def __init__(self, game: Game, coalition: Coalition) -> None: + self.game = game + self.coalition = coalition + + @staticmethod + def squadron_directories() -> Iterator[Path]: + from game import persistency + + yield Path(persistency.base_path()) / "Liberation/Squadrons" + yield Path("resources/squadrons") + + def load(self) -> dict[AircraftType, list[SquadronDef]]: + squadrons: dict[AircraftType, list[SquadronDef]] = defaultdict(list) + country = self.coalition.country_name + faction = self.coalition.faction + any_country = country.startswith("Combined Joint Task Forces ") + for directory in self.squadron_directories(): + for path, squadron_def in self.load_squadrons_from(directory): + if not any_country and squadron_def.country != country: + logging.debug( + "Not using squadron for non-matching country (is " + f"{squadron_def.country}, need {country}: {path}" + ) + continue + if squadron_def.aircraft not in faction.aircrafts: + logging.debug( + f"Not using squadron because {faction.name} cannot use " + f"{squadron_def.aircraft}: {path}" + ) + continue + logging.debug( + f"Found {squadron_def.name} {squadron_def.aircraft} " + f"{squadron_def.role} compatible with {faction.name}" + ) + + squadrons[squadron_def.aircraft].append(squadron_def) + return squadrons + + @staticmethod + def load_squadrons_from(directory: Path) -> Iterator[Tuple[Path, SquadronDef]]: + logging.debug(f"Looking for factions in {directory}") + # First directory level is the aircraft type so that historical squadrons that + # have flown multiple airframes can be defined as many times as needed. The main + # load() method is responsible for filtering out squadrons that aren't + # compatible with the faction. + for squadron_path in directory.glob("*/*.yaml"): + try: + yield squadron_path, SquadronDef.from_yaml(squadron_path) + except Exception as ex: + raise RuntimeError( + f"Failed to load squadron defined by {squadron_path}" + ) from ex diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 4ac622cf..7cf3fcd2 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -241,6 +241,12 @@ class ConflictTheater: return i raise KeyError(f"Cannot find ControlPoint with ID {id}") + def control_point_named(self, name: str) -> ControlPoint: + for cp in self.controlpoints: + if cp.name == name: + return cp + raise KeyError(f"Cannot find ControlPoint named {name}") + @property def seasonal_conditions(self) -> SeasonalConditions: raise NotImplementedError diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index f0c5286b..2960bf77 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -54,6 +54,7 @@ from ..weather import Conditions if TYPE_CHECKING: from game import Game from gen.flights.flight import FlightType + from game.squadrons.squadron import Squadron from ..transfers import PendingTransfers FREE_FRONTLINE_UNIT_SUPPLY: int = 15 @@ -322,6 +323,8 @@ class ControlPoint(MissionTarget, ABC): self.target_position: Optional[Point] = None + self.squadrons: list[Squadron] = [] + def __repr__(self) -> str: return f"<{self.__class__}: {self.name}>" diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 4ad78ec3..1281490d 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -30,7 +30,7 @@ from game.theater.theatergroundobject import ( ) from game.utils import Heading from game.version import VERSION -from gen import namegen +from gen.naming import namegen from gen.coastal.coastal_group_generator import generate_coastal_group from gen.defenses.armor_group_generator import generate_armor_group from gen.fleet.ship_group_generator import ( @@ -49,6 +49,7 @@ from . import ( Fob, OffMapSpawn, ) +from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig from ..profiling import logged_duration from ..settings import Settings @@ -85,6 +86,7 @@ class GameGenerator: player: Faction, enemy: Faction, theater: ConflictTheater, + air_wing_config: CampaignAirWingConfig, settings: Settings, generator_settings: GeneratorSettings, mod_settings: ModSettings, @@ -92,6 +94,7 @@ class GameGenerator: self.player = player self.enemy = enemy self.theater = theater + self.air_wing_config = air_wing_config self.settings = settings self.generator_settings = generator_settings self.mod_settings = mod_settings @@ -105,6 +108,7 @@ class GameGenerator: player_faction=self.player.apply_mod_settings(self.mod_settings), enemy_faction=self.enemy.apply_mod_settings(self.mod_settings), theater=self.theater, + air_wing_config=self.air_wing_config, start_date=self.generator_settings.start_date, settings=self.settings, player_budget=self.generator_settings.player_budget, diff --git a/game/transfers.py b/game/transfers.py index 643bcacb..9146c5b8 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -51,7 +51,6 @@ from dcs.mapping import Point from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.procurement import AircraftProcurementRequest -from game.squadrons import Squadron from game.theater import ControlPoint, MissionTarget from game.theater.transitnetwork import ( TransitConnection, @@ -68,6 +67,7 @@ from gen.naming import namegen if TYPE_CHECKING: from game import Game from game.inventory import ControlPointAircraftInventory + from game.squadrons import Squadron class Transport: @@ -318,7 +318,7 @@ class AirliftPlanner: inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: squadrons = air_wing.auto_assignable_for_task_with_type( - unit_type, FlightType.TRANSPORT + unit_type, FlightType.TRANSPORT, cp ) for squadron in squadrons: if self.compatible_with_mission(unit_type, cp): diff --git a/game/version.py b/game/version.py index d89d875d..6d7afc30 100644 --- a/game/version.py +++ b/game/version.py @@ -110,4 +110,9 @@ VERSION = _build_version_string() #: Version 8.0 #: * DCS 2.7.4.9632 changed scenery target IDs. Any mission using map buildings as #: strike targets must check and potentially recreate all those objectives. -CAMPAIGN_FORMAT_VERSION = (8, 0) +#: +#: Version 9.0 +#: * Campaign files now define the initial squadron layouts. See TODO. +#: * CV and LHA control points now get their names from the group name in the campaign +#: miz. +CAMPAIGN_FORMAT_VERSION = (9, 0) diff --git a/game/weather.py b/game/weather.py index fb0ea68c..0d20b370 100644 --- a/game/weather.py +++ b/game/weather.py @@ -10,7 +10,6 @@ from typing import Optional, TYPE_CHECKING, Any from dcs.cloud_presets import Clouds as PydcsClouds from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind -from game.savecompat import has_save_compat_for from game.settings import Settings from game.utils import Distance, Heading, meters, interpolate, Pressure, inches_hg @@ -36,13 +35,6 @@ class AtmosphericConditions: #: Temperature at sea level in Celcius. temperature_celsius: float - @has_save_compat_for(5) - def __setstate__(self, state: dict[str, Any]) -> None: - if "qnh" not in state: - state["qnh"] = inches_hg(state["qnh_inches_mercury"]) - del state["qnh_inches_mercury"] - self.__dict__.update(state) - @dataclass(frozen=True) class WindConditions: diff --git a/gen/__init__.py b/gen/__init__.py index 6fd6547c..e69de29b 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -1,13 +0,0 @@ -from .aircraft import * -from .armor import * -from .airsupportgen import * -from .conflictgen import * -from .visualgen import * -from .triggergen import * -from .environmentgen import * -from .groundobjectsgen import * -from .briefinggen import * -from .forcedoptionsgen import * -from .kneeboard import * - -from . import naming diff --git a/gen/aircraft.py b/gen/aircraft.py index d42e52f2..3564d1ec 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -69,7 +69,6 @@ from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum from game.dcs.aircrafttype import AircraftType from game.factions.faction import Faction from game.settings import Settings -from game.squadrons import Pilot from game.theater.controlpoint import ( Airfield, ControlPoint, @@ -109,6 +108,7 @@ from .naming import namegen if TYPE_CHECKING: from game import Game + from game.squadrons import Pilot WARM_START_HELI_ALT = meters(500) WARM_START_ALTITUDE = meters(3000) diff --git a/gen/airsupport.py b/gen/airsupport.py index 1ce520de..fa189376 100644 --- a/gen/airsupport.py +++ b/gen/airsupport.py @@ -5,7 +5,8 @@ from datetime import timedelta from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: - from gen import RadioFrequency, TacanChannel + from gen.radios import RadioFrequency + from gen.tacan import TacanChannel @dataclass diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 2f20a7c3..44456dcc 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -16,8 +16,7 @@ from dcs.task import ( from dcs.unittype import UnitType from game.utils import Heading -from . import AirSupport -from .airsupport import TankerInfo, AwacsInfo +from .airsupport import AirSupport, TankerInfo, AwacsInfo from .callsigns import callsign_for_support_unit from .conflictgen import Conflict from .flights.ai_flight_planner_db import AEWC_CAPABLE diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 9bb0c9bc..6b60ef01 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -2,20 +2,19 @@ from __future__ import annotations from datetime import timedelta from enum import Enum -from typing import List, Optional, TYPE_CHECKING, Union, Sequence, Any +from typing import List, Optional, TYPE_CHECKING, Union, Sequence from dcs.mapping import Point from dcs.point import MovingPoint, PointAction from dcs.unit import Unit from game.dcs.aircrafttype import AircraftType -from game.savecompat import has_save_compat_for -from game.squadrons import Pilot, Squadron from game.theater.controlpoint import ControlPoint, MissionTarget from game.utils import Distance, meters from gen.flights.loadouts import Loadout if TYPE_CHECKING: + from game.squadrons import Pilot, Squadron from game.transfers import TransferOrder from gen.ato import Package from gen.flights.flightplan import FlightPlan @@ -50,6 +49,8 @@ class FlightType(Enum): strike-like missions will need more specialized control. * ai_flight_planner.py: Use the new mission type in propose_missions so the AI will plan the new mission type. + * FlightType.is_air_to_air and FlightType.is_air_to_ground: If the new mission type + fits either of these categories, update those methods accordingly. """ TARCAP = "TARCAP" @@ -80,6 +81,30 @@ class FlightType(Enum): return entry raise KeyError(f"No FlightType with name {name}") + @property + def is_air_to_air(self) -> bool: + return self in { + FlightType.TARCAP, + FlightType.BARCAP, + FlightType.INTERCEPTION, + FlightType.ESCORT, + FlightType.SWEEP, + } + + @property + def is_air_to_ground(self) -> bool: + return self in { + FlightType.CAS, + FlightType.STRIKE, + FlightType.ANTISHIP, + FlightType.SEAD, + FlightType.DEAD, + FlightType.BAI, + FlightType.OCA_RUNWAY, + FlightType.OCA_AIRCRAFT, + FlightType.SEAD_ESCORT, + } + class FlightWaypointType(Enum): """Enumeration of waypoint types. @@ -169,12 +194,6 @@ class FlightWaypoint: self.tot: Optional[timedelta] = None self.departure_time: Optional[timedelta] = None - @has_save_compat_for(5) - def __setstate__(self, state: dict[str, Any]) -> None: - if "min_fuel" not in state: - state["min_fuel"] = None - self.__dict__.update(state) - @property def position(self) -> Point: return Point(self.x, self.y) diff --git a/gen/naming.py b/gen/naming.py index e43d629c..9f820a86 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -1,12 +1,16 @@ +from __future__ import annotations + import random import time -from typing import List, Any +from typing import List, Any, TYPE_CHECKING from dcs.country import Country from game.dcs.aircrafttype import AircraftType from game.dcs.unittype import UnitType -from gen.flights.flight import Flight + +if TYPE_CHECKING: + from gen.flights.flight import Flight ALPHA_MILITARY = [ "Alpha", diff --git a/qt_ui/main.py b/qt_ui/main.py index e7aad522..c4d6e4e0 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -233,10 +233,12 @@ def create_game( # way. inject_custom_payloads(Path(persistency.base_path())) campaign = Campaign.from_file(campaign_path) + theater = campaign.load_theater() generator = GameGenerator( FACTIONS[blue], FACTIONS[red], - campaign.load_theater(), + theater, + campaign.load_air_wing_config(theater), Settings( supercarrier=supercarrier, automate_runway_repair=auto_procurement, diff --git a/qt_ui/models.py b/qt_ui/models.py index ee842de5..4ea93965 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -13,7 +13,7 @@ from PySide2.QtCore import ( from PySide2.QtGui import QIcon from game.game import Game -from game.squadrons import Squadron, Pilot +from game.squadrons.squadron import Pilot, Squadron from game.theater.missiontarget import MissionTarget from game.transfers import TransferOrder, PendingTransfers from gen.ato import AirTaskingOrder, Package diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index 32b01bd6..af500dab 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 game.theater import ControlPointType, BuildingGroundObject from game.utils import Distance -from gen import BuildingGroundObject, Conflict, FlightWaypointType -from gen.flights.flight import FlightWaypoint +from gen.conflictgen import Conflict +from gen.flights.flight import FlightWaypoint, FlightWaypointType from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index ac6000f6..82638f3a 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -28,7 +28,7 @@ from PySide2.QtWidgets import ( from game import Game from game.dcs.aircrafttype import AircraftType -from game.squadrons import Squadron, AirWing, Pilot +from game.squadrons import AirWing, Pilot, Squadron from gen.flights.flight import FlightType from qt_ui.models import AirWingModel, SquadronModel from qt_ui.uiconstants import AIRCRAFT_ICONS diff --git a/qt_ui/windows/AirWingDialog.py b/qt_ui/windows/AirWingDialog.py index df0cf81c..f7da67da 100644 --- a/qt_ui/windows/AirWingDialog.py +++ b/qt_ui/windows/AirWingDialog.py @@ -34,13 +34,17 @@ class SquadronDelegate(TwoColumnRowDelegate): return index.data(AirWingModel.SquadronRole) def text_for(self, index: QModelIndex, row: int, column: int) -> str: + squadron = self.squadron(index) if (row, column) == (0, 0): - return self.squadron(index).name + if squadron.nickname: + nickname = f' "{squadron.nickname}"' + else: + nickname = "" + return f"{squadron.name}{nickname}" elif (row, column) == (0, 1): - squadron = self.air_wing_model.data(index, AirWingModel.SquadronRole) return squadron.aircraft.name elif (row, column) == (1, 0): - return self.squadron(index).nickname or "" + return squadron.location.name elif (row, column) == (1, 1): squadron = self.squadron(index) active = len(squadron.active_pilots) diff --git a/qt_ui/windows/SquadronDialog.py b/qt_ui/windows/SquadronDialog.py index a932caee..c17e5312 100644 --- a/qt_ui/windows/SquadronDialog.py +++ b/qt_ui/windows/SquadronDialog.py @@ -14,7 +14,6 @@ from PySide2.QtWidgets import ( QVBoxLayout, QPushButton, QHBoxLayout, - QGridLayout, QLabel, QCheckBox, ) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 88ddbffd..803fb21a 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -45,22 +45,10 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): row = 0 unit_types: Set[AircraftType] = set() - for unit_type in self.game_model.game.blue.faction.aircrafts: - if self.cp.is_carrier and not unit_type.carrier_capable: - continue - if self.cp.is_lha and not unit_type.lha_capable: - continue - if ( - self.cp.cptype in [ControlPointType.FOB, ControlPointType.FARP] - and unit_type not in helicopter_map.values() - ): - continue - unit_types.add(unit_type) + for squadron in cp.squadrons: + unit_types.add(squadron.aircraft) - sorted_units = sorted( - unit_types, - key=lambda u: u.name, - ) + sorted_units = sorted(unit_types, key=lambda u: u.name) for row, unit_type in enumerate(sorted_units): self.add_purchase_row(unit_type, task_box_layout, row) stretch = QVBoxLayout() diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 3c0a1e74..eeeb54de 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -14,7 +14,7 @@ from PySide2.QtWidgets import ( from dcs.unittype import FlyingType from game import Game -from game.squadrons import Squadron +from game.squadrons.squadron import Squadron from game.theater import ControlPoint, OffMapSpawn from gen.ato import Package from gen.flights.flight import Flight, FlightRoster diff --git a/qt_ui/windows/mission/flight/SquadronSelector.py b/qt_ui/windows/mission/flight/SquadronSelector.py index e9b7ae7f..a931f9aa 100644 --- a/qt_ui/windows/mission/flight/SquadronSelector.py +++ b/qt_ui/windows/mission/flight/SquadronSelector.py @@ -4,7 +4,7 @@ from typing import Type, Optional from PySide2.QtWidgets import QComboBox from dcs.unittype import FlyingType -from game.squadrons import AirWing +from game.squadrons.airwing import AirWing from gen.flights.flight import FlightType diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index 3db6ab2d..3545ff1e 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -14,7 +14,7 @@ from PySide2.QtWidgets import ( ) from game import Game -from game.squadrons import Pilot +from game.squadrons.pilot import Pilot from gen.flights.flight import Flight, FlightRoster from qt_ui.models import PackageModel diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 0bbd0715..0b1f4539 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -113,10 +113,12 @@ class NewGameWizard(QtWidgets.QWizard): blue_faction = self.faction_selection_page.selected_blue_faction red_faction = self.faction_selection_page.selected_red_faction + theater = campaign.load_theater() generator = GameGenerator( blue_faction, red_faction, - campaign.load_theater(), + theater, + campaign.load_air_wing_config(theater), settings, generator_settings, mod_settings, From 9768fb349378faebfe20d946be0e3eb177d4f13d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 18:32:21 -0700 Subject: [PATCH 35/41] Add base selection UI to startup. https://github.com/dcs-liberation/dcs_liberation/issues/1145 --- qt_ui/windows/AirWingConfigurationDialog.py | 70 +++++++++++++++++---- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index 82638f3a..e97053cd 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -1,4 +1,4 @@ -from typing import Optional, Callable +from typing import Optional, Callable, Iterable from PySide2.QtCore import ( QItemSelectionModel, @@ -24,11 +24,13 @@ from PySide2.QtWidgets import ( QHBoxLayout, QStackedLayout, QTabWidget, + QComboBox, ) from game import Game from game.dcs.aircrafttype import AircraftType from game.squadrons import AirWing, Pilot, Squadron +from game.theater import ControlPoint, ConflictTheater from gen.flights.flight import FlightType from qt_ui.models import AirWingModel, SquadronModel from qt_ui.uiconstants import AIRCRAFT_ICONS @@ -96,8 +98,41 @@ class AllowedMissionTypeControls(QVBoxLayout): self.allowed_mission_types.remove(task) +class SquadronBaseSelector(QComboBox): + """A combo box for selecting a squadrons home air base. + + The combo box will automatically be populated with all air bases compatible with the + squadron. + """ + + def __init__( + self, + bases: Iterable[ControlPoint], + squadron: Squadron, + ) -> None: + super().__init__() + self.bases = list(bases) + self.squadron = squadron + self.setSizeAdjustPolicy(self.AdjustToContents) + + for base in self.bases: + if not base.can_operate(self.squadron.aircraft): + continue + self.addItem(base.name, base) + self.model().sort(0) + self.setCurrentText(self.squadron.location.name) + + @property + def available(self) -> int: + origin = self.currentData() + if origin is None: + return 0 + inventory = self.global_inventory.for_control_point(origin) + return inventory.available(self.aircraft) + + class SquadronConfigurationBox(QGroupBox): - def __init__(self, squadron: Squadron) -> None: + def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None: super().__init__() self.setCheckable(True) self.squadron = squadron @@ -119,6 +154,13 @@ class SquadronConfigurationBox(QGroupBox): self.nickname_edit.textChanged.connect(self.on_nickname_changed) left_column.addWidget(self.nickname_edit) + left_column.addWidget(QLabel("Base:")) + self.base_selector = SquadronBaseSelector( + theater.control_points_for(squadron.player), squadron + ) + self.base_selector.currentIndexChanged.connect(self.on_base_changed) + left_column.addWidget(self.base_selector) + if squadron.player: player_label = QLabel( "Players (one per line, leave empty for an AI-only squadron):" @@ -149,6 +191,12 @@ class SquadronConfigurationBox(QGroupBox): def on_nickname_changed(self, text: str) -> None: self.squadron.nickname = text + def on_base_changed(self, index: int) -> None: + base = self.base_selector.itemData(index) + if base is None: + raise RuntimeError("Base cannot be none") + self.squadron.assign_to_base(base) + def reset_title(self) -> None: self.setTitle(f"{self.squadron.name} - {self.squadron.aircraft}") @@ -165,11 +213,11 @@ class SquadronConfigurationBox(QGroupBox): class SquadronConfigurationLayout(QVBoxLayout): - def __init__(self, squadrons: list[Squadron]) -> None: + def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None: super().__init__() self.squadron_configs = [] for squadron in squadrons: - squadron_config = SquadronConfigurationBox(squadron) + squadron_config = SquadronConfigurationBox(squadron, theater) self.squadron_configs.append(squadron_config) self.addWidget(squadron_config) @@ -182,12 +230,12 @@ class SquadronConfigurationLayout(QVBoxLayout): class AircraftSquadronsPage(QWidget): - def __init__(self, squadrons: list[Squadron]) -> None: + def __init__(self, squadrons: list[Squadron], theater: ConflictTheater) -> None: super().__init__() layout = QVBoxLayout() self.setLayout(layout) - self.squadrons_config = SquadronConfigurationLayout(squadrons) + self.squadrons_config = SquadronConfigurationLayout(squadrons, theater) scrolling_widget = QWidget() scrolling_widget.setLayout(self.squadrons_config) @@ -205,12 +253,12 @@ class AircraftSquadronsPage(QWidget): class AircraftSquadronsPanel(QStackedLayout): - def __init__(self, air_wing: AirWing) -> None: + def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None: super().__init__() self.air_wing = air_wing self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {} for aircraft, squadrons in self.air_wing.squadrons.items(): - page = AircraftSquadronsPage(squadrons) + page = AircraftSquadronsPage(squadrons, theater) self.addWidget(page) self.squadrons_pages[aircraft] = page @@ -262,7 +310,7 @@ class AircraftTypeList(QListView): class AirWingConfigurationTab(QWidget): - def __init__(self, air_wing: AirWing) -> None: + def __init__(self, air_wing: AirWing, theater: ConflictTheater) -> None: super().__init__() layout = QHBoxLayout() @@ -272,7 +320,7 @@ class AirWingConfigurationTab(QWidget): type_list.page_index_changed.connect(self.on_aircraft_changed) layout.addWidget(type_list) - self.squadrons_panel = AircraftSquadronsPanel(air_wing) + self.squadrons_panel = AircraftSquadronsPanel(air_wing, theater) layout.addLayout(self.squadrons_panel) def apply(self) -> None: @@ -317,7 +365,7 @@ class AirWingConfigurationDialog(QDialog): self.tabs = [] for coalition in game.coalitions: - coalition_tab = AirWingConfigurationTab(coalition.air_wing) + coalition_tab = AirWingConfigurationTab(coalition.air_wing, game.theater) name = "Blue" if coalition.player else "Red" tab_widget.addTab(coalition_tab, name) self.tabs.append(coalition_tab) From 357487b7678f72cc91cb1b4708cf739dfbcff643 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 20:07:38 -0700 Subject: [PATCH 36/41] Remove random purchase pessimization. Aircraft variety is now handled by explicit squadron selection, so no longer needed. --- game/procurement.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/game/procurement.py b/game/procurement.py index 0690cf39..559f5891 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -227,7 +227,6 @@ class ProcurementAi: def affordable_aircraft_for( self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float ) -> Optional[AircraftType]: - best_choice: Optional[AircraftType] = None for unit in aircraft_for_task(request.task_capability): if unit.price * request.number > budget: continue @@ -242,13 +241,9 @@ class ProcurementAi: if distance_to_target > unit.max_mission_range: continue - # Affordable, compatible, and we have a squadron capable of the task. To - # keep some variety, skip with a 50/50 chance. Might be a good idea to have - # the chance to skip based on the price compared to the rest of the choices. - best_choice = unit - if random.choice([True, False]): - break - return best_choice + # Affordable, compatible, and we have a squadron capable of the task. + return unit + return None def fulfill_aircraft_request( self, request: AircraftProcurementRequest, budget: float From 55c6728c42aabfa0ecb8b61533a61708d95d4ac1 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 12:13:06 -0700 Subject: [PATCH 37/41] Update Black Sea to campaign version 9.0. --- resources/campaigns/black_sea.miz | Bin 86333 -> 86612 bytes resources/campaigns/black_sea.yaml | 145 ++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/resources/campaigns/black_sea.miz b/resources/campaigns/black_sea.miz index e32a1fb2fc28d79fcff848ceb2860e53d96223ea..975000407424309303ca2a52f8b31ef32aaf2171 100644 GIT binary patch delta 67664 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%0DtZmwJVba; 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

QlqtO$rAjfLyvqjzO#5`;s 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 delta 63521 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 diff --git a/resources/campaigns/black_sea.yaml b/resources/campaigns/black_sea.yaml index d015ac36..f7452d70 100644 --- a/resources/campaigns/black_sea.yaml +++ b/resources/campaigns/black_sea.yaml @@ -5,4 +5,147 @@ authors: Colonel Panic description:

A medium sized theater with bases along the coast of the Black Sea.

miz: black_sea.miz performance: 2, -version: "8.0" \ No newline at end of file +version: "9.0" +squadrons: + # Anapa-Vityazevo + 12: + - primary: BARCAP + aircraft: + - Su-30 Flanker-C + - Su-27 Flanker-B + - primary: AEW&C + aircraft: + - A-50 + - primary: Refueling + aircraft: + - IL-78M + - primary: Transport + aircraft: + - IL-78MD + - primary: Strike + secondary: air-to-ground + aircraft: + - Tu-160 Blackjack + # Krasnodar-Center + 13: + - primary: BARCAP + secondary: air-to-air + aircraft: + - MiG-31 Foxhound + - MiG-25PD Foxbat-E + - primary: SEAD + secondary: any + - primary: DEAD + secondary: any + # Maykop-Khanskaya + 16: + - primary: CAS + secondary: air-to-ground + aircraft: + - Su-25 Frogfoot + - primary: BAI + secondary: air-to-ground + aircraft: + - Su-34 Fullback + - Su-24M Fencer-D + - primary: DEAD + secondary: air-to-ground + - primary: CAS + secondary: air-to-ground + aircraft: + - Mi-24P Hind-F + - Mi-24P Hind-E + # Senaki-Kholki + 23: + - primary: CAS + secondary: air-to-ground + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - primary: BAI + secondary: air-to-ground + aircraft: + - F-15E Strike Eagle + - primary: DEAD + secondary: air-to-ground + aircraft: + - F-16CM Fighting Falcon (Block 50) + - primary: Transport + aircraft: + - UH-60A + # Kobuleti + 24: + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - primary: SEAD + secondary: any + aircraft: + - F-16CM Fighting Falcon (Block 50) + - primary: DEAD + secondary: any + aircraft: + - F-16CM Fighting Falcon (Block 50) + # Kutaisi + 25: + - primary: BARCAP + aircraft: + - F-15C Eagle + - primary: AEW&C + aircraft: + - E-3A + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - primary: Transport + aircraft: + - C-17A + - primary: Strike + secondary: air-to-ground + aircraft: + - B-1B Lancer + Blue CV: + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-14B Tomcat + - primary: BARCAP + secondary: any + aircraft: + - F-14B Tomcat + - primary: Strike + secondary: any + aircraft: + - F/A-18C Hornet (Lot 20) + - primary: BAI + secondary: any + aircraft: + - F/A-18C Hornet (Lot 20) + - primary: Refueling + aircraft: + - S-3B Tanker + Blue LHA: + - primary: BAI + secondary: air-to-ground + aircraft: + - AV-8B Harrier II Night Attack + - primary: CAS + secondary: air-to-ground + aircraft: + - UH-1H Iroquois + Red CV: + - primary: BARCAP + secondary: air-to-air + - primary: BARCAP + secondary: any + - primary: Strike + secondary: any + - primary: BAI + secondary: any + - primary: Refueling + Red LHA: + - primary: BAI + secondary: air-to-ground + - primary: CAS + secondary: air-to-ground \ No newline at end of file From 99274133ff2daac9e69a139ab716a73ae7f890df Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Aug 2021 21:40:11 -0700 Subject: [PATCH 38/41] Add range estimates for the F-15s. --- resources/units/aircraft/F-15C.yaml | 4 +++- resources/units/aircraft/F-15E.yaml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/units/aircraft/F-15C.yaml b/resources/units/aircraft/F-15C.yaml index 36f39a11..601148bb 100644 --- a/resources/units/aircraft/F-15C.yaml +++ b/resources/units/aircraft/F-15C.yaml @@ -1,4 +1,5 @@ -description: The F-15 has often been labeled as the greatest U.S. fighter aircraft +description: + The F-15 has often been labeled as the greatest U.S. fighter aircraft from the 1970s until the early 21st century. The F-15C is a pure fighter with outstanding performance and has scored over 100 air-to-air victories without suffering any confirmed losses. @@ -7,6 +8,7 @@ manufacturer: McDonnell Douglas origin: USA price: 20 role: Air-Superiority Fighter +max_range: 400 variants: F-15C Eagle: {} F-15J Eagle: {} diff --git a/resources/units/aircraft/F-15E.yaml b/resources/units/aircraft/F-15E.yaml index 3a80a4cb..ecba91bd 100644 --- a/resources/units/aircraft/F-15E.yaml +++ b/resources/units/aircraft/F-15E.yaml @@ -1,4 +1,5 @@ -description: The F-15 has often been labeled as the greatest U.S. fighter aircraft +description: + The F-15 has often been labeled as the greatest U.S. fighter aircraft from the 1970s until the early 21st century. The F-15E is a multirole fighter and exceeds in CAS operations. It served worldwide without suffering any confirmed losses. introduced: 1988 @@ -6,5 +7,6 @@ manufacturer: McDonnell Douglas origin: USA price: 24 role: Multirole Strike Fighter +max_range: 300 variants: F-15E Strike Eagle: {} From 4423288a53d056e1e9a0169db00ab6459ed9cce1 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 15 Aug 2021 15:41:39 -0700 Subject: [PATCH 39/41] Assign aircraft to squadrons rather than bases. This is needed to support the upcoming squadron transfers, since squadrons need to bring their aircraft with them. https://github.com/dcs-liberation/dcs_liberation/issues/1145 --- changelog.md | 1 + game/coalition.py | 9 +- game/commander/aircraftallocator.py | 16 +- game/commander/objectivefinder.py | 5 +- game/commander/packagebuilder.py | 12 +- game/commander/packagefulfiller.py | 11 +- game/commander/tasks/packageplanningtask.py | 3 - game/commander/theaterstate.py | 5 +- game/event/event.py | 78 +------- game/game.py | 13 +- game/{unitdelivery.py => groundunitorders.py} | 60 ++---- game/inventory.py | 142 -------------- game/models/game_stats.py | 6 +- game/procurement.py | 121 ++++-------- game/purchaseadapter.py | 180 ++++++++++++++++++ game/squadrons/airwing.py | 19 +- game/squadrons/squadron.py | 41 +++- game/theater/base.py | 56 +----- game/theater/controlpoint.py | 66 +++---- game/transfers.py | 61 +++--- gen/aircraft.py | 25 +-- gen/flights/flight.py | 5 +- qt_ui/models.py | 6 +- .../widgets/combos/QOriginAirfieldSelector.py | 69 ------- qt_ui/windows/AirWingConfigurationDialog.py | 8 - qt_ui/windows/AirWingDialog.py | 24 +-- qt_ui/windows/basemenu/QBaseMenu2.py | 8 +- ...itBehaviour.py => UnitTransactionFrame.py} | 164 +++++++++------- .../airfield/QAircraftRecruitmentMenu.py | 95 ++------- .../ground_forces/QArmorRecruitmentMenu.py | 31 +-- qt_ui/windows/basemenu/intel/QIntelInfo.py | 2 +- qt_ui/windows/intel.py | 13 +- qt_ui/windows/mission/QPackageDialog.py | 4 +- .../windows/mission/flight/QFlightCreator.py | 54 ++---- .../mission/flight/SquadronSelector.py | 21 +- .../flight/settings/QFlightSlotEditor.py | 10 +- 36 files changed, 574 insertions(+), 870 deletions(-) rename game/{unitdelivery.py => groundunitorders.py} (72%) delete mode 100644 game/inventory.py create mode 100644 game/purchaseadapter.py delete mode 100644 qt_ui/widgets/combos/QOriginAirfieldSelector.py rename qt_ui/windows/basemenu/{QRecruitBehaviour.py => UnitTransactionFrame.py} (59%) diff --git a/changelog.md b/changelog.md index e1f3b9e0..8b01de88 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ Saves from 4.x are not compatible with 5.0. * **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated. * **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts. * **[Campaign]** (WIP) Squadrons now have a home base and will not operate out of other bases. See https://github.com/dcs-liberation/dcs_liberation/issues/1145 for status. +* **[Campaign]** Aircraft now belong to squadrons rather than bases to support squadron location transfers. * **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions. * **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI. * **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points. diff --git a/game/coalition.py b/game/coalition.py index 9804d553..e15d916d 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -10,7 +10,6 @@ from game.campaignloader.defaultsquadronassigner import DefaultSquadronAssigner from game.commander import TheaterCommander from game.commander.missionscheduler import MissionScheduler from game.income import Income -from game.inventory import GlobalAircraftInventory from game.navmesh import NavMesh from game.orderedset import OrderedSet from game.profiling import logged_duration, MultiEventTracer @@ -88,10 +87,6 @@ class Coalition: assert self._navmesh is not None return self._navmesh - @property - def aircraft_inventory(self) -> GlobalAircraftInventory: - return self.game.aircraft_inventory - def __getstate__(self) -> dict[str, Any]: state = self.__dict__.copy() # Avoid persisting any volatile types that can be deterministically @@ -196,7 +191,9 @@ class Coalition: return for cp in self.game.theater.control_points_for(self.player): - cp.pending_unit_deliveries.refund_all(self) + cp.ground_unit_orders.refund_all(self) + for squadron in self.air_wing.iter_squadrons(): + squadron.refund_orders() def plan_missions(self) -> None: color = "Blue" if self.player else "Red" diff --git a/game/commander/aircraftallocator.py b/game/commander/aircraftallocator.py index b8bfa812..0339ff27 100644 --- a/game/commander/aircraftallocator.py +++ b/game/commander/aircraftallocator.py @@ -1,9 +1,8 @@ from typing import Optional, Tuple from game.commander.missionproposals import ProposedFlight -from game.inventory import GlobalAircraftInventory -from game.squadrons.squadron import Squadron from game.squadrons.airwing import AirWing +from game.squadrons.squadron import Squadron from game.theater import ControlPoint, MissionTarget from game.utils import meters from gen.flights.ai_flight_planner_db import aircraft_for_task @@ -15,15 +14,10 @@ class AircraftAllocator: """Finds suitable aircraft for proposed missions.""" def __init__( - self, - air_wing: AirWing, - closest_airfields: ClosestAirfields, - global_inventory: GlobalAircraftInventory, - is_player: bool, + self, air_wing: AirWing, closest_airfields: ClosestAirfields, is_player: bool ) -> None: self.air_wing = air_wing self.closest_airfields = closest_airfields - self.global_inventory = global_inventory self.is_player = is_player def find_squadron_for_flight( @@ -56,12 +50,9 @@ class AircraftAllocator: for airfield in self.closest_airfields.operational_airfields: if not airfield.is_friendly(self.is_player): continue - inventory = self.global_inventory.for_control_point(airfield) for aircraft in types: if not airfield.can_operate(aircraft): continue - if inventory.available(aircraft) < flight.num_aircraft: - continue distance_to_target = meters(target.distance_to(airfield)) if distance_to_target > aircraft.max_mission_range: continue @@ -71,9 +62,8 @@ class AircraftAllocator: aircraft, task, airfield ) for squadron in squadrons: - if squadron.operates_from(airfield) and squadron.can_provide_pilots( + if squadron.operates_from(airfield) and squadron.can_fulfill_flight( flight.num_aircraft ): - inventory.remove_aircraft(aircraft, flight.num_aircraft) return airfield, squadron return None diff --git a/game/commander/objectivefinder.py b/game/commander/objectivefinder.py index cf5c6102..e684b98e 100644 --- a/game/commander/objectivefinder.py +++ b/game/commander/objectivefinder.py @@ -157,7 +157,10 @@ class ObjectiveFinder: for control_point in self.enemy_control_points(): if not isinstance(control_point, Airfield): continue - if control_point.base.total_aircraft >= min_aircraft: + if ( + control_point.allocated_aircraft(self.game).total_present + >= min_aircraft + ): airfields.append(control_point) return self._targets_by_range(airfields) diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index f39c0e07..a4baf9c7 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -1,13 +1,12 @@ from typing import Optional +from game.commander.aircraftallocator import AircraftAllocator from game.commander.missionproposals import ProposedFlight from game.dcs.aircrafttype import AircraftType -from game.inventory import GlobalAircraftInventory from game.squadrons.airwing import AirWing from game.theater import MissionTarget, OffMapSpawn, ControlPoint from game.utils import nautical_miles from gen.ato import Package -from game.commander.aircraftallocator import AircraftAllocator from gen.flights.closestairfields import ClosestAirfields from gen.flights.flight import Flight @@ -19,7 +18,6 @@ class PackageBuilder: self, location: MissionTarget, closest_airfields: ClosestAirfields, - global_inventory: GlobalAircraftInventory, air_wing: AirWing, is_player: bool, package_country: str, @@ -30,10 +28,7 @@ class PackageBuilder: self.is_player = is_player self.package_country = package_country self.package = Package(location, auto_asap=asap) - self.allocator = AircraftAllocator( - air_wing, closest_airfields, global_inventory, is_player - ) - self.global_inventory = global_inventory + self.allocator = AircraftAllocator(air_wing, closest_airfields, is_player) self.start_type = start_type def plan_flight(self, plan: ProposedFlight) -> bool: @@ -93,6 +88,5 @@ class PackageBuilder: """Returns any planned flights to the inventory.""" flights = list(self.package.flights) for flight in flights: - self.global_inventory.return_from_flight(flight) - flight.clear_roster() + flight.return_pilots_and_aircraft() self.package.remove_flight(flight) diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index 87842a9e..a8cca58a 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -5,15 +5,14 @@ from collections import defaultdict from typing import Set, Iterable, Dict, TYPE_CHECKING, Optional from game.commander.missionproposals import ProposedMission, ProposedFlight, EscortType +from game.commander.packagebuilder import PackageBuilder from game.data.doctrine import Doctrine -from game.inventory import GlobalAircraftInventory from game.procurement import AircraftProcurementRequest from game.profiling import MultiEventTracer from game.settings import Settings from game.squadrons import AirWing from game.theater import ConflictTheater from game.threatzones import ThreatZones -from game.commander.packagebuilder import PackageBuilder from gen.ato import AirTaskingOrder, Package from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flight import FlightType @@ -27,15 +26,10 @@ class PackageFulfiller: """Responsible for package aircraft allocation and flight plan layout.""" def __init__( - self, - coalition: Coalition, - theater: ConflictTheater, - aircraft_inventory: GlobalAircraftInventory, - settings: Settings, + self, coalition: Coalition, theater: ConflictTheater, settings: Settings ) -> None: self.coalition = coalition self.theater = theater - self.aircraft_inventory = aircraft_inventory self.player_missions_asap = settings.auto_ato_player_missions_asap self.default_start_type = settings.default_start_type @@ -137,7 +131,6 @@ class PackageFulfiller: builder = PackageBuilder( mission.location, ObjectiveDistanceCache.get_closest_airfields(mission.location), - self.aircraft_inventory, self.air_wing, self.is_player, self.coalition.country_name, diff --git a/game/commander/tasks/packageplanningtask.py b/game/commander/tasks/packageplanningtask.py index 65a7ffd8..a45774c3 100644 --- a/game/commander/tasks/packageplanningtask.py +++ b/game/commander/tasks/packageplanningtask.py @@ -53,8 +53,6 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]): def execute(self, coalition: Coalition) -> None: if self.package is None: raise RuntimeError("Attempted to execute failed package planning task") - for flight in self.package.flights: - coalition.aircraft_inventory.claim_for_flight(flight) coalition.ato.add_package(self.package) @abstractmethod @@ -99,7 +97,6 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]): fulfiller = PackageFulfiller( state.context.coalition, state.context.theater, - state.available_aircraft, state.context.settings, ) self.package = fulfiller.plan_mission( diff --git a/game/commander/theaterstate.py b/game/commander/theaterstate.py index 4450c95b..8732206b 100644 --- a/game/commander/theaterstate.py +++ b/game/commander/theaterstate.py @@ -10,9 +10,9 @@ from typing import TYPE_CHECKING, Any, Union, Optional from game.commander.garrisons import Garrisons from game.commander.objectivefinder import ObjectiveFinder from game.htn import WorldState -from game.inventory import GlobalAircraftInventory from game.profiling import MultiEventTracer from game.settings import Settings +from game.squadrons import AirWing from game.theater import ControlPoint, FrontLine, MissionTarget, ConflictTheater from game.theater.theatergroundobject import ( TheaterGroundObject, @@ -58,7 +58,6 @@ class TheaterState(WorldState["TheaterState"]): strike_targets: list[TheaterGroundObject[Any]] enemy_barcaps: list[ControlPoint] threat_zones: ThreatZones - available_aircraft: GlobalAircraftInventory def _rebuild_threat_zones(self) -> None: """Recreates the theater's threat zones based on the current planned state.""" @@ -122,7 +121,6 @@ class TheaterState(WorldState["TheaterState"]): strike_targets=list(self.strike_targets), enemy_barcaps=list(self.enemy_barcaps), threat_zones=self.threat_zones, - available_aircraft=self.available_aircraft.clone(), # Persistent properties are not copied. These are a way for failed subtasks # to communicate requirements to other tasks. For example, the task to # attack enemy garrisons might fail because the target area has IADS @@ -172,5 +170,4 @@ class TheaterState(WorldState["TheaterState"]): strike_targets=list(finder.strike_targets()), enemy_barcaps=list(game.theater.control_points_for(not player)), threat_zones=game.threat_zone_for(not player), - available_aircraft=game.aircraft_inventory.clone(), ) diff --git a/game/event/event.py b/game/event/event.py index caf68a38..e40cb76f 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -7,13 +7,12 @@ from dcs.mapping import Point from dcs.task import Task from game import persistency -from game.debriefing import AirLosses, Debriefing +from game.debriefing import Debriefing from game.infos.information import Information from game.operation.operation import Operation from game.theater import ControlPoint from gen.ato import AirTaskingOrder from gen.ground_forces.combat_stance import CombatStance -from ..dcs.groundunittype import GroundUnitType from ..unitmap import UnitMap if TYPE_CHECKING: @@ -67,59 +66,6 @@ class Event: ) return unit_map - @staticmethod - def _transfer_aircraft( - ato: AirTaskingOrder, losses: AirLosses, for_player: bool - ) -> None: - for package in ato.packages: - for flight in package.flights: - # No need to transfer to the same location. - if flight.departure == flight.arrival: - continue - - # Don't transfer to bases that were captured. Note that if the - # airfield was back-filling transfers it may overflow. We could - # attempt to be smarter in the future by performing transfers in - # order up a graph to prevent transfers to full airports and - # send overflow off-map, but overflow is fine for now. - if flight.arrival.captured != for_player: - logging.info( - f"Not transferring {flight} because {flight.arrival} " - "was captured" - ) - continue - - transfer_count = losses.surviving_flight_members(flight) - if transfer_count < 0: - logging.error( - f"{flight} had {flight.count} aircraft but " - f"{transfer_count} losses were recorded." - ) - continue - - aircraft = flight.unit_type - available = flight.departure.base.total_units_of_type(aircraft) - if available < transfer_count: - logging.error( - f"Found killed {aircraft} from {flight.departure} but " - f"that airbase has only {available} available." - ) - continue - - flight.departure.base.aircraft[aircraft] -= transfer_count - if aircraft not in flight.arrival.base.aircraft: - # TODO: Should use defaultdict. - flight.arrival.base.aircraft[aircraft] = 0 - flight.arrival.base.aircraft[aircraft] += transfer_count - - def complete_aircraft_transfers(self, debriefing: Debriefing) -> None: - self._transfer_aircraft( - self.game.blue.ato, debriefing.air_losses, for_player=True - ) - self._transfer_aircraft( - self.game.red.ato, debriefing.air_losses, for_player=False - ) - def commit_air_losses(self, debriefing: Debriefing) -> None: for loss in debriefing.air_losses.losses: if loss.pilot is not None and ( @@ -127,18 +73,18 @@ class Event: or not self.game.settings.invulnerable_player_pilots ): loss.pilot.kill() + squadron = loss.flight.squadron aircraft = loss.flight.unit_type - cp = loss.flight.departure - available = cp.base.total_units_of_type(aircraft) + available = squadron.owned_aircraft if available <= 0: logging.error( - f"Found killed {aircraft} from {cp} but that airbase has " + f"Found killed {aircraft} from {squadron} but that airbase has " "none available." ) continue - logging.info(f"{aircraft} destroyed from {cp}") - cp.base.aircraft[aircraft] -= 1 + logging.info(f"{aircraft} destroyed from {squadron}") + squadron.owned_aircraft -= 1 @staticmethod def _commit_pilot_experience(ato: AirTaskingOrder) -> None: @@ -276,7 +222,6 @@ class Event: self.commit_building_losses(debriefing) self.commit_damaged_runways(debriefing) self.commit_captures(debriefing) - self.complete_aircraft_transfers(debriefing) # Destroyed units carcass # ------------------------- @@ -458,15 +403,10 @@ class Event: source.base.commit_losses(moved_units) # Also transfer pending deliveries. - for unit_type, count in source.pending_unit_deliveries.units.items(): - if not isinstance(unit_type, GroundUnitType): - continue - if count <= 0: - # Don't transfer *sales*... - continue + for unit_type, count in source.ground_unit_orders.units.items(): move_count = int(count * move_factor) - source.pending_unit_deliveries.sell({unit_type: move_count}) - destination.pending_unit_deliveries.order({unit_type: move_count}) + source.ground_unit_orders.sell({unit_type: move_count}) + destination.ground_unit_orders.order({unit_type: move_count}) total_units_redeployed += move_count if total_units_redeployed > 0: diff --git a/game/game.py b/game/game.py index 8401b6ad..2da28c2a 100644 --- a/game/game.py +++ b/game/game.py @@ -13,7 +13,6 @@ from dcs.task import CAP, CAS, PinpointStrike from dcs.vehicles import AirDefence from faker import Faker -from game.inventory import GlobalAircraftInventory from game.models.game_stats import GameStats from game.plugins import LuaPluginManager from gen import naming @@ -127,8 +126,6 @@ class Game: self.blue.configure_default_air_wing(air_wing_config) self.red.configure_default_air_wing(air_wing_config) - self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) - self.on_load(game_still_initializing=True) def __setstate__(self, state: dict[str, Any]) -> None: @@ -392,9 +389,9 @@ class Game: # Plan Coalition specific turn if for_blue: - self.initialize_turn_for(player=True) + self.blue.initialize_turn() if for_red: - self.initialize_turn_for(player=False) + self.red.initialize_turn() # Plan GroundWar self.ground_planners = {} @@ -404,12 +401,6 @@ class Game: gplanner.plan_groundwar() self.ground_planners[cp.id] = gplanner - def initialize_turn_for(self, player: bool) -> None: - self.aircraft_inventory.reset(player) - for cp in self.theater.control_points_for(player): - self.aircraft_inventory.set_from_control_point(cp) - self.coalition_for(player).initialize_turn() - def message(self, text: str) -> None: self.informations.append(Information(text, turn=self.turn)) diff --git a/game/unitdelivery.py b/game/groundunitorders.py similarity index 72% rename from game/unitdelivery.py rename to game/groundunitorders.py index 9bba6130..187e5373 100644 --- a/game/unitdelivery.py +++ b/game/groundunitorders.py @@ -2,13 +2,11 @@ from __future__ import annotations import logging from collections import defaultdict -from dataclasses import dataclass -from typing import Optional, TYPE_CHECKING, Any +from typing import Optional, TYPE_CHECKING from game.theater import ControlPoint from .coalition import Coalition from .dcs.groundunittype import GroundUnitType -from .dcs.unittype import UnitType from .theater.transitnetwork import ( NoPathError, TransitNetwork, @@ -19,58 +17,41 @@ if TYPE_CHECKING: from .game import Game -@dataclass(frozen=True) -class GroundUnitSource: - control_point: ControlPoint - - -class PendingUnitDeliveries: +class GroundUnitOrders: def __init__(self, destination: ControlPoint) -> None: self.destination = destination # Maps unit type to order quantity. - self.units: dict[UnitType[Any], int] = defaultdict(int) + self.units: dict[GroundUnitType, int] = defaultdict(int) def __str__(self) -> str: - return f"Pending delivery to {self.destination}" + return f"Pending ground unit delivery to {self.destination}" - def order(self, units: dict[UnitType[Any], int]) -> None: + def order(self, units: dict[GroundUnitType, int]) -> None: for k, v in units.items(): self.units[k] += v - def sell(self, units: dict[UnitType[Any], int]) -> None: + def sell(self, units: dict[GroundUnitType, int]) -> None: for k, v in units.items(): self.units[k] -= v if self.units[k] == 0: del self.units[k] def refund_all(self, coalition: Coalition) -> None: - self.refund(coalition, self.units) + self._refund(coalition, self.units) self.units = defaultdict(int) - def refund_ground_units(self, coalition: Coalition) -> None: - ground_units: dict[UnitType[Any], int] = { - u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType) - } - self.refund(coalition, ground_units) - for gu in ground_units.keys(): - del self.units[gu] - - def refund(self, coalition: Coalition, units: dict[UnitType[Any], int]) -> None: + def _refund(self, coalition: Coalition, units: dict[GroundUnitType, int]) -> None: for unit_type, count in units.items(): logging.info(f"Refunding {count} {unit_type} at {self.destination.name}") coalition.adjust_budget(unit_type.price * count) - def pending_orders(self, unit_type: UnitType[Any]) -> int: + def pending_orders(self, unit_type: GroundUnitType) -> int: pending_units = self.units.get(unit_type) if pending_units is None: pending_units = 0 return pending_units - def available_next_turn(self, unit_type: UnitType[Any]) -> int: - current_units = self.destination.base.total_units_of_type(unit_type) - return self.pending_orders(unit_type) + current_units - def process(self, game: Game) -> None: coalition = game.coalition_for(self.destination.captured) ground_unit_source = self.find_ground_unit_source(game) @@ -79,36 +60,33 @@ class PendingUnitDeliveries: f"{self.destination.name} lost its source for ground unit " "reinforcements. Refunding purchase price." ) - self.refund_ground_units(coalition) + self.refund_all(coalition) - bought_units: dict[UnitType[Any], int] = {} + bought_units: dict[GroundUnitType, int] = {} units_needing_transfer: dict[GroundUnitType, int] = {} - sold_units: dict[UnitType[Any], int] = {} for unit_type, count in self.units.items(): allegiance = "Ally" if self.destination.captured else "Enemy" - d: dict[Any, int] - if ( - isinstance(unit_type, GroundUnitType) - and self.destination != ground_unit_source - ): + d: dict[GroundUnitType, int] + if self.destination != ground_unit_source: source = ground_unit_source d = units_needing_transfer else: source = self.destination d = bought_units - if count >= 0: + if count < 0: + logging.error( + f"Attempted sale of {unit_type} at {self.destination} but ground " + "units cannot be sold" + ) + elif count > 0: d[unit_type] = count game.message( f"{allegiance} reinforcements: {unit_type} x {count} at {source}" ) - else: - sold_units[unit_type] = -count - game.message(f"{allegiance} sold: {unit_type} x {-count} at {source}") self.units = defaultdict(int) self.destination.base.commission_units(bought_units) - self.destination.base.commit_losses(sold_units) if units_needing_transfer: if ground_unit_source is None: diff --git a/game/inventory.py b/game/inventory.py deleted file mode 100644 index e4d40789..00000000 --- a/game/inventory.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Inventory management APIs.""" -from __future__ import annotations - -from collections import defaultdict, Iterator, Iterable -from typing import TYPE_CHECKING - -from game.dcs.aircrafttype import AircraftType - -if TYPE_CHECKING: - from game.theater import ControlPoint - from gen.flights.flight import Flight - - -class ControlPointAircraftInventory: - """Aircraft inventory for a single control point.""" - - def __init__(self, control_point: ControlPoint) -> None: - self.control_point = control_point - self.inventory: dict[AircraftType, int] = defaultdict(int) - - def clone(self) -> ControlPointAircraftInventory: - new = ControlPointAircraftInventory(self.control_point) - new.inventory = self.inventory.copy() - return new - - def add_aircraft(self, aircraft: AircraftType, count: int) -> None: - """Adds aircraft to the inventory. - - Args: - aircraft: The type of aircraft to add. - count: The number of aircraft to add. - """ - self.inventory[aircraft] += count - - def remove_aircraft(self, aircraft: AircraftType, count: int) -> None: - """Removes aircraft from the inventory. - - Args: - aircraft: The type of aircraft to remove. - count: The number of aircraft to remove. - - Raises: - ValueError: The control point cannot fulfill the requested number of - aircraft. - """ - available = self.inventory[aircraft] - if available < count: - raise ValueError( - f"Cannot remove {count} {aircraft} from " - f"{self.control_point.name}. Only have {available}." - ) - self.inventory[aircraft] -= count - - def available(self, aircraft: AircraftType) -> int: - """Returns the number of available aircraft of the given type. - - Args: - aircraft: The type of aircraft to query. - """ - try: - return self.inventory[aircraft] - except KeyError: - return 0 - - @property - def types_available(self) -> Iterator[AircraftType]: - """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[AircraftType, int]]: - """Iterates over all available aircraft types, including amounts.""" - for aircraft, count in self.inventory.items(): - if count > 0: - yield aircraft, count - - def clear(self) -> None: - """Clears all aircraft from the inventory.""" - self.inventory.clear() - - -class GlobalAircraftInventory: - """Game-wide aircraft inventory.""" - - def __init__(self, control_points: Iterable[ControlPoint]) -> None: - self.inventories: dict[ControlPoint, ControlPointAircraftInventory] = { - cp: ControlPointAircraftInventory(cp) for cp in control_points - } - - def clone(self) -> GlobalAircraftInventory: - new = GlobalAircraftInventory([]) - new.inventories = { - cp: inventory.clone() for cp, inventory in self.inventories.items() - } - return new - - def reset(self, for_player: bool) -> None: - """Clears the inventory of every control point owned by the given coalition.""" - for inventory in self.inventories.values(): - if inventory.control_point.captured == for_player: - inventory.clear() - - def set_from_control_point(self, control_point: ControlPoint) -> None: - """Set the control point's aircraft inventory. - - If the inventory for the given control point has already been set for - the turn, it will be overwritten. - """ - inventory = self.inventories[control_point] - for aircraft, count in control_point.base.aircraft.items(): - inventory.add_aircraft(aircraft, count) - - def for_control_point( - self, control_point: ControlPoint - ) -> ControlPointAircraftInventory: - """Returns the inventory specific to the given control point.""" - return self.inventories[control_point] - - @property - def available_types_for_player(self) -> Iterator[AircraftType]: - """Iterates over all aircraft types available to the player.""" - seen: set[AircraftType] = set() - for control_point, inventory in self.inventories.items(): - if control_point.captured: - for aircraft in inventory.types_available: - if not control_point.can_operate(aircraft): - continue - if aircraft not in seen: - seen.add(aircraft) - yield aircraft - - def claim_for_flight(self, flight: Flight) -> None: - """Removes aircraft from the inventory for the given flight.""" - inventory = self.for_control_point(flight.from_cp) - inventory.remove_aircraft(flight.unit_type, flight.count) - - def return_from_flight(self, flight: Flight) -> None: - """Returns a flight's aircraft to the inventory.""" - inventory = self.for_control_point(flight.from_cp) - inventory.add_aircraft(flight.unit_type, flight.count) diff --git a/game/models/game_stats.py b/game/models/game_stats.py index 7e828021..780565d4 100644 --- a/game/models/game_stats.py +++ b/game/models/game_stats.py @@ -56,10 +56,12 @@ class GameStats: for cp in game.theater.controlpoints: if cp.captured: - turn_data.allied_units.aircraft_count += sum(cp.base.aircraft.values()) + for squadron in cp.squadrons: + turn_data.allied_units.aircraft_count += squadron.owned_aircraft turn_data.allied_units.vehicles_count += sum(cp.base.armor.values()) else: - turn_data.enemy_units.aircraft_count += sum(cp.base.aircraft.values()) + for squadron in cp.squadrons: + turn_data.enemy_units.aircraft_count += squadron.owned_aircraft turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values()) self.data_per_turn.append(turn_data) diff --git a/game/procurement.py b/game/procurement.py index 559f5891..46048170 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -7,7 +7,6 @@ from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple from game import db from game.data.groundunitclass import GroundUnitClass -from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.factions.faction import Faction from game.squadrons import Squadron @@ -98,37 +97,10 @@ class ProcurementAi: budget -= armor_budget budget += self.reinforce_front_line(armor_budget) - # Don't sell overstock aircraft until after we've bought runways and - # front lines. Any budget we free up should be earmarked for aircraft. - if not self.is_player: - budget += self.sell_incomplete_squadrons() if self.manage_aircraft: budget = self.purchase_aircraft(budget) return budget - def sell_incomplete_squadrons(self) -> float: - # Selling incomplete squadrons gives us more money to spend on the next - # turn. This serves as a short term fix for - # https://github.com/dcs-liberation/dcs_liberation/issues/41. - # - # Only incomplete squadrons which are unlikely to get used will be sold - # rather than all unused aircraft because the unused aircraft are what - # make OCA strikes worthwhile. - # - # This option is only used by the AI since players cannot cancel sales - # (https://github.com/dcs-liberation/dcs_liberation/issues/365). - total = 0.0 - for cp in self.game.theater.control_points_for(self.is_player): - inventory = self.game.aircraft_inventory.for_control_point(cp) - for aircraft, available in inventory.all_aircraft: - # We only ever plan even groups, so the odd aircraft is unlikely - # to get used. - if available % 2 == 0: - continue - inventory.remove_aircraft(aircraft, 1) - total += aircraft.price - return total - def repair_runways(self, budget: float) -> float: for control_point in self.owned_points: if budget < db.RUNWAY_REPAIR_COST: @@ -181,7 +153,7 @@ class ProcurementAi: break budget -= unit.price - cp.pending_unit_deliveries.order({unit: 1}) + cp.ground_unit_orders.order({unit: 1}) return budget @@ -211,64 +183,28 @@ class ProcurementAi: return worst_balanced @staticmethod - def _compatible_squadron_at( - aircraft: AircraftType, airbase: ControlPoint, task: FlightType, count: int - ) -> Optional[Squadron]: - for squadron in airbase.squadrons: - if squadron.aircraft != aircraft: - continue - if not squadron.can_auto_assign(task): - continue - if not squadron.can_provide_pilots(count): - continue - return squadron - return None - - def affordable_aircraft_for( - self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float - ) -> Optional[AircraftType]: - for unit in aircraft_for_task(request.task_capability): - if unit.price * request.number > budget: - continue - - squadron = self._compatible_squadron_at( - unit, airbase, request.task_capability, request.number - ) - if squadron is None: - continue - - distance_to_target = meters(request.near.distance_to(airbase)) - if distance_to_target > unit.max_mission_range: - continue - - # Affordable, compatible, and we have a squadron capable of the task. - return unit - return None - def fulfill_aircraft_request( - self, request: AircraftProcurementRequest, budget: float + squadrons: list[Squadron], quantity: int, budget: float ) -> Tuple[float, bool]: - for airbase in self.best_airbases_for(request): - unit = self.affordable_aircraft_for(request, airbase, budget) - if unit is None: - # Can't afford any aircraft capable of performing the - # required mission that can operate from this airbase. We - # might be able to afford aircraft at other airbases though, - # in the case where the airbase we attempted to use is only - # able to operate expensive aircraft. + for squadron in squadrons: + price = squadron.aircraft.price * quantity + if price > budget: continue - budget -= unit.price * request.number - airbase.pending_unit_deliveries.order({unit: request.number}) + squadron.pending_deliveries += quantity + budget -= price return budget, True return budget, False def purchase_aircraft(self, budget: float) -> float: for request in self.game.coalition_for(self.is_player).procurement_requests: - if not list(self.best_airbases_for(request)): + squadrons = list(self.best_squadrons_for(request)) + if not squadrons: # No airbases in range of this request. Skip it. continue - budget, fulfilled = self.fulfill_aircraft_request(request, budget) + budget, fulfilled = self.fulfill_aircraft_request( + squadrons, request.number, budget + ) if not fulfilled: # The request was not fulfilled because we could not afford any suitable # aircraft. Rather than continuing, which could proceed to buy tons of @@ -285,9 +221,32 @@ class ProcurementAi: else: return self.game.theater.enemy_points() - def best_airbases_for( + @staticmethod + def squadron_rank_for_task(squadron: Squadron, task: FlightType) -> int: + return aircraft_for_task(task).index(squadron.aircraft) + + def compatible_squadrons_at_airbase( + self, airbase: ControlPoint, request: AircraftProcurementRequest + ) -> Iterator[Squadron]: + compatible: list[Squadron] = [] + for squadron in airbase.squadrons: + if not squadron.can_auto_assign(request.task_capability): + continue + if not squadron.can_provide_pilots(request.number): + continue + + distance_to_target = meters(request.near.distance_to(airbase)) + if distance_to_target > squadron.aircraft.max_mission_range: + continue + compatible.append(squadron) + yield from sorted( + compatible, + key=lambda s: self.squadron_rank_for_task(s, request.task_capability), + ) + + def best_squadrons_for( self, request: AircraftProcurementRequest - ) -> Iterator[ControlPoint]: + ) -> Iterator[Squadron]: distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near) threatened = [] for cp in distance_cache.operational_airfields: @@ -297,8 +256,10 @@ class ProcurementAi: continue if self.threat_zones.threatened(cp.position): threatened.append(cp) - yield cp - yield from threatened + continue + yield from self.compatible_squadrons_at_airbase(cp, request) + for threatened_base in threatened: + yield from self.compatible_squadrons_at_airbase(threatened_base, request) def ground_reinforcement_candidate(self) -> Optional[ControlPoint]: worst_supply = math.inf diff --git a/game/purchaseadapter.py b/game/purchaseadapter.py new file mode 100644 index 00000000..6376f15c --- /dev/null +++ b/game/purchaseadapter.py @@ -0,0 +1,180 @@ +from abc import abstractmethod +from typing import TypeVar, Generic + +from game import Game +from game.coalition import Coalition +from game.dcs.groundunittype import GroundUnitType +from game.squadrons import Squadron +from game.theater import ControlPoint + +ItemType = TypeVar("ItemType") + + +class TransactionError(RuntimeError): + def __init__(self, message: str) -> None: + super().__init__(message) + + +class PurchaseAdapter(Generic[ItemType]): + def __init__(self, coalition: Coalition) -> None: + self.coalition = coalition + + def buy(self, item: ItemType, quantity: int) -> None: + for _ in range(quantity): + if self.has_pending_sales(item): + self.do_cancel_sale(item) + elif self.can_buy(item): + self.do_purchase(item) + else: + raise TransactionError(f"Cannot buy more {item}") + self.coalition.adjust_budget(-self.price_of(item)) + + def sell(self, item: ItemType, quantity: int) -> None: + for _ in range(quantity): + if self.has_pending_orders(item): + self.do_cancel_purchase(item) + elif self.can_sell(item): + self.do_sale(item) + else: + raise TransactionError(f"Cannot sell more {item}") + self.coalition.adjust_budget(self.price_of(item)) + + def has_pending_orders(self, item: ItemType) -> bool: + return self.pending_delivery_quantity(item) > 0 + + def has_pending_sales(self, item: ItemType) -> bool: + return self.pending_delivery_quantity(item) < 0 + + @abstractmethod + def current_quantity_of(self, item: ItemType) -> int: + ... + + @abstractmethod + def pending_delivery_quantity(self, item: ItemType) -> int: + ... + + def expected_quantity_next_turn(self, item: ItemType) -> int: + return self.current_quantity_of(item) + self.pending_delivery_quantity(item) + + def can_buy(self, item: ItemType) -> bool: + return self.coalition.budget >= self.price_of(item) + + def can_sell_or_cancel(self, item: ItemType) -> bool: + return self.can_sell(item) or self.has_pending_orders(item) + + @abstractmethod + def can_sell(self, item: ItemType) -> bool: + ... + + @abstractmethod + def do_purchase(self, item: ItemType) -> None: + ... + + @abstractmethod + def do_cancel_purchase(self, item: ItemType) -> None: + ... + + @abstractmethod + def do_sale(self, item: ItemType) -> None: + ... + + @abstractmethod + def do_cancel_sale(self, item: ItemType) -> None: + ... + + @abstractmethod + def price_of(self, item: ItemType) -> int: + ... + + @abstractmethod + def name_of(self, item: ItemType, multiline: bool = False) -> str: + ... + + +class AircraftPurchaseAdapter(PurchaseAdapter[Squadron]): + def __init__( + self, control_point: ControlPoint, coalition: Coalition, game: Game + ) -> None: + super().__init__(coalition) + self.control_point = control_point + self.game = game + + def pending_delivery_quantity(self, item: Squadron) -> int: + return item.pending_deliveries + + def current_quantity_of(self, item: Squadron) -> int: + return item.owned_aircraft + + def can_buy(self, item: Squadron) -> bool: + return ( + super().can_buy(item) + and self.control_point.unclaimed_parking(self.game) > 0 + ) + + def can_sell(self, item: Squadron) -> bool: + return item.untasked_aircraft > 0 + + def do_purchase(self, item: Squadron) -> None: + item.pending_deliveries += 1 + + def do_cancel_purchase(self, item: Squadron) -> None: + item.pending_deliveries -= 1 + + def do_sale(self, item: Squadron) -> None: + item.untasked_aircraft -= 1 + item.pending_deliveries -= 1 + + def do_cancel_sale(self, item: Squadron) -> None: + item.untasked_aircraft += 1 + item.pending_deliveries += 1 + + def price_of(self, item: Squadron) -> int: + return item.aircraft.price + + def name_of(self, item: Squadron, multiline: bool = False) -> str: + if multiline: + separator = "
" + else: + separator = " " + return separator.join([item.aircraft.name, str(item)]) + + +class GroundUnitPurchaseAdapter(PurchaseAdapter[GroundUnitType]): + def __init__( + self, control_point: ControlPoint, coalition: Coalition, game: Game + ) -> None: + super().__init__(coalition) + self.control_point = control_point + self.game = game + + def pending_delivery_quantity(self, item: GroundUnitType) -> int: + return self.control_point.ground_unit_orders.pending_orders(item) + + def current_quantity_of(self, item: GroundUnitType) -> int: + return self.control_point.base.total_units_of_type(item) + + def can_buy(self, item: GroundUnitType) -> bool: + return super().can_buy(item) and self.control_point.has_ground_unit_source( + self.game + ) + + def can_sell(self, item: GroundUnitType) -> bool: + return False + + def do_purchase(self, item: GroundUnitType) -> None: + self.control_point.ground_unit_orders.order({item: 1}) + + def do_cancel_purchase(self, item: GroundUnitType) -> None: + self.control_point.ground_unit_orders.sell({item: 1}) + + def do_sale(self, item: GroundUnitType) -> None: + raise TransactionError("Sale of ground units not allowed") + + def do_cancel_sale(self, item: GroundUnitType) -> None: + raise TransactionError("Sale of ground units not allowed") + + def price_of(self, item: GroundUnitType) -> int: + return item.price + + def name_of(self, item: GroundUnitType, multiline: bool = False) -> str: + return f"{item}" diff --git a/game/squadrons/airwing.py b/game/squadrons/airwing.py index a710f918..74ad5f83 100644 --- a/game/squadrons/airwing.py +++ b/game/squadrons/airwing.py @@ -5,12 +5,12 @@ from collections import defaultdict from typing import Sequence, Iterator, TYPE_CHECKING from game.dcs.aircrafttype import AircraftType -from gen.flights.flight import FlightType from .squadron import Squadron from ..theater import ControlPoint if TYPE_CHECKING: from game import Game + from gen.flights.flight import FlightType class AirWing: @@ -32,11 +32,26 @@ class AirWing: except StopIteration: return False + @property + def available_aircraft_types(self) -> Iterator[AircraftType]: + for aircraft, squadrons in self.squadrons.items(): + for squadron in squadrons: + if squadron.untasked_aircraft: + yield aircraft + break + def auto_assignable_for_task(self, task: FlightType) -> Iterator[Squadron]: for squadron in self.iter_squadrons(): if squadron.can_auto_assign(task): yield squadron + def auto_assignable_for_task_at( + self, task: FlightType, base: ControlPoint + ) -> Iterator[Squadron]: + for squadron in self.iter_squadrons(): + if squadron.can_auto_assign(task) and squadron.location == base: + yield squadron + def auto_assignable_for_task_with_type( self, aircraft: AircraftType, task: FlightType, base: ControlPoint ) -> Iterator[Squadron]: @@ -67,7 +82,7 @@ class AirWing: def reset(self) -> None: for squadron in self.iter_squadrons(): - squadron.return_all_pilots() + squadron.return_all_pilots_and_aircraft() @property def size(self) -> int: diff --git a/game/squadrons/squadron.py b/game/squadrons/squadron.py index 369514c5..b5169d26 100644 --- a/game/squadrons/squadron.py +++ b/game/squadrons/squadron.py @@ -54,6 +54,10 @@ class Squadron: location: ControlPoint + owned_aircraft: int = field(init=False, hash=False, compare=False, default=0) + untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0) + pending_deliveries: int = field(init=False, hash=False, compare=False, default=0) + def __post_init__(self) -> None: self.auto_assignable_mission_types = set(self.mission_types) @@ -62,6 +66,17 @@ class Squadron: return self.name return f'{self.name} "{self.nickname}"' + def __hash__(self) -> int: + return hash( + ( + self.name, + self.nickname, + self.country, + self.role, + self.aircraft, + ) + ) + @property def player(self) -> bool: return self.coalition.player @@ -165,8 +180,9 @@ class Squadron: if replenish_count > 0: self._recruit_pilots(replenish_count) - def return_all_pilots(self) -> None: + def return_all_pilots_and_aircraft(self) -> None: self.available_pilots = list(self.active_pilots) + self.untasked_aircraft = self.owned_aircraft @staticmethod def send_on_leave(pilot: Pilot) -> None: @@ -238,6 +254,29 @@ class Squadron: def pilot_at_index(self, index: int) -> Pilot: return self.current_roster[index] + def claim_inventory(self, count: int) -> None: + if self.untasked_aircraft < count: + raise ValueError( + f"Cannot remove {count} from {self.name}. Only have " + f"{self.untasked_aircraft}." + ) + self.untasked_aircraft -= count + + def can_fulfill_flight(self, count: int) -> bool: + return self.can_provide_pilots(count) and self.untasked_aircraft >= count + + def refund_orders(self) -> None: + self.coalition.adjust_budget(self.aircraft.price * self.pending_deliveries) + self.pending_deliveries = 0 + + def deliver_orders(self) -> None: + self.owned_aircraft += self.pending_deliveries + self.pending_deliveries = 0 + + @property + def max_fulfillable_aircraft(self) -> int: + return max(self.number_of_available_pilots, self.untasked_aircraft) + @classmethod def create_from( cls, diff --git a/game/theater/base.py b/game/theater/base.py index 02839481..a4d7568b 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -1,10 +1,4 @@ -import itertools -import logging -from typing import Any - -from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType -from game.dcs.unittype import UnitType BASE_MAX_STRENGTH = 1.0 BASE_MIN_STRENGTH = 0.0 @@ -12,14 +6,9 @@ BASE_MIN_STRENGTH = 0.0 class Base: def __init__(self) -> None: - self.aircraft: dict[AircraftType, int] = {} self.armor: dict[GroundUnitType, int] = {} self.strength = 1.0 - @property - def total_aircraft(self) -> int: - return sum(self.aircraft.values()) - @property def total_armor(self) -> int: return sum(self.armor.values()) @@ -31,49 +20,24 @@ class Base: total += unit_type.price * count return total - def total_units_of_type(self, unit_type: UnitType[Any]) -> int: - return sum( - [ - c - for t, c in itertools.chain(self.aircraft.items(), self.armor.items()) - if t == unit_type - ] - ) + def total_units_of_type(self, unit_type: GroundUnitType) -> int: + return sum([c for t, c in self.armor.items() if t == unit_type]) - def commission_units(self, units: dict[Any, int]) -> None: + def commission_units(self, units: dict[GroundUnitType, int]) -> None: for unit_type, unit_count in units.items(): if unit_count <= 0: continue + self.armor[unit_type] = self.armor.get(unit_type, 0) + unit_count - target_dict: dict[Any, int] - if isinstance(unit_type, AircraftType): - target_dict = self.aircraft - elif isinstance(unit_type, GroundUnitType): - target_dict = self.armor - else: - logging.error(f"Unexpected unit type of {unit_type}") - return - - target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count - - def commit_losses(self, units_lost: dict[Any, int]) -> None: + def commit_losses(self, units_lost: dict[GroundUnitType, int]) -> None: for unit_type, count in units_lost.items(): - target_dict: dict[Any, int] - if unit_type in self.aircraft: - target_dict = self.aircraft - elif unit_type in self.armor: - target_dict = self.armor - else: - print("Base didn't find event type {}".format(unit_type)) + if unit_type not in self.armor: + print("Base didn't find unit type {}".format(unit_type)) continue - if unit_type not in target_dict: - print("Base didn't find event type {}".format(unit_type)) - continue - - target_dict[unit_type] = max(target_dict[unit_type] - count, 0) - if target_dict[unit_type] == 0: - del target_dict[unit_type] + self.armor[unit_type] = max(self.armor[unit_type] - count, 0) + if self.armor[unit_type] == 0: + del self.armor[unit_type] def affect_strength(self, amount: float) -> None: self.strength += amount diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 2960bf77..8fe4dbb5 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -317,9 +317,9 @@ class ControlPoint(MissionTarget, ABC): self.cptype = cptype # TODO: Should be Airbase specific. self.stances: Dict[int, CombatStance] = {} - from ..unitdelivery import PendingUnitDeliveries + from ..groundunitorders import GroundUnitOrders - self.pending_unit_deliveries = PendingUnitDeliveries(self) + self.ground_unit_orders = GroundUnitOrders(self) self.target_position: Optional[Point] = None @@ -578,25 +578,14 @@ class ControlPoint(MissionTarget, ABC): return airbase return None - def _retreat_air_units( - self, game: Game, airframe: AircraftType, count: int - ) -> None: - while count: - logging.debug(f"Retreating {count} {airframe} from {self.name}") - destination = self.aircraft_retreat_destination(game, airframe) - if destination is None: - self.capture_aircraft(game, airframe, count) - return - parking = destination.unclaimed_parking(game) - transfer_amount = min([parking, count]) - destination.base.commission_units({airframe: transfer_amount}) - count -= transfer_amount + @staticmethod + def _retreat_squadron(squadron: Squadron) -> None: + logging.error("Air unit retreat not currently implemented") def retreat_air_units(self, game: Game) -> None: # TODO: Capture in order of price to retain maximum value? - while self.base.aircraft: - airframe, count = self.base.aircraft.popitem() - self._retreat_air_units(game, airframe, count) + for squadron in self.squadrons: + self._retreat_squadron(squadron) def depopulate_uncapturable_tgos(self) -> None: for tgo in self.connected_objectives: @@ -605,7 +594,10 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. def capture(self, game: Game, for_player: bool) -> None: - self.pending_unit_deliveries.refund_all(game.coalition_for(for_player)) + coalition = game.coalition_for(for_player) + self.ground_unit_orders.refund_all(coalition) + for squadron in self.squadrons: + squadron.refund_orders() self.retreat_ground_units(game) self.retreat_air_units(game) self.depopulate_uncapturable_tgos() @@ -621,19 +613,6 @@ class ControlPoint(MissionTarget, ABC): def can_operate(self, aircraft: AircraftType) -> bool: ... - def aircraft_transferring(self, game: Game) -> dict[AircraftType, int]: - ato = game.coalition_for(self.captured).ato - transferring: defaultdict[AircraftType, int] = defaultdict(int) - for package in ato.packages: - for flight in package.flights: - if flight.departure == flight.arrival: - continue - if flight.departure == self: - transferring[flight.unit_type] -= flight.count - elif flight.arrival == self: - transferring[flight.unit_type] += flight.count - return transferring - def unclaimed_parking(self, game: Game) -> int: return self.total_aircraft_parking - self.allocated_aircraft(game).total @@ -663,7 +642,9 @@ class ControlPoint(MissionTarget, ABC): self.runway_status.begin_repair() def process_turn(self, game: Game) -> None: - self.pending_unit_deliveries.process(game) + self.ground_unit_orders.process(game) + for squadron in self.squadrons: + squadron.deliver_orders() runway_status = self.runway_status if runway_status is not None: @@ -685,21 +666,22 @@ class ControlPoint(MissionTarget, ABC): u.position.x = u.position.x + delta.x u.position.y = u.position.y + delta.y - def allocated_aircraft(self, game: Game) -> AircraftAllocations: - on_order = {} - for unit_bought, count in self.pending_unit_deliveries.units.items(): - if isinstance(unit_bought, AircraftType): - on_order[unit_bought] = count + def allocated_aircraft(self, _game: Game) -> AircraftAllocations: + present: dict[AircraftType, int] = defaultdict(int) + on_order: dict[AircraftType, int] = defaultdict(int) + for squadron in self.squadrons: + present[squadron.aircraft] += squadron.owned_aircraft + # TODO: Only if this is the squadron destination, not location. + on_order[squadron.aircraft] += squadron.pending_deliveries - return AircraftAllocations( - self.base.aircraft, on_order, self.aircraft_transferring(game) - ) + # TODO: Implement squadron transfers. + return AircraftAllocations(present, on_order, transferring={}) def allocated_ground_units( self, transfers: PendingTransfers ) -> GroundUnitAllocations: on_order = {} - for unit_bought, count in self.pending_unit_deliveries.units.items(): + for unit_bought, count in self.ground_unit_orders.units.items(): if isinstance(unit_bought, GroundUnitType): on_order[unit_bought] = count diff --git a/game/transfers.py b/game/transfers.py index 9146c5b8..3a8d62f6 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -66,7 +66,6 @@ from gen.naming import namegen if TYPE_CHECKING: from game import Game - from game.inventory import ControlPointAircraftInventory from game.squadrons import Squadron @@ -315,29 +314,20 @@ class AirliftPlanner: if cp.captured != self.for_player: continue - inventory = self.game.aircraft_inventory.for_control_point(cp) - for unit_type, available in inventory.all_aircraft: - squadrons = air_wing.auto_assignable_for_task_with_type( - unit_type, FlightType.TRANSPORT, cp - ) - for squadron in squadrons: - if self.compatible_with_mission(unit_type, cp): - while ( - available - and squadron.has_available_pilots - and self.transfer.transport is None - ): - flight_size = self.create_airlift_flight( - squadron, inventory - ) - available -= flight_size + squadrons = air_wing.auto_assignable_for_task_at(FlightType.TRANSPORT, cp) + for squadron in squadrons: + if self.compatible_with_mission(squadron.aircraft, cp): + while ( + squadron.untasked_aircraft + and squadron.has_available_pilots + and self.transfer.transport is None + ): + self.create_airlift_flight(squadron) if self.package.flights: self.game.ato_for(self.for_player).add_package(self.package) - def create_airlift_flight( - self, squadron: Squadron, inventory: ControlPointAircraftInventory - ) -> int: - available_aircraft = inventory.available(squadron.aircraft) + def create_airlift_flight(self, squadron: Squadron) -> int: + available_aircraft = squadron.untasked_aircraft capacity_each = 1 if squadron.aircraft.dcs_unit_type.helicopter else 2 required = math.ceil(self.transfer.size / capacity_each) flight_size = min( @@ -348,8 +338,8 @@ class AirliftPlanner: # TODO: Use number_of_available_pilots directly once feature flag is gone. # The number of currently available pilots is not relevant when pilot limits # are disabled. - if not squadron.can_provide_pilots(flight_size): - flight_size = squadron.number_of_available_pilots + if not squadron.can_fulfill_flight(flight_size): + flight_size = squadron.max_fulfillable_aircraft capacity = flight_size * capacity_each if capacity < self.transfer.size: @@ -359,16 +349,15 @@ class AirliftPlanner: else: transfer = self.transfer - player = inventory.control_point.captured flight = Flight( self.package, - self.game.country_for(player), + self.game.country_for(squadron.player), squadron, flight_size, FlightType.TRANSPORT, self.game.settings.default_start_type, - departure=inventory.control_point, - arrival=inventory.control_point, + departure=squadron.location, + arrival=squadron.location, divert=None, cargo=transfer, ) @@ -381,7 +370,6 @@ class AirliftPlanner: self.package, self.game.coalition_for(self.for_player), self.game.theater ) planner.populate_flight_plan(flight) - self.game.aircraft_inventory.claim_for_flight(flight) return flight_size @@ -652,8 +640,7 @@ class PendingTransfers: flight.package.remove_flight(flight) if not flight.package.flights: self.game.ato_for(self.player).remove_package(flight.package) - self.game.aircraft_inventory.return_from_flight(flight) - flight.clear_roster() + flight.return_pilots_and_aircraft() @cancel_transport.register def _cancel_transport_convoy( @@ -756,16 +743,12 @@ class PendingTransfers: return 0 - def current_airlift_capacity(self, control_point: ControlPoint) -> int: - inventory = self.game.aircraft_inventory.for_control_point(control_point) - squadrons = self.game.air_wing_for( - control_point.captured - ).auto_assignable_for_task(FlightType.TRANSPORT) - unit_types = {s.aircraft for s in squadrons} + @staticmethod + def current_airlift_capacity(control_point: ControlPoint) -> int: return sum( - count - for unit_type, count in inventory.all_aircraft - if unit_type in unit_types + s.owned_aircraft + for s in control_point.squadrons + if s.can_auto_assign(FlightType.TRANSPORT) ) def order_airlift_assets_at(self, control_point: ControlPoint) -> None: diff --git a/gen/aircraft.py b/gen/aircraft.py index 3564d1ec..59fe1c86 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -108,7 +108,7 @@ from .naming import namegen if TYPE_CHECKING: from game import Game - from game.squadrons import Pilot + from game.squadrons import Pilot, Squadron WARM_START_HELI_ALT = meters(500) WARM_START_ALTITUDE = meters(3000) @@ -594,8 +594,7 @@ class AircraftConflictGenerator: def spawn_unused_aircraft( self, player_country: Country, enemy_country: Country ) -> None: - inventories = self.game.aircraft_inventory.inventories - for control_point, inventory in inventories.items(): + for control_point in self.game.theater.controlpoints: if not isinstance(control_point, Airfield): continue @@ -605,11 +604,9 @@ class AircraftConflictGenerator: else: country = enemy_country - for aircraft, available in inventory.all_aircraft: + for squadron in control_point.squadrons: try: - self._spawn_unused_at( - control_point, country, faction, aircraft, available - ) + self._spawn_unused_at(control_point, country, faction, squadron) except NoParkingSlotError: # If we run out of parking, stop spawning aircraft. return @@ -619,17 +616,16 @@ class AircraftConflictGenerator: control_point: Airfield, country: Country, faction: Faction, - aircraft: AircraftType, - number: int, + squadron: Squadron, ) -> None: - for _ in range(number): + for _ in range(squadron.untasked_aircraft): # Creating a flight even those this isn't a fragged mission lets us # reuse the existing debriefing code. # TODO: Special flight type? flight = Flight( Package(control_point), faction.country, - self.game.air_wing_for(control_point.captured).squadron_for(aircraft), + squadron, 1, FlightType.BARCAP, "Cold", @@ -641,16 +637,13 @@ class AircraftConflictGenerator: group = self._generate_at_airport( name=namegen.next_aircraft_name(country, control_point.id, flight), side=country, - unit_type=aircraft.dcs_unit_type, + unit_type=squadron.aircraft.dcs_unit_type, count=1, start_type="Cold", airport=control_point.airport, ) - if aircraft in faction.liveries_overrides: - livery = random.choice(faction.liveries_overrides[aircraft]) - for unit in group.units: - unit.livery_id = livery + self._setup_livery(flight, group) group.uncontrolled = True self.unit_map.add_aircraft(group, flight) diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 6b60ef01..a4a2b427 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -290,6 +290,7 @@ class Flight: self.package = package self.country = country self.squadron = squadron + self.squadron.claim_inventory(count) if roster is None: self.roster = FlightRoster(self.squadron, initial_size=count) else: @@ -338,6 +339,7 @@ class Flight: return self.flight_plan.waypoints[1:] def resize(self, new_size: int) -> None: + self.squadron.claim_inventory(new_size - self.count) self.roster.resize(new_size) def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None: @@ -347,8 +349,9 @@ class Flight: def missing_pilots(self) -> int: return self.roster.missing_pilots - def clear_roster(self) -> None: + def return_pilots_and_aircraft(self) -> None: self.roster.clear() + self.squadron.claim_inventory(-self.count) def __repr__(self) -> str: if self.custom_name: diff --git a/qt_ui/models.py b/qt_ui/models.py index 4ea93965..730e588c 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -165,8 +165,7 @@ class PackageModel(QAbstractListModel): self.beginRemoveRows(QModelIndex(), index, index) if flight.cargo is not None: flight.cargo.transport = None - self.game_model.game.aircraft_inventory.return_from_flight(flight) - flight.clear_roster() + flight.return_pilots_and_aircraft() self.package.remove_flight(flight) self.endRemoveRows() self.update_tot() @@ -258,8 +257,7 @@ class AtoModel(QAbstractListModel): self.beginRemoveRows(QModelIndex(), index, index) self.ato.remove_package(package) for flight in package.flights: - self.game.aircraft_inventory.return_from_flight(flight) - flight.clear_roster() + flight.return_pilots_and_aircraft() if flight.cargo is not None: flight.cargo.transport = None self.endRemoveRows() diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py deleted file mode 100644 index 9453a45c..00000000 --- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Combo box for selecting a departure airfield.""" -from typing import Iterable, Optional - -from PySide2.QtCore import Signal -from PySide2.QtWidgets import QComboBox -from dcs.unittype import FlyingType - -from game.dcs.aircrafttype import AircraftType -from game.inventory import GlobalAircraftInventory -from game.theater.controlpoint import ControlPoint - - -class QOriginAirfieldSelector(QComboBox): - """A combo box for selecting a flight's departure airfield. - - The combo box will automatically be populated with all departure airfields - that have unassigned inventory of the given aircraft type. - """ - - availability_changed = Signal(int) - - def __init__( - self, - global_inventory: GlobalAircraftInventory, - origins: Iterable[ControlPoint], - aircraft: Optional[AircraftType], - ) -> None: - super().__init__() - self.global_inventory = global_inventory - self.origins = list(origins) - self.aircraft = aircraft - self.rebuild_selector() - self.currentIndexChanged.connect(self.index_changed) - self.setSizeAdjustPolicy(self.AdjustToContents) - - def change_aircraft(self, aircraft: Optional[FlyingType]) -> None: - if self.aircraft == aircraft: - return - self.aircraft = aircraft - self.rebuild_selector() - - def rebuild_selector(self) -> None: - self.clear() - if self.aircraft is None: - return - for origin in self.origins: - if not origin.can_operate(self.aircraft): - continue - - inventory = self.global_inventory.for_control_point(origin) - available = inventory.available(self.aircraft) - if available: - self.addItem(f"{origin.name} ({available} available)", origin) - self.model().sort(0) - - @property - def available(self) -> int: - origin = self.currentData() - if origin is None: - return 0 - inventory = self.global_inventory.for_control_point(origin) - return inventory.available(self.aircraft) - - def index_changed(self, index: int) -> None: - origin = self.itemData(index) - if origin is None: - return - inventory = self.global_inventory.for_control_point(origin) - self.availability_changed.emit(inventory.available(self.aircraft)) diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index e97053cd..745cac5c 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -122,14 +122,6 @@ class SquadronBaseSelector(QComboBox): self.model().sort(0) self.setCurrentText(self.squadron.location.name) - @property - def available(self) -> int: - origin = self.currentData() - if origin is None: - return 0 - inventory = self.global_inventory.for_control_point(origin) - return inventory.available(self.aircraft) - class SquadronConfigurationBox(QGroupBox): def __init__(self, squadron: Squadron, theater: ConflictTheater) -> None: diff --git a/qt_ui/windows/AirWingDialog.py b/qt_ui/windows/AirWingDialog.py index f7da67da..a8f8ca3f 100644 --- a/qt_ui/windows/AirWingDialog.py +++ b/qt_ui/windows/AirWingDialog.py @@ -16,7 +16,6 @@ from PySide2.QtWidgets import ( QWidget, ) -from game.inventory import ControlPointAircraftInventory from game.squadrons import Squadron from gen.flights.flight import Flight from qt_ui.delegates import TwoColumnRowDelegate @@ -127,19 +126,13 @@ class AircraftInventoryData: ) @classmethod - def each_from_inventory( - cls, inventory: ControlPointAircraftInventory + def each_untasked_from_squadron( + cls, squadron: Squadron ) -> Iterator[AircraftInventoryData]: - for unit_type, num_units in inventory.all_aircraft: - for _ in range(0, num_units): - yield AircraftInventoryData( - inventory.control_point.name, - unit_type.name, - "Idle", - "N/A", - "N/A", - "N/A", - ) + for _ in range(0, squadron.untasked_aircraft): + yield AircraftInventoryData( + squadron.name, squadron.aircraft.name, "Idle", "N/A", "N/A", "N/A" + ) class AirInventoryView(QWidget): @@ -188,9 +181,8 @@ class AirInventoryView(QWidget): def iter_unallocated_aircraft(self) -> Iterator[AircraftInventoryData]: game = self.game_model.game - for control_point, inventory in game.aircraft_inventory.inventories.items(): - if control_point.captured: - yield from AircraftInventoryData.each_from_inventory(inventory) + for squadron in game.blue.air_wing.iter_squadrons(): + yield from AircraftInventoryData.each_untasked_from_squadron(squadron) def get_data(self, only_unallocated: bool) -> Iterator[AircraftInventoryData]: yield from self.iter_unallocated_aircraft() diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index d10e5bc7..7c333e1b 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -24,7 +24,7 @@ from qt_ui.uiconstants import EVENT_ICONS from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs -from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour +from qt_ui.windows.basemenu.UnitTransactionFrame import UnitTransactionFrame class QBaseMenu2(QDialog): @@ -108,7 +108,7 @@ class QBaseMenu2(QDialog): capture_button.clicked.connect(self.cheat_capture) self.budget_display = QLabel( - QRecruitBehaviour.BUDGET_FORMAT.format(self.game_model.game.blue.budget) + UnitTransactionFrame.BUDGET_FORMAT.format(self.game_model.game.blue.budget) ) self.budget_display.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.budget_display.setProperty("style", "budget-label") @@ -190,7 +190,7 @@ class QBaseMenu2(QDialog): self.repair_button.setDisabled(True) def update_intel_summary(self) -> None: - aircraft = self.cp.base.total_aircraft + aircraft = self.cp.allocated_aircraft(self.game_model.game).total_present parking = self.cp.total_aircraft_parking ground_unit_limit = self.cp.frontline_unit_count_limit deployable_unit_info = "" @@ -258,5 +258,5 @@ class QBaseMenu2(QDialog): def update_budget(self, game: Game) -> None: self.budget_display.setText( - QRecruitBehaviour.BUDGET_FORMAT.format(game.blue.budget) + UnitTransactionFrame.BUDGET_FORMAT.format(game.blue.budget) ) diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/UnitTransactionFrame.py similarity index 59% rename from qt_ui/windows/basemenu/QRecruitBehaviour.py rename to qt_ui/windows/basemenu/UnitTransactionFrame.py index 21684682..d9cbe57f 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/UnitTransactionFrame.py @@ -1,6 +1,9 @@ from __future__ import annotations import logging +from enum import Enum +from typing import TypeVar, Generic + from PySide2.QtCore import Qt from PySide2.QtWidgets import ( QGroupBox, @@ -11,15 +14,15 @@ from PySide2.QtWidgets import ( QSpacerItem, QGridLayout, QApplication, + QFrame, + QMessageBox, ) from game.dcs.unittype import UnitType -from game.theater import ControlPoint -from game.unitdelivery import PendingUnitDeliveries from qt_ui.models import GameModel from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.QUnitInfoWindow import QUnitInfoWindow -from enum import Enum +from game.purchaseadapter import PurchaseAdapter, TransactionError class RecruitType(Enum): @@ -27,21 +30,28 @@ class RecruitType(Enum): SELL = 1 -class PurchaseGroup(QGroupBox): - def __init__(self, unit_type: UnitType, recruiter: QRecruitBehaviour) -> None: +TransactionItemType = TypeVar("TransactionItemType") + + +class PurchaseGroup(QGroupBox, Generic[TransactionItemType]): + def __init__( + self, + item: TransactionItemType, + recruiter: UnitTransactionFrame[TransactionItemType], + ) -> None: super().__init__() - self.unit_type = unit_type + self.item = item self.recruiter = recruiter self.setProperty("style", "buy-box") - self.setMaximumHeight(36) + self.setMaximumHeight(72) self.setMinimumHeight(36) layout = QHBoxLayout() self.setLayout(layout) self.sell_button = QPushButton("-") self.sell_button.setProperty("style", "btn-sell") - self.sell_button.setDisabled(not recruiter.enable_sale(unit_type)) + self.sell_button.setDisabled(not recruiter.enable_sale(item)) self.sell_button.setMinimumSize(16, 16) self.sell_button.setMaximumSize(16, 16) self.sell_button.setSizePolicy( @@ -49,7 +59,7 @@ class PurchaseGroup(QGroupBox): ) self.sell_button.clicked.connect( - lambda: self.recruiter.recruit_handler(RecruitType.SELL, self.unit_type) + lambda: self.recruiter.recruit_handler(RecruitType.SELL, self.item) ) self.amount_bought = QLabel() @@ -59,12 +69,12 @@ class PurchaseGroup(QGroupBox): self.buy_button = QPushButton("+") self.buy_button.setProperty("style", "btn-buy") - self.buy_button.setDisabled(not recruiter.enable_purchase(unit_type)) + self.buy_button.setDisabled(not recruiter.enable_purchase(item)) self.buy_button.setMinimumSize(16, 16) self.buy_button.setMaximumSize(16, 16) self.buy_button.clicked.connect( - lambda: self.recruiter.recruit_handler(RecruitType.BUY, self.unit_type) + lambda: self.recruiter.recruit_handler(RecruitType.BUY, self.item) ) self.buy_button.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) @@ -76,36 +86,53 @@ class PurchaseGroup(QGroupBox): @property def pending_units(self) -> int: - return self.recruiter.pending_deliveries.units.get(self.unit_type, 0) + return self.recruiter.pending_delivery_quantity(self.item) def update_state(self) -> None: - self.buy_button.setEnabled(self.recruiter.enable_purchase(self.unit_type)) + self.buy_button.setEnabled(self.recruiter.enable_purchase(self.item)) self.buy_button.setToolTip( self.recruiter.purchase_tooltip(self.buy_button.isEnabled()) ) - self.sell_button.setEnabled(self.recruiter.enable_sale(self.unit_type)) + self.sell_button.setEnabled(self.recruiter.enable_sale(self.item)) self.sell_button.setToolTip( self.recruiter.sell_tooltip(self.sell_button.isEnabled()) ) self.amount_bought.setText(f"{self.pending_units}") -class QRecruitBehaviour: - game_model: GameModel - cp: ControlPoint - purchase_groups: dict[UnitType, PurchaseGroup] - existing_units_labels = None - maximum_units = -1 +class UnitTransactionFrame(QFrame, Generic[TransactionItemType]): BUDGET_FORMAT = "Available Budget: ${:.2f}M" - def __init__(self) -> None: + def __init__( + self, + game_model: GameModel, + purchase_adapter: PurchaseAdapter[TransactionItemType], + ) -> None: + super().__init__() + self.game_model = game_model + self.purchase_adapter = purchase_adapter self.existing_units_labels = {} - self.purchase_groups = {} + self.purchase_groups: dict[ + TransactionItemType, PurchaseGroup[TransactionItemType] + ] = {} self.update_available_budget() - @property - def pending_deliveries(self) -> PendingUnitDeliveries: - return self.cp.pending_unit_deliveries + def current_quantity_of(self, item: TransactionItemType) -> int: + return self.purchase_adapter.current_quantity_of(item) + + def pending_delivery_quantity(self, item: TransactionItemType) -> int: + return self.purchase_adapter.pending_delivery_quantity(item) + + def expected_quantity_next_turn(self, item: TransactionItemType) -> int: + return self.purchase_adapter.expected_quantity_next_turn(item) + + def display_name_of( + self, item: TransactionItemType, multiline: bool = False + ) -> str: + return self.purchase_adapter.name_of(item, multiline) + + def price_of(self, item: TransactionItemType) -> int: + return self.purchase_adapter.price_of(item) @property def budget(self) -> float: @@ -117,20 +144,20 @@ class QRecruitBehaviour: def add_purchase_row( self, - unit_type: UnitType, + item: TransactionItemType, layout: QGridLayout, row: int, ) -> None: exist = QGroupBox() exist.setProperty("style", "buy-box") - exist.setMaximumHeight(36) + exist.setMaximumHeight(72) exist.setMinimumHeight(36) existLayout = QHBoxLayout() exist.setLayout(existLayout) - existing_units = self.cp.base.total_units_of_type(unit_type) + existing_units = self.current_quantity_of(item) - unitName = QLabel(f"{unit_type.name}") + unitName = QLabel(f"{self.display_name_of(item, multiline=True)}") unitName.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) ) @@ -138,17 +165,17 @@ class QRecruitBehaviour: existing_units = QLabel(str(existing_units)) existing_units.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - self.existing_units_labels[unit_type] = existing_units + self.existing_units_labels[item] = existing_units - price = QLabel(f"$ {unit_type.price} M") + price = QLabel(f"$ {self.price_of(item)} M") price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - purchase_group = PurchaseGroup(unit_type, self) - self.purchase_groups[unit_type] = purchase_group + purchase_group = PurchaseGroup(item, self) + self.purchase_groups[item] = purchase_group info = QGroupBox() info.setProperty("style", "buy-box") - info.setMaximumHeight(36) + info.setMaximumHeight(72) info.setMinimumHeight(36) infolayout = QHBoxLayout() info.setLayout(infolayout) @@ -157,7 +184,7 @@ class QRecruitBehaviour: unitInfo.setProperty("style", "btn-info") unitInfo.setMinimumSize(16, 16) unitInfo.setMaximumSize(16, 16) - unitInfo.clicked.connect(lambda: self.info(unit_type)) + unitInfo.clicked.connect(lambda: self.info(item)) unitInfo.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) existLayout.addWidget(unitName) @@ -179,7 +206,9 @@ class QRecruitBehaviour: def update_available_budget(self) -> None: GameUpdateSignal.get_instance().updateBudget(self.game_model.game) - def recruit_handler(self, recruit_type: RecruitType, unit_type: UnitType) -> None: + def recruit_handler( + self, recruit_type: RecruitType, item: TransactionItemType + ) -> None: # Lookup if Keyboard Modifiers were pressed # Shift = 10 times # CTRL = 5 Times @@ -191,51 +220,54 @@ class QRecruitBehaviour: else: amount = 1 - for i in range(amount): - if recruit_type == RecruitType.SELL: - if not self.sell(unit_type): - return - elif recruit_type == RecruitType.BUY: - if not self.buy(unit_type): - return + if recruit_type == RecruitType.SELL: + self.sell(item, amount) + elif recruit_type == RecruitType.BUY: + self.buy(item, amount) - def buy(self, unit_type: UnitType) -> bool: - - if not self.enable_purchase(unit_type): - logging.error(f"Purchase of {unit_type} not allowed at {self.cp.name}") - return False - - self.pending_deliveries.order({unit_type: 1}) - self.budget -= unit_type.price + def post_transaction_update(self) -> None: self.update_purchase_controls() self.update_available_budget() + + def buy(self, item: TransactionItemType, quantity: int) -> bool: + try: + self.purchase_adapter.buy(item, quantity) + except TransactionError as ex: + logging.exception(f"Purchase of {self.display_name_of(item)} failed") + QMessageBox.warning(self, "Purchase failed", str(ex), QMessageBox.Ok) + return False + self.post_transaction_update() return True - def sell(self, unit_type: UnitType) -> bool: - if self.pending_deliveries.available_next_turn(unit_type) > 0: - self.budget += unit_type.price - self.pending_deliveries.sell({unit_type: 1}) - self.update_purchase_controls() - self.update_available_budget() + def sell(self, item: TransactionItemType, quantity: int) -> bool: + try: + self.purchase_adapter.sell(item, quantity) + except TransactionError as ex: + logging.exception(f"Sale of {self.display_name_of(item)} failed") + QMessageBox.warning(self, "Sale failed", str(ex), QMessageBox.Ok) + return False + self.post_transaction_update() return True def update_purchase_controls(self) -> None: for group in self.purchase_groups.values(): group.update_state() - def enable_purchase(self, unit_type: UnitType) -> bool: - return self.budget >= unit_type.price + def enable_purchase(self, item: TransactionItemType) -> bool: + return self.purchase_adapter.can_buy(item) - def enable_sale(self, unit_type: UnitType) -> bool: - return True + def enable_sale(self, item: TransactionItemType) -> bool: + return self.purchase_adapter.can_sell_or_cancel(item) - def purchase_tooltip(self, is_enabled: bool) -> str: + @staticmethod + def purchase_tooltip(is_enabled: bool) -> str: if is_enabled: return "Buy unit. Use Shift or Ctrl key to buy multiple units at once." else: return "Unit can not be bought." - def sell_tooltip(self, is_enabled: bool) -> str: + @staticmethod + def sell_tooltip(is_enabled: bool) -> str: if is_enabled: return "Sell unit. Use Shift or Ctrl key to buy multiple units at once." else: @@ -244,9 +276,3 @@ class QRecruitBehaviour: def info(self, unit_type: UnitType) -> None: self.info_window = QUnitInfoWindow(self.game_model.game, unit_type) self.info_window.show() - - def set_maximum_units(self, maximum_units): - """ - Set the maximum number of units that can be bought - """ - self.maximum_units = maximum_units diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 803fb21a..99b18d5e 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -1,38 +1,38 @@ -import logging from typing import Set from PySide2.QtCore import Qt from PySide2.QtWidgets import ( - QFrame, QGridLayout, QHBoxLayout, QLabel, - QMessageBox, QScrollArea, QVBoxLayout, QWidget, ) -from dcs.helicopters import helicopter_map from game.dcs.aircrafttype import AircraftType -from game.theater import ControlPoint, ControlPointType +from game.squadrons import Squadron +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 qt_ui.windows.basemenu.UnitTransactionFrame import UnitTransactionFrame +from game.purchaseadapter import AircraftPurchaseAdapter -class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): +class QAircraftRecruitmentMenu(UnitTransactionFrame[Squadron]): def __init__(self, cp: ControlPoint, game_model: GameModel) -> None: - QFrame.__init__(self) + super().__init__( + game_model, + AircraftPurchaseAdapter( + cp, game_model.game.coalition_for(cp.captured), game_model.game + ), + ) self.cp = cp self.game_model = game_model self.purchase_groups = {} self.bought_amount_labels = {} self.existing_units_labels = {} - # Determine maximum number of aircrafts that can be bought - self.set_maximum_units(self.cp.total_aircraft_parking) - self.bought_amount_labels = {} self.existing_units_labels = {} @@ -48,9 +48,9 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): for squadron in cp.squadrons: unit_types.add(squadron.aircraft) - sorted_units = sorted(unit_types, key=lambda u: u.name) - for row, unit_type in enumerate(sorted_units): - self.add_purchase_row(unit_type, task_box_layout, row) + sorted_squadrons = sorted(cp.squadrons, key=lambda s: (s.aircraft.name, s.name)) + for row, squadron in enumerate(sorted_squadrons): + self.add_purchase_row(squadron, task_box_layout, row) stretch = QVBoxLayout() stretch.addStretch() task_box_layout.addLayout(stretch, row, 0) @@ -65,76 +65,19 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): main_layout.addWidget(scroll) self.setLayout(main_layout) - def enable_purchase(self, unit_type: AircraftType) -> bool: - if not super().enable_purchase(unit_type): - return False - if not self.cp.can_operate(unit_type): - return False - return True - - def enable_sale(self, unit_type: AircraftType) -> bool: - return self.can_be_sold(unit_type) - def sell_tooltip(self, is_enabled: bool) -> str: if is_enabled: return "Sell unit. Use Shift or Ctrl key to sell multiple units at once." else: - return "Can not be sold because either no aircraft are available or are already assigned to a mission." - - def buy(self, unit_type: AircraftType) -> bool: - if self.maximum_units > 0: - if self.cp.unclaimed_parking(self.game_model.game) <= 0: - logging.debug(f"No space for additional aircraft at {self.cp}.") - QMessageBox.warning( - self, - "No space for additional aircraft", - f"There is no parking space left at {self.cp.name} to accommodate " - "another plane.", - QMessageBox.Ok, - ) - return False - # If we change our mind about selling, we want the aircraft to be put - # back in the inventory immediately. - elif 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) - inventory.add_aircraft(unit_type, 1) - - super().buy(unit_type) - self.hangar_status.update_label() - return True - - def can_be_sold(self, unit_type: AircraftType) -> bool: - inventory = self.game_model.game.aircraft_inventory.for_control_point(self.cp) - pending_deliveries = self.pending_deliveries.units.get(unit_type, 0) - return self.cp.can_operate(unit_type) and ( - pending_deliveries > 0 or inventory.available(unit_type) > 0 - ) - - def sell(self, unit_type: AircraftType) -> bool: - # Don't need to remove aircraft from the inventory if we're canceling - # orders. - if not self.can_be_sold(unit_type): - QMessageBox.critical( - self, - "Could not sell aircraft", - f"Attempted to sell one {unit_type} at {self.cp.name} " - "but none are available. Are all aircraft currently " - "assigned to a mission?", - QMessageBox.Ok, + return ( + "Can not be sold because either no aircraft are available or are " + "already assigned to a mission." ) - return False - inventory = self.game_model.game.aircraft_inventory.for_control_point(self.cp) - pending_deliveries = self.pending_deliveries.units.get(unit_type, 0) - if pending_deliveries <= 0 < inventory.available(unit_type): - inventory.remove_aircraft(unit_type, 1) - - super().sell(unit_type) + def post_transaction_update(self) -> None: + super().post_transaction_update() self.hangar_status.update_label() - return True - class QHangarStatus(QHBoxLayout): def __init__(self, game_model: GameModel, control_point: ControlPoint) -> None: diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 898d1cc4..77e2af8f 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -1,30 +1,27 @@ from PySide2.QtCore import Qt -from PySide2.QtWidgets import ( - QFrame, - QGridLayout, - QScrollArea, - QVBoxLayout, - QWidget, -) +from PySide2.QtWidgets import QGridLayout, QScrollArea, QVBoxLayout, QWidget from game.dcs.groundunittype import GroundUnitType from game.theater import ControlPoint from qt_ui.models import GameModel -from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour +from qt_ui.windows.basemenu.UnitTransactionFrame import UnitTransactionFrame +from game.purchaseadapter import GroundUnitPurchaseAdapter -class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): +class QArmorRecruitmentMenu(UnitTransactionFrame[GroundUnitType]): def __init__(self, cp: ControlPoint, game_model: GameModel): - QFrame.__init__(self) + super().__init__( + game_model, + GroundUnitPurchaseAdapter( + cp, game_model.game.coalition_for(cp.captured), game_model.game + ), + ) self.cp = cp self.game_model = game_model self.purchase_groups = {} self.bought_amount_labels = {} self.existing_units_labels = {} - self.init_ui() - - def init_ui(self): main_layout = QVBoxLayout() scroll_content = QWidget() @@ -50,11 +47,3 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): scroll.setWidget(scroll_content) main_layout.addWidget(scroll) self.setLayout(main_layout) - - def enable_purchase(self, unit_type: GroundUnitType) -> bool: - if not super().enable_purchase(unit_type): - return False - return self.cp.has_ground_unit_source(self.game_model.game) - - def enable_sale(self, unit_type: GroundUnitType) -> bool: - return self.pending_deliveries.pending_orders(unit_type) > 0 diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py index c8bf03e8..d73682db 100644 --- a/qt_ui/windows/basemenu/intel/QIntelInfo.py +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -26,7 +26,7 @@ class QIntelInfo(QFrame): intel_layout = QVBoxLayout() units_by_task: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) - for unit_type, count in self.cp.base.aircraft.items(): + for unit_type, count in self.cp.allocated_aircraft(game).present.items(): if count: task_type = unit_type.dcs_unit_type.task_default.name units_by_task[task_type][unit_type.name] += count diff --git a/qt_ui/windows/intel.py b/qt_ui/windows/intel.py index 64b539d0..288b87fe 100644 --- a/qt_ui/windows/intel.py +++ b/qt_ui/windows/intel.py @@ -77,14 +77,15 @@ class AircraftIntelLayout(IntelTableLayout): total = 0 for control_point in game.theater.control_points_for(player): - base = control_point.base - total += base.total_aircraft - if not base.total_aircraft: + allocation = control_point.allocated_aircraft(game) + base_total = allocation.total_present + total += base_total + if not base_total: continue - self.add_header(f"{control_point.name} ({base.total_aircraft})") - for airframe in sorted(base.aircraft, key=lambda k: k.name): - count = base.aircraft[airframe] + self.add_header(f"{control_point.name} ({base_total})") + for airframe in sorted(allocation.present, key=lambda k: k.name): + count = allocation.present[airframe] if not count: continue self.add_row(f" {airframe.name}", count) diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index c86987ae..2f83c85b 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -177,7 +177,6 @@ class QPackageDialog(QDialog): def add_flight(self, flight: Flight) -> None: """Adds the new flight to the package.""" - self.game.aircraft_inventory.claim_for_flight(flight) self.package_model.add_flight(flight) planner = FlightPlanBuilder( self.package_model.package, self.game.blue, self.game.theater @@ -251,8 +250,7 @@ class QNewPackageDialog(QPackageDialog): def on_cancel(self) -> None: super().on_cancel() for flight in self.package_model.package.flights: - self.game.aircraft_inventory.return_from_flight(flight) - flight.clear_roster() + flight.return_pilots_and_aircraft() class QEditPackageDialog(QPackageDialog): diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index eeeb54de..cc465422 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -24,7 +24,6 @@ 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 qt_ui.windows.mission.flight.SquadronSelector import SquadronSelector from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import FlightRosterEditor @@ -34,6 +33,7 @@ class QFlightCreator(QDialog): def __init__(self, game: Game, package: Package, parent=None) -> None: super().__init__(parent=parent) + self.setMinimumWidth(400) self.game = game self.package = package @@ -51,7 +51,7 @@ class QFlightCreator(QDialog): layout.addLayout(QLabeledWidget("Task:", self.task_selector)) self.aircraft_selector = QAircraftTypeSelector( - self.game.aircraft_inventory.available_types_for_player, + self.game.blue.air_wing.available_aircraft_types, self.task_selector.currentData(), ) self.aircraft_selector.setCurrentIndex(0) @@ -66,22 +66,6 @@ class QFlightCreator(QDialog): self.squadron_selector.setCurrentIndex(0) layout.addLayout(QLabeledWidget("Squadron:", self.squadron_selector)) - self.departure = QOriginAirfieldSelector( - self.game.aircraft_inventory, - [cp for cp in game.theater.controlpoints if cp.captured], - self.aircraft_selector.currentData(), - ) - self.departure.availability_changed.connect(self.update_max_size) - self.departure.currentIndexChanged.connect(self.on_departure_changed) - 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(), @@ -90,7 +74,7 @@ class QFlightCreator(QDialog): layout.addLayout(QLabeledWidget("Divert:", self.divert)) self.flight_size_spinner = QFlightSizeSpinner() - self.update_max_size(self.departure.available) + self.update_max_size(self.squadron_selector.aircraft_available) layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) squadron = self.squadron_selector.currentData() @@ -144,8 +128,6 @@ class QFlightCreator(QDialog): self.setLayout(layout) - self.on_departure_changed(self.departure.currentIndex()) - def reject(self) -> None: super().reject() # Clear the roster to return pilots to the pool. @@ -161,25 +143,19 @@ class QFlightCreator(QDialog): def verify_form(self) -> Optional[str]: aircraft: Optional[Type[FlyingType]] = self.aircraft_selector.currentData() squadron: Optional[Squadron] = self.squadron_selector.currentData() - origin: Optional[ControlPoint] = self.departure.currentData() - arrival: Optional[ControlPoint] = self.arrival.currentData() divert: Optional[ControlPoint] = self.divert.currentData() size: int = self.flight_size_spinner.value() if aircraft is None: return "You must select an aircraft type." if squadron is None: return "You must select a squadron." - 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) + available = squadron.untasked_aircraft if not available: - return f"{origin.name} has no {aircraft.id} available." + return f"{squadron} has no aircraft available." if size > available: - return f"{origin.name} has only {available} {aircraft.id} available." + return f"{squadron} has only {available} aircraft available." if size <= 0: return f"Flight must have at least one aircraft." if self.custom_name_text and "|" in self.custom_name_text: @@ -194,14 +170,9 @@ class QFlightCreator(QDialog): task = self.task_selector.currentData() squadron = self.squadron_selector.currentData() - origin = self.departure.currentData() - arrival = self.arrival.currentData() divert = self.divert.currentData() roster = self.roster_editor.roster - if arrival is None: - arrival = origin - flight = Flight( self.package, self.country, @@ -211,8 +182,8 @@ class QFlightCreator(QDialog): roster.max_size, task, self.start_type.currentText(), - origin, - arrival, + squadron.location, + squadron.location, divert, custom_name=self.custom_name_text, roster=roster, @@ -228,11 +199,9 @@ class QFlightCreator(QDialog): self.task_selector.currentData(), new_aircraft ) self.departure.change_aircraft(new_aircraft) - self.arrival.change_aircraft(new_aircraft) self.divert.change_aircraft(new_aircraft) - def on_departure_changed(self, index: int) -> None: - departure = self.departure.itemData(index) + def on_departure_changed(self, departure: ControlPoint) -> None: if isinstance(departure, OffMapSpawn): previous_type = self.start_type.currentText() if previous_type != "In Flight": @@ -248,12 +217,12 @@ class QFlightCreator(QDialog): def on_task_changed(self, index: int) -> None: task = self.task_selector.itemData(index) self.aircraft_selector.update_items( - task, self.game.aircraft_inventory.available_types_for_player + task, self.game.blue.air_wing.available_aircraft_types ) self.squadron_selector.update_items(task, self.aircraft_selector.currentData()) def on_squadron_changed(self, index: int) -> None: - squadron = self.squadron_selector.itemData(index) + squadron: Optional[Squadron] = self.squadron_selector.itemData(index) # Clear the roster first so we return the pilots to the pool. This way if we end # up repopulating from the same squadron we'll get the same pilots back. self.roster_editor.replace(None) @@ -261,6 +230,7 @@ class QFlightCreator(QDialog): self.roster_editor.replace( FlightRoster(squadron, self.flight_size_spinner.value()) ) + self.on_departure_changed(squadron.location) def update_max_size(self, available: int) -> None: aircraft = self.aircraft_selector.currentData() diff --git a/qt_ui/windows/mission/flight/SquadronSelector.py b/qt_ui/windows/mission/flight/SquadronSelector.py index a931f9aa..290e821b 100644 --- a/qt_ui/windows/mission/flight/SquadronSelector.py +++ b/qt_ui/windows/mission/flight/SquadronSelector.py @@ -1,9 +1,9 @@ """Combo box for selecting squadrons.""" -from typing import Type, Optional +from typing import Optional from PySide2.QtWidgets import QComboBox -from dcs.unittype import FlyingType +from game.dcs.aircrafttype import AircraftType from game.squadrons.airwing import AirWing from gen.flights.flight import FlightType @@ -15,7 +15,7 @@ class SquadronSelector(QComboBox): self, air_wing: AirWing, task: Optional[FlightType], - aircraft: Optional[Type[FlyingType]], + aircraft: Optional[AircraftType], ) -> None: super().__init__() self.air_wing = air_wing @@ -24,8 +24,15 @@ class SquadronSelector(QComboBox): self.setSizeAdjustPolicy(self.AdjustToContents) self.update_items(task, aircraft) + @property + def aircraft_available(self) -> int: + squadron = self.currentData() + if squadron is None: + return 0 + return squadron.untasked_aircraft + def update_items( - self, task: Optional[FlightType], aircraft: Optional[Type[FlyingType]] + self, task: Optional[FlightType], aircraft: Optional[AircraftType] ) -> None: current_squadron = self.currentData() self.blockSignals(True) @@ -41,12 +48,12 @@ class SquadronSelector(QComboBox): return for squadron in self.air_wing.squadrons_for(aircraft): - if task in squadron.mission_types: - self.addItem(f"{squadron}", squadron) + if task in squadron.mission_types and squadron.untasked_aircraft: + self.addItem(f"{squadron.location}: {squadron}", squadron) if self.count() == 0: self.addItem("No capable aircraft available", None) return if current_squadron is not None: - self.setCurrentText(f"{current_squadron}") + self.setCurrentText(f"{current_squadron.location}: {current_squadron}") diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index 3545ff1e..665e45ae 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -195,8 +195,7 @@ class QFlightSlotEditor(QGroupBox): self.package_model = package_model self.flight = flight self.game = game - self.inventory = self.game.aircraft_inventory.for_control_point(flight.from_cp) - available = self.inventory.available(self.flight.unit_type) + available = self.flight.squadron.untasked_aircraft max_count = self.flight.count + available if max_count > 4: max_count = 4 @@ -225,21 +224,18 @@ class QFlightSlotEditor(QGroupBox): def _changed_aircraft_count(self): old_count = self.flight.count new_count = int(self.aircraft_count_spinner.value()) - self.game.aircraft_inventory.return_from_flight(self.flight) - self.flight.resize(new_count) try: - self.game.aircraft_inventory.claim_for_flight(self.flight) + self.flight.resize(new_count) except ValueError: # The UI should have prevented this, but if we ran out of aircraft # then roll back the inventory change. difference = new_count - self.flight.count - available = self.inventory.available(self.flight.unit_type) + available = self.flight.squadron.untasked_aircraft logging.error( f"Could not add {difference} additional aircraft to " f"{self.flight} because {self.flight.departure} has only " f"{available} {self.flight.unit_type} remaining" ) - self.game.aircraft_inventory.claim_for_flight(self.flight) self.flight.resize(old_count) return self.roster_editor.resize(new_count) From 08365bcbdab5f5f698df0e997563af114bec49e0 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Fri, 13 Aug 2021 23:43:09 +0200 Subject: [PATCH 40/41] Simplify and enhance tarcap flight planning --- gen/flights/flightplan.py | 52 ++++++++++----------------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 408eb5fd..032c84e2 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -1115,7 +1115,7 @@ class FlightPlanBuilder: if isinstance(location, FrontLine): raise InvalidObjectiveLocation(flight.flight_type, location) - start_pos, end_pos = self.racetrack_for_objective(location, barcap=True) + start_pos, end_pos = self.cap_racetrack_for_objective(location, barcap=True) preferred_alt = flight.unit_type.preferred_patrol_altitude randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000) @@ -1244,7 +1244,7 @@ class FlightPlanBuilder: bullseye=builder.bullseye(), ) - def racetrack_for_objective( + def cap_racetrack_for_objective( self, location: MissionTarget, barcap: bool ) -> Tuple[Point, Point]: closest_cache = ObjectiveDistanceCache.get_closest_airfields(location) @@ -1276,6 +1276,7 @@ class FlightPlanBuilder: - self.doctrine.cap_engagement_range - nautical_miles(5) ) + max_track_length = self.doctrine.cap_max_track_length else: # Other race tracks (TARCAPs, currently) just try to keep some # distance from the nearest enemy airbase, but since they are by @@ -1289,6 +1290,11 @@ class FlightPlanBuilder: ) distance_to_no_fly = distance_to_airfield - min_distance_from_enemy + # TARCAPs fly short racetracks because they need to react faster. + max_track_length = self.doctrine.cap_min_track_length + 0.3 * ( + self.doctrine.cap_max_track_length - self.doctrine.cap_min_track_length + ) + min_cap_distance = min( self.doctrine.cap_min_distance_from_cp, distance_to_no_fly ) @@ -1300,11 +1306,12 @@ class FlightPlanBuilder: heading.degrees, random.randint(int(min_cap_distance.meters), int(max_cap_distance.meters)), ) - diameter = random.randint( + + track_length = random.randint( int(self.doctrine.cap_min_track_length.meters), - int(self.doctrine.cap_max_track_length.meters), + int(max_track_length.meters), ) - start = end.point_from_heading(heading.opposite.degrees, diameter) + start = end.point_from_heading(heading.opposite.degrees, track_length) return start, end def aewc_orbit(self, location: MissionTarget) -> Point: @@ -1327,33 +1334,6 @@ class FlightPlanBuilder: orbit_heading.degrees, orbit_distance.meters ) - def racetrack_for_frontline( - self, origin: Point, front_line: FrontLine - ) -> Tuple[Point, Point]: - # Find targets waypoints - ingress, heading, distance = Conflict.frontline_vector(front_line, self.theater) - center = ingress.point_from_heading(heading.degrees, distance / 2) - orbit_center = center.point_from_heading( - heading.left.degrees, - random.randint( - int(nautical_miles(6).meters), int(nautical_miles(15).meters) - ), - ) - - combat_width = distance / 2 - if combat_width > 500000: - combat_width = 500000 - if combat_width < 35000: - combat_width = 35000 - - radius = combat_width * 1.25 - start = orbit_center.point_from_heading(heading.degrees, radius) - end = orbit_center.point_from_heading(heading.opposite.degrees, radius) - - if end.distance_to_point(origin) < start.distance_to_point(origin): - start, end = end, start - return start, end - def generate_tarcap(self, flight: Flight) -> TarCapFlightPlan: """Generate a CAP flight plan for the given front line. @@ -1375,13 +1355,7 @@ class FlightPlanBuilder: # Create points builder = WaypointBuilder(flight, self.coalition) - - if isinstance(location, FrontLine): - orbit0p, orbit1p = self.racetrack_for_frontline( - flight.departure.position, location - ) - else: - orbit0p, orbit1p = self.racetrack_for_objective(location, barcap=False) + orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False) start, end = builder.race_track(orbit0p, orbit1p, patrol_alt) return TarCapFlightPlan( From 8f5b6f58d12ef0f0edd5f9ebcf44c71dc2a5ff47 Mon Sep 17 00:00:00 2001 From: Magnus Wolffelt Date: Mon, 16 Aug 2021 10:03:40 +0200 Subject: [PATCH 41/41] Add rmul to distance and speeds, so that reversed operands work --- game/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/game/utils.py b/game/utils.py index 119a741a..fbb28d0e 100644 --- a/game/utils.py +++ b/game/utils.py @@ -62,6 +62,8 @@ class Distance: def __mul__(self, other: Union[float, int]) -> Distance: return meters(self.meters * other) + __rmul__ = __mul__ + def __truediv__(self, other: Union[float, int]) -> Distance: return meters(self.meters / other) @@ -147,6 +149,8 @@ class Speed: def __mul__(self, other: Union[float, int]) -> Speed: return kph(self.kph * other) + __rmul__ = __mul__ + def __truediv__(self, other: Union[float, int]) -> Speed: return kph(self.kph / other)