mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Replan (#3527)
This PR - Introduces replanning of missions after continuing a turn. - A new doctrine field setting the AEWC mission duration that was previously hard coded.
This commit is contained in:
parent
d09a15a7f3
commit
c4a195646f
@ -12,6 +12,7 @@ from .flightmembers import FlightMembers
|
|||||||
from .flightroster import FlightRoster
|
from .flightroster import FlightRoster
|
||||||
from .flightstate import FlightState, Navigating, Uninitialized
|
from .flightstate import FlightState, Navigating, Uninitialized
|
||||||
from .flightstate.killed import Killed
|
from .flightstate.killed import Killed
|
||||||
|
from .flighttype import FlightType
|
||||||
from ..sidc import (
|
from ..sidc import (
|
||||||
Entity,
|
Entity,
|
||||||
SidcDescribable,
|
SidcDescribable,
|
||||||
@ -31,7 +32,6 @@ if TYPE_CHECKING:
|
|||||||
from game.data.weapons import WeaponType
|
from game.data.weapons import WeaponType
|
||||||
from .flightmember import FlightMember
|
from .flightmember import FlightMember
|
||||||
from .flightplans.flightplan import FlightPlan
|
from .flightplans.flightplan import FlightPlan
|
||||||
from .flighttype import FlightType
|
|
||||||
from .flightwaypoint import FlightWaypoint
|
from .flightwaypoint import FlightWaypoint
|
||||||
from .package import Package
|
from .package import Package
|
||||||
from .starttype import StartType
|
from .starttype import StartType
|
||||||
@ -58,7 +58,8 @@ class Flight(SidcDescribable):
|
|||||||
self.coalition = squadron.coalition
|
self.coalition = squadron.coalition
|
||||||
self.squadron = squadron
|
self.squadron = squadron
|
||||||
self.flight_type = flight_type
|
self.flight_type = flight_type
|
||||||
self.squadron.claim_inventory(count)
|
if flight_type != FlightType.IDLE:
|
||||||
|
self.squadron.claim_inventory(count)
|
||||||
if roster is None:
|
if roster is None:
|
||||||
self.roster = FlightMembers(self, initial_size=count)
|
self.roster = FlightMembers(self, initial_size=count)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_m
|
|||||||
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
@property
|
@property
|
||||||
def patrol_duration(self) -> timedelta:
|
def patrol_duration(self) -> timedelta:
|
||||||
return timedelta(hours=4)
|
return self.flight.coalition.doctrine.aewc.duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_speed(self) -> Speed:
|
def patrol_speed(self) -> Speed:
|
||||||
|
|||||||
@ -64,6 +64,7 @@ class FlightPlanBuilderTypes:
|
|||||||
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
||||||
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
||||||
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
||||||
|
FlightType.IDLE: BarCapFlightPlan.builder_type(),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return builder_dict[flight.flight_type]
|
return builder_dict[flight.flight_type]
|
||||||
|
|||||||
@ -57,6 +57,7 @@ class FlightType(Enum):
|
|||||||
REFUELING = "Refueling"
|
REFUELING = "Refueling"
|
||||||
FERRY = "Ferry"
|
FERRY = "Ferry"
|
||||||
AIR_ASSAULT = "Air Assault"
|
AIR_ASSAULT = "Air Assault"
|
||||||
|
IDLE = "Idle"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.value
|
return self.value
|
||||||
|
|||||||
@ -47,6 +47,17 @@ class MissionScheduler:
|
|||||||
margin=5 * 60,
|
margin=5 * 60,
|
||||||
)
|
)
|
||||||
for package in self.coalition.ato.packages:
|
for package in self.coalition.ato.packages:
|
||||||
|
if package.time_over_target > datetime.min:
|
||||||
|
if package.primary_task in dca_types:
|
||||||
|
if (
|
||||||
|
package.mission_departure_time is not None
|
||||||
|
and package.mission_departure_time
|
||||||
|
> previous_cap_end_time[package.target]
|
||||||
|
):
|
||||||
|
previous_cap_end_time[package.target] = (
|
||||||
|
package.mission_departure_time
|
||||||
|
)
|
||||||
|
continue # If package already has TOT, leave it.
|
||||||
tot = TotEstimator(package).earliest_tot(now)
|
tot = TotEstimator(package).earliest_tot(now)
|
||||||
if package.primary_task in dca_types:
|
if package.primary_task in dca_types:
|
||||||
previous_end_time = previous_cap_end_time[package.target]
|
previous_end_time = previous_cap_end_time[package.target]
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from game.theater.theatergroundobject import (
|
|||||||
VehicleGroupGroundObject,
|
VehicleGroupGroundObject,
|
||||||
)
|
)
|
||||||
from game.threatzones import ThreatZones
|
from game.threatzones import ThreatZones
|
||||||
|
from game.ato.flighttype import FlightType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -77,7 +78,8 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
self.threatening_air_defenses.remove(target)
|
self.threatening_air_defenses.remove(target)
|
||||||
if target in self.detecting_air_defenses:
|
if target in self.detecting_air_defenses:
|
||||||
self.detecting_air_defenses.remove(target)
|
self.detecting_air_defenses.remove(target)
|
||||||
self.enemy_air_defenses.remove(target)
|
if target in self.enemy_air_defenses:
|
||||||
|
self.enemy_air_defenses.remove(target)
|
||||||
self._rebuild_threat_zones()
|
self._rebuild_threat_zones()
|
||||||
|
|
||||||
def eliminate_ship(self, target: NavalGroundObject) -> None:
|
def eliminate_ship(self, target: NavalGroundObject) -> None:
|
||||||
@ -85,7 +87,8 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
self.threatening_air_defenses.remove(target)
|
self.threatening_air_defenses.remove(target)
|
||||||
if target in self.detecting_air_defenses:
|
if target in self.detecting_air_defenses:
|
||||||
self.detecting_air_defenses.remove(target)
|
self.detecting_air_defenses.remove(target)
|
||||||
self.enemy_ships.remove(target)
|
if target in self.enemy_ships:
|
||||||
|
self.enemy_ships.remove(target)
|
||||||
self._rebuild_threat_zones()
|
self._rebuild_threat_zones()
|
||||||
|
|
||||||
def has_battle_position(self, target: VehicleGroupGroundObject) -> bool:
|
def has_battle_position(self, target: VehicleGroupGroundObject) -> bool:
|
||||||
@ -155,21 +158,16 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
tracer,
|
tracer,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Plan enough rounds of CAP that the target has coverage over the expected
|
|
||||||
# mission duration.
|
|
||||||
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
|
|
||||||
barcap_duration = coalition.doctrine.cap.duration.total_seconds()
|
|
||||||
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
|
||||||
|
|
||||||
refueling_targets: list[MissionTarget] = []
|
refueling_targets: list[MissionTarget] = []
|
||||||
theater_refuling_point = finder.preferred_theater_refueling_control_point()
|
theater_refuling_point = finder.preferred_theater_refueling_control_point()
|
||||||
if theater_refuling_point is not None:
|
if theater_refuling_point is not None:
|
||||||
refueling_targets.append(theater_refuling_point)
|
refueling_targets.append(theater_refuling_point)
|
||||||
|
|
||||||
return TheaterState(
|
theater_state = TheaterState(
|
||||||
context=context,
|
context=context,
|
||||||
barcaps_needed={
|
barcaps_needed={
|
||||||
cp: barcap_rounds for cp in finder.vulnerable_control_points()
|
cp: cls._barcap_rounds(game, player, now, cp)
|
||||||
|
for cp in finder.vulnerable_control_points()
|
||||||
},
|
},
|
||||||
active_front_lines=list(finder.front_lines()),
|
active_front_lines=list(finder.front_lines()),
|
||||||
front_line_stances={f: None for f in finder.front_lines()},
|
front_line_stances={f: None for f in finder.front_lines()},
|
||||||
@ -191,3 +189,62 @@ class TheaterState(WorldState["TheaterState"]):
|
|||||||
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
enemy_barcaps=list(game.theater.control_points_for(not player)),
|
||||||
threat_zones=game.threat_zone_for(not player),
|
threat_zones=game.threat_zone_for(not player),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Look through packages already planned in the ATO and eliminate from the
|
||||||
|
# list of targets.
|
||||||
|
for package in coalition.ato.packages:
|
||||||
|
if isinstance(package.target, NavalGroundObject):
|
||||||
|
theater_state.eliminate_ship(package.target)
|
||||||
|
if package.primary_task == FlightType.BAI and isinstance(
|
||||||
|
package.target, VehicleGroupGroundObject
|
||||||
|
):
|
||||||
|
theater_state.eliminate_battle_position(package.target)
|
||||||
|
if isinstance(package.target, IadsGroundObject):
|
||||||
|
theater_state.eliminate_air_defense(package.target)
|
||||||
|
if (
|
||||||
|
package.primary_task == FlightType.STRIKE
|
||||||
|
and isinstance(package.target, TheaterGroundObject)
|
||||||
|
and package.target in theater_state.strike_targets
|
||||||
|
):
|
||||||
|
theater_state.strike_targets.remove(package.target)
|
||||||
|
if package.primary_task == FlightType.AEWC:
|
||||||
|
# If a planned AEWC mission covers the target beyond the planned mission duration, it can safely be removed
|
||||||
|
if (
|
||||||
|
package.time_over_target + coalition.doctrine.aewc.duration
|
||||||
|
> now + game.settings.desired_player_mission_duration
|
||||||
|
) and package.target in theater_state.aewc_targets:
|
||||||
|
theater_state.aewc_targets.remove(package.target)
|
||||||
|
if (
|
||||||
|
package.primary_task
|
||||||
|
in (
|
||||||
|
FlightType.OCA_AIRCRAFT,
|
||||||
|
FlightType.OCA_RUNWAY,
|
||||||
|
)
|
||||||
|
and isinstance(package.target, ControlPoint)
|
||||||
|
and package.target in theater_state.oca_targets
|
||||||
|
):
|
||||||
|
theater_state.oca_targets.remove(package.target)
|
||||||
|
return theater_state
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _barcap_rounds(
|
||||||
|
cls, game: Game, player: bool, now: datetime, control_point: ControlPoint
|
||||||
|
) -> int:
|
||||||
|
"""Calculate number of additional rounds of CAP required to cover mission duration."""
|
||||||
|
coalition = game.coalition_for(player)
|
||||||
|
|
||||||
|
# Look through ATO for any existing planned CAP missions and calculate last planned CAP end
|
||||||
|
planned_cap_coverage_end_time = now
|
||||||
|
for package in coalition.ato.packages:
|
||||||
|
if package.target == control_point:
|
||||||
|
cap_end_time = (
|
||||||
|
package.time_over_target + coalition.doctrine.cap.duration
|
||||||
|
)
|
||||||
|
if cap_end_time > planned_cap_coverage_end_time:
|
||||||
|
planned_cap_coverage_end_time = cap_end_time
|
||||||
|
# When mission is expected to finish
|
||||||
|
mission_end_time = now + game.settings.desired_player_mission_duration
|
||||||
|
return math.ceil(
|
||||||
|
(mission_end_time - planned_cap_coverage_end_time).total_seconds()
|
||||||
|
/ coalition.doctrine.cap.duration.total_seconds()
|
||||||
|
)
|
||||||
|
|||||||
@ -52,6 +52,16 @@ class Helicopter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Aewc:
|
||||||
|
#: The duration that AEWC flights will remain on-station
|
||||||
|
duration: timedelta
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(data: dict[str, Any]) -> Aewc:
|
||||||
|
return Aewc(duration=timedelta(minutes=data["duration_minutes"]))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Cas:
|
class Cas:
|
||||||
#: The duration that CAP flights will remain on-station.
|
#: The duration that CAP flights will remain on-station.
|
||||||
@ -169,6 +179,9 @@ class Doctrine:
|
|||||||
#: Helicopter specific doctrines.
|
#: Helicopter specific doctrines.
|
||||||
helicopter: Helicopter
|
helicopter: Helicopter
|
||||||
|
|
||||||
|
#: Doctrine for AEWC missions.
|
||||||
|
aewc: Aewc
|
||||||
|
|
||||||
#: Doctrine for CAS missions.
|
#: Doctrine for CAS missions.
|
||||||
cas: Cas
|
cas: Cas
|
||||||
|
|
||||||
@ -238,6 +251,7 @@ class Doctrine:
|
|||||||
data["ground_unit_procurement_ratios"]
|
data["ground_unit_procurement_ratios"]
|
||||||
),
|
),
|
||||||
helicopter=Helicopter.from_dict(data["helicopter"]),
|
helicopter=Helicopter.from_dict(data["helicopter"]),
|
||||||
|
aewc=Aewc.from_dict(data["aewc"]),
|
||||||
cas=Cas.from_dict(data["cas"]),
|
cas=Cas.from_dict(data["cas"]),
|
||||||
cap=Cap.from_dict(data["cap"]),
|
cap=Cap.from_dict(data["cap"]),
|
||||||
sweep=Sweep.from_dict(data["sweep"]),
|
sweep=Sweep.from_dict(data["sweep"]),
|
||||||
|
|||||||
@ -148,7 +148,7 @@ class AircraftGenerator:
|
|||||||
faction.country,
|
faction.country,
|
||||||
squadron,
|
squadron,
|
||||||
1,
|
1,
|
||||||
FlightType.BARCAP,
|
FlightType.IDLE,
|
||||||
StartType.COLD,
|
StartType.COLD,
|
||||||
divert=None,
|
divert=None,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -102,6 +102,12 @@ class MissionSimulation:
|
|||||||
# Always skip combat as we are processing results from DCS. Any combat has already
|
# Always skip combat as we are processing results from DCS. Any combat has already
|
||||||
# been resolved in-game
|
# been resolved in-game
|
||||||
self.tick(events, CombatResolutionMethod.SKIP, force_continue=True)
|
self.tick(events, CombatResolutionMethod.SKIP, force_continue=True)
|
||||||
|
self.game.blue.plan_missions(self.game.simulation_time)
|
||||||
|
self.game.red.plan_missions(self.game.simulation_time)
|
||||||
|
self.game.game_stats.update(self.game)
|
||||||
|
# Generate begin_new_turn event which triggers a refresh of the React map screen to
|
||||||
|
# show newly planned missions.
|
||||||
|
events.begin_new_turn()
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
self.unit_map = None
|
self.unit_map = None
|
||||||
|
|||||||
@ -6,6 +6,8 @@ max_ingress_distance_nm: 30
|
|||||||
min_ingress_distance_nm: 10
|
min_ingress_distance_nm: 10
|
||||||
rendezvous_altitude_ft_msl: 22000
|
rendezvous_altitude_ft_msl: 22000
|
||||||
combat_altitude_ft_msl: 18000
|
combat_altitude_ft_msl: 18000
|
||||||
|
aewc:
|
||||||
|
duration_minutes: 240
|
||||||
cap:
|
cap:
|
||||||
duration_minutes: 30
|
duration_minutes: 30
|
||||||
min_track_length_nm: 12
|
min_track_length_nm: 12
|
||||||
|
|||||||
@ -6,6 +6,8 @@ max_ingress_distance_nm: 45
|
|||||||
min_ingress_distance_nm: 10
|
min_ingress_distance_nm: 10
|
||||||
rendezvous_altitude_ft_msl: 25000
|
rendezvous_altitude_ft_msl: 25000
|
||||||
combat_altitude_ft_msl: 20000
|
combat_altitude_ft_msl: 20000
|
||||||
|
aewc:
|
||||||
|
duration_minutes: 240
|
||||||
cap:
|
cap:
|
||||||
duration_minutes: 30
|
duration_minutes: 30
|
||||||
min_track_length_nm: 15
|
min_track_length_nm: 15
|
||||||
|
|||||||
@ -6,6 +6,8 @@ max_ingress_distance_nm: 7
|
|||||||
min_ingress_distance_nm: 5
|
min_ingress_distance_nm: 5
|
||||||
rendezvous_altitude_ft_msl: 10000
|
rendezvous_altitude_ft_msl: 10000
|
||||||
combat_altitude_ft_msl: 8000
|
combat_altitude_ft_msl: 8000
|
||||||
|
aewc:
|
||||||
|
duration_minutes: 240
|
||||||
cap:
|
cap:
|
||||||
duration_minutes: 30
|
duration_minutes: 30
|
||||||
min_track_length_nm: 8
|
min_track_length_nm: 8
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user