mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Purchase reserves for CAP/CAS.
Next turn's defenses should be planned in preference to expanding offensive capabilities. Fixes https://github.com/Khopa/dcs_liberation/issues/511
This commit is contained in:
parent
c7f9bfbb43
commit
a43b100781
@ -9,6 +9,7 @@ Saves from 2.3 are not compatible with 2.4.
|
||||
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
|
||||
* **[Campaign AI]** Auto-purchase now prefers the best aircraft for the task, but will attempt to maintain some variety.
|
||||
* **[Campaign AI]** Opfor now sells off odd aircraft since they're unlikely to be used.
|
||||
* **[Campaign AI]** Reserve aircraft will be ordered if needed to prioritize next turn's CAP/CAS over offensive missions.
|
||||
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
|
||||
* **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany.
|
||||
|
||||
|
||||
@ -469,8 +469,16 @@ class CoalitionMissionPlanner:
|
||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||
self.procurement_requests: List[AircraftProcurementRequest] = []
|
||||
|
||||
def propose_missions(self) -> Iterator[ProposedMission]:
|
||||
"""Identifies and iterates over potential mission in priority order."""
|
||||
def critical_missions(self) -> Iterator[ProposedMission]:
|
||||
"""Identifies the most important missions to plan this turn.
|
||||
|
||||
Non-critical missions that cannot be fulfilled will create purchase
|
||||
orders for the next turn. Critical missions will create a purchase order
|
||||
unless the mission can be doubly fulfilled. In other words, the AI will
|
||||
attempt to have *double* the aircraft it needs for these missions to
|
||||
ensure that they can be planned again next turn even if all aircraft are
|
||||
eliminated this turn.
|
||||
"""
|
||||
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
|
||||
for cp in self.objective_finder.vulnerable_control_points():
|
||||
yield ProposedMission(cp, [
|
||||
@ -485,6 +493,10 @@ class CoalitionMissionPlanner:
|
||||
EscortType.AirToAir),
|
||||
])
|
||||
|
||||
def propose_missions(self) -> Iterator[ProposedMission]:
|
||||
"""Identifies and iterates over potential mission in priority order."""
|
||||
yield from self.critical_missions()
|
||||
|
||||
# Find enemy SAM sites with ranges that cover friendly CPs, front lines,
|
||||
# or objects, plan DEAD.
|
||||
# Find enemy SAM sites with ranges that extend to within 50 nmi of
|
||||
@ -542,6 +554,9 @@ class CoalitionMissionPlanner:
|
||||
for proposed_mission in self.propose_missions():
|
||||
self.plan_mission(proposed_mission)
|
||||
|
||||
for critical_mission in self.critical_missions():
|
||||
self.plan_mission(critical_mission, reserves=True)
|
||||
|
||||
self.stagger_missions()
|
||||
|
||||
for cp in self.objective_finder.friendly_control_points():
|
||||
@ -551,32 +566,40 @@ class CoalitionMissionPlanner:
|
||||
f"{available} {aircraft.id} from {cp}")
|
||||
|
||||
def plan_flight(self, mission: ProposedMission, flight: ProposedFlight,
|
||||
builder: PackageBuilder,
|
||||
missing_types: Set[FlightType]) -> None:
|
||||
builder: PackageBuilder, missing_types: Set[FlightType],
|
||||
for_reserves: bool) -> None:
|
||||
if not builder.plan_flight(flight):
|
||||
missing_types.add(flight.task)
|
||||
self.procurement_requests.append(AircraftProcurementRequest(
|
||||
purchase_order = AircraftProcurementRequest(
|
||||
near=mission.location,
|
||||
range=flight.max_distance,
|
||||
task_capability=flight.task,
|
||||
number=flight.num_aircraft
|
||||
))
|
||||
)
|
||||
if for_reserves:
|
||||
# Reserves are planned for critical missions, so prioritize
|
||||
# those orders over aircraft needed for non-critical missions.
|
||||
self.procurement_requests.insert(0, purchase_order)
|
||||
else:
|
||||
self.procurement_requests.append(purchase_order)
|
||||
|
||||
def scrub_mission_missing_aircraft(
|
||||
self, mission: ProposedMission, builder: PackageBuilder,
|
||||
missing_types: Set[FlightType],
|
||||
not_attempted: Iterable[ProposedFlight]) -> None:
|
||||
not_attempted: Iterable[ProposedFlight],
|
||||
reserves: bool) -> None:
|
||||
# Try to plan the rest of the mission just so we can count the missing
|
||||
# types to buy.
|
||||
for flight in not_attempted:
|
||||
self.plan_flight(mission, flight, builder, missing_types)
|
||||
self.plan_flight(mission, flight, builder, missing_types, reserves)
|
||||
|
||||
missing_types_str = ", ".join(
|
||||
sorted([t.name for t in missing_types]))
|
||||
builder.release_planned_aircraft()
|
||||
desc = "reserve aircraft" if reserves else "aircraft"
|
||||
self.message(
|
||||
"Insufficient aircraft",
|
||||
f"Not enough aircraft in range for {mission.location.name} "
|
||||
f"Not enough {desc} in range for {mission.location.name} "
|
||||
f"capable of: {missing_types_str}")
|
||||
|
||||
def check_needed_escorts(
|
||||
@ -589,7 +612,8 @@ class CoalitionMissionPlanner:
|
||||
threats[EscortType.Sead] = True
|
||||
return threats
|
||||
|
||||
def plan_mission(self, mission: ProposedMission) -> None:
|
||||
def plan_mission(self, mission: ProposedMission,
|
||||
reserves: bool = False) -> None:
|
||||
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
||||
|
||||
if self.game.settings.perf_ai_parking_start:
|
||||
@ -616,11 +640,12 @@ class CoalitionMissionPlanner:
|
||||
# If the package does not need escorts they may be pruned.
|
||||
escorts.append(proposed_flight)
|
||||
continue
|
||||
self.plan_flight(mission, proposed_flight, builder, missing_types)
|
||||
self.plan_flight(mission, proposed_flight, builder, missing_types,
|
||||
reserves)
|
||||
|
||||
if missing_types:
|
||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
||||
escorts)
|
||||
escorts, reserves)
|
||||
return
|
||||
|
||||
# Create flight plans for the main flights of the package so we can
|
||||
@ -640,14 +665,20 @@ class CoalitionMissionPlanner:
|
||||
# impossible.
|
||||
assert escort.escort_type is not None
|
||||
if needed_escorts[escort.escort_type]:
|
||||
self.plan_flight(mission, escort, builder,
|
||||
missing_types)
|
||||
self.plan_flight(mission, escort, builder, missing_types,
|
||||
reserves)
|
||||
|
||||
# Check again for unavailable aircraft. If the escort was required and
|
||||
# none were found, scrub the mission.
|
||||
if missing_types:
|
||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
||||
escorts)
|
||||
escorts, reserves)
|
||||
return
|
||||
|
||||
if reserves:
|
||||
# Mission is planned reserves which will not be used this turn.
|
||||
# Return reserves to the inventory.
|
||||
builder.release_planned_aircraft()
|
||||
return
|
||||
|
||||
package = builder.build()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user