mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
parent
0d57f710aa
commit
531625ff08
@ -19,6 +19,7 @@
|
|||||||
* **[UI]** Configurable ICLS for capable Carriers & LHAs.
|
* **[UI]** Configurable ICLS for capable Carriers & LHAs.
|
||||||
* **[UI]** Configurable LINK4 for Carriers.
|
* **[UI]** Configurable LINK4 for Carriers.
|
||||||
* **[Kneeboard]** Show package information in Support page
|
* **[Kneeboard]** Show package information in Support page
|
||||||
|
* **[Campaign Design]** Ability to define designated CTLD zones for Control Points (Airbases & FOBs/FARPs)
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[UI]** Removed deprecated options
|
* **[UI]** Removed deprecated options
|
||||||
|
|||||||
20
game/ato/flightplans/_common_ctld.py
Normal file
20
game/ato/flightplans/_common_ctld.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
from typing import Union, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from dcs import Point
|
||||||
|
|
||||||
|
from game.theater import ControlPoint
|
||||||
|
from game.theater.interfaces.CTLD import CTLD
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_ctld_point(cp: Union[ControlPoint, CTLD]) -> Point:
|
||||||
|
if isinstance(cp, CTLD) and cp.ctld_zones:
|
||||||
|
zone: Tuple[Point, float] = random.choice(cp.ctld_zones)
|
||||||
|
pos, radius = zone
|
||||||
|
return pos.random_point_within(radius)
|
||||||
|
elif isinstance(cp, CTLD) and isinstance(cp, ControlPoint):
|
||||||
|
return cp.position.random_point_within(2000, 200)
|
||||||
|
raise RuntimeError("Could not generate CTLD point")
|
||||||
@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Iterator, TYPE_CHECKING, Type
|
from typing import Iterator, TYPE_CHECKING, Type
|
||||||
|
|
||||||
|
from ._common_ctld import generate_random_ctld_point
|
||||||
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
|
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
|
||||||
from game.theater.controlpoint import ControlPointType
|
from game.theater.controlpoint import ControlPointType
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
@ -13,8 +14,10 @@ from .planningerror import PlanningError
|
|||||||
from .uizonedisplay import UiZone, UiZoneDisplay
|
from .uizonedisplay import UiZone, UiZoneDisplay
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from ..flightwaypoint import FlightWaypointType
|
from ..flightwaypoint import FlightWaypointType
|
||||||
|
from ...theater.interfaces.CTLD import CTLD
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from dcs import Point
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
|
||||||
|
|
||||||
@ -104,16 +107,10 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
pickup = None
|
pickup = None
|
||||||
pickup_position = self.flight.departure.position
|
pickup_position = self.flight.departure.position
|
||||||
else:
|
else:
|
||||||
# TODO The calculation of the Pickup LZ is currently randomized. This
|
|
||||||
# leads to the problem that we can not gurantee that the LZ is clear of
|
|
||||||
# obstacles. This has to be improved in the future so that the Mission can
|
|
||||||
# be autoplanned. In the current state the User has to check the created
|
|
||||||
# Waypoints for the Pickup and Dropoff LZs are free of obstacles.
|
|
||||||
# Create a special pickup zone for Helos from Airbase / FOB
|
|
||||||
pickup = builder.pickup_zone(
|
pickup = builder.pickup_zone(
|
||||||
MissionTarget(
|
MissionTarget(
|
||||||
"Pickup Zone",
|
"Pickup Zone",
|
||||||
self.flight.departure.position.random_point_within(1200, 600),
|
self._generate_ctld_pickup(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pickup_position = pickup.position
|
pickup_position = pickup.position
|
||||||
@ -123,7 +120,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
assault_area.only_for_player = False
|
assault_area.only_for_player = False
|
||||||
assault_area.alt = feet(1000)
|
assault_area.alt = feet(1000)
|
||||||
|
|
||||||
# TODO we can not gurantee a safe LZ for DropOff. See comment above.
|
# TODO: define CTLD dropoff zones in campaign miz?
|
||||||
drop_off_zone = MissionTarget(
|
drop_off_zone = MissionTarget(
|
||||||
"Dropoff zone",
|
"Dropoff zone",
|
||||||
self.package.target.position.point_from_heading(heading, 1200),
|
self.package.target.position.point_from_heading(heading, 1200),
|
||||||
@ -159,3 +156,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
|
|
||||||
def build(self) -> AirAssaultFlightPlan:
|
def build(self) -> AirAssaultFlightPlan:
|
||||||
return AirAssaultFlightPlan(self.flight, self.layout())
|
return AirAssaultFlightPlan(self.flight, self.layout())
|
||||||
|
|
||||||
|
def _generate_ctld_pickup(self) -> Point:
|
||||||
|
assert isinstance(self.flight.departure, CTLD)
|
||||||
|
return generate_random_ctld_point(self.flight.departure)
|
||||||
|
|||||||
@ -5,15 +5,18 @@ from dataclasses import dataclass
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
|
from ._common_ctld import generate_random_ctld_point
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.utils import feet
|
from game.utils import feet
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
from .standard import StandardFlightPlan, StandardLayout
|
from .standard import StandardFlightPlan, StandardLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
from ...theater.interfaces.CTLD import CTLD
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..flightwaypoint import FlightWaypoint
|
from ..flightwaypoint import FlightWaypoint
|
||||||
|
from dcs import Point
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -106,15 +109,10 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
|
|||||||
if self.flight.is_helo:
|
if self.flight.is_helo:
|
||||||
# Create CTLD Zones for Helo flights
|
# Create CTLD Zones for Helo flights
|
||||||
pickup_zone = builder.pickup_zone(
|
pickup_zone = builder.pickup_zone(
|
||||||
MissionTarget(
|
MissionTarget("Pickup Zone", self._generate_ctld_pickup())
|
||||||
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
drop_off_zone = builder.dropoff_zone(
|
drop_off_zone = builder.dropoff_zone(
|
||||||
MissionTarget(
|
MissionTarget("Dropoff zone", self._generate_ctld_dropoff())
|
||||||
"Dropoff zone",
|
|
||||||
cargo.next_stop.position.random_point_within(1000, 200),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# Show the zone waypoints only to the player
|
# Show the zone waypoints only to the player
|
||||||
pickup_zone.only_for_player = True
|
pickup_zone.only_for_player = True
|
||||||
@ -153,3 +151,15 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
|
|||||||
|
|
||||||
def build(self) -> AirliftFlightPlan:
|
def build(self) -> AirliftFlightPlan:
|
||||||
return AirliftFlightPlan(self.flight, self.layout())
|
return AirliftFlightPlan(self.flight, self.layout())
|
||||||
|
|
||||||
|
def _generate_ctld_pickup(self) -> Point:
|
||||||
|
cargo = self.flight.cargo
|
||||||
|
if cargo and cargo.origin and isinstance(cargo.origin, CTLD):
|
||||||
|
return generate_random_ctld_point(cargo.origin)
|
||||||
|
raise RuntimeError("Could not generate CTLD pickup")
|
||||||
|
|
||||||
|
def _generate_ctld_dropoff(self) -> Point:
|
||||||
|
cargo = self.flight.cargo
|
||||||
|
if cargo and cargo.transport and isinstance(cargo.transport.destination, CTLD):
|
||||||
|
return generate_random_ctld_point(cargo.transport.destination)
|
||||||
|
raise RuntimeError("Could not generate CTLD dropoff")
|
||||||
|
|||||||
@ -14,6 +14,7 @@ from typing import (
|
|||||||
|
|
||||||
from dcs.mapping import Point, Vector2
|
from dcs.mapping import Point, Vector2
|
||||||
|
|
||||||
|
from game.ato.flightplans._common_ctld import generate_random_ctld_point
|
||||||
from game.ato.flightwaypoint import AltitudeReference, FlightWaypoint
|
from game.ato.flightwaypoint import AltitudeReference, FlightWaypoint
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.theater import (
|
from game.theater import (
|
||||||
@ -23,6 +24,7 @@ from game.theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
TheaterUnit,
|
TheaterUnit,
|
||||||
)
|
)
|
||||||
|
from game.theater.interfaces.CTLD import CTLD
|
||||||
from game.utils import Distance, meters, nautical_miles, feet
|
from game.utils import Distance, meters, nautical_miles, feet
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -557,10 +559,14 @@ class WaypointBuilder:
|
|||||||
"""Creates a cargo stop waypoint.
|
"""Creates a cargo stop waypoint.
|
||||||
This waypoint is used by AirLift as a landing and stopover waypoint
|
This waypoint is used by AirLift as a landing and stopover waypoint
|
||||||
"""
|
"""
|
||||||
|
if isinstance(control_point, CTLD) and control_point.ctld_zones:
|
||||||
|
pos = generate_random_ctld_point(control_point)
|
||||||
|
else:
|
||||||
|
pos = control_point.position
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"CARGOSTOP",
|
"CARGOSTOP",
|
||||||
FlightWaypointType.CARGO_STOP,
|
FlightWaypointType.CARGO_STOP,
|
||||||
control_point.position,
|
pos,
|
||||||
meters(0),
|
meters(0),
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description=f"Stop for cargo at {control_point.name}",
|
description=f"Stop for cargo at {control_point.name}",
|
||||||
|
|||||||
@ -32,6 +32,7 @@ from game.utils import Distance, meters
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.theater.conflicttheater import ConflictTheater
|
from game.theater.conflicttheater import ConflictTheater
|
||||||
|
from dcs import Point
|
||||||
|
|
||||||
|
|
||||||
class MizCampaignLoader:
|
class MizCampaignLoader:
|
||||||
@ -107,8 +108,12 @@ class MizCampaignLoader:
|
|||||||
if self.mission.country(self.RED_COUNTRY.name) is None:
|
if self.mission.country(self.RED_COUNTRY.name) is None:
|
||||||
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
|
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
|
||||||
|
|
||||||
def control_point_from_airport(self, airport: Airport) -> ControlPoint:
|
def control_point_from_airport(
|
||||||
cp = Airfield(airport, self.theater, starts_blue=airport.is_blue())
|
self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
|
||||||
|
) -> ControlPoint:
|
||||||
|
cp = Airfield(
|
||||||
|
airport, self.theater, starts_blue=airport.is_blue(), ctld_zones=ctld_zones
|
||||||
|
)
|
||||||
|
|
||||||
# Use the unlimited aircraft option to determine if an airfield should
|
# Use the unlimited aircraft option to determine if an airfield should
|
||||||
# be owned by the player when the campaign is "inverted".
|
# be owned by the player when the campaign is "inverted".
|
||||||
@ -245,7 +250,8 @@ class MizCampaignLoader:
|
|||||||
control_points = {}
|
control_points = {}
|
||||||
for airport in self.mission.terrain.airport_list():
|
for airport in self.mission.terrain.airport_list():
|
||||||
if airport.is_blue() or airport.is_red():
|
if airport.is_blue() or airport.is_red():
|
||||||
control_point = self.control_point_from_airport(airport)
|
ctld_zones = self.get_ctld_zones(airport.name)
|
||||||
|
control_point = self.control_point_from_airport(airport, ctld_zones)
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
|
|
||||||
for blue in (False, True):
|
for blue in (False, True):
|
||||||
@ -268,8 +274,13 @@ class MizCampaignLoader:
|
|||||||
control_point.captured_invert = ship.late_activation
|
control_point.captured_invert = ship.late_activation
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
for fob in self.fobs(blue):
|
for fob in self.fobs(blue):
|
||||||
|
ctld_zones = self.get_ctld_zones(fob.name)
|
||||||
control_point = Fob(
|
control_point = Fob(
|
||||||
str(fob.name), fob.position, self.theater, starts_blue=blue
|
str(fob.name),
|
||||||
|
fob.position,
|
||||||
|
self.theater,
|
||||||
|
starts_blue=blue,
|
||||||
|
ctld_zones=ctld_zones,
|
||||||
)
|
)
|
||||||
control_point.captured_invert = fob.late_activation
|
control_point.captured_invert = fob.late_activation
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
@ -460,3 +471,9 @@ class MizCampaignLoader:
|
|||||||
self.add_preset_locations()
|
self.add_preset_locations()
|
||||||
self.add_supply_routes()
|
self.add_supply_routes()
|
||||||
self.add_shipping_lanes()
|
self.add_shipping_lanes()
|
||||||
|
|
||||||
|
def get_ctld_zones(self, prefix: str) -> List[Tuple[Point, float]]:
|
||||||
|
zones = [t for t in self.mission.triggers.zones() if prefix + " CTLD" in t.name]
|
||||||
|
for z in zones:
|
||||||
|
self.mission.triggers.zones().remove(z)
|
||||||
|
return [(z.position, z.radius) for z in zones]
|
||||||
|
|||||||
@ -41,6 +41,7 @@ from dcs.ships import (
|
|||||||
Hms_invincible,
|
Hms_invincible,
|
||||||
)
|
)
|
||||||
from dcs.terrain.terrain import Airport, ParkingSlot
|
from dcs.terrain.terrain import Airport, ParkingSlot
|
||||||
|
from dcs.triggers import TriggerZone
|
||||||
from dcs.unitgroup import ShipGroup, StaticGroup
|
from dcs.unitgroup import ShipGroup, StaticGroup
|
||||||
from dcs.unittype import ShipType
|
from dcs.unittype import ShipType
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ from game.theater.presetlocation import PresetLocation
|
|||||||
from game.utils import Distance, Heading, meters
|
from game.utils import Distance, Heading, meters
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .frontline import FrontLine
|
from .frontline import FrontLine
|
||||||
|
from .interfaces.CTLD import CTLD
|
||||||
from .missiontarget import MissionTarget
|
from .missiontarget import MissionTarget
|
||||||
from .theatergroundobject import (
|
from .theatergroundobject import (
|
||||||
GenericCarrierGroundObject,
|
GenericCarrierGroundObject,
|
||||||
@ -1047,9 +1049,13 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class Airfield(ControlPoint):
|
class Airfield(ControlPoint, CTLD):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, airport: Airport, theater: ConflictTheater, starts_blue: bool
|
self,
|
||||||
|
airport: Airport,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
starts_blue: bool,
|
||||||
|
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
airport.name,
|
airport.name,
|
||||||
@ -1061,6 +1067,7 @@ class Airfield(ControlPoint):
|
|||||||
)
|
)
|
||||||
self.airport = airport
|
self.airport = airport
|
||||||
self._runway_status = RunwayStatus()
|
self._runway_status = RunwayStatus()
|
||||||
|
self.ctld_zones = ctld_zones
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dcs_airport(self) -> Airport:
|
def dcs_airport(self) -> Airport:
|
||||||
@ -1399,14 +1406,20 @@ class OffMapSpawn(ControlPoint):
|
|||||||
return ControlPointStatus.Functional
|
return ControlPointStatus.Functional
|
||||||
|
|
||||||
|
|
||||||
class Fob(ControlPoint, RadioFrequencyContainer):
|
class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
|
self,
|
||||||
):
|
name: str,
|
||||||
|
at: Point,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
starts_blue: bool,
|
||||||
|
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name, at, at, theater, starts_blue, cptype=ControlPointType.FOB
|
name, at, at, theater, starts_blue, cptype=ControlPointType.FOB
|
||||||
)
|
)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.ctld_zones = ctld_zones
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
||||||
|
|||||||
7
game/theater/interfaces/CTLD.py
Normal file
7
game/theater/interfaces/CTLD.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from typing import Optional, List, Tuple
|
||||||
|
|
||||||
|
from dcs import Point
|
||||||
|
|
||||||
|
|
||||||
|
class CTLD:
|
||||||
|
ctld_zones: Optional[List[Tuple[Point, float]]] = None
|
||||||
@ -173,5 +173,8 @@ VERSION = _build_version_string()
|
|||||||
#: * Campaign designers can now define more settings:
|
#: * Campaign designers can now define more settings:
|
||||||
#: `max_frontline_length: 25` (in km)
|
#: `max_frontline_length: 25` (in km)
|
||||||
#: `culling_exclusion_radius: 35` (in km)
|
#: `culling_exclusion_radius: 35` (in km)
|
||||||
|
#:
|
||||||
|
#: Version 10.6
|
||||||
|
#: * Designated CTLD zones for ControlPoints (Airbases & FOBs/FARPs)
|
||||||
|
|
||||||
CAMPAIGN_FORMAT_VERSION = (10, 5)
|
CAMPAIGN_FORMAT_VERSION = (10, 6)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user