mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge remote-tracking branch 'remotes/dcs-retribution/dcs-retribution/dev' into pretense-generator
This commit is contained in:
@@ -249,6 +249,22 @@ class WaypointBuilder:
|
||||
objective: MissionTarget,
|
||||
) -> FlightWaypoint:
|
||||
alt = self.get_combat_altitude
|
||||
if ingress_type in [
|
||||
FlightWaypointType.INGRESS_CAS,
|
||||
FlightWaypointType.INGRESS_OCA_AIRCRAFT,
|
||||
]:
|
||||
weather = self.flight.coalition.game.conditions.weather
|
||||
max_alt = feet(30000)
|
||||
if weather.clouds and (
|
||||
weather.clouds.preset
|
||||
and "overcast" in weather.clouds.preset.description.lower()
|
||||
or weather.clouds.density > 5
|
||||
):
|
||||
max_alt = meters(
|
||||
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
||||
)
|
||||
alt = min(alt, max_alt)
|
||||
|
||||
alt_type: AltitudeReference = "BARO"
|
||||
if self.is_helo or self.flight.is_hercules:
|
||||
alt_type = "RADIO"
|
||||
@@ -381,13 +397,23 @@ class WaypointBuilder:
|
||||
return waypoint
|
||||
|
||||
def cas(self, position: Point) -> FlightWaypoint:
|
||||
weather = self.flight.coalition.game.conditions.weather
|
||||
max_alt = feet(30000)
|
||||
if weather.clouds and (
|
||||
weather.clouds.preset
|
||||
and "overcast" in weather.clouds.preset.description.lower()
|
||||
or weather.clouds.density > 5
|
||||
):
|
||||
max_alt = meters(
|
||||
max(feet(500).meters, weather.clouds.base - feet(500).meters)
|
||||
)
|
||||
return FlightWaypoint(
|
||||
"CAS",
|
||||
FlightWaypointType.CAS,
|
||||
position,
|
||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
||||
if self.is_helo
|
||||
else meters(1000),
|
||||
else min(meters(1000), max_alt),
|
||||
"RADIO",
|
||||
description="Provide CAS",
|
||||
pretty_name="CAS",
|
||||
|
||||
@@ -9,7 +9,7 @@ from uuid import UUID
|
||||
from dcs import Mission
|
||||
from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed
|
||||
from dcs.country import Country
|
||||
from dcs.planes import F_15C, A_10A, AJS37
|
||||
from dcs.planes import F_15C, A_10A, AJS37, C_130
|
||||
from dcs.ships import HandyWind, LHA_Tarawa, Stennis, USS_Arleigh_Burke_IIa
|
||||
from dcs.statics import Fortification, Warehouse
|
||||
from dcs.terrain import Airport
|
||||
@@ -43,6 +43,7 @@ class MizCampaignLoader:
|
||||
OFF_MAP_UNIT_TYPE = F_15C.id
|
||||
GROUND_SPAWN_UNIT_TYPE = A_10A.id
|
||||
GROUND_SPAWN_ROADBASE_UNIT_TYPE = AJS37.id
|
||||
GROUND_SPAWN_LARGE_UNIT_TYPE = C_130.id
|
||||
|
||||
CV_UNIT_TYPE = Stennis.id
|
||||
LHA_UNIT_TYPE = LHA_Tarawa.id
|
||||
@@ -237,6 +238,12 @@ class MizCampaignLoader:
|
||||
if group.units[0].type == self.GROUND_SPAWN_ROADBASE_UNIT_TYPE:
|
||||
yield group
|
||||
|
||||
@property
|
||||
def ground_spawns_large(self) -> Iterator[PlaneGroup]:
|
||||
for group in itertools.chain(self.blue.plane_group, self.red.plane_group):
|
||||
if group.units[0].type == self.GROUND_SPAWN_LARGE_UNIT_TYPE:
|
||||
yield group
|
||||
|
||||
@property
|
||||
def ground_spawns(self) -> Iterator[PlaneGroup]:
|
||||
for group in itertools.chain(self.blue.plane_group, self.red.plane_group):
|
||||
@@ -536,6 +543,10 @@ class MizCampaignLoader:
|
||||
closest, distance = self.objective_info(plane_group)
|
||||
self._add_ground_spawn(closest.ground_spawns_roadbase, plane_group)
|
||||
|
||||
for plane_group in self.ground_spawns_large:
|
||||
closest, distance = self.objective_info(plane_group)
|
||||
self._add_ground_spawn(closest.ground_spawns_large, plane_group)
|
||||
|
||||
for plane_group in self.ground_spawns:
|
||||
closest, distance = self.objective_info(plane_group)
|
||||
self._add_ground_spawn(closest.ground_spawns, plane_group)
|
||||
|
||||
@@ -532,6 +532,15 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
for task_name, priority in data.get("tasks", {}).items():
|
||||
task_priorities[FlightType(task_name)] = priority
|
||||
|
||||
if (
|
||||
FlightType.SEAD_SWEEP not in task_priorities
|
||||
and FlightType.SEAD in task_priorities
|
||||
):
|
||||
task_priorities[FlightType.SEAD_SWEEP] = task_priorities[FlightType.SEAD]
|
||||
|
||||
cls._custom_weapon_injections(aircraft, data)
|
||||
cls._user_weapon_injections(aircraft)
|
||||
|
||||
display_name = data.get("display_name", variant_id)
|
||||
return AircraftType(
|
||||
dcs_unit_type=aircraft,
|
||||
|
||||
@@ -402,12 +402,17 @@ class Faction:
|
||||
self.remove_aircraft("VSN_F106B")
|
||||
if not mod_settings.a6a_intruder:
|
||||
self.remove_aircraft("VSN_A6A")
|
||||
if not mod_settings.ea6b_prowler:
|
||||
self.remove_aircraft("EA_6B")
|
||||
if not mod_settings.jas39_gripen:
|
||||
self.remove_aircraft("JAS39Gripen")
|
||||
self.remove_aircraft("JAS39Gripen_BVR")
|
||||
self.remove_aircraft("JAS39Gripen_AG")
|
||||
if not mod_settings.super_etendard:
|
||||
self.remove_aircraft("VSN_SEM")
|
||||
if not mod_settings.su15_flagon:
|
||||
self.remove_aircraft("Su_15")
|
||||
self.remove_aircraft("Su_15TM")
|
||||
if not mod_settings.su30_flanker_h:
|
||||
self.remove_aircraft("Su-30MKA")
|
||||
self.remove_aircraft("Su-30MKI")
|
||||
|
||||
@@ -96,6 +96,7 @@ class Migrator:
|
||||
try_set_attr(cp, "ground_spawns_roadbase", [])
|
||||
try_set_attr(cp, "helipads_quad", [])
|
||||
try_set_attr(cp, "helipads_invisible", [])
|
||||
try_set_attr(cp, "ground_spawns_large", [])
|
||||
if (
|
||||
cp.dcs_airport and is_sinai and cp.dcs_airport.id == 20
|
||||
): # fix for Hatzor
|
||||
|
||||
@@ -56,6 +56,7 @@ class AircraftGenerator:
|
||||
mission_data: MissionData,
|
||||
helipads: dict[ControlPoint, list[StaticGroup]],
|
||||
ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns_large: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
) -> None:
|
||||
self.mission = mission
|
||||
@@ -69,6 +70,7 @@ class AircraftGenerator:
|
||||
self.mission_data = mission_data
|
||||
self.helipads = helipads
|
||||
self.ground_spawns_roadbase = ground_spawns_roadbase
|
||||
self.ground_spawns_large = ground_spawns_large
|
||||
self.ground_spawns = ground_spawns
|
||||
|
||||
self.ewrj_package_dict: Dict[int, List[FlyingGroup[Any]]] = {}
|
||||
@@ -208,6 +210,7 @@ class AircraftGenerator:
|
||||
self.mission,
|
||||
self.helipads,
|
||||
self.ground_spawns_roadbase,
|
||||
self.ground_spawns_large,
|
||||
self.ground_spawns,
|
||||
self.mission_data,
|
||||
).create_idle_aircraft()
|
||||
@@ -239,6 +242,7 @@ class AircraftGenerator:
|
||||
self.mission,
|
||||
self.helipads,
|
||||
self.ground_spawns_roadbase,
|
||||
self.ground_spawns_large,
|
||||
self.ground_spawns,
|
||||
self.mission_data,
|
||||
).create_flight_group()
|
||||
|
||||
@@ -67,6 +67,7 @@ class FlightGroupSpawner:
|
||||
mission: Mission,
|
||||
helipads: dict[ControlPoint, list[StaticGroup]],
|
||||
ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns_large: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
mission_data: MissionData,
|
||||
) -> None:
|
||||
@@ -75,6 +76,7 @@ class FlightGroupSpawner:
|
||||
self.mission = mission
|
||||
self.helipads = helipads
|
||||
self.ground_spawns_roadbase = ground_spawns_roadbase
|
||||
self.ground_spawns_large = ground_spawns_large
|
||||
self.ground_spawns = ground_spawns
|
||||
self.mission_data = mission_data
|
||||
|
||||
@@ -178,6 +180,8 @@ class FlightGroupSpawner:
|
||||
raise RuntimeError(
|
||||
f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots."
|
||||
)
|
||||
is_large = self.flight.unit_type.dcs_unit_type.width > 40
|
||||
|
||||
pilot_count = len(self.flight.roster.members)
|
||||
if (
|
||||
not is_heli
|
||||
@@ -193,10 +197,18 @@ class FlightGroupSpawner:
|
||||
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 and is_large:
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp, is_large)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli):
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
else:
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp, True)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
return self._generate_over_departure(name, cp)
|
||||
elif isinstance(cp, Airfield):
|
||||
is_heli = self.flight.squadron.aircraft.helicopter
|
||||
@@ -204,6 +216,35 @@ class FlightGroupSpawner:
|
||||
pad_group = self._generate_at_cp_helipad(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
# Large planes (wingspan more than 40 meters, looking at you, C-130)
|
||||
# First try spawning on large ground spawns
|
||||
# Then try the regular airfield ramp spawns
|
||||
is_large = self.flight.unit_type.dcs_unit_type.width > 40
|
||||
if (
|
||||
cp.has_ground_spawns
|
||||
and is_large
|
||||
and len(self.ground_spawns_large[cp]) >= self.flight.count
|
||||
and (self.flight.client_count > 0)
|
||||
):
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp, is_large)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
# Below 40 meter wingspan aircraft
|
||||
# First try spawning on regular or roadbase ground spawns
|
||||
# Then try the regular airfield ramp spawns
|
||||
# Then, if both of the above fail, use the large ground spawns
|
||||
if (
|
||||
cp.has_ground_spawns
|
||||
and len(self.ground_spawns[cp])
|
||||
+ len(self.ground_spawns_roadbase[cp])
|
||||
+ len(self.ground_spawns_large[cp])
|
||||
>= self.flight.count
|
||||
and (self.flight.client_count > 0 or is_heli)
|
||||
):
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
|
||||
if (
|
||||
cp.has_ground_spawns
|
||||
and len(self.ground_spawns[cp])
|
||||
@@ -214,33 +255,45 @@ class FlightGroupSpawner:
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
|
||||
# TODO: get rid of the nevatim hack once fixed in DCS...
|
||||
if self._check_nevatim_hack(cp):
|
||||
slots = [
|
||||
slot
|
||||
for slot in cp.dcs_airport.free_parking_slots(
|
||||
self.flight.squadron.aircraft.dcs_unit_type
|
||||
)
|
||||
if slot.slot_name in [str(n) for n in range(55, 66)]
|
||||
]
|
||||
return self._generate_at_airfield(name, cp, slots)
|
||||
elif self._check_ramon_airbase_hack(cp):
|
||||
# TODO: get rid of the ramon airbase hack once fixed in DCS...
|
||||
slots = [
|
||||
slot
|
||||
for slot in cp.dcs_airport.free_parking_slots(
|
||||
self.flight.squadron.aircraft.dcs_unit_type
|
||||
)
|
||||
if slot.slot_name
|
||||
not in [
|
||||
str(n)
|
||||
for n in [1, 2, 3, 4, 5, 6, 13, 14, 15, 16, 17, 18, 61]
|
||||
try:
|
||||
# TODO: get rid of the nevatim hack once fixed in DCS...
|
||||
if self._check_nevatim_hack(cp):
|
||||
slots = [
|
||||
slot
|
||||
for slot in cp.dcs_airport.free_parking_slots(
|
||||
self.flight.squadron.aircraft.dcs_unit_type
|
||||
)
|
||||
if slot.slot_name in [str(n) for n in range(55, 66)]
|
||||
]
|
||||
]
|
||||
return self._generate_at_airfield(name, cp, slots)
|
||||
else:
|
||||
return self._generate_at_airfield(name, cp)
|
||||
return self._generate_at_airfield(name, cp, slots)
|
||||
elif self._check_ramon_airbase_hack(cp):
|
||||
# TODO: get rid of the ramon airbase hack once fixed in DCS...
|
||||
slots = [
|
||||
slot
|
||||
for slot in cp.dcs_airport.free_parking_slots(
|
||||
self.flight.squadron.aircraft.dcs_unit_type
|
||||
)
|
||||
if slot.slot_name
|
||||
not in [
|
||||
str(n)
|
||||
for n in [1, 2, 3, 4, 5, 6, 13, 14, 15, 16, 17, 18, 61]
|
||||
]
|
||||
]
|
||||
return self._generate_at_airfield(name, cp, slots)
|
||||
else:
|
||||
return self._generate_at_airfield(name, cp)
|
||||
except NoParkingSlotError:
|
||||
if (
|
||||
cp.has_ground_spawns
|
||||
and len(self.ground_spawns_large[cp]) >= self.flight.count
|
||||
and (self.flight.client_count > 0 or is_heli)
|
||||
):
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp, True)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
else:
|
||||
raise NoParkingSlotError
|
||||
return self._generate_at_airfield(name, cp)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})"
|
||||
@@ -440,22 +493,26 @@ class FlightGroupSpawner:
|
||||
return group
|
||||
|
||||
def _generate_at_cp_ground_spawn(
|
||||
self, name: str, cp: ControlPoint
|
||||
self, name: str, cp: ControlPoint, is_large: bool = False
|
||||
) -> Optional[FlyingGroup[Any]]:
|
||||
is_airbase = False
|
||||
is_roadbase = False
|
||||
|
||||
try:
|
||||
if len(self.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||
is_roadbase = True
|
||||
if is_large:
|
||||
if len(self.ground_spawns_large[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_large[cp].pop()
|
||||
is_airbase = True
|
||||
else:
|
||||
ground_spawn = self.ground_spawns[cp].pop()
|
||||
is_airbase = True
|
||||
if len(self.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||
is_roadbase = True
|
||||
if len(self.ground_spawns[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns[cp].pop()
|
||||
is_airbase = True
|
||||
except IndexError as ex:
|
||||
logging.warning("Not enough STOL slots available at " + str(ex))
|
||||
logging.warning("Not enough ground spawn slots available at " + str(ex))
|
||||
return None
|
||||
# raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex
|
||||
|
||||
group = self._generate_at_group(name, ground_spawn[0])
|
||||
|
||||
@@ -524,10 +581,14 @@ class FlightGroupSpawner:
|
||||
for i in range(self.flight.count - 1):
|
||||
try:
|
||||
terrain = cp.coalition.game.theater.terrain
|
||||
if len(self.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||
if is_large:
|
||||
if len(self.ground_spawns_large[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_large[cp].pop()
|
||||
else:
|
||||
ground_spawn = self.ground_spawns[cp].pop()
|
||||
if len(self.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||
else:
|
||||
ground_spawn = self.ground_spawns[cp].pop()
|
||||
group.units[1 + i].position = Point(
|
||||
ground_spawn[0].x, ground_spawn[0].y, terrain=terrain
|
||||
)
|
||||
|
||||
@@ -249,6 +249,7 @@ class MissionGenerator:
|
||||
mission_data=self.mission_data,
|
||||
helipads=tgo_generator.helipads,
|
||||
ground_spawns_roadbase=tgo_generator.ground_spawns_roadbase,
|
||||
ground_spawns_large=tgo_generator.ground_spawns_large,
|
||||
ground_spawns=tgo_generator.ground_spawns,
|
||||
)
|
||||
|
||||
|
||||
@@ -1042,6 +1042,123 @@ class GroundSpawnRoadbaseGenerator:
|
||||
self.ground_spawns_roadbase = []
|
||||
|
||||
|
||||
class GroundSpawnLargeGenerator:
|
||||
"""
|
||||
Generates STOL aircraft starting positions for given control point
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mission: Mission,
|
||||
cp: ControlPoint,
|
||||
game: Game,
|
||||
radio_registry: RadioRegistry,
|
||||
tacan_registry: TacanRegistry,
|
||||
):
|
||||
self.m = mission
|
||||
self.cp = cp
|
||||
self.game = game
|
||||
self.radio_registry = radio_registry
|
||||
self.tacan_registry = tacan_registry
|
||||
self.ground_spawns_large: list[Tuple[StaticGroup, Point]] = []
|
||||
|
||||
def create_ground_spawn_large(
|
||||
self, i: int, vtol_pad: Tuple[PointWithHeading, Point]
|
||||
) -> None:
|
||||
# Note: FARPs are generated as neutral object in order not to interfere with
|
||||
# capture triggers
|
||||
neutral_country = self.m.country(self.game.neutral_country.name)
|
||||
country = self.m.country(
|
||||
self.game.coalition_for(self.cp.captured).faction.country.name
|
||||
)
|
||||
terrain = self.cp.coalition.game.theater.terrain
|
||||
|
||||
name = f"{self.cp.name} large ground spawn {i}"
|
||||
logging.info("Generating Large Ground Spawn static : " + name)
|
||||
|
||||
pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain)
|
||||
|
||||
pad.position = Point(vtol_pad[0].x, vtol_pad[0].y, terrain=terrain)
|
||||
pad.heading = vtol_pad[0].heading.degrees
|
||||
sg = unitgroup.StaticGroup(self.m.next_group_id(), name)
|
||||
sg.add_unit(pad)
|
||||
sp = StaticPoint(pad.position)
|
||||
sg.add_point(sp)
|
||||
neutral_country.add_static_group(sg)
|
||||
|
||||
self.ground_spawns_large.append((sg, vtol_pad[1]))
|
||||
|
||||
# tanker_type: Type[VehicleType]
|
||||
# ammo_truck_type: Type[VehicleType]
|
||||
|
||||
tanker_type, ammo_truck_type, power_truck_type = farp_truck_types_for_country(
|
||||
country.id
|
||||
)
|
||||
|
||||
# Generate a FARP Ammo and Fuel stack for each pad
|
||||
if self.game.settings.ground_start_trucks:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=tanker_type,
|
||||
position=pad.position.point_from_heading(
|
||||
vtol_pad[0].heading.degrees - 175, 45
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=ammo_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
vtol_pad[0].heading.degrees - 185, 45
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
else:
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=Fortification.FARP_Fuel_Depot,
|
||||
position=pad.position.point_from_heading(
|
||||
vtol_pad[0].heading.degrees - 180, 55
|
||||
),
|
||||
heading=pad.heading,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||
position=pad.position.point_from_heading(
|
||||
vtol_pad[0].heading.degrees - 180, 45
|
||||
),
|
||||
heading=pad.heading + 270,
|
||||
)
|
||||
if self.game.settings.ground_start_ground_power_trucks:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_power"),
|
||||
_type=power_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
vtol_pad[0].heading.degrees - 185, 45
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
def generate(self) -> None:
|
||||
try:
|
||||
for i, vtol_pad in enumerate(self.cp.ground_spawns_large):
|
||||
self.create_ground_spawn_large(i, vtol_pad)
|
||||
except AttributeError:
|
||||
self.ground_spawns_large = []
|
||||
|
||||
|
||||
class GroundSpawnGenerator:
|
||||
"""
|
||||
Generates STOL aircraft starting positions for given control point
|
||||
@@ -1207,6 +1324,9 @@ class TgoGenerator:
|
||||
self.ground_spawns_roadbase: dict[
|
||||
ControlPoint, list[Tuple[StaticGroup, Point]]
|
||||
] = defaultdict(list)
|
||||
self.ground_spawns_large: dict[
|
||||
ControlPoint, list[Tuple[StaticGroup, Point]]
|
||||
] = defaultdict(list)
|
||||
self.ground_spawns: dict[
|
||||
ControlPoint, list[Tuple[StaticGroup, Point]]
|
||||
] = defaultdict(list)
|
||||
@@ -1233,7 +1353,15 @@ class TgoGenerator:
|
||||
] = ground_spawn_roadbase_gen.ground_spawns_roadbase
|
||||
random.shuffle(self.ground_spawns_roadbase[cp])
|
||||
|
||||
# Generate STOL pads
|
||||
# Generate Large Ground Spawn slots
|
||||
ground_large_spawn_gen = GroundSpawnLargeGenerator(
|
||||
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
||||
)
|
||||
ground_large_spawn_gen.generate()
|
||||
self.ground_spawns_large[cp] = ground_large_spawn_gen.ground_spawns_large
|
||||
random.shuffle(self.ground_spawns_large[cp])
|
||||
|
||||
# Generate Ground Spawn slots
|
||||
ground_spawn_gen = GroundSpawnGenerator(
|
||||
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
||||
)
|
||||
|
||||
@@ -159,11 +159,13 @@ class MissionResultsProcessor:
|
||||
captured.control_point.capture(
|
||||
self.game, events, captured.captured_by_player
|
||||
)
|
||||
logging.info(f"Will run redeploy for {captured.control_point}")
|
||||
self.redeploy_units(captured.control_point)
|
||||
except Exception:
|
||||
logging.exception(f"Could not process base capture {captured}")
|
||||
|
||||
for captured in debriefing.base_captures:
|
||||
logging.info(f"Will run redeploy for {captured.control_point}")
|
||||
self.redeploy_units(captured.control_point)
|
||||
|
||||
def record_carcasses(self, debriefing: Debriefing) -> None:
|
||||
for destroyed_unit in debriefing.state_data.destroyed_statics:
|
||||
self.game.add_destroyed_units(destroyed_unit)
|
||||
@@ -301,10 +303,6 @@ class MissionResultsProcessor:
|
||||
""" "
|
||||
Auto redeploy units to newly captured base
|
||||
"""
|
||||
|
||||
ally_connected_cps = [
|
||||
ocp for ocp in cp.connected_points if cp.captured == ocp.captured
|
||||
]
|
||||
enemy_connected_cps = [
|
||||
ocp for ocp in cp.connected_points if cp.captured != ocp.captured
|
||||
]
|
||||
@@ -314,28 +312,54 @@ class MissionResultsProcessor:
|
||||
if len(enemy_connected_cps) == 0:
|
||||
return
|
||||
|
||||
ally_connected_cps = [
|
||||
ocp
|
||||
for ocp in cp.transitive_connected_friendly_destinations()
|
||||
if cp.captured == ocp.captured and ocp.base.total_armor
|
||||
]
|
||||
|
||||
settings = cp.coalition.game.settings
|
||||
factor = (
|
||||
settings.frontline_reserves_factor
|
||||
if cp.captured
|
||||
else settings.frontline_reserves_factor_red
|
||||
)
|
||||
|
||||
# From each ally cp, send reinforcements
|
||||
for ally_cp in ally_connected_cps:
|
||||
for ally_cp in sorted(
|
||||
ally_connected_cps,
|
||||
key=lambda x: len(
|
||||
[cp for cp in x.connected_points if x.captured != cp.captured]
|
||||
),
|
||||
):
|
||||
self.redeploy_between(cp, ally_cp)
|
||||
if cp.base.total_armor > factor * cp.deployable_front_line_units:
|
||||
break
|
||||
|
||||
def redeploy_between(self, destination: ControlPoint, source: ControlPoint) -> None:
|
||||
total_units_redeployed = 0
|
||||
moved_units = {}
|
||||
|
||||
if source.has_active_frontline or not destination.captured:
|
||||
# If there are still active front lines to defend at the
|
||||
# transferring CP we should not transfer all units.
|
||||
#
|
||||
# Opfor also does not transfer all of their units.
|
||||
# TODO: Balance the CPs rather than moving half from everywhere.
|
||||
move_factor = 0.5
|
||||
else:
|
||||
# Otherwise we can move everything.
|
||||
move_factor = 1
|
||||
settings = source.coalition.game.settings
|
||||
reserves = max(
|
||||
1,
|
||||
settings.reserves_procurement_target
|
||||
if source.captured
|
||||
else settings.reserves_procurement_target_red,
|
||||
)
|
||||
total_units = source.base.total_armor
|
||||
reserves_factor = (reserves - 1) / total_units # slight underestimation
|
||||
|
||||
source_frontline_count = len(
|
||||
[cp for cp in source.connected_points if not source.is_friendly_to(cp)]
|
||||
)
|
||||
|
||||
move_factor = max(0.0, 1 / (source_frontline_count + 1) - reserves_factor)
|
||||
|
||||
for frontline_unit, count in source.base.armor.items():
|
||||
moved_units[frontline_unit] = int(count * move_factor)
|
||||
total_units_redeployed = total_units_redeployed + int(count * move_factor)
|
||||
moved_count = int(count * move_factor)
|
||||
moved_units[frontline_unit] = moved_count
|
||||
total_units_redeployed += moved_count
|
||||
|
||||
destination.base.commission_units(moved_units)
|
||||
source.base.commit_losses(moved_units)
|
||||
|
||||
@@ -390,6 +390,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
self.helipads_quad: List[PointWithHeading] = []
|
||||
self.helipads_invisible: List[PointWithHeading] = []
|
||||
self.ground_spawns_roadbase: List[Tuple[PointWithHeading, Point]] = []
|
||||
self.ground_spawns_large: List[Tuple[PointWithHeading, Point]] = []
|
||||
self.ground_spawns: List[Tuple[PointWithHeading, Point]] = []
|
||||
|
||||
self._coalition: Optional[Coalition] = None
|
||||
@@ -572,6 +573,23 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
connected.extend(cp.transitive_friendly_shipping_destinations(seen))
|
||||
return connected
|
||||
|
||||
def transitive_connected_friendly_destinations(
|
||||
self, seen: Optional[Set[ControlPoint]] = None
|
||||
) -> List[ControlPoint]:
|
||||
if seen is None:
|
||||
seen = {self}
|
||||
|
||||
connected = []
|
||||
for cp in set(self.connected_points + list(self.shipping_lanes.keys())):
|
||||
if cp.captured != self.captured:
|
||||
continue
|
||||
if cp in seen:
|
||||
continue
|
||||
seen.add(cp)
|
||||
connected.append(cp)
|
||||
connected.extend(cp.transitive_connected_friendly_destinations(seen))
|
||||
return connected
|
||||
|
||||
@property
|
||||
def has_factory(self) -> bool:
|
||||
for tgo in self.connected_objectives:
|
||||
@@ -594,7 +612,12 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
"""
|
||||
Returns true if cp can operate STOL aircraft
|
||||
"""
|
||||
return len(self.ground_spawns_roadbase) + len(self.ground_spawns) > 0
|
||||
return (
|
||||
len(self.ground_spawns_roadbase)
|
||||
+ len(self.ground_spawns_large)
|
||||
+ len(self.ground_spawns)
|
||||
> 0
|
||||
)
|
||||
|
||||
def can_recruit_ground_units(self, game: Game) -> bool:
|
||||
"""Returns True if this control point is capable of recruiting ground units."""
|
||||
@@ -1266,6 +1289,7 @@ class Airfield(ControlPoint, CTLD):
|
||||
if parking_type.include_fixed_wing_stol:
|
||||
parking_slots += len(self.ground_spawns)
|
||||
parking_slots += len(self.ground_spawns_roadbase)
|
||||
parking_slots += len(self.ground_spawns_large)
|
||||
if parking_type.include_fixed_wing:
|
||||
parking_slots += len(self.airport.parking_slots)
|
||||
return parking_slots
|
||||
@@ -1655,13 +1679,19 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
|
||||
+ len(self.helipads_invisible)
|
||||
)
|
||||
|
||||
try:
|
||||
if parking_type.include_fixed_wing_stol:
|
||||
if parking_type.include_fixed_wing_stol:
|
||||
try:
|
||||
parking_slots += len(self.ground_spawns)
|
||||
except AttributeError:
|
||||
self.ground_spawns_roadbase = []
|
||||
try:
|
||||
parking_slots += len(self.ground_spawns_roadbase)
|
||||
except AttributeError:
|
||||
self.ground_spawns_roadbase = []
|
||||
self.ground_spawns = []
|
||||
except AttributeError:
|
||||
self.ground_spawns_large = []
|
||||
try:
|
||||
parking_slots += len(self.ground_spawns_large)
|
||||
except AttributeError:
|
||||
self.ground_spawns = []
|
||||
return parking_slots
|
||||
|
||||
def can_operate(self, aircraft: AircraftType) -> bool:
|
||||
|
||||
@@ -68,6 +68,7 @@ class ModSettings:
|
||||
a4_skyhawk: bool = False
|
||||
a6a_intruder: bool = False
|
||||
a7e_corsair2: bool = False
|
||||
ea6b_prowler: bool = False
|
||||
f4bc_phantom: bool = False
|
||||
f9f_panther: bool = False
|
||||
f15d_baz: bool = False
|
||||
@@ -86,6 +87,7 @@ class ModSettings:
|
||||
uh_60l: bool = False
|
||||
jas39_gripen: bool = False
|
||||
super_etendard: bool = False
|
||||
su15_flagon: bool = False
|
||||
su30_flanker_h: bool = False
|
||||
su57_felon: bool = False
|
||||
frenchpack: bool = False
|
||||
|
||||
@@ -283,6 +283,10 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
|
||||
def coalition(self) -> Coalition:
|
||||
return self.control_point.coalition
|
||||
|
||||
@property
|
||||
def is_naval_control_point(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class BuildingGroundObject(TheaterGroundObject):
|
||||
def __init__(
|
||||
@@ -384,6 +388,10 @@ class GenericCarrierGroundObject(NavalGroundObject, ABC):
|
||||
def is_control_point(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_naval_control_point(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# TODO: Why is this both a CP and a TGO?
|
||||
class CarrierGroundObject(GenericCarrierGroundObject):
|
||||
|
||||
@@ -66,6 +66,18 @@ class TheaterUnit:
|
||||
if self.ground_object.is_iads:
|
||||
iads = self.ground_object.control_point.coalition.game.theater.iads_network
|
||||
iads.update_tgo(self.ground_object, events)
|
||||
if self.ground_object.is_naval_control_point:
|
||||
cp = self.ground_object.control_point
|
||||
for squadron in cp.squadrons:
|
||||
cp.coalition.air_wing.squadrons[squadron.aircraft].remove(squadron)
|
||||
|
||||
def revive(self, events: GameUpdateEvents) -> None:
|
||||
self.alive = True
|
||||
self.ground_object.threat_poly()
|
||||
events.update_tgo(self.ground_object)
|
||||
if self.ground_object.is_iads:
|
||||
iads = self.ground_object.control_point.coalition.game.theater.iads_network
|
||||
iads.update_tgo(self.ground_object, events)
|
||||
|
||||
@property
|
||||
def unit_name(self) -> str:
|
||||
|
||||
Reference in New Issue
Block a user