mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
956 lines
37 KiB
Python
956 lines
37 KiB
Python
"""Generators for creating the groups for ground objectives.
|
|
|
|
The classes in this file are responsible for creating the vehicle groups, ship
|
|
groups, statics, missile sites, and AA sites for the mission. Each of these
|
|
objectives is defined in the Theater by a TheaterGroundObject. These classes
|
|
create the pydcs groups and statics for those areas and add them to the mission.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
import logging
|
|
from collections import defaultdict
|
|
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type, Iterator
|
|
|
|
from dcs import Mission, Point
|
|
from dcs.countries import *
|
|
from dcs.country import Country
|
|
from dcs.ships import Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal, LHA_Tarawa
|
|
from dcs.unitgroup import StaticGroup, VehicleGroup
|
|
from dcs.unittype import VehicleType
|
|
|
|
from game.coalition import Coalition
|
|
from game.data.units import UnitClass
|
|
from game.dcs.groundunittype import GroundUnitType
|
|
from game.missiongenerator.groundforcepainter import (
|
|
GroundForcePainter,
|
|
)
|
|
from game.missiongenerator.missiondata import MissionData, CarrierInfo
|
|
from game.missiongenerator.tgogenerator import (
|
|
TgoGenerator,
|
|
HelipadGenerator,
|
|
GroundSpawnRoadbaseGenerator,
|
|
GroundSpawnGenerator,
|
|
GroundObjectGenerator,
|
|
CarrierGenerator,
|
|
LhaGenerator,
|
|
MissileSiteGenerator,
|
|
GenericCarrierGenerator,
|
|
)
|
|
from game.point_with_heading import PointWithHeading
|
|
from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator
|
|
from game.radio.radios import RadioRegistry
|
|
from game.radio.tacan import TacanRegistry, TacanBand, TacanUsage
|
|
from game.runways import RunwayData
|
|
from game.theater import (
|
|
ControlPoint,
|
|
TheaterGroundObject,
|
|
TheaterUnit,
|
|
NavalControlPoint,
|
|
PresetLocation,
|
|
)
|
|
from game.theater.theatergroundobject import (
|
|
CarrierGroundObject,
|
|
LhaGroundObject,
|
|
MissileSiteGroundObject,
|
|
BuildingGroundObject,
|
|
VehicleGroupGroundObject,
|
|
GenericCarrierGroundObject,
|
|
)
|
|
from game.theater.theatergroup import TheaterGroup
|
|
from game.unitmap import UnitMap
|
|
from game.utils import Heading
|
|
from pydcs_extensions import (
|
|
Char_M551_Sheridan,
|
|
BV410_RBS70,
|
|
BV410_RBS90,
|
|
BV410,
|
|
VAB__50,
|
|
VAB_T20_13,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
|
|
FARP_FRONTLINE_DISTANCE = 10000
|
|
AA_CP_MIN_DISTANCE = 40000
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE = 5
|
|
PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [
|
|
vehicles.Armor.Stug_III,
|
|
vehicles.Artillery.Grad_URAL,
|
|
]
|
|
PRETENSE_AMPHIBIOUS_UNITS = [
|
|
vehicles.Unarmed.LARC_V,
|
|
vehicles.Armor.AAV7,
|
|
vehicles.Armor.LAV_25,
|
|
vehicles.Armor.TPZ,
|
|
vehicles.Armor.PT_76,
|
|
vehicles.Armor.BMD_1,
|
|
vehicles.Armor.BMP_1,
|
|
vehicles.Armor.BMP_2,
|
|
vehicles.Armor.BMP_3,
|
|
vehicles.Armor.BTR_80,
|
|
vehicles.Armor.BTR_82A,
|
|
vehicles.Armor.BRDM_2,
|
|
vehicles.Armor.BTR_D,
|
|
vehicles.Armor.MTLB,
|
|
vehicles.Armor.ZBD04A,
|
|
vehicles.Armor.VAB_Mephisto,
|
|
VAB__50,
|
|
VAB_T20_13,
|
|
Char_M551_Sheridan,
|
|
BV410_RBS70,
|
|
BV410_RBS90,
|
|
BV410,
|
|
]
|
|
|
|
|
|
class PretenseGroundObjectGenerator(GroundObjectGenerator):
|
|
"""generates the DCS groups and units from the TheaterGroundObject"""
|
|
|
|
def __init__(
|
|
self,
|
|
ground_object: TheaterGroundObject,
|
|
country: Country,
|
|
game: Game,
|
|
mission: Mission,
|
|
unit_map: UnitMap,
|
|
) -> None:
|
|
super().__init__(
|
|
ground_object,
|
|
country,
|
|
game,
|
|
mission,
|
|
unit_map,
|
|
)
|
|
|
|
self.ground_object = ground_object
|
|
self.country = country
|
|
self.game = game
|
|
self.m = mission
|
|
self.unit_map = unit_map
|
|
self.coalition = ground_object.coalition
|
|
|
|
@property
|
|
def culled(self) -> bool:
|
|
return self.game.iads_considerate_culling(self.ground_object)
|
|
|
|
@staticmethod
|
|
def ground_unit_of_class(
|
|
coalition: Coalition, unit_class: UnitClass
|
|
) -> Optional[GroundUnitType]:
|
|
"""
|
|
Returns a GroundUnitType of the specified class that belongs to the
|
|
TheaterGroundObject faction.
|
|
|
|
Units, which are known to have pathfinding issues in Pretense missions
|
|
are removed based on a pre-defined list.
|
|
|
|
Args:
|
|
coalition: Coalition to return the unit for.
|
|
unit_class: Class of unit to return.
|
|
"""
|
|
faction_units = (
|
|
set(coalition.faction.frontline_units)
|
|
| set(coalition.faction.artillery_units)
|
|
| set(coalition.faction.air_defense_units)
|
|
| set(coalition.faction.logistics_units)
|
|
)
|
|
of_class = list({u for u in faction_units if u.unit_class is unit_class})
|
|
|
|
# Remove units from list with known pathfinding issues in Pretense missions
|
|
for unit_to_remove in PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT:
|
|
for groundunittype_to_remove in GroundUnitType.for_dcs_type(unit_to_remove):
|
|
if groundunittype_to_remove in of_class:
|
|
of_class.remove(groundunittype_to_remove)
|
|
|
|
if len(of_class) > 0:
|
|
return random.choice(of_class)
|
|
else:
|
|
return None
|
|
|
|
def generate_ground_unit_of_class(
|
|
self,
|
|
unit_class: UnitClass,
|
|
group: TheaterGroup,
|
|
vehicle_units: list[TheaterUnit],
|
|
cp_name: str,
|
|
group_role: str,
|
|
max_num: int,
|
|
) -> None:
|
|
"""
|
|
Generates a single land based TheaterUnit for a Pretense unit group
|
|
for a specific TheaterGroup, provided that the group still has room
|
|
(defined by the max_num argument). Land based groups don't have
|
|
restrictions on the unit types, other than that they must be
|
|
accessible by the faction and must be of the specified class.
|
|
|
|
Generated units are placed 30 meters from the TheaterGroup
|
|
position in a random direction.
|
|
|
|
Args:
|
|
unit_class: Class of unit to generate.
|
|
group: The TheaterGroup to generate the unit/group for.
|
|
vehicle_units: List of TheaterUnits. The new unit will be appended to this list.
|
|
cp_name: Name of the Control Point.
|
|
group_role: Pretense group role, "support" or "assault".
|
|
max_num: Maximum number of units to generate per group.
|
|
"""
|
|
|
|
if self.coalition.faction.has_access_to_unit_class(unit_class):
|
|
unit_type = self.ground_unit_of_class(self.coalition, unit_class)
|
|
if unit_type is not None and len(vehicle_units) < max_num:
|
|
unit_id = self.game.next_unit_id()
|
|
unit_name = f"{cp_name}-{group_role}-{unit_id}"
|
|
|
|
spread_out_heading = random.randrange(1, 360)
|
|
spread_out_position = group.position.point_from_heading(
|
|
spread_out_heading, 30
|
|
)
|
|
ground_unit_pos = PointWithHeading.from_point(
|
|
spread_out_position, group.position.heading
|
|
)
|
|
|
|
theater_unit = TheaterUnit(
|
|
unit_id,
|
|
unit_name,
|
|
unit_type.dcs_unit_type,
|
|
ground_unit_pos,
|
|
group.ground_object,
|
|
)
|
|
vehicle_units.append(theater_unit)
|
|
|
|
def generate_amphibious_unit_of_class(
|
|
self,
|
|
unit_class: UnitClass,
|
|
group: TheaterGroup,
|
|
vehicle_units: list[TheaterUnit],
|
|
cp_name: str,
|
|
group_role: str,
|
|
max_num: int,
|
|
) -> None:
|
|
"""
|
|
Generates a single amphibious TheaterUnit for a Pretense unit group
|
|
for a specific TheaterGroup, provided that the group still has room
|
|
(defined by the max_num argument). Amphibious units are selected
|
|
out of a pre-defined list. Units which the faction has access to
|
|
are preferred, but certain default unit types are selected as
|
|
a fall-back to ensure that all the generated units can swim.
|
|
|
|
Generated units are placed 30 meters from the TheaterGroup
|
|
position in a random direction.
|
|
|
|
Args:
|
|
unit_class: Class of unit to generate.
|
|
group: The TheaterGroup to generate the unit/group for.
|
|
vehicle_units: List of TheaterUnits. The new unit will be appended to this list.
|
|
cp_name: Name of the Control Point.
|
|
group_role: Pretense group role, "support" or "assault".
|
|
max_num: Maximum number of units to generate per group.
|
|
"""
|
|
unit_type = None
|
|
faction = self.coalition.faction
|
|
is_player = True
|
|
side = (
|
|
2
|
|
if self.country == self.game.coalition_for(is_player).faction.country
|
|
else 1
|
|
)
|
|
default_amphibious_unit = unit_type
|
|
default_logistics_unit = unit_type
|
|
default_tank_unit_blue = unit_type
|
|
default_apc_unit_blue = unit_type
|
|
default_ifv_unit_blue = unit_type
|
|
default_recon_unit_blue = unit_type
|
|
default_atgm_unit_blue = unit_type
|
|
default_tank_unit_red = unit_type
|
|
default_apc_unit_red = unit_type
|
|
default_ifv_unit_red = unit_type
|
|
default_recon_unit_red = unit_type
|
|
default_atgm_unit_red = unit_type
|
|
default_ifv_unit_chinese = unit_type
|
|
pretense_amphibious_units = PRETENSE_AMPHIBIOUS_UNITS
|
|
random.shuffle(pretense_amphibious_units)
|
|
for unit in pretense_amphibious_units:
|
|
for groundunittype in GroundUnitType.for_dcs_type(unit):
|
|
if unit == vehicles.Unarmed.LARC_V:
|
|
default_logistics_unit = groundunittype
|
|
elif unit == Char_M551_Sheridan:
|
|
default_tank_unit_blue = groundunittype
|
|
elif unit == vehicles.Armor.AAV7:
|
|
default_apc_unit_blue = groundunittype
|
|
elif unit == vehicles.Armor.LAV_25:
|
|
default_ifv_unit_blue = groundunittype
|
|
elif unit == vehicles.Armor.TPZ:
|
|
default_recon_unit_blue = groundunittype
|
|
elif unit == vehicles.Armor.VAB_Mephisto:
|
|
default_atgm_unit_blue = groundunittype
|
|
elif unit == vehicles.Armor.PT_76:
|
|
default_tank_unit_red = groundunittype
|
|
elif unit == vehicles.Armor.BTR_80:
|
|
default_apc_unit_red = groundunittype
|
|
elif unit == vehicles.Armor.BMD_1:
|
|
default_ifv_unit_red = groundunittype
|
|
elif unit == vehicles.Armor.BRDM_2:
|
|
default_recon_unit_red = groundunittype
|
|
elif unit == vehicles.Armor.BTR_D:
|
|
default_atgm_unit_red = groundunittype
|
|
elif unit == vehicles.Armor.ZBD04A:
|
|
default_ifv_unit_chinese = groundunittype
|
|
elif unit == vehicles.Armor.MTLB:
|
|
default_amphibious_unit = groundunittype
|
|
if self.coalition.faction.has_access_to_dcs_type(unit):
|
|
if groundunittype.unit_class == unit_class:
|
|
unit_type = groundunittype
|
|
break
|
|
if unit_type is None:
|
|
if unit_class == UnitClass.LOGISTICS:
|
|
unit_type = default_logistics_unit
|
|
elif faction.country.id == China.id:
|
|
unit_type = default_ifv_unit_chinese
|
|
elif side == 2 and unit_class == UnitClass.TANK:
|
|
if faction.mod_settings is not None and faction.mod_settings.frenchpack:
|
|
unit_type = default_tank_unit_blue
|
|
else:
|
|
unit_type = default_apc_unit_blue
|
|
elif side == 2 and unit_class == UnitClass.IFV:
|
|
unit_type = default_ifv_unit_blue
|
|
elif side == 2 and unit_class == UnitClass.APC:
|
|
unit_type = default_apc_unit_blue
|
|
elif side == 2 and unit_class == UnitClass.ATGM:
|
|
unit_type = default_atgm_unit_blue
|
|
elif side == 2 and unit_class == UnitClass.RECON:
|
|
unit_type = default_recon_unit_blue
|
|
elif side == 1 and unit_class == UnitClass.TANK:
|
|
unit_type = default_tank_unit_red
|
|
elif side == 1 and unit_class == UnitClass.IFV:
|
|
unit_type = default_ifv_unit_red
|
|
elif side == 1 and unit_class == UnitClass.APC:
|
|
unit_type = default_apc_unit_red
|
|
elif side == 1 and unit_class == UnitClass.ATGM:
|
|
unit_type = default_atgm_unit_red
|
|
elif side == 1 and unit_class == UnitClass.RECON:
|
|
unit_type = default_recon_unit_red
|
|
else:
|
|
unit_type = default_amphibious_unit
|
|
if unit_type is not None and len(vehicle_units) < max_num:
|
|
unit_id = self.game.next_unit_id()
|
|
unit_name = f"{cp_name}-{group_role}-{unit_id}"
|
|
|
|
spread_out_heading = random.randrange(1, 360)
|
|
spread_out_position = group.position.point_from_heading(
|
|
spread_out_heading, 30
|
|
)
|
|
ground_unit_pos = PointWithHeading.from_point(
|
|
spread_out_position, group.position.heading
|
|
)
|
|
|
|
theater_unit = TheaterUnit(
|
|
unit_id,
|
|
unit_name,
|
|
unit_type.dcs_unit_type,
|
|
ground_unit_pos,
|
|
group.ground_object,
|
|
)
|
|
vehicle_units.append(theater_unit)
|
|
|
|
def generate(self) -> None:
|
|
if self.culled:
|
|
return
|
|
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
|
|
self.ground_object.control_point.name
|
|
)
|
|
country_name_trimmed = "".join(
|
|
[i for i in self.country.shortname.lower() if i.isalpha()]
|
|
)
|
|
|
|
for group in self.ground_object.groups:
|
|
vehicle_units: list[TheaterUnit] = []
|
|
|
|
for unit in group.units:
|
|
if unit.is_static:
|
|
# Add supply convoy
|
|
group_role = "supply"
|
|
group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}"
|
|
group.name = group_name
|
|
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.LOGISTICS,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
elif unit.is_vehicle and unit.alive:
|
|
# Add armor group
|
|
group_role = "assault"
|
|
group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}"
|
|
group.name = group_name
|
|
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.TANK,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 4,
|
|
)
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.TANK,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 3,
|
|
)
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.ATGM,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 2,
|
|
)
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.APC,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 1,
|
|
)
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.IFV,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.RECON,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
if random.randrange(0, 100) > 75:
|
|
self.generate_ground_unit_of_class(
|
|
UnitClass.SHORAD,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
elif unit.is_ship and unit.alive:
|
|
# Attach this group to the closest naval group, if available
|
|
control_point = self.ground_object.control_point
|
|
for (
|
|
other_cp
|
|
) in self.game.theater.closest_friendly_control_points_to(
|
|
self.ground_object.control_point
|
|
):
|
|
if other_cp.is_fleet:
|
|
control_point = other_cp
|
|
break
|
|
|
|
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
|
|
control_point.name
|
|
)
|
|
is_player = True
|
|
side = (
|
|
2
|
|
if self.country
|
|
== self.game.coalition_for(is_player).faction.country
|
|
else 1
|
|
)
|
|
|
|
try:
|
|
number_of_supply_groups = len(
|
|
self.game.pretense_ground_supply[side][cp_name_trimmed]
|
|
)
|
|
except KeyError:
|
|
number_of_supply_groups = 0
|
|
self.game.pretense_ground_supply[side][cp_name_trimmed] = list()
|
|
self.game.pretense_ground_assault[side][
|
|
cp_name_trimmed
|
|
] = list()
|
|
|
|
if number_of_supply_groups == 0:
|
|
# Add supply convoy
|
|
group_role = "supply"
|
|
group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}"
|
|
group.name = group_name
|
|
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.LOGISTICS,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
else:
|
|
# Add armor group
|
|
group_role = "assault"
|
|
group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}"
|
|
group.name = group_name
|
|
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.TANK,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 4,
|
|
)
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.TANK,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 3,
|
|
)
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.ATGM,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 2,
|
|
)
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.APC,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE - 1,
|
|
)
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.IFV,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
self.generate_amphibious_unit_of_class(
|
|
UnitClass.RECON,
|
|
group,
|
|
vehicle_units,
|
|
cp_name_trimmed,
|
|
group_role,
|
|
PRETENSE_GROUND_UNIT_GROUP_SIZE,
|
|
)
|
|
if vehicle_units:
|
|
self.create_vehicle_group(group.group_name, vehicle_units)
|
|
|
|
def create_vehicle_group(
|
|
self, group_name: str, units: list[TheaterUnit]
|
|
) -> VehicleGroup:
|
|
vehicle_group: Optional[VehicleGroup] = None
|
|
|
|
control_point = self.ground_object.control_point
|
|
for unit in self.ground_object.units:
|
|
if unit.is_ship:
|
|
# Unit is naval/amphibious. Attach this group to the closest naval group, if available.
|
|
for other_cp in self.game.theater.closest_friendly_control_points_to(
|
|
self.ground_object.control_point
|
|
):
|
|
if other_cp.is_fleet:
|
|
control_point = other_cp
|
|
break
|
|
|
|
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
|
|
control_point.name
|
|
)
|
|
is_player = True
|
|
side = (
|
|
2
|
|
if self.country == self.game.coalition_for(is_player).faction.country
|
|
else 1
|
|
)
|
|
|
|
for unit in units:
|
|
assert issubclass(unit.type, VehicleType)
|
|
faction = self.coalition.faction
|
|
if vehicle_group is None:
|
|
vehicle_group = self.m.vehicle_group(
|
|
self.country,
|
|
group_name,
|
|
unit.type,
|
|
position=unit.position,
|
|
heading=unit.position.heading.degrees,
|
|
)
|
|
vehicle_group.units[0].player_can_drive = True
|
|
self.enable_eplrs(vehicle_group, unit.type)
|
|
vehicle_group.units[0].name = unit.unit_name
|
|
self.set_alarm_state(vehicle_group)
|
|
GroundForcePainter(faction, vehicle_group.units[0]).apply_livery()
|
|
|
|
group_role = group_name.split("-")[2]
|
|
if group_role == "supply":
|
|
self.game.pretense_ground_supply[side][cp_name_trimmed].append(
|
|
f"{vehicle_group.name}"
|
|
)
|
|
elif group_role == "assault":
|
|
self.game.pretense_ground_assault[side][cp_name_trimmed].append(
|
|
f"{vehicle_group.name}"
|
|
)
|
|
else:
|
|
vehicle_unit = self.m.vehicle(unit.unit_name, unit.type)
|
|
vehicle_unit.player_can_drive = True
|
|
vehicle_unit.position = unit.position
|
|
vehicle_unit.heading = unit.position.heading.degrees
|
|
GroundForcePainter(faction, vehicle_unit).apply_livery()
|
|
vehicle_group.add_unit(vehicle_unit)
|
|
self._register_theater_unit(unit, vehicle_group.units[-1])
|
|
if vehicle_group is None:
|
|
raise RuntimeError(f"Error creating VehicleGroup for {group_name}")
|
|
return vehicle_group
|
|
|
|
|
|
class PretenseGenericCarrierGenerator(GenericCarrierGenerator):
|
|
"""Base type for carrier group generation.
|
|
|
|
Used by both CV(N) groups and LHA groups.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
ground_object: GenericCarrierGroundObject,
|
|
control_point: NavalControlPoint,
|
|
country: Country,
|
|
game: Game,
|
|
mission: Mission,
|
|
radio_registry: RadioRegistry,
|
|
tacan_registry: TacanRegistry,
|
|
icls_alloc: Iterator[int],
|
|
runways: Dict[str, RunwayData],
|
|
unit_map: UnitMap,
|
|
mission_data: MissionData,
|
|
) -> None:
|
|
super().__init__(
|
|
ground_object,
|
|
control_point,
|
|
country,
|
|
game,
|
|
mission,
|
|
radio_registry,
|
|
tacan_registry,
|
|
icls_alloc,
|
|
runways,
|
|
unit_map,
|
|
mission_data,
|
|
)
|
|
self.ground_object = ground_object
|
|
self.control_point = control_point
|
|
self.radio_registry = radio_registry
|
|
self.tacan_registry = tacan_registry
|
|
self.icls_alloc = icls_alloc
|
|
self.runways = runways
|
|
self.mission_data = mission_data
|
|
|
|
def generate(self) -> None:
|
|
if self.control_point.frequency is not None:
|
|
atc = self.control_point.frequency
|
|
if atc not in self.radio_registry.allocated_channels:
|
|
self.radio_registry.reserve(atc)
|
|
else:
|
|
atc = self.radio_registry.alloc_uhf()
|
|
|
|
for g_id, group in enumerate(self.ground_object.groups):
|
|
if not group.units:
|
|
logging.warning(f"Found empty carrier group in {self.control_point}")
|
|
continue
|
|
|
|
ship_units = []
|
|
for unit in group.units:
|
|
if unit.alive:
|
|
# All alive Ships
|
|
print(
|
|
f"Added {unit.unit_name} to ship_units of group {group.group_name}"
|
|
)
|
|
ship_units.append(unit)
|
|
|
|
if not ship_units:
|
|
# Empty array (no alive units), skip this group
|
|
continue
|
|
|
|
ship_group = self.create_ship_group(group.group_name, ship_units, atc)
|
|
|
|
if self.game.settings.pretense_carrier_steams_into_wind:
|
|
# Always steam into the wind, even if the carrier is being moved.
|
|
# There are multiple unsimulated hours between turns, so we can
|
|
# count those as the time the carrier uses to move and the mission
|
|
# time as the recovery window.
|
|
brc = self.steam_into_wind(ship_group)
|
|
else:
|
|
brc = Heading(0)
|
|
|
|
# Set Carrier Specific Options
|
|
if g_id == 0 and self.control_point.runway_is_operational():
|
|
# Get Correct unit type for the carrier.
|
|
# This will upgrade to super carrier if option is enabled
|
|
carrier_type = self.carrier_type
|
|
if carrier_type is None:
|
|
raise RuntimeError(
|
|
f"Error generating carrier group for {self.control_point.name}"
|
|
)
|
|
ship_group.units[0].type = carrier_type.id
|
|
if self.control_point.tacan is None:
|
|
tacan = self.tacan_registry.alloc_for_band(
|
|
TacanBand.X, TacanUsage.TransmitReceive
|
|
)
|
|
else:
|
|
tacan = self.control_point.tacan
|
|
if self.control_point.tcn_name is None:
|
|
tacan_callsign = self.tacan_callsign()
|
|
else:
|
|
tacan_callsign = self.control_point.tcn_name
|
|
link4 = None
|
|
link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal]
|
|
if carrier_type in link4carriers:
|
|
if self.control_point.link4 is None:
|
|
link4 = self.radio_registry.alloc_uhf()
|
|
else:
|
|
link4 = self.control_point.link4
|
|
icls = None
|
|
icls_name = self.control_point.icls_name
|
|
if carrier_type in link4carriers or carrier_type == LHA_Tarawa:
|
|
if self.control_point.icls_channel is None:
|
|
icls = next(self.icls_alloc)
|
|
else:
|
|
icls = self.control_point.icls_channel
|
|
self.activate_beacons(
|
|
ship_group, tacan, tacan_callsign, icls, icls_name, link4
|
|
)
|
|
self.add_runway_data(
|
|
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
|
|
)
|
|
self.mission_data.carriers.append(
|
|
CarrierInfo(
|
|
group_name=ship_group.name,
|
|
unit_name=ship_group.units[0].name,
|
|
callsign=tacan_callsign,
|
|
freq=atc,
|
|
tacan=tacan,
|
|
icls_channel=icls,
|
|
link4_freq=link4,
|
|
blue=self.control_point.captured,
|
|
)
|
|
)
|
|
|
|
|
|
class PretenseCarrierGenerator(PretenseGenericCarrierGenerator):
|
|
def tacan_callsign(self) -> str:
|
|
# TODO: Assign these properly.
|
|
return random.choice(
|
|
[
|
|
"STE",
|
|
"CVN",
|
|
"CVH",
|
|
"CCV",
|
|
"ACC",
|
|
"ARC",
|
|
"GER",
|
|
"ABR",
|
|
"LIN",
|
|
"TRU",
|
|
]
|
|
)
|
|
|
|
|
|
class PretenseLhaGenerator(PretenseGenericCarrierGenerator):
|
|
def tacan_callsign(self) -> str:
|
|
# TODO: Assign these properly.
|
|
return random.choice(
|
|
[
|
|
"LHD",
|
|
"LHA",
|
|
"LHB",
|
|
"LHC",
|
|
"LHD",
|
|
"LDS",
|
|
]
|
|
)
|
|
|
|
|
|
class PretenseTgoGenerator(TgoGenerator):
|
|
"""Creates DCS groups and statics for the theater during mission generation.
|
|
|
|
Most of the work of group/static generation is delegated to the other
|
|
generator classes. This class is responsible for finding each of the
|
|
locations for spawning ground objects, determining their types, and creating
|
|
the appropriate generators.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
mission: Mission,
|
|
game: Game,
|
|
radio_registry: RadioRegistry,
|
|
tacan_registry: TacanRegistry,
|
|
unit_map: UnitMap,
|
|
mission_data: MissionData,
|
|
) -> None:
|
|
super().__init__(
|
|
mission,
|
|
game,
|
|
radio_registry,
|
|
tacan_registry,
|
|
unit_map,
|
|
mission_data,
|
|
)
|
|
|
|
self.m = mission
|
|
self.game = game
|
|
self.radio_registry = radio_registry
|
|
self.tacan_registry = tacan_registry
|
|
self.unit_map = unit_map
|
|
self.icls_alloc = iter(range(1, 21))
|
|
self.runways: Dict[str, RunwayData] = {}
|
|
self.helipads: dict[ControlPoint, list[StaticGroup]] = defaultdict(list)
|
|
self.ground_spawns_roadbase: dict[
|
|
ControlPoint, list[Tuple[StaticGroup, Point]]
|
|
] = defaultdict(list)
|
|
self.ground_spawns: dict[
|
|
ControlPoint, list[Tuple[StaticGroup, Point]]
|
|
] = defaultdict(list)
|
|
self.mission_data = mission_data
|
|
|
|
def generate(self) -> None:
|
|
for cp in self.game.theater.controlpoints:
|
|
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name)
|
|
for side in range(1, 3):
|
|
if cp_name_trimmed not in self.game.pretense_ground_supply[side]:
|
|
self.game.pretense_ground_supply[side][cp_name_trimmed] = list()
|
|
if cp_name_trimmed not in self.game.pretense_ground_assault[side]:
|
|
self.game.pretense_ground_assault[side][cp_name_trimmed] = list()
|
|
|
|
# First generate units for the coalition, which initially holds this CP
|
|
country = self.m.country(cp.coalition.faction.country.name)
|
|
|
|
# Generate helipads
|
|
helipad_gen = HelipadGenerator(
|
|
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
|
)
|
|
helipad_gen.generate()
|
|
self.helipads[cp] = helipad_gen.helipads
|
|
|
|
# Generate Highway Strip slots
|
|
ground_spawn_roadbase_gen = GroundSpawnRoadbaseGenerator(
|
|
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
|
)
|
|
ground_spawn_roadbase_gen.generate()
|
|
self.ground_spawns_roadbase[
|
|
cp
|
|
] = ground_spawn_roadbase_gen.ground_spawns_roadbase
|
|
random.shuffle(self.ground_spawns_roadbase[cp])
|
|
|
|
# Generate STOL pads
|
|
ground_spawn_gen = GroundSpawnGenerator(
|
|
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
|
)
|
|
ground_spawn_gen.generate()
|
|
self.ground_spawns[cp] = ground_spawn_gen.ground_spawns
|
|
random.shuffle(self.ground_spawns[cp])
|
|
|
|
for ground_object in cp.ground_objects:
|
|
generator: GroundObjectGenerator
|
|
if isinstance(ground_object, CarrierGroundObject) and isinstance(
|
|
cp, NavalControlPoint
|
|
):
|
|
generator = PretenseCarrierGenerator(
|
|
ground_object,
|
|
cp,
|
|
country,
|
|
self.game,
|
|
self.m,
|
|
self.radio_registry,
|
|
self.tacan_registry,
|
|
self.icls_alloc,
|
|
self.runways,
|
|
self.unit_map,
|
|
self.mission_data,
|
|
)
|
|
elif isinstance(ground_object, LhaGroundObject) and isinstance(
|
|
cp, NavalControlPoint
|
|
):
|
|
generator = PretenseLhaGenerator(
|
|
ground_object,
|
|
cp,
|
|
country,
|
|
self.game,
|
|
self.m,
|
|
self.radio_registry,
|
|
self.tacan_registry,
|
|
self.icls_alloc,
|
|
self.runways,
|
|
self.unit_map,
|
|
self.mission_data,
|
|
)
|
|
elif isinstance(ground_object, MissileSiteGroundObject):
|
|
generator = MissileSiteGenerator(
|
|
ground_object, country, self.game, self.m, self.unit_map
|
|
)
|
|
else:
|
|
generator = PretenseGroundObjectGenerator(
|
|
ground_object, country, self.game, self.m, self.unit_map
|
|
)
|
|
generator.generate()
|
|
# Then generate ground supply and assault groups for the other coalition
|
|
other_coalition = cp.coalition
|
|
for coalition in cp.coalition.game.coalitions:
|
|
if coalition == cp.coalition:
|
|
continue
|
|
else:
|
|
other_coalition = coalition
|
|
country = self.m.country(other_coalition.faction.country.name)
|
|
new_ground_object: TheaterGroundObject
|
|
for ground_object in cp.ground_objects:
|
|
if isinstance(ground_object, BuildingGroundObject):
|
|
new_ground_object = BuildingGroundObject(
|
|
name=ground_object.name,
|
|
category=ground_object.category,
|
|
location=PresetLocation(
|
|
f"{ground_object.name} {ground_object.id}",
|
|
ground_object.position,
|
|
ground_object.heading,
|
|
),
|
|
control_point=ground_object.control_point,
|
|
is_fob_structure=ground_object.is_fob_structure,
|
|
task=ground_object.task,
|
|
)
|
|
new_ground_object.groups = ground_object.groups
|
|
generator = PretenseGroundObjectGenerator(
|
|
new_ground_object, country, self.game, self.m, self.unit_map
|
|
)
|
|
elif isinstance(ground_object, VehicleGroupGroundObject):
|
|
new_ground_object = VehicleGroupGroundObject(
|
|
name=ground_object.name,
|
|
location=PresetLocation(
|
|
f"{ground_object.name} {ground_object.id}",
|
|
ground_object.position,
|
|
ground_object.heading,
|
|
),
|
|
control_point=ground_object.control_point,
|
|
task=ground_object.task,
|
|
)
|
|
new_ground_object.groups = ground_object.groups
|
|
generator = PretenseGroundObjectGenerator(
|
|
new_ground_object, country, self.game, self.m, self.unit_map
|
|
)
|
|
else:
|
|
continue
|
|
|
|
generator.coalition = other_coalition
|
|
generator.generate()
|
|
|
|
self.mission_data.runways = list(self.runways.values())
|