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:
Druss99
2024-12-22 23:39:10 -05:00
committed by GitHub
parent a4671571bc
commit dd7e4c908e
46 changed files with 395 additions and 25 deletions

View File

@@ -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

View File

@@ -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 (

View File

@@ -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

View File

@@ -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

View 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)]

View File

@@ -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")

View File

@@ -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)

View File

@@ -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())

View File

@@ -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()

View File

@@ -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()

View File

@@ -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())

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View 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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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=[],