Implemented generating amphibious units for Pretense unit groups. 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.

This commit is contained in:
MetalStormGhost 2023-10-04 18:38:41 +03:00
parent 08930f4a9a
commit ced7aa4d72
2 changed files with 319 additions and 48 deletions

View File

@ -7,51 +7,22 @@ create the pydcs groups and statics for those areas and add them to the mission.
""" """
from __future__ import annotations from __future__ import annotations
import logging
import random import random
from collections import defaultdict from collections import defaultdict
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type, Tuple from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type
import dcs.vehicles from dcs import Mission, Point
from dcs import Mission, Point, unitgroup
from dcs.action import DoScript, SceneryDestructionZone
from dcs.condition import MapObjectIsDead
from dcs.countries import * from dcs.countries import *
from dcs.country import Country from dcs.country import Country
from dcs.point import StaticPoint, PointAction from dcs.unitgroup import StaticGroup, VehicleGroup
from dcs.ships import ( from dcs.unittype import VehicleType
CVN_71,
CVN_72,
CVN_73,
CVN_75,
Stennis,
Forrestal,
LHA_Tarawa,
)
from dcs.statics import Fortification
from dcs.task import (
ActivateBeaconCommand,
ActivateICLSCommand,
ActivateLink4Command,
ActivateACLSCommand,
EPLRS,
FireAtPoint,
OptAlarmState,
)
from dcs.translation import String
from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone
from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import ShipType, VehicleType
from dcs.vehicles import vehicle_map, Unarmed
from game.data.units import UnitClass from game.data.units import UnitClass
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.missiongenerator.groundforcepainter import ( from game.missiongenerator.groundforcepainter import (
NavalForcePainter,
GroundForcePainter, GroundForcePainter,
) )
from game.missiongenerator.missiondata import CarrierInfo, MissionData from game.missiongenerator.missiondata import MissionData
from game.missiongenerator.tgogenerator import ( from game.missiongenerator.tgogenerator import (
TgoGenerator, TgoGenerator,
HelipadGenerator, HelipadGenerator,
@ -63,9 +34,8 @@ from game.missiongenerator.tgogenerator import (
MissileSiteGenerator, MissileSiteGenerator,
) )
from game.point_with_heading import PointWithHeading from game.point_with_heading import PointWithHeading
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer from game.radio.radios import RadioRegistry
from game.radio.radios import RadioFrequency, RadioRegistry from game.radio.tacan import TacanRegistry
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
from game.runways import RunwayData from game.runways import RunwayData
from game.theater import ( from game.theater import (
ControlPoint, ControlPoint,
@ -76,15 +46,21 @@ from game.theater import (
) )
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
CarrierGroundObject, CarrierGroundObject,
GenericCarrierGroundObject,
LhaGroundObject, LhaGroundObject,
MissileSiteGroundObject, MissileSiteGroundObject,
BuildingGroundObject, BuildingGroundObject,
VehicleGroupGroundObject, VehicleGroupGroundObject,
) )
from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup from game.theater.theatergroup import TheaterGroup
from game.unitmap import UnitMap from game.unitmap import UnitMap
from game.utils import Heading, feet, knots, mps from pydcs_extensions import (
Char_M551_Sheridan,
BV410_RBS70,
BV410_RBS90,
BV410,
VAB__50,
VAB_T20_13,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -96,6 +72,30 @@ PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [
vehicles.Armor.Stug_III, vehicles.Armor.Stug_III,
vehicles.Artillery.Grad_URAL, 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): class PretenseGroundObjectGenerator(GroundObjectGenerator):
@ -128,6 +128,16 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
return self.game.iads_considerate_culling(self.ground_object) return self.game.iads_considerate_culling(self.ground_object)
def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: def ground_unit_of_class(self, 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:
unit_class: Class of unit to return.
"""
faction_units = ( faction_units = (
set(self.ground_object.coalition.faction.frontline_units) set(self.ground_object.coalition.faction.frontline_units)
| set(self.ground_object.coalition.faction.artillery_units) | set(self.ground_object.coalition.faction.artillery_units)
@ -155,6 +165,25 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
group_role: str, group_role: str,
max_num: int, max_num: int,
) -> None: ) -> 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.ground_object.coalition.faction.has_access_to_unit_class(unit_class): if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class):
unit_type = self.ground_unit_of_class(unit_class) unit_type = self.ground_unit_of_class(unit_class)
if unit_type is not None and len(vehicle_units) < max_num: if unit_type is not None and len(vehicle_units) < max_num:
@ -178,6 +207,140 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
) )
vehicle_units.append(theater_unit) 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.ground_object.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.ground_object.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: def generate(self) -> None:
if self.culled: if self.culled:
return return
@ -187,7 +350,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
for group in self.ground_object.groups: for group in self.ground_object.groups:
vehicle_units: list[TheaterUnit] = [] vehicle_units: list[TheaterUnit] = []
ship_units: list[TheaterUnit] = []
# Split the different unit types to be compliant to dcs limitation # Split the different unit types to be compliant to dcs limitation
for unit in group.units: for unit in group.units:
if unit.is_static: if unit.is_static:
@ -268,20 +430,129 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
PRETENSE_GROUND_UNIT_GROUP_SIZE, PRETENSE_GROUND_UNIT_GROUP_SIZE,
) )
elif unit.is_ship and unit.alive: elif unit.is_ship and unit.alive:
# All alive Ships print(f"Generating amphibious group at {unit.unit_name}")
ship_units.append(unit) # 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 = "".join(
[i for i in control_point.name.lower() if i.isalnum()]
)
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}-{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}-{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: if vehicle_units:
self.create_vehicle_group(group.group_name, vehicle_units) self.create_vehicle_group(group.group_name, vehicle_units)
if ship_units:
self.create_ship_group(group.group_name, ship_units)
def create_vehicle_group( def create_vehicle_group(
self, group_name: str, units: list[TheaterUnit] self, group_name: str, units: list[TheaterUnit]
) -> VehicleGroup: ) -> VehicleGroup:
vehicle_group: Optional[VehicleGroup] = None 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 = "".join( cp_name_trimmed = "".join(
[i for i in self.ground_object.control_point.name.lower() if i.isalnum()] [i for i in control_point.name.lower() if i.isalnum()]
) )
is_player = True is_player = True
side = ( side = (

View File

@ -1,4 +1,4 @@
class: Logistics class: Logistics
price: 2 price: 3
variants: variants:
LARC-V: null LARC-V Amphibious Cargo Vehicle: null