mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
- Pretense: Extra friendly zone connections - Add connections from each zone to this many closest friendly zones, which don't have an existing supply route defined in the campaign. - Number of cargo planes per side - Number of AI SEAD flights per control point / zone - Number of AI CAS flights per control point / zone - Number of AI BAI flights per control point / zone - Number of AI Strike flights per control point / zone - Number of AI BARCAP flights per control point / zone - Number of AI aircraft per flight - Number of player flights per aircraft type at each base - Number of AI cargo planes per side Implemented CAS helo mission handling for Pretense. Implemented separate pretense_air_groups container for storing/referencing Flight objects. Tweaked the supply costs of SAM sites and Command centers. Will no longer generate player air starts at roadbases either. Restored the missing DEAD flights to Pretense. Removed spawning of frontline units and moved the JTAC spawning to pretensemissiongenerator.py
229 lines
9.2 KiB
Python
229 lines
9.2 KiB
Python
import logging
|
|
import random
|
|
from typing import Any, Tuple
|
|
|
|
from dcs import Mission
|
|
from dcs.country import Country
|
|
from dcs.mapping import Vector2, Point
|
|
from dcs.terrain import NoParkingSlotError
|
|
from dcs.unitgroup import (
|
|
FlyingGroup,
|
|
ShipGroup,
|
|
StaticGroup,
|
|
)
|
|
|
|
from game.ato import Flight
|
|
from game.ato.flightstate import InFlight
|
|
from game.ato.starttype import StartType
|
|
from game.missiongenerator.aircraft.flightgroupspawner import (
|
|
FlightGroupSpawner,
|
|
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL,
|
|
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL,
|
|
STACK_SEPARATION,
|
|
)
|
|
from game.missiongenerator.missiondata import MissionData
|
|
from game.naming import NameGenerator
|
|
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint
|
|
|
|
|
|
class PretenseNameGenerator(NameGenerator):
|
|
@classmethod
|
|
def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str:
|
|
cls.aircraft_number += 1
|
|
cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()])
|
|
return "{}-{}-{}".format(
|
|
cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number
|
|
)
|
|
|
|
|
|
namegen = PretenseNameGenerator
|
|
|
|
|
|
class PretenseFlightGroupSpawner(FlightGroupSpawner):
|
|
def __init__(
|
|
self,
|
|
flight: Flight,
|
|
country: Country,
|
|
mission: Mission,
|
|
helipads: dict[ControlPoint, list[StaticGroup]],
|
|
ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
|
ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
|
mission_data: MissionData,
|
|
) -> None:
|
|
super().__init__(
|
|
flight,
|
|
country,
|
|
mission,
|
|
helipads,
|
|
ground_spawns_roadbase,
|
|
ground_spawns,
|
|
mission_data,
|
|
)
|
|
|
|
self.flight = flight
|
|
self.country = country
|
|
self.mission = mission
|
|
self.helipads = helipads
|
|
self.ground_spawns_roadbase = ground_spawns_roadbase
|
|
self.ground_spawns = ground_spawns
|
|
self.mission_data = mission_data
|
|
|
|
def insert_into_pretense(self, name: str) -> None:
|
|
cp = self.flight.departure
|
|
is_player = True
|
|
cp_side = (
|
|
2
|
|
if self.flight.coalition
|
|
== self.flight.coalition.game.coalition_for(is_player)
|
|
else 1
|
|
)
|
|
cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()])
|
|
|
|
if self.flight.client_count == 0:
|
|
self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][
|
|
self.flight.flight_type
|
|
].append(name)
|
|
try:
|
|
self.flight.coalition.game.pretense_air_groups[name] = self.flight
|
|
except AttributeError:
|
|
self.flight.coalition.game.pretense_air_groups = {}
|
|
self.flight.coalition.game.pretense_air_groups[name] = self.flight
|
|
|
|
def generate_flight_at_departure(self) -> FlyingGroup[Any]:
|
|
cp = self.flight.departure
|
|
name = namegen.next_pretense_aircraft_name(cp, self.flight)
|
|
is_player = True
|
|
cp_side = (
|
|
2
|
|
if self.flight.coalition
|
|
== self.flight.coalition.game.coalition_for(is_player)
|
|
else 1
|
|
)
|
|
cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()])
|
|
|
|
try:
|
|
if self.start_type is StartType.IN_FLIGHT:
|
|
self.insert_into_pretense(name)
|
|
group = self._generate_over_departure(name, cp)
|
|
return group
|
|
elif isinstance(cp, NavalControlPoint):
|
|
group_name = cp.get_carrier_group_name()
|
|
carrier_group = self.mission.find_group(group_name)
|
|
if not isinstance(carrier_group, ShipGroup):
|
|
raise RuntimeError(
|
|
f"Carrier group {carrier_group} is a "
|
|
f"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
|
)
|
|
self.insert_into_pretense(name)
|
|
return self._generate_at_group(name, carrier_group)
|
|
elif isinstance(cp, Fob):
|
|
is_heli = self.flight.squadron.aircraft.helicopter
|
|
is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable
|
|
if not is_heli and not is_vtol and not cp.has_ground_spawns:
|
|
raise RuntimeError(
|
|
f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots."
|
|
)
|
|
pilot_count = len(self.flight.roster.members)
|
|
if (
|
|
not is_heli
|
|
and self.flight.roster.player_count != pilot_count
|
|
and not self.flight.coalition.game.settings.ground_start_ai_planes
|
|
):
|
|
raise RuntimeError(
|
|
f"Fixed-wing aircraft at {cp} must be piloted by humans exclusively because"
|
|
f' the "AI fixed-wing aircraft can use roadbases / bases with only ground'
|
|
f' spawns" setting is currently disabled.'
|
|
)
|
|
if cp.has_helipads and (is_heli or is_vtol):
|
|
self.insert_into_pretense(name)
|
|
pad_group = self._generate_at_cp_helipad(name, cp)
|
|
if pad_group is not None:
|
|
return pad_group
|
|
if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli):
|
|
self.insert_into_pretense(name)
|
|
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
|
if pad_group is not None:
|
|
return pad_group
|
|
raise NoParkingSlotError
|
|
elif isinstance(cp, Airfield):
|
|
is_heli = self.flight.squadron.aircraft.helicopter
|
|
if cp.has_helipads and is_heli:
|
|
self.insert_into_pretense(name)
|
|
pad_group = self._generate_at_cp_helipad(name, cp)
|
|
if pad_group is not None:
|
|
return pad_group
|
|
if (
|
|
cp.has_ground_spawns
|
|
and len(self.ground_spawns[cp])
|
|
+ len(self.ground_spawns_roadbase[cp])
|
|
>= self.flight.count
|
|
and (self.flight.client_count > 0 or is_heli)
|
|
):
|
|
self.insert_into_pretense(name)
|
|
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
|
if pad_group is not None:
|
|
return pad_group
|
|
self.insert_into_pretense(name)
|
|
return self._generate_at_airfield(name, cp)
|
|
else:
|
|
raise NotImplementedError(
|
|
f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})"
|
|
)
|
|
except NoParkingSlotError:
|
|
# Generated when there is no place on Runway or on Parking Slots
|
|
if self.flight.client_count > 0:
|
|
# Don't generate player airstarts
|
|
logging.warning(
|
|
"No room on runway or parking slots. Not generating a player air-start."
|
|
)
|
|
raise NoParkingSlotError
|
|
else:
|
|
logging.warning(
|
|
"No room on runway or parking slots. Starting from the air."
|
|
)
|
|
self.flight.start_type = StartType.IN_FLIGHT
|
|
self.insert_into_pretense(name)
|
|
group = self._generate_over_departure(name, cp)
|
|
return group
|
|
|
|
def generate_mid_mission(self) -> FlyingGroup[Any]:
|
|
assert isinstance(self.flight.state, InFlight)
|
|
name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight)
|
|
|
|
speed = self.flight.state.estimate_speed()
|
|
pos = self.flight.state.estimate_position()
|
|
pos += Vector2(random.randint(100, 1000), random.randint(100, 1000))
|
|
alt, alt_type = self.flight.state.estimate_altitude()
|
|
cp = self.flight.squadron.location.id
|
|
|
|
if cp not in self.mission_data.cp_stack:
|
|
self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
|
|
|
|
# We don't know where the ground is, so just make sure that any aircraft
|
|
# spawning at an MSL altitude is spawned at some minimum altitude.
|
|
# https://github.com/dcs-liberation/dcs_liberation/issues/1941
|
|
if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL:
|
|
alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL
|
|
|
|
# Set a minimum AGL value for 'alt' if needed,
|
|
# otherwise planes might crash in trees and stuff.
|
|
if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]:
|
|
alt = self.mission_data.cp_stack[cp]
|
|
self.mission_data.cp_stack[cp] += STACK_SEPARATION
|
|
|
|
self.insert_into_pretense(name)
|
|
group = self.mission.flight_group(
|
|
country=self.country,
|
|
name=name,
|
|
aircraft_type=self.flight.unit_type.dcs_unit_type,
|
|
airport=None,
|
|
position=pos,
|
|
altitude=alt.meters,
|
|
speed=speed.kph,
|
|
maintask=None,
|
|
group_size=self.flight.count,
|
|
)
|
|
|
|
group.points[0].alt_type = alt_type
|
|
return group
|