Check for available aircraft as task precondition.

This makes it so that the mission planning effects are applied only if
the package can be fulfilled. For example, breakthrough will be used
only if all the BAI missions were fulfilled, not if they will *attempt*
to be fulfilled.
This commit is contained in:
Dan Albert
2021-07-13 14:25:30 -07:00
parent 24f6aff8c8
commit ccf6b6ef5f
17 changed files with 578 additions and 597 deletions

View File

@@ -15,5 +15,5 @@ class CaptureBase(CompoundTask[TheaterState]):
front_line: FrontLine
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
yield [BreakthroughAttack(self.front_line, state.player)]
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
yield [DestroyEnemyGroundUnits(self.front_line)]

View File

@@ -14,6 +14,6 @@ class DefendBase(CompoundTask[TheaterState]):
front_line: FrontLine
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
yield [DefensiveStance(self.front_line, state.player)]
yield [RetreatStance(self.front_line, state.player)]
yield [DefensiveStance(self.front_line, state.context.coalition.player)]
yield [RetreatStance(self.front_line, state.context.coalition.player)]
yield [PlanCas(self.front_line)]

View File

@@ -14,6 +14,6 @@ class DestroyEnemyGroundUnits(CompoundTask[TheaterState]):
front_line: FrontLine
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
yield [EliminationAttack(self.front_line, state.player)]
yield [AggressiveAttack(self.front_line, state.player)]
yield [EliminationAttack(self.front_line, state.context.coalition.player)]
yield [AggressiveAttack(self.front_line, state.context.coalition.player)]
yield [PlanCas(self.front_line)]

View File

@@ -7,5 +7,6 @@ from game.htn import CompoundTask, Method
class ProtectAirSpace(CompoundTask[TheaterState]):
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
for cp in state.vulnerable_control_points:
yield [PlanBarcap(cp, state.barcap_rounds)]
for cp, needed in state.barcaps_needed.items():
if needed > 0:
yield [PlanBarcap(cp)]

View File

@@ -6,12 +6,11 @@ from typing import TYPE_CHECKING
from game.commander.tasks.theatercommandertask import TheaterCommanderTask
from game.commander.theaterstate import TheaterState
from game.profiling import MultiEventTracer
from game.theater import FrontLine
from gen.ground_forces.combat_stance import CombatStance
if TYPE_CHECKING:
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from game.coalition import Coalition
class FrontLineStanceTask(TheaterCommanderTask, ABC):
@@ -27,7 +26,10 @@ class FrontLineStanceTask(TheaterCommanderTask, ABC):
@staticmethod
def management_allowed(state: TheaterState) -> bool:
return not state.player or state.stance_automation_enabled
return (
not state.context.coalition.player
or state.context.settings.automate_front_line_stance
)
def better_stance_already_set(self, state: TheaterState) -> bool:
current_stance = state.front_line_stances[self.front_line]
@@ -69,7 +71,5 @@ class FrontLineStanceTask(TheaterCommanderTask, ABC):
def apply_effects(self, state: TheaterState) -> None:
state.front_line_stances[self.front_line] = self.stance
def execute(
self, mission_planner: CoalitionMissionPlanner, tracer: MultiEventTracer
) -> None:
def execute(self, coalition: Coalition) -> None:
self.friendly_cp.stances[self.enemy_cp.id] = self.stance

View File

@@ -8,18 +8,19 @@ from enum import unique, IntEnum, auto
from typing import TYPE_CHECKING, Optional, Generic, TypeVar, Iterator, Union
from game.commander.missionproposals import ProposedFlight, EscortType, ProposedMission
from game.commander.packagefulfiller import PackageFulfiller
from game.commander.tasks.theatercommandertask import TheaterCommanderTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.profiling import MultiEventTracer
from game.settings import AutoAtoBehavior
from game.theater import MissionTarget
from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject
from game.utils import Distance, meters
from gen import Package
from gen.flights.flight import FlightType
if TYPE_CHECKING:
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from game.coalition import Coalition
MissionTargetT = TypeVar("MissionTargetT", bound=MissionTarget)
@@ -36,18 +37,26 @@ class RangeType(IntEnum):
class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
target: MissionTargetT
flights: list[ProposedFlight] = field(init=False)
package: Optional[Package] = field(init=False, default=None)
def __post_init__(self) -> None:
self.flights = []
self.package = Package(self.target)
def preconditions_met(self, state: TheaterState) -> bool:
return not state.player or state.ato_automation_enabled
if (
state.context.coalition.player
and state.context.settings.auto_ato_behavior is AutoAtoBehavior.Disabled
):
return False
return self.fulfill_mission(state)
def execute(
self, mission_planner: CoalitionMissionPlanner, tracer: MultiEventTracer
) -> None:
self.propose_flights(mission_planner.doctrine)
mission_planner.plan_mission(ProposedMission(self.target, self.flights), tracer)
def execute(self, coalition: Coalition) -> None:
if self.package is None:
raise RuntimeError("Attempted to execute failed package planning task")
for flight in self.package.flights:
coalition.aircraft_inventory.claim_for_flight(flight)
coalition.ato.add_package(self.package)
@abstractmethod
def propose_flights(self, doctrine: Doctrine) -> None:
@@ -70,6 +79,19 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
def asap(self) -> bool:
return False
def fulfill_mission(self, state: TheaterState) -> bool:
self.propose_flights(state.context.coalition.doctrine)
fulfiller = PackageFulfiller(
state.context.coalition,
state.context.theater,
state.available_aircraft,
state.context.settings,
)
self.package = fulfiller.plan_mission(
ProposedMission(self.target, self.flights), state.context.tracer
)
return self.package is not None
def propose_common_escorts(self, doctrine: Doctrine) -> None:
self.propose_flight(
FlightType.SEAD_ESCORT,

View File

@@ -1,46 +1,23 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from game.commander.missionproposals import ProposedMission, ProposedFlight
from game.commander.tasks.theatercommandertask import TheaterCommanderTask
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.profiling import MultiEventTracer
from game.data.doctrine import Doctrine
from game.theater import ControlPoint
from gen.flights.flight import FlightType
if TYPE_CHECKING:
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
@dataclass
class PlanBarcap(TheaterCommanderTask):
target: ControlPoint
rounds: int
class PlanBarcap(PackagePlanningTask[ControlPoint]):
def preconditions_met(self, state: TheaterState) -> bool:
if state.player and not state.ato_automation_enabled:
if not super().preconditions_met(state):
return False
return self.target in state.vulnerable_control_points
return state.barcaps_needed[self.target] > 0
def apply_effects(self, state: TheaterState) -> None:
state.vulnerable_control_points.remove(self.target)
state.barcaps_needed[self.target] -= 1
def execute(
self, mission_planner: CoalitionMissionPlanner, tracer: MultiEventTracer
) -> None:
for _ in range(self.rounds):
mission_planner.plan_mission(
ProposedMission(
self.target,
[
ProposedFlight(
FlightType.BARCAP,
2,
mission_planner.doctrine.mission_ranges.cap,
),
],
),
tracer,
)
def propose_flights(self, doctrine: Doctrine) -> None:
self.propose_flight(FlightType.BARCAP, 2, doctrine.mission_ranges.cap)

View File

@@ -5,16 +5,12 @@ from typing import TYPE_CHECKING
from game.commander.theaterstate import TheaterState
from game.htn import PrimitiveTask
from game.profiling import MultiEventTracer
if TYPE_CHECKING:
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from game.coalition import Coalition
# TODO: Refactor so that we don't need to call up to the mission planner.
class TheaterCommanderTask(PrimitiveTask[TheaterState]):
@abstractmethod
def execute(
self, mission_planner: CoalitionMissionPlanner, tracer: MultiEventTracer
) -> None:
def execute(self, coalition: Coalition) -> None:
...