Merge branch 'develop' into helipads

# Conflicts:
#	game/theater/conflicttheater.py
#	gen/flights/flightplan.py
This commit is contained in:
Khopa
2021-06-06 15:46:30 +02:00
103 changed files with 1992 additions and 3914 deletions

View File

@@ -803,7 +803,7 @@ class AircraftConflictGenerator:
self._setup_payload(flight, group)
self._setup_livery(flight, group)
for unit, pilot in zip(group.units, flight.pilots):
for unit, pilot in zip(group.units, flight.roster.pilots):
player = pilot is not None and pilot.player
self.set_skill(unit, pilot, blue=flight.departure.captured)
# Do not generate player group with late activation.

View File

@@ -162,7 +162,7 @@ class AircraftAllocator:
self, flight: ProposedFlight, task: FlightType
) -> Optional[Tuple[ControlPoint, Squadron]]:
types = aircraft_for_task(task)
airfields_in_range = self.closest_airfields.airfields_within(
airfields_in_range = self.closest_airfields.operational_airfields_within(
flight.max_distance
)
@@ -180,7 +180,7 @@ class AircraftAllocator:
# Valid location with enough aircraft available. Find a squadron to fit
# the role.
for squadron in self.air_wing.squadrons_for(aircraft):
if task not in squadron.mission_types:
if task not in squadron.auto_assignable_mission_types:
continue
if len(squadron.available_pilots) >= flight.num_aircraft:
inventory.remove_aircraft(aircraft, flight.num_aircraft)
@@ -258,7 +258,9 @@ class PackageBuilder:
self, aircraft: Type[FlyingType], arrival: ControlPoint
) -> Optional[ControlPoint]:
divert_limit = nautical_miles(150)
for airfield in self.closest_airfields.airfields_within(divert_limit):
for airfield in self.closest_airfields.operational_airfields_within(
divert_limit
):
if airfield.captured != self.is_player:
continue
if airfield == arrival:
@@ -433,7 +435,7 @@ class ObjectiveFinder:
is_building = isinstance(ground_object, BuildingGroundObject)
is_fob = isinstance(enemy_cp, Fob)
if is_building and is_fob and ground_object.airbase_group:
if is_building and is_fob and ground_object.is_control_point:
# This is the FOB structure itself. Can't be repaired or
# targeted by the player, so shouldn't be targetable by the
# AI.
@@ -467,8 +469,10 @@ class ObjectiveFinder:
# Off-map spawn locations don't need protection.
continue
airfields_in_proximity = self.closest_airfields_to(cp)
airfields_in_threat_range = airfields_in_proximity.airfields_within(
self.AIRFIELD_THREAT_RANGE
airfields_in_threat_range = (
airfields_in_proximity.operational_airfields_within(
self.AIRFIELD_THREAT_RANGE
)
)
for airfield in airfields_in_threat_range:
if not airfield.is_friendly(self.is_player):
@@ -502,31 +506,23 @@ class ObjectiveFinder:
c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player)
)
def farthest_friendly_control_point(self) -> Optional[ControlPoint]:
"""
Iterates over all friendly control points and find the one farthest away from the frontline
BUT! prefer Cvs. Everybody likes CVs!
"""
from_frontline = 0
cp = None
first_friendly_cp = None
def farthest_friendly_control_point(self) -> ControlPoint:
"""Finds the friendly control point that is farthest from any threats."""
threat_zones = self.game.threat_zone_for(not self.is_player)
for c in self.game.theater.controlpoints:
if c.is_friendly(self.is_player):
if first_friendly_cp is None:
first_friendly_cp = c
if c.is_carrier:
return c
if c.has_active_frontline:
if c.distance_to(self.front_lines().__next__()) > from_frontline:
from_frontline = c.distance_to(self.front_lines().__next__())
cp = c
farthest = None
max_distance = meters(0)
for cp in self.friendly_control_points():
if isinstance(cp, OffMapSpawn):
continue
distance = threat_zones.distance_to_threat(cp.position)
if distance > max_distance:
farthest = cp
max_distance = distance
# If no frontlines on the map, return the first friendly cp
if cp is None:
return first_friendly_cp
else:
return cp
if farthest is None:
raise RuntimeError("Found no friendly control points. You probably lost.")
return farthest
def enemy_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over all enemy control points."""
@@ -608,7 +604,7 @@ class CoalitionMissionPlanner:
for squadron in self.game.air_wing_for(self.is_player).iter_squadrons():
if (
squadron.aircraft in all_compatible
and mission_type in squadron.mission_types
and mission_type in squadron.auto_assignable_mission_types
):
return True
return False
@@ -624,15 +620,13 @@ class CoalitionMissionPlanner:
eliminated this turn.
"""
# Find farthest, friendly CP for AEWC
cp = self.objective_finder.farthest_friendly_control_point()
if cp is not None:
yield ProposedMission(
cp,
[ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)],
# Supports all the early CAP flights, so should be in the air ASAP.
asap=True,
)
# Find farthest, friendly CP for AEWC.
yield ProposedMission(
self.objective_finder.farthest_friendly_control_point(),
[ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)],
# Supports all the early CAP flights, so should be in the air ASAP.
asap=True,
)
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
for cp in self.objective_finder.vulnerable_control_points():
@@ -1012,7 +1006,7 @@ class CoalitionMissionPlanner:
interval = (latest - earliest) // count
for time in range(earliest, latest, interval):
error = random.randint(-margin, margin)
yield timedelta(minutes=max(0, time + error))
yield timedelta(seconds=max(0, time + error))
dca_types = {
FlightType.BARCAP,
@@ -1026,11 +1020,11 @@ class CoalitionMissionPlanner:
start_time = start_time_generator(
count=len(non_dca_packages),
earliest=5,
earliest=5 * 60,
latest=int(
self.game.settings.desired_player_mission_duration.total_seconds() / 60
self.game.settings.desired_player_mission_duration.total_seconds()
),
margin=5,
margin=5 * 60,
)
for package in self.ato.packages:
tot = TotEstimator(package).earliest_tot()

View File

@@ -31,17 +31,35 @@ class ClosestAirfields:
if c.runway_is_operational() or c.has_helipads
)
def airfields_within(self, distance: Distance) -> Iterator[ControlPoint]:
def _airfields_within(
self, distance: Distance, operational: bool
) -> Iterator[ControlPoint]:
airfields = (
self.operational_airfields if operational else self.closest_airfields
)
for cp in airfields:
if cp.distance_to(self.target) < distance.meters:
yield cp
else:
break
def operational_airfields_within(
self, distance: Distance
) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
for cp in self.closest_airfields:
if cp.distance_to(self.target) < distance.meters:
yield cp
else:
break
return self._airfields_within(distance, operational=True)
def all_airfields_within(self, distance: Distance) -> Iterator[ControlPoint]:
"""Iterates over all airfields within the given range of the target.
Note that this iterates over *all* airfields, not just friendly
airfields.
"""
return self._airfields_within(distance, operational=False)
class ObjectiveDistanceCache:

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum
from typing import List, Optional, TYPE_CHECKING, Type, Union
@@ -202,6 +203,49 @@ class FlightWaypoint:
return waypoint
class FlightRoster:
def __init__(self, squadron: Squadron, initial_size: int = 0) -> None:
self.squadron = squadron
self.pilots: list[Optional[Pilot]] = []
self.resize(initial_size)
@property
def max_size(self) -> int:
return len(self.pilots)
@property
def player_count(self) -> int:
return len([p for p in self.pilots if p is not None and p.player])
@property
def missing_pilots(self) -> int:
return len([p for p in self.pilots if p is None])
def resize(self, new_size: int) -> None:
if self.max_size > new_size:
self.squadron.return_pilots(
[p for p in self.pilots[new_size:] if p is not None]
)
self.pilots = self.pilots[:new_size]
return
self.pilots.extend(
[
self.squadron.claim_available_pilot()
for _ in range(new_size - self.max_size)
]
)
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
if pilot is not None:
self.squadron.claim_pilot(pilot)
if (current_pilot := self.pilots[index]) is not None:
self.squadron.return_pilot(current_pilot)
self.pilots[index] = pilot
def clear(self) -> None:
self.squadron.return_pilots([p for p in self.pilots if p is not None])
class Flight:
def __init__(
self,
@@ -216,11 +260,15 @@ class Flight:
divert: Optional[ControlPoint],
custom_name: Optional[str] = None,
cargo: Optional[TransferOrder] = None,
roster: Optional[FlightRoster] = None,
) -> None:
self.package = package
self.country = country
self.squadron = squadron
self.pilots = [squadron.claim_available_pilot() for _ in range(count)]
if roster is None:
self.roster = FlightRoster(self.squadron, initial_size=count)
else:
self.roster = roster
self.departure = departure
self.arrival = arrival
self.divert = divert
@@ -246,11 +294,11 @@ class Flight:
@property
def count(self) -> int:
return len(self.pilots)
return self.roster.max_size
@property
def client_count(self) -> int:
return len([p for p in self.pilots if p is not None and p.player])
return self.roster.player_count
@property
def unit_type(self) -> Type[FlyingType]:
@@ -265,32 +313,17 @@ class Flight:
return self.flight_plan.waypoints[1:]
def resize(self, new_size: int) -> None:
if self.count > new_size:
self.squadron.return_pilots(
p for p in self.pilots[new_size:] if p is not None
)
self.pilots = self.pilots[:new_size]
return
self.pilots.extend(
[
self.squadron.claim_available_pilot()
for _ in range(new_size - self.count)
]
)
self.roster.resize(new_size)
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
if pilot is not None:
self.squadron.claim_pilot(pilot)
if (current_pilot := self.pilots[index]) is not None:
self.squadron.return_pilot(current_pilot)
self.pilots[index] = pilot
self.roster.set_pilot(index, pilot)
@property
def missing_pilots(self) -> int:
return len([p for p in self.pilots if p is None])
return self.roster.missing_pilots
def clear_roster(self) -> None:
self.squadron.return_pilots([p for p in self.pilots if p is not None])
self.roster.clear()
def __repr__(self):
name = db.unit_type_name(self.unit_type)

View File

@@ -1057,7 +1057,7 @@ class FlightPlanBuilder:
"""
location = self.package.target
start = self.aewc_orbit(location)
orbit_location = self.aewc_orbit(location)
# As high as possible to maximize detection and on-station time.
if flight.unit_type == E_2C:
@@ -1072,22 +1072,22 @@ class FlightPlanBuilder:
patrol_alt = feet(25000)
builder = WaypointBuilder(flight, self.game, self.is_player)
start = builder.orbit(start, patrol_alt)
orbit_location = builder.orbit(orbit_location, patrol_alt)
return AwacsFlightPlan(
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path(
flight.departure.position, start.position, patrol_alt
flight.departure.position, orbit_location.position, patrol_alt
),
nav_from=builder.nav_path(
start.position, flight.arrival.position, patrol_alt
orbit_location.position, flight.arrival.position, patrol_alt
),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
hold=start,
hold=orbit_location,
hold_duration=timedelta(hours=4),
)
@@ -1339,20 +1339,24 @@ class FlightPlanBuilder:
return start, end
def aewc_orbit(self, location: MissionTarget) -> Point:
# in threat zone
closest_boundary = self.threat_zones.closest_boundary(location.position)
heading_to_threat_boundary = location.position.heading_between_point(
closest_boundary
)
distance_to_threat = meters(
location.position.distance_to_point(closest_boundary)
)
orbit_heading = heading_to_threat_boundary
# Station 100nm outside the threat zone.
threat_buffer = nautical_miles(100)
if self.threat_zones.threatened(location.position):
# Borderpoint
closest_boundary = self.threat_zones.closest_boundary(location.position)
# Heading + Distance to border point
heading = location.position.heading_between_point(closest_boundary)
distance = location.position.distance_to_point(closest_boundary)
return location.position.point_from_heading(heading, distance)
# this Part is fine. No threat zone, just use our point
orbit_distance = distance_to_threat + threat_buffer
else:
return location.position
orbit_distance = distance_to_threat - threat_buffer
return location.position.point_from_heading(
orbit_heading, orbit_distance.meters
)
def racetrack_for_frontline(
self, origin: Point, front_line: FrontLine
@@ -1807,7 +1811,7 @@ class FlightPlanBuilder:
# We'll always have a package, but if this is being planned via the UI
# it could be the first flight in the package.
if not self.package.flights:
raise RuntimeError(
raise PlanningError(
"Cannot determine source airfield for package with no flights"
)
@@ -1819,5 +1823,4 @@ class FlightPlanBuilder:
for flight in self.package.flights:
if flight.departure == airfield:
return airfield
raise RuntimeError("Could not find any airfield assigned to this package")
raise PlanningError("Could not find any airfield assigned to this package")

View File

@@ -1,3 +1,4 @@
import logging
import random
from enum import Enum
from typing import Dict, List
@@ -5,7 +6,8 @@ from typing import Dict, List
from dcs.unittype import VehicleType
from game.theater import ControlPoint
from gen.ground_forces.ai_ground_planner_db import *
from game.data.groundunitclass import GroundUnitClass
from gen.ground_forces.combat_stance import CombatStance
MAX_COMBAT_GROUP_PER_CP = 10
@@ -91,37 +93,35 @@ class GroundPlanner:
group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[CombatStance.DEFENSIVE]
# Create combat groups and assign them randomly to each enemy CP
for key in self.cp.base.armor.keys():
role = None
collection = None
if key in TYPE_TANKS:
for unit_type in self.cp.base.armor:
if unit_type in GroundUnitClass.Tank:
collection = self.tank_groups
role = CombatGroupRole.TANK
elif key in TYPE_APC:
elif unit_type in GroundUnitClass.Apc:
collection = self.apc_group
role = CombatGroupRole.APC
elif key in TYPE_ARTILLERY:
elif unit_type in GroundUnitClass.Artillery:
collection = self.art_group
role = CombatGroupRole.ARTILLERY
elif key in TYPE_IFV:
elif unit_type in GroundUnitClass.Ifv:
collection = self.ifv_group
role = CombatGroupRole.IFV
elif key in TYPE_LOGI:
elif unit_type in GroundUnitClass.Logistics:
collection = self.logi_groups
role = CombatGroupRole.LOGI
elif key in TYPE_ATGM:
elif unit_type in GroundUnitClass.Atgm:
collection = self.atgm_group
role = CombatGroupRole.ATGM
elif key in TYPE_SHORAD:
elif unit_type in GroundUnitClass.Shorads:
collection = self.shorad_groups
role = CombatGroupRole.SHORAD
else:
print("Warning unit type not handled by ground generator")
print(key)
logging.warning(
f"Unused front line vehicle at base {unit_type}: unknown unit class"
)
continue
available = self.cp.base.armor[key]
available = self.cp.base.armor[unit_type]
if available > remaining_available_frontline_units:
available = remaining_available_frontline_units
@@ -151,7 +151,7 @@ class GroundPlanner:
group.assigned_enemy_cp = "__reserve__"
for i in range(n):
group.units.append(key)
group.units.append(unit_type)
collection.append(group)
if remaining_available_frontline_units == 0:
@@ -161,7 +161,7 @@ class GroundPlanner:
print("Ground Planner : ")
print(self.cp.name)
print("------------------")
for key in self.units_per_cp.keys():
print("For : #" + str(key))
for group in self.units_per_cp[key]:
for unit_type in self.units_per_cp.keys():
print("For : #" + str(unit_type))
for group in self.units_per_cp[unit_type]:
print(str(group))

View File

@@ -1,189 +0,0 @@
from dcs.vehicles import AirDefence, Infantry, Unarmed, Artillery, Armor
from pydcs_extensions.frenchpack import frenchpack
TYPE_TANKS = [
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2A4,
Armor.MBT_Leopard_2A4_Trs,
Armor.MBT_Leopard_2A5,
Armor.MBT_Leopard_2A6M,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_Chieftain_Mk_3,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_IV,
Armor.ZTZ_96B,
Armor.LT_PT_76,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.Tk_PzIV_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.Tk_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.SPG_StuG_IV,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
Armor.SPG_Sturmpanzer_IV_Brummbar,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_HMMWV,
Armor.ATGM_VAB_Mephisto,
Armor.ATGM_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Unarmed.Carrier_M30_Cargo,
Armor.SPG_Jagdpanzer_IV,
Armor.SPG_Jagdpanther_G1,
Armor.SPG_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_Warrior,
Armor.IFV_LAV_25,
Armor.SPG_Stryker_MGS,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Car_M8_Greyhound_Armored,
Armor.Car_Daimler_Armored,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13,
]
TYPE_APC = [
Armor.Scout_HMMWV,
Armor.IFV_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.IFV_BTR_82A,
Armor.APC_MTLB,
Armor.APC_M2A1_Halftrack,
Armor.Scout_Cobra,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.APC_AAV_7_Amphibious,
Armor.APC_TPz_Fuchs,
Armor.Scout_BRDM_2,
Armor.APC_BTR_RD,
Artillery.Grad_MRL_FDDM__FC,
# WW2
Armor.APC_M2A1_Halftrack,
Armor.APC_Sd_Kfz_251_Halftrack,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
Artillery.MLRS_9A52_Smerch_HE_300mm,
Artillery.SPH_2S1_Gvozdika_122mm,
Artillery.SPH_2S3_Akatsia_152mm,
Artillery.MLRS_BM_21_Grad_122mm,
Artillery.MLRS_9K57_Uragan_BM_27_220mm,
Artillery.SPH_M109_Paladin_155mm,
Artillery.MLRS_M270_227mm,
Artillery.SPM_2S9_Nona_120mm_M,
Artillery.SPH_Dana_vz77_152mm,
Artillery.SPH_T155_Firtina_155mm,
Artillery.PLZ_05,
Artillery.SPH_2S19_Msta_152mm,
Artillery.MLRS_9A52_Smerch_CM_300mm,
# WW2
Artillery.SPG_M12_GMC_155mm,
]
TYPE_LOGI = [
Unarmed.Truck_M818_6x6,
Unarmed.Truck_KAMAZ_43101,
Unarmed.Truck_Ural_375,
Unarmed.Truck_GAZ_66,
Unarmed.Truck_GAZ_3307,
Unarmed.Truck_GAZ_3308,
Unarmed.Truck_Ural_4320_31_Arm_d,
Unarmed.Truck_Ural_4320T,
Unarmed.Truck_Opel_Blitz,
Unarmed.LUV_Kubelwagen_82,
Unarmed.Carrier_Sd_Kfz_7_Tractor,
Unarmed.LUV_Kettenrad,
Unarmed.Car_Willys_Jeep,
Unarmed.LUV_Land_Rover_109,
Unarmed.Truck_Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
Infantry.Insurgent_AK_74,
Infantry.Infantry_AK_74,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_AK_74_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Infantry_M249,
Infantry.Infantry_M4,
Infantry.Infantry_RPG,
]
TYPE_SHORAD = [
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375,
AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375,
AirDefence.SPAAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish,
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL,
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL,
AirDefence.SAM_SA_15_Tor_Gauntlet,
AirDefence.SAM_SA_19_Tunguska_Grison,
AirDefence.SPAAA_Gepard,
AirDefence.SPAAA_Vulcan_M163,
AirDefence.SAM_Linebacker___Bradley_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger__Stinger,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_S_60_57mm,
AirDefence.AAA_M1_37mm,
AirDefence.AAA_QF_3_7,
]

View File

@@ -15,7 +15,7 @@ from dcs import Mission, Point, unitgroup
from dcs.action import SceneryDestructionZone
from dcs.country import Country
from dcs.point import StaticPoint
from dcs.statics import Fortification, fortification_map, warehouse_map, Warehouse
from dcs.statics import Fortification, fortification_map, warehouse_map
from dcs.task import (
ActivateBeaconCommand,
ActivateICLSCommand,
@@ -24,7 +24,7 @@ from dcs.task import (
FireAtPoint,
)
from dcs.triggers import TriggerStart, TriggerZone
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad, Static
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import StaticType, UnitType
from dcs.vehicles import vehicle_map
@@ -76,8 +76,12 @@ class GenericGroundObjectGenerator:
self.m = mission
self.unit_map = unit_map
@property
def culled(self) -> bool:
return self.game.position_culled(self.ground_object.position)
def generate(self) -> None:
if self.game.position_culled(self.ground_object.position):
if self.culled:
return
for group in self.ground_object.groups:
@@ -130,6 +134,12 @@ class GenericGroundObjectGenerator:
class MissileSiteGenerator(GenericGroundObjectGenerator):
@property
def culled(self) -> bool:
# Don't cull missile sites - their range is long enough to make them easily
# culled despite being a threat.
return False
def generate(self) -> None:
super(MissileSiteGenerator, self).generate()
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)

View File

@@ -312,10 +312,6 @@ class NameGenerator:
db.unit_type_name(unit_type),
)
@staticmethod
def next_basedefense_name():
return "basedefense_aa|0|0|"
@classmethod
def next_awacs_name(cls, country: Country):
cls.number += 1
@@ -352,7 +348,7 @@ class NameGenerator:
for _ in range(10):
alpha = random.choice(ALPHA_MILITARY).upper()
number = str(random.randint(0, 100))
number = random.randint(0, 100)
alpha_mil_name = f"{alpha} #{number:02}"
if alpha_mil_name not in cls.existing_alphas:
cls.existing_alphas.append(alpha_mil_name)

View File

@@ -21,6 +21,8 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
This is the base for all SAM group generators
"""
price: int
def __init__(self, game: Game, ground_object: SamGroundObject) -> None:
ground_object.skynet_capable = True
super().__init__(game, ground_object)

View File

@@ -17,8 +17,8 @@ from gen.sam.ewrs import (
SnowDriftGenerator,
StraightFlushGenerator,
TallRackGenerator,
EwrGenerator,
)
from gen.sam.group_generator import GroupGenerator
EWR_MAP = {
"BoxSpringGenerator": BoxSpringGenerator,
@@ -36,7 +36,7 @@ EWR_MAP = {
def get_faction_possible_ewrs_generator(
faction: Faction,
) -> List[Type[GroupGenerator]]:
) -> List[Type[EwrGenerator]]:
"""
Return the list of possible EWR generators for the given faction
:param faction: Faction name to search units for

View File

@@ -5,9 +5,16 @@ from gen.sam.group_generator import GroupGenerator
class EwrGenerator(GroupGenerator):
@property
def unit_type(self) -> VehicleType:
raise NotImplementedError
unit_type: VehicleType
@classmethod
def name(cls) -> str:
return cls.unit_type.name
@staticmethod
def price() -> int:
# TODO: Differentiate sites.
return 20
def generate(self) -> None:
self.add_unit(

View File

@@ -10,14 +10,12 @@ from dcs.condition import (
FlagIsFalse,
FlagIsTrue,
)
from dcs.unitgroup import FlyingGroup
from dcs.mission import Mission
from dcs.task import Option
from dcs.translation import String
from dcs.triggers import (
Event,
TriggerOnce,
TriggerZone,
TriggerCondition,
)
from dcs.unit import Skill
@@ -25,7 +23,6 @@ from dcs.unit import Skill
from game.theater import Airfield
from game.theater.controlpoint import Fob
if TYPE_CHECKING:
from game.game import Game
@@ -115,19 +112,22 @@ class TriggersGenerator:
mark_trigger.add_condition(TimeAfter(1))
v = 10
for cp in self.game.theater.controlpoints:
added = []
seen = set()
for ground_object in cp.ground_objects:
if ground_object.obj_name not in added:
if ground_object.obj_name in seen:
continue
seen.add(ground_object.obj_name)
for location in ground_object.mark_locations:
zone = self.mission.triggers.add_triggerzone(
ground_object.position, radius=10, hidden=True, name="MARK"
location, radius=10, hidden=True, name="MARK"
)
if cp.captured:
name = ground_object.obj_name + " [ALLY]"
else:
name = ground_object.obj_name + " [ENEMY]"
mark_trigger.add_action(MarkToAll(v, zone.id, String(name)))
v = v + 1
added.append(ground_object.obj_name)
v += 1
self.mission.triggerrules.triggers.append(mark_trigger)
def _generate_capture_triggers(