mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
7 Commits
6.1.0
...
develop-6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
801e9efe8c | ||
|
|
5708fe625b | ||
|
|
1dda0c9497 | ||
|
|
56d7641251 | ||
|
|
938d6b4bdf | ||
|
|
5caf07f476 | ||
|
|
d1d4fde6a3 |
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@@ -11,6 +11,7 @@ jobs:
|
|||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
- uses: psf/black@stable
|
- uses: psf/black@stable
|
||||||
with:
|
with:
|
||||||
|
version: ~=22.12
|
||||||
src: "."
|
src: "."
|
||||||
options: "--check"
|
options: "--check"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
# 6.1.1
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
* **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect.
|
||||||
|
* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix.
|
||||||
|
* **[Mission Generation]** Using heliports (airports without any runways) will no longer cause mission generation to fail.
|
||||||
|
* **[Mission Generation]** Prevent helicopters from spawning into collisions at FARPs when more than one flight uses the same FARP.
|
||||||
|
|
||||||
# 6.1.0
|
# 6.1.0
|
||||||
|
|
||||||
Saves from 6.0.0 are compatible with 6.1.0
|
Saves from 6.0.0 are compatible with 6.1.0
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import operator
|
|||||||
from collections.abc import Iterable, Iterator
|
from collections.abc import Iterable, Iterator
|
||||||
from typing import TYPE_CHECKING, TypeVar
|
from typing import TYPE_CHECKING, TypeVar
|
||||||
|
|
||||||
|
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
Airfield,
|
Airfield,
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
@@ -15,12 +16,11 @@ from game.theater import (
|
|||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
BuildingGroundObject,
|
BuildingGroundObject,
|
||||||
|
IadsBuildingGroundObject,
|
||||||
IadsGroundObject,
|
IadsGroundObject,
|
||||||
NavalGroundObject,
|
NavalGroundObject,
|
||||||
IadsBuildingGroundObject,
|
|
||||||
)
|
)
|
||||||
from game.utils import meters, nautical_miles
|
from game.utils import meters, nautical_miles
|
||||||
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@@ -209,22 +209,20 @@ class ObjectiveFinder:
|
|||||||
raise RuntimeError("Found no friendly control points. You probably lost.")
|
raise RuntimeError("Found no friendly control points. You probably lost.")
|
||||||
return farthest
|
return farthest
|
||||||
|
|
||||||
def closest_friendly_control_point(self) -> ControlPoint:
|
def preferred_theater_refueling_control_point(self) -> ControlPoint | None:
|
||||||
"""Finds the friendly control point that is closest to any threats."""
|
"""Finds the friendly control point that is closest to any threats."""
|
||||||
threat_zones = self.game.threat_zone_for(not self.is_player)
|
threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||||
|
|
||||||
closest = None
|
closest = None
|
||||||
min_distance = meters(math.inf)
|
min_distance = meters(math.inf)
|
||||||
for cp in self.friendly_control_points():
|
for cp in self.friendly_control_points():
|
||||||
if isinstance(cp, OffMapSpawn):
|
if isinstance(cp, OffMapSpawn) or cp.is_fleet:
|
||||||
continue
|
continue
|
||||||
distance = threat_zones.distance_to_threat(cp.position)
|
distance = threat_zones.distance_to_threat(cp.position)
|
||||||
if distance < min_distance:
|
if distance < min_distance:
|
||||||
closest = cp
|
closest = cp
|
||||||
min_distance = distance
|
min_distance = distance
|
||||||
|
|
||||||
if closest is None:
|
|
||||||
raise RuntimeError("Found no friendly control points. You probably lost.")
|
|
||||||
return closest
|
return closest
|
||||||
|
|
||||||
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
||||||
|
|||||||
@@ -153,6 +153,11 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
barcap_duration = coalition.doctrine.cap_duration.total_seconds()
|
barcap_duration = coalition.doctrine.cap_duration.total_seconds()
|
||||||
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
||||||
|
|
||||||
|
refueling_targets: list[MissionTarget] = []
|
||||||
|
theater_refuling_point = finder.preferred_theater_refueling_control_point()
|
||||||
|
if theater_refuling_point is not None:
|
||||||
|
refueling_targets.append(theater_refuling_point)
|
||||||
|
|
||||||
return TheaterState(
|
return TheaterState(
|
||||||
context=context,
|
context=context,
|
||||||
barcaps_needed={
|
barcaps_needed={
|
||||||
@@ -162,7 +167,7 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
front_line_stances={f: None for f in finder.front_lines()},
|
front_line_stances={f: None for f in finder.front_lines()},
|
||||||
vulnerable_front_lines=list(finder.front_lines()),
|
vulnerable_front_lines=list(finder.front_lines()),
|
||||||
aewc_targets=[finder.farthest_friendly_control_point()],
|
aewc_targets=[finder.farthest_friendly_control_point()],
|
||||||
refueling_targets=[finder.closest_friendly_control_point()],
|
refueling_targets=refueling_targets,
|
||||||
enemy_air_defenses=list(finder.enemy_air_defenses()),
|
enemy_air_defenses=list(finder.enemy_air_defenses()),
|
||||||
threatening_air_defenses=[],
|
threatening_air_defenses=[],
|
||||||
detecting_air_defenses=[],
|
detecting_air_defenses=[],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from game.settings import Settings
|
|||||||
from game.theater.controlpoint import (
|
from game.theater.controlpoint import (
|
||||||
Airfield,
|
Airfield,
|
||||||
ControlPoint,
|
ControlPoint,
|
||||||
|
Fob,
|
||||||
)
|
)
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from .aircraftpainter import AircraftPainter
|
from .aircraftpainter import AircraftPainter
|
||||||
@@ -180,4 +181,12 @@ class AircraftGenerator:
|
|||||||
self.unit_map,
|
self.unit_map,
|
||||||
).configure()
|
).configure()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wpt = group.waypoint("LANDING")
|
||||||
|
if flight.is_helo and isinstance(flight.arrival, Fob) and wpt:
|
||||||
|
hpad = self.helipads[flight.arrival].units.pop(0)
|
||||||
|
wpt.helipad_id = hpad.id
|
||||||
|
wpt.link_unit = hpad.id
|
||||||
|
self.helipads[flight.arrival].units.append(hpad)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|||||||
@@ -230,16 +230,22 @@ class FlightGroupSpawner:
|
|||||||
def _generate_at_cp_helipad(self, name: str, cp: ControlPoint) -> FlyingGroup[Any]:
|
def _generate_at_cp_helipad(self, name: str, cp: ControlPoint) -> FlyingGroup[Any]:
|
||||||
try:
|
try:
|
||||||
helipad = self.helipads[cp]
|
helipad = self.helipads[cp]
|
||||||
except IndexError as ex:
|
except IndexError:
|
||||||
raise NoParkingSlotError()
|
raise NoParkingSlotError()
|
||||||
|
|
||||||
group = self._generate_at_group(name, helipad)
|
group = self._generate_at_group(name, helipad)
|
||||||
|
|
||||||
if self.start_type is not StartType.COLD:
|
if self.start_type is StartType.WARM:
|
||||||
group.points[0].type = "TakeOffParkingHot"
|
group.points[0].type = "TakeOffParkingHot"
|
||||||
|
hpad = helipad.units[0]
|
||||||
for i in range(self.flight.count):
|
for i in range(self.flight.count):
|
||||||
group.units[i].position = helipad.units[i].position
|
group.units[i].position = hpad.position
|
||||||
group.units[i].heading = helipad.units[i].heading
|
group.units[i].heading = hpad.heading
|
||||||
|
# pydcs has just `parking_id = None`, so mypy thinks str is invalid. Ought
|
||||||
|
# to fix pydcs, but that's not the kind of change we want to pull into the
|
||||||
|
# 6.1 branch, and frankly we should probably just improve pydcs's handling
|
||||||
|
# of FARPs instead.
|
||||||
|
group.units[i].parking_id = str(i + 1) # type: ignore
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def dcs_start_type(self) -> DcsStartType:
|
def dcs_start_type(self) -> DcsStartType:
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ class RecoveryTankerBuilder(PydcsWaypointBuilder):
|
|||||||
for key, value in theater_objects.items():
|
for key, value in theater_objects.items():
|
||||||
# Check name and position in case there are multiple of same carrier.
|
# Check name and position in case there are multiple of same carrier.
|
||||||
if name in key and value.theater_unit.position == carrier_position:
|
if name in key and value.theater_unit.position == carrier_position:
|
||||||
theater_mapping = value
|
return value.dcs_group_id
|
||||||
break
|
raise RuntimeError(
|
||||||
assert theater_mapping is not None
|
f"Could not find a carrier in the mission matching {name} at "
|
||||||
return theater_mapping.dcs_group_id
|
f"({carrier_position.x}, {carrier_position.y})"
|
||||||
|
)
|
||||||
|
|
||||||
def configure_tanker_tacan(self, waypoint: MovingPoint) -> None:
|
def configure_tanker_tacan(self, waypoint: MovingPoint) -> None:
|
||||||
|
|
||||||
|
|||||||
@@ -878,6 +878,11 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
|||||||
) -> RunwayData:
|
) -> RunwayData:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def stub_runway_data(self) -> RunwayData:
|
||||||
|
return RunwayData(
|
||||||
|
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def airdrome_id_for_landing(self) -> Optional[int]:
|
def airdrome_id_for_landing(self) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
@@ -1134,6 +1139,14 @@ class Airfield(ControlPoint):
|
|||||||
conditions: Conditions,
|
conditions: Conditions,
|
||||||
dynamic_runways: Dict[str, RunwayData],
|
dynamic_runways: Dict[str, RunwayData],
|
||||||
) -> RunwayData:
|
) -> RunwayData:
|
||||||
|
if not self.airport.runways:
|
||||||
|
# Some airfields are heliports and don't have any runways. This isn't really
|
||||||
|
# the best fix, since we should still try to generate partial data for TACAN
|
||||||
|
# beacons, but it'll do for a bug fix, and the proper fix probably involves
|
||||||
|
# making heliports their own CP type.
|
||||||
|
# https://github.com/dcs-liberation/dcs_liberation/issues/2710
|
||||||
|
return self.stub_runway_data()
|
||||||
|
|
||||||
assigner = RunwayAssigner(conditions)
|
assigner = RunwayAssigner(conditions)
|
||||||
return assigner.get_preferred_runway(theater, self.airport)
|
return assigner.get_preferred_runway(theater, self.airport)
|
||||||
|
|
||||||
@@ -1269,8 +1282,6 @@ class Carrier(NavalControlPoint):
|
|||||||
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER
|
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER
|
||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from game.ato import FlightType
|
|
||||||
|
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
@@ -1375,9 +1386,7 @@ class OffMapSpawn(ControlPoint):
|
|||||||
dynamic_runways: Dict[str, RunwayData],
|
dynamic_runways: Dict[str, RunwayData],
|
||||||
) -> RunwayData:
|
) -> RunwayData:
|
||||||
logging.warning("TODO: Off map spawns have no runways.")
|
logging.warning("TODO: Off map spawns have no runways.")
|
||||||
return RunwayData(
|
return self.stub_runway_data()
|
||||||
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def runway_status(self) -> RunwayStatus:
|
def runway_status(self) -> RunwayStatus:
|
||||||
@@ -1419,9 +1428,7 @@ class Fob(ControlPoint):
|
|||||||
dynamic_runways: Dict[str, RunwayData],
|
dynamic_runways: Dict[str, RunwayData],
|
||||||
) -> RunwayData:
|
) -> RunwayData:
|
||||||
logging.warning("TODO: FOBs have no runways.")
|
logging.warning("TODO: FOBs have no runways.")
|
||||||
return RunwayData(
|
return self.stub_runway_data()
|
||||||
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def runway_status(self) -> RunwayStatus:
|
def runway_status(self) -> RunwayStatus:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
MAJOR_VERSION = 6
|
MAJOR_VERSION = 6
|
||||||
MINOR_VERSION = 1
|
MINOR_VERSION = 1
|
||||||
MICRO_VERSION = 0
|
MICRO_VERSION = 1
|
||||||
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
|
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user