mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Recovery tanker support (#429)
* fix conflict * squash bugs and reuse patrol layout * fix tanker tacan and formatting * fix unlimited fuel option * update pretense for tanker changes * reuse refueling flight plan and bugfix for sunken carrier changelog * remove unitmap dependency * formatting and more unit map removal * more formatting * typing and black * keep tanker out of clouds * fix if there are no clouds * better cloud handling * groundwork for recovery task * remove changes to game/commander * Finishing up recovery tankers --------- Co-authored-by: Raffson <Raffson@users.noreply.github.com>
This commit is contained in:
@@ -40,6 +40,8 @@ class MissionScheduler:
|
||||
p for p in self.coalition.ato.packages if p.primary_task not in dca_types
|
||||
]
|
||||
|
||||
carrier_etas = []
|
||||
|
||||
start_time = start_time_generator(
|
||||
count=len(non_dca_packages),
|
||||
earliest=5 * 60,
|
||||
@@ -47,6 +49,8 @@ class MissionScheduler:
|
||||
margin=5 * 60,
|
||||
)
|
||||
for package in self.coalition.ato.packages:
|
||||
if package.primary_task is FlightType.RECOVERY:
|
||||
continue
|
||||
tot = TotEstimator(package).earliest_tot(now)
|
||||
if package.primary_task in dca_types:
|
||||
previous_end_time = previous_cap_end_time[package.target]
|
||||
@@ -74,3 +78,19 @@ class MissionScheduler:
|
||||
# to be present. Runway and air started aircraft will be
|
||||
# delayed until their takeoff time by AirConflictGenerator.
|
||||
package.time_over_target = next(start_time) + tot
|
||||
arrivals = []
|
||||
for f in package.flights:
|
||||
if f.departure.is_fleet and not f.is_helo:
|
||||
arrivals.append(f.flight_plan.landing_time - timedelta(minutes=10))
|
||||
if arrivals:
|
||||
carrier_etas.append(min(arrivals))
|
||||
|
||||
for package in [
|
||||
p
|
||||
for p in self.coalition.ato.packages
|
||||
if p.primary_task is FlightType.RECOVERY
|
||||
]:
|
||||
if carrier_etas:
|
||||
package.time_over_target = carrier_etas.pop(0)
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -246,6 +246,9 @@ class ObjectiveFinder:
|
||||
raise RuntimeError("Found no friendly control points. You probably lost.")
|
||||
return closest
|
||||
|
||||
def friendly_naval_control_points(self) -> Iterator[ControlPoint]:
|
||||
return (cp for cp in self.friendly_control_points() if cp.is_fleet)
|
||||
|
||||
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
||||
"""Iterates over all enemy control points."""
|
||||
return (
|
||||
|
||||
@@ -53,7 +53,8 @@ class PackageBuilder:
|
||||
if pf:
|
||||
target = (
|
||||
pf.departure
|
||||
if pf.flight_type in [FlightType.AEWC, FlightType.REFUELING]
|
||||
if pf.flight_type
|
||||
in [FlightType.AEWC, FlightType.REFUELING, FlightType.RECOVERY]
|
||||
else target
|
||||
)
|
||||
heli = pf.is_helo
|
||||
|
||||
@@ -14,6 +14,7 @@ from game.commander.tasks.compound.interdictreinforcements import (
|
||||
InterdictReinforcements,
|
||||
)
|
||||
from game.commander.tasks.compound.protectairspace import ProtectAirSpace
|
||||
from game.commander.tasks.compound.recoverysupport import RecoverySupport
|
||||
from game.commander.tasks.compound.theatersupport import TheaterSupport
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.htn import CompoundTask, Method
|
||||
@@ -34,3 +35,4 @@ class PlanNextAction(CompoundTask[TheaterState]):
|
||||
yield [AttackBuildings()]
|
||||
yield [AttackShips()]
|
||||
yield [DegradeIads()]
|
||||
yield [RecoverySupport()] # for recovery tankers
|
||||
|
||||
16
game/commander/tasks/compound/recoverysupport.py
Normal file
16
game/commander/tasks/compound/recoverysupport.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from collections.abc import Iterator
|
||||
|
||||
from game.commander.tasks.primitive.recovery import PlanRecovery
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.htn import CompoundTask, Method
|
||||
|
||||
|
||||
class RecoverySupport(CompoundTask[TheaterState]):
|
||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||
yield [PlanRecoverySupport()]
|
||||
|
||||
|
||||
class PlanRecoverySupport(CompoundTask[TheaterState]):
|
||||
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
|
||||
for target in state.recovery_targets:
|
||||
yield [PlanRecovery(target)]
|
||||
@@ -16,7 +16,7 @@ from game.commander.tasks.theatercommandertask import TheaterCommanderTask
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.data.groups import GroupTask
|
||||
from game.settings import AutoAtoBehavior
|
||||
from game.theater import MissionTarget
|
||||
from game.theater import MissionTarget, ControlPoint
|
||||
from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject
|
||||
from game.utils import Distance, meters
|
||||
|
||||
@@ -51,6 +51,15 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
|
||||
return False
|
||||
return self.fulfill_mission(state)
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
seen: set[ControlPoint] = set()
|
||||
if not self.package:
|
||||
return
|
||||
for f in self.package.flights:
|
||||
if f.departure.is_fleet and not f.is_helo and f.departure not in seen:
|
||||
state.recovery_targets[f.departure] += f.count
|
||||
seen.add(f.departure)
|
||||
|
||||
def execute(self, coalition: Coalition) -> None:
|
||||
if self.package is None:
|
||||
raise RuntimeError("Attempted to execute failed package planning task")
|
||||
|
||||
@@ -22,6 +22,7 @@ class PlanAewc(PackagePlanningTask[MissionTarget]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.aewc_targets.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.AEWC, 1)
|
||||
|
||||
@@ -19,6 +19,7 @@ class PlanAirAssault(PackagePlanningTask[ControlPoint]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.vulnerable_control_points.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.AIR_ASSAULT, self.get_flight_size())
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from random import randint
|
||||
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.commander.missionproposals import EscortType
|
||||
@@ -21,6 +20,7 @@ class PlanAntiShip(PackagePlanningTask[NavalGroundObject]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.eliminate_ship(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
size = self.get_flight_size()
|
||||
|
||||
@@ -20,6 +20,7 @@ class PlanAntiShipping(PackagePlanningTask[CargoShip]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.enemy_shipping.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
size = self.get_flight_size()
|
||||
|
||||
@@ -19,6 +19,7 @@ class PlanArmedRecon(PackagePlanningTask[ControlPoint]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.control_point_priority_queue.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.ARMED_RECON, self.get_flight_size())
|
||||
|
||||
@@ -19,6 +19,7 @@ class PlanBai(PackagePlanningTask[VehicleGroupGroundObject]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.eliminate_battle_position(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
tgt_count = self.target.alive_unit_count
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from random import randint
|
||||
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.commander.tasks.packageplanningtask import PackagePlanningTask
|
||||
@@ -21,6 +19,7 @@ class PlanBarcap(PackagePlanningTask[ControlPoint]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.barcaps_needed[self.target] -= 1
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
size = self.get_flight_size()
|
||||
|
||||
@@ -28,6 +28,7 @@ class PlanCas(PackagePlanningTask[FrontLine]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.vulnerable_front_lines.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
size = self.get_flight_size()
|
||||
|
||||
@@ -19,6 +19,7 @@ class PlanConvoyInterdiction(PackagePlanningTask[Convoy]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.enemy_convoys.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.BAI, 2)
|
||||
|
||||
@@ -23,6 +23,7 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.eliminate_air_defense(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
tgt_count = self.target.alive_unit_count
|
||||
|
||||
@@ -22,6 +22,7 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.oca_targets.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
size = self.get_flight_size()
|
||||
|
||||
32
game/commander/tasks/primitive/recovery.py
Normal file
32
game/commander/tasks/primitive/recovery.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.commander.tasks.packageplanningtask import PackagePlanningTask
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.theater import ControlPoint
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlanRecovery(PackagePlanningTask[ControlPoint]):
|
||||
def preconditions_met(self, state: TheaterState) -> bool:
|
||||
if (
|
||||
state.context.coalition.player
|
||||
and not state.context.settings.auto_ato_behavior_tankers
|
||||
):
|
||||
return False
|
||||
ac_per_tanker = state.context.settings.aircraft_per_recovery_tanker
|
||||
if not (
|
||||
self.target in state.recovery_targets
|
||||
and state.recovery_targets[self.target] >= ac_per_tanker
|
||||
):
|
||||
return False
|
||||
return super().preconditions_met(state)
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
ac_per_tanker = state.context.settings.aircraft_per_recovery_tanker
|
||||
state.recovery_targets[self.target] -= ac_per_tanker
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.RECOVERY, 1)
|
||||
@@ -22,6 +22,7 @@ class PlanRefueling(PackagePlanningTask[MissionTarget]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.refueling_targets.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
self.propose_flight(FlightType.REFUELING, 1)
|
||||
|
||||
@@ -20,6 +20,7 @@ class PlanStrike(PackagePlanningTask[TheaterGroundObject]):
|
||||
|
||||
def apply_effects(self, state: TheaterState) -> None:
|
||||
state.strike_targets.remove(self.target)
|
||||
super().apply_effects(state)
|
||||
|
||||
def propose_flights(self) -> None:
|
||||
tgt_count = self.target.alive_unit_count
|
||||
|
||||
@@ -15,7 +15,12 @@ from game.ground_forces.combat_stance import CombatStance
|
||||
from game.htn import WorldState
|
||||
from game.profiling import MultiEventTracer
|
||||
from game.settings import Settings
|
||||
from game.theater import ConflictTheater, ControlPoint, FrontLine, MissionTarget
|
||||
from game.theater import (
|
||||
ConflictTheater,
|
||||
ControlPoint,
|
||||
FrontLine,
|
||||
MissionTarget,
|
||||
)
|
||||
from game.theater.theatergroundobject import (
|
||||
BuildingGroundObject,
|
||||
IadsGroundObject,
|
||||
@@ -51,6 +56,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
vulnerable_front_lines: list[FrontLine]
|
||||
aewc_targets: list[MissionTarget]
|
||||
refueling_targets: list[MissionTarget]
|
||||
recovery_targets: dict[ControlPoint, int]
|
||||
enemy_air_defenses: list[IadsGroundObject]
|
||||
threatening_air_defenses: list[Union[IadsGroundObject, NavalGroundObject]]
|
||||
detecting_air_defenses: list[Union[IadsGroundObject, NavalGroundObject]]
|
||||
@@ -118,6 +124,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
vulnerable_front_lines=list(self.vulnerable_front_lines),
|
||||
aewc_targets=list(self.aewc_targets),
|
||||
refueling_targets=list(self.refueling_targets),
|
||||
recovery_targets=dict(self.recovery_targets),
|
||||
enemy_air_defenses=list(self.enemy_air_defenses),
|
||||
enemy_convoys=list(self.enemy_convoys),
|
||||
enemy_shipping=list(self.enemy_shipping),
|
||||
@@ -186,6 +193,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
vulnerable_front_lines=list(finder.front_lines()),
|
||||
aewc_targets=[finder.farthest_friendly_control_point()],
|
||||
refueling_targets=[finder.closest_friendly_control_point()],
|
||||
recovery_targets={cp: 0 for cp in finder.friendly_naval_control_points()},
|
||||
enemy_air_defenses=list(finder.enemy_air_defenses()),
|
||||
threatening_air_defenses=[],
|
||||
detecting_air_defenses=[],
|
||||
|
||||
Reference in New Issue
Block a user