mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge pull request #204 from MetalStormGhost/pretense-generator
Pretense generator + additional options
This commit is contained in:
commit
ecb81ec32d
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,5 +22,6 @@ env/
|
||||
|
||||
/logs/
|
||||
/resources/logging.yaml
|
||||
/resources/plugins/pretense/pretense_output.lua
|
||||
|
||||
*.psd
|
||||
|
||||
@ -19,6 +19,7 @@ from .ocaaircraft import OcaAircraftFlightPlan
|
||||
from .ocarunway import OcaRunwayFlightPlan
|
||||
from .packagerefueling import PackageRefuelingFlightPlan
|
||||
from .planningerror import PlanningError
|
||||
from .pretensecargo import PretenseCargoFlightPlan
|
||||
from .sead import SeadFlightPlan
|
||||
from .seadsweep import SeadSweepFlightPlan
|
||||
from .strike import StrikeFlightPlan
|
||||
@ -61,6 +62,7 @@ class FlightPlanBuilderTypes:
|
||||
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
||||
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
||||
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
||||
FlightType.PRETENSE_CARGO: PretenseCargoFlightPlan.builder_type(),
|
||||
FlightType.ARMED_RECON: ArmedReconFlightPlan.builder_type(),
|
||||
}
|
||||
try:
|
||||
|
||||
112
game/ato/flightplans/pretensecargo.py
Normal file
112
game/ato/flightplans/pretensecargo.py
Normal file
@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Type
|
||||
|
||||
from game.utils import feet
|
||||
from .ferry import FerryLayout
|
||||
from .ibuilder import IBuilder
|
||||
from .planningerror import PlanningError
|
||||
from .standard import StandardFlightPlan, StandardLayout
|
||||
from .waypointbuilder import WaypointBuilder
|
||||
from ...theater import OffMapSpawn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..flightwaypoint import FlightWaypoint
|
||||
|
||||
|
||||
PRETENSE_CARGO_FLIGHT_DISTANCE = 100000
|
||||
PRETENSE_CARGO_FLIGHT_HEADING_RANGE = 20
|
||||
|
||||
|
||||
class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]):
|
||||
@staticmethod
|
||||
def builder_type() -> Type[Builder]:
|
||||
return Builder
|
||||
|
||||
@property
|
||||
def tot_waypoint(self) -> FlightWaypoint:
|
||||
return self.layout.arrival
|
||||
|
||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None:
|
||||
# TOT planning isn't really useful for ferries. They're behind the front
|
||||
# lines so no need to wait for escorts or for other missions to complete.
|
||||
return None
|
||||
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def mission_begin_on_station_time(self) -> datetime | None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def mission_departure_time(self) -> datetime:
|
||||
return self.package.time_over_target
|
||||
|
||||
|
||||
class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]):
|
||||
def layout(self) -> FerryLayout:
|
||||
# Find the spawn location for off-map transport planes
|
||||
distance_to_flot = 0.0
|
||||
heading_from_flot = 0.0
|
||||
offmap_transport_cp_id = self.flight.departure.id
|
||||
for front_line_cp in self.coalition.game.theater.controlpoints:
|
||||
if isinstance(front_line_cp, OffMapSpawn):
|
||||
continue
|
||||
for front_line in self.coalition.game.theater.conflicts():
|
||||
if front_line_cp.captured == self.flight.coalition.player:
|
||||
if (
|
||||
front_line_cp.position.distance_to_point(front_line.position)
|
||||
> distance_to_flot
|
||||
):
|
||||
distance_to_flot = front_line_cp.position.distance_to_point(
|
||||
front_line.position
|
||||
)
|
||||
heading_from_flot = front_line.position.heading_between_point(
|
||||
front_line_cp.position
|
||||
)
|
||||
offmap_transport_cp_id = front_line_cp.id
|
||||
offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id(
|
||||
offmap_transport_cp_id
|
||||
)
|
||||
offmap_heading = random.randrange(
|
||||
int(heading_from_flot - PRETENSE_CARGO_FLIGHT_HEADING_RANGE),
|
||||
int(heading_from_flot + PRETENSE_CARGO_FLIGHT_HEADING_RANGE),
|
||||
)
|
||||
offmap_transport_spawn = offmap_transport_cp.position.point_from_heading(
|
||||
offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE
|
||||
)
|
||||
|
||||
altitude_is_agl = self.flight.is_helo
|
||||
altitude = (
|
||||
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
||||
if altitude_is_agl
|
||||
else self.flight.unit_type.preferred_patrol_altitude
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(self.flight)
|
||||
ferry_layout = FerryLayout(
|
||||
departure=builder.join(offmap_transport_spawn),
|
||||
nav_to=builder.nav_path(
|
||||
offmap_transport_spawn,
|
||||
self.flight.arrival.position,
|
||||
altitude,
|
||||
altitude_is_agl,
|
||||
),
|
||||
arrival=builder.land(self.flight.arrival),
|
||||
divert=builder.divert(self.flight.divert),
|
||||
bullseye=builder.bullseye(),
|
||||
nav_from=[],
|
||||
custom_waypoints=list(),
|
||||
)
|
||||
ferry_layout.departure = builder.join(offmap_transport_spawn)
|
||||
ferry_layout.nav_to.append(builder.join(offmap_transport_spawn))
|
||||
ferry_layout.nav_from.append(builder.join(offmap_transport_spawn))
|
||||
return ferry_layout
|
||||
|
||||
def build(self, dump_debug_info: bool = False) -> PretenseCargoFlightPlan:
|
||||
return PretenseCargoFlightPlan(self.flight, self.layout())
|
||||
@ -58,6 +58,7 @@ class FlightType(Enum):
|
||||
FERRY = "Ferry"
|
||||
AIR_ASSAULT = "Air Assault"
|
||||
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
|
||||
PRETENSE_CARGO = "Cargo Transport" # For Pretense campaign AI cargo planes
|
||||
ARMED_RECON = "Armed Recon"
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -124,5 +125,6 @@ class FlightType(Enum):
|
||||
FlightType.SWEEP: AirEntity.FIGHTER,
|
||||
FlightType.TARCAP: AirEntity.FIGHTER,
|
||||
FlightType.TRANSPORT: AirEntity.UTILITY,
|
||||
FlightType.PRETENSE_CARGO: AirEntity.UTILITY,
|
||||
FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING,
|
||||
}.get(self, AirEntity.UNSPECIFIED)
|
||||
|
||||
@ -23,6 +23,8 @@ class SquadronDefGenerator:
|
||||
def generate_for_task(
|
||||
self, task: FlightType, control_point: ControlPoint
|
||||
) -> Optional[SquadronDef]:
|
||||
settings = control_point.coalition.game.settings
|
||||
squadron_random_chance = settings.squadron_random_chance
|
||||
aircraft_choice: Optional[AircraftType] = None
|
||||
for aircraft in AircraftType.priority_list_for_task(task):
|
||||
if aircraft not in self.faction.all_aircrafts:
|
||||
@ -30,9 +32,9 @@ class SquadronDefGenerator:
|
||||
if not control_point.can_operate(aircraft):
|
||||
continue
|
||||
aircraft_choice = aircraft
|
||||
# 50/50 chance to keep looking for an aircraft that isn't as far up the
|
||||
# squadron_random_chance percent chance to keep looking for an aircraft that isn't as far up the
|
||||
# priority list to maintain some unit variety.
|
||||
if random.choice([True, False]):
|
||||
if squadron_random_chance >= random.randint(1, 100):
|
||||
break
|
||||
|
||||
if aircraft_choice is None:
|
||||
|
||||
11
game/game.py
11
game/game.py
@ -22,6 +22,7 @@ from game.models.game_stats import GameStats
|
||||
from game.plugins import LuaPluginManager
|
||||
from game.utils import Distance
|
||||
from . import naming, persistency
|
||||
from .ato import Flight
|
||||
from .ato.flighttype import FlightType
|
||||
from .campaignloader import CampaignAirWingConfig
|
||||
from .coalition import Coalition
|
||||
@ -148,6 +149,16 @@ class Game:
|
||||
self.blue.configure_default_air_wing(air_wing_config)
|
||||
self.red.configure_default_air_wing(air_wing_config)
|
||||
|
||||
# Side, control point, mission type
|
||||
self.pretense_ground_supply: dict[int, dict[str, List[str]]] = {1: {}, 2: {}}
|
||||
self.pretense_ground_assault: dict[int, dict[str, List[str]]] = {1: {}, 2: {}}
|
||||
self.pretense_air: dict[int, dict[str, dict[FlightType, List[str]]]] = {
|
||||
1: {},
|
||||
2: {},
|
||||
}
|
||||
self.pretense_air_groups: dict[str, Flight] = {}
|
||||
self.pretense_carrier_zones: List[str] = []
|
||||
|
||||
self.on_load(game_still_initializing=True)
|
||||
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import random
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
@ -19,7 +20,12 @@ from game.data.weapons import Pylon, WeaponType
|
||||
from game.missiongenerator.logisticsgenerator import LogisticsGenerator
|
||||
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
|
||||
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||
from game.radio.tacan import (
|
||||
TacanBand,
|
||||
TacanRegistry,
|
||||
TacanUsage,
|
||||
OutOfTacanChannelsError,
|
||||
)
|
||||
from game.runways import RunwayData
|
||||
from game.squadrons import Pilot
|
||||
from .aircraftbehavior import AircraftBehavior
|
||||
@ -210,9 +216,12 @@ class FlightGroupConfigurator:
|
||||
) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan):
|
||||
tacan = self.flight.tacan
|
||||
if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan:
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.Y, TacanUsage.AirToAir
|
||||
)
|
||||
try:
|
||||
tacan = self.tacan_registry.alloc_for_band(
|
||||
TacanBand.Y, TacanUsage.AirToAir
|
||||
)
|
||||
except OutOfTacanChannelsError:
|
||||
tacan = random.choice(list(self.tacan_registry.allocated_channels))
|
||||
else:
|
||||
tacan = self.flight.tacan
|
||||
self.mission_data.tankers.append(
|
||||
|
||||
@ -15,6 +15,7 @@ from dcs.planes import (
|
||||
C_101CC,
|
||||
Su_33,
|
||||
MiG_15bis,
|
||||
M_2000C,
|
||||
)
|
||||
from dcs.point import PointAction
|
||||
from dcs.ships import KUZNECOW
|
||||
@ -36,7 +37,7 @@ from game.missiongenerator.missiondata import MissionData
|
||||
from game.naming import namegen
|
||||
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
|
||||
from game.utils import feet, meters
|
||||
from pydcs_extensions import A_4E_C
|
||||
from pydcs_extensions import A_4E_C, VSN_F4B, VSN_F4C
|
||||
|
||||
WARM_START_HELI_ALT = meters(500)
|
||||
WARM_START_ALTITUDE = meters(3000)
|
||||
@ -496,7 +497,7 @@ class FlightGroupSpawner:
|
||||
) -> Optional[FlyingGroup[Any]]:
|
||||
is_airbase = False
|
||||
is_roadbase = False
|
||||
ground_spawn = None
|
||||
ground_spawn: Optional[Tuple[StaticGroup, Point]] = None
|
||||
|
||||
if not is_large and len(self.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||
@ -519,6 +520,8 @@ class FlightGroupSpawner:
|
||||
group.points[0].type = "TakeOffGround"
|
||||
group.units[0].heading = ground_spawn[0].units[0].heading
|
||||
|
||||
self._remove_invisible_farps_if_requested(cp, ground_spawn[0], group)
|
||||
|
||||
# Hot start aircraft which require ground power to start, when ground power
|
||||
# trucks have been disabled for performance reasons
|
||||
ground_power_available = (
|
||||
@ -529,10 +532,31 @@ class FlightGroupSpawner:
|
||||
and self.flight.coalition.game.settings.ground_start_ground_power_trucks_roadbase
|
||||
)
|
||||
|
||||
if self.start_type is not StartType.COLD or (
|
||||
not ground_power_available
|
||||
and self.flight.unit_type.dcs_unit_type
|
||||
in [A_4E_C, F_5E_3, F_86F_Sabre, MiG_15bis, F_14A_135_GR, F_14B, C_101CC]
|
||||
# Also hot start aircraft which require ground crew support (ground air or chock removal)
|
||||
# which might not be available at roadbases
|
||||
if (
|
||||
self.start_type is not StartType.COLD
|
||||
or (
|
||||
not ground_power_available
|
||||
and self.flight.unit_type.dcs_unit_type
|
||||
in [
|
||||
A_4E_C,
|
||||
F_86F_Sabre,
|
||||
MiG_15bis,
|
||||
F_14A_135_GR,
|
||||
F_14B,
|
||||
C_101CC,
|
||||
]
|
||||
)
|
||||
or (
|
||||
self.flight.unit_type.dcs_unit_type
|
||||
in [
|
||||
F_5E_3,
|
||||
M_2000C,
|
||||
VSN_F4B,
|
||||
VSN_F4C,
|
||||
]
|
||||
)
|
||||
):
|
||||
group.points[0].action = PointAction.FromGroundAreaHot
|
||||
group.points[0].type = "TakeOffGroundHot"
|
||||
@ -556,12 +580,33 @@ class FlightGroupSpawner:
|
||||
ground_spawn[0].x, ground_spawn[0].y, terrain=terrain
|
||||
)
|
||||
group.units[1 + i].heading = ground_spawn[0].units[0].heading
|
||||
|
||||
self._remove_invisible_farps_if_requested(cp, ground_spawn[0])
|
||||
except IndexError as ex:
|
||||
raise RuntimeError(
|
||||
f"Not enough ground spawn slots available at {cp}"
|
||||
raise NoParkingSlotError(
|
||||
f"Not enough STOL slots available at {cp}"
|
||||
) from ex
|
||||
return group
|
||||
|
||||
def _remove_invisible_farps_if_requested(
|
||||
self,
|
||||
cp: ControlPoint,
|
||||
ground_spawn: StaticGroup,
|
||||
group: Optional[FlyingGroup[Any]] = None,
|
||||
) -> None:
|
||||
if (
|
||||
cp.coalition.game.settings.ground_start_airbase_statics_farps_remove
|
||||
and isinstance(cp, Airfield)
|
||||
):
|
||||
# Remove invisible FARPs from airfields because they are unnecessary
|
||||
neutral_country = self.mission.country(
|
||||
cp.coalition.game.neutral_country.name
|
||||
)
|
||||
neutral_country.remove_static_group(ground_spawn)
|
||||
if group:
|
||||
group.points[0].link_unit = None
|
||||
group.points[0].helipad_id = None
|
||||
|
||||
def dcs_start_type(self) -> DcsStartType:
|
||||
if self.start_type is StartType.RUNWAY:
|
||||
return DcsStartType.Runway
|
||||
|
||||
@ -52,6 +52,8 @@ class CarrierInfo(UnitInfo):
|
||||
"""Carrier information."""
|
||||
|
||||
tacan: TacanChannel
|
||||
icls_channel: int | None
|
||||
link4_freq: RadioFrequency | None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@ -67,6 +67,7 @@ from game.theater import (
|
||||
TheaterGroundObject,
|
||||
TheaterUnit,
|
||||
NavalControlPoint,
|
||||
Airfield,
|
||||
)
|
||||
from game.theater.theatergroundobject import (
|
||||
CarrierGroundObject,
|
||||
@ -297,11 +298,9 @@ class GroundObjectGenerator:
|
||||
# All alive Ships
|
||||
ship_units.append(unit)
|
||||
if vehicle_units:
|
||||
vg = self.create_vehicle_group(group.group_name, vehicle_units)
|
||||
vg.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||
self.create_vehicle_group(group.group_name, vehicle_units)
|
||||
if ship_units:
|
||||
sg = self.create_ship_group(group.group_name, ship_units)
|
||||
sg.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||
self.create_ship_group(group.group_name, ship_units)
|
||||
|
||||
def create_vehicle_group(
|
||||
self, group_name: str, units: list[TheaterUnit]
|
||||
@ -333,6 +332,7 @@ class GroundObjectGenerator:
|
||||
self._register_theater_unit(unit, vehicle_group.units[-1])
|
||||
if vehicle_group is None:
|
||||
raise RuntimeError(f"Error creating VehicleGroup for {group_name}")
|
||||
vehicle_group.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||
return vehicle_group
|
||||
|
||||
def create_ship_group(
|
||||
@ -369,6 +369,7 @@ class GroundObjectGenerator:
|
||||
self._register_theater_unit(unit, ship_group.units[-1])
|
||||
if ship_group is None:
|
||||
raise RuntimeError(f"Error creating ShipGroup for {group_name}")
|
||||
ship_group.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||
return ship_group
|
||||
|
||||
def create_static_group(self, unit: TheaterUnit) -> None:
|
||||
@ -645,6 +646,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
callsign=tacan_callsign,
|
||||
freq=atc,
|
||||
tacan=tacan,
|
||||
icls_channel=icls,
|
||||
link4_freq=link4,
|
||||
blue=self.control_point.captured,
|
||||
)
|
||||
)
|
||||
@ -844,6 +847,20 @@ class HelipadGenerator:
|
||||
else:
|
||||
self.helipads.append(sg)
|
||||
|
||||
if self.game.position_culled(helipad):
|
||||
cull_farp_statics = True
|
||||
if self.cp.coalition.player:
|
||||
for package in self.cp.coalition.ato.packages:
|
||||
for flight in package.flights:
|
||||
if flight.squadron.location == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
elif flight.divert and flight.divert == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
else:
|
||||
cull_farp_statics = False
|
||||
|
||||
warehouse = Airport(
|
||||
pad.position,
|
||||
self.m.terrain,
|
||||
@ -852,30 +869,31 @@ class HelipadGenerator:
|
||||
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||
|
||||
# Generate a FARP Ammo and Fuel stack for each pad
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=Fortification.FARP_Fuel_Depot,
|
||||
position=pad.position.point_from_heading(helipad.heading.degrees, 35),
|
||||
heading=pad.heading + 180,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||
position=pad.position.point_from_heading(
|
||||
helipad.heading.degrees, 35
|
||||
).point_from_heading(helipad.heading.degrees + 90, 10),
|
||||
heading=pad.heading + 90,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ws"),
|
||||
_type=Fortification.Windsock,
|
||||
position=helipad.point_from_heading(helipad.heading.degrees + 45, 35),
|
||||
heading=pad.heading,
|
||||
)
|
||||
if not cull_farp_statics:
|
||||
# Generate a FARP Ammo and Fuel stack for each pad
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=Fortification.FARP_Fuel_Depot,
|
||||
position=pad.position.point_from_heading(helipad.heading.degrees, 35),
|
||||
heading=pad.heading + 180,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||
position=pad.position.point_from_heading(
|
||||
helipad.heading.degrees, 35
|
||||
).point_from_heading(helipad.heading.degrees + 90, 10),
|
||||
heading=pad.heading + 90,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ws"),
|
||||
_type=Fortification.Windsock,
|
||||
position=helipad.point_from_heading(helipad.heading.degrees + 45, 35),
|
||||
heading=pad.heading,
|
||||
)
|
||||
|
||||
def append_helipad(
|
||||
self,
|
||||
@ -952,61 +970,88 @@ class GroundSpawnRoadbaseGenerator:
|
||||
country.id
|
||||
)
|
||||
|
||||
# Generate ammo truck/farp and fuel truck/stack for each pad
|
||||
if self.game.settings.ground_start_trucks_roadbase:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=tanker_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=ammo_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 10),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
|
||||
self.cp, Airfield
|
||||
):
|
||||
cull_farp_statics = True
|
||||
elif self.game.position_culled(ground_spawn[0]):
|
||||
cull_farp_statics = True
|
||||
if self.cp.coalition.player:
|
||||
for package in self.cp.coalition.ato.packages:
|
||||
for flight in package.flights:
|
||||
if flight.squadron.location == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
elif flight.divert and flight.divert == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
else:
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=Fortification.FARP_Fuel_Depot,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
),
|
||||
heading=pad.heading + 270,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 10),
|
||||
heading=pad.heading + 180,
|
||||
)
|
||||
if self.game.settings.ground_start_ground_power_trucks_roadbase:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_power"),
|
||||
_type=power_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 20),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
cull_farp_statics = False
|
||||
|
||||
warehouse = Airport(
|
||||
pad.position,
|
||||
self.m.terrain,
|
||||
).dict()
|
||||
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
|
||||
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||
|
||||
if not cull_farp_statics:
|
||||
# Generate ammo truck/farp and fuel truck/stack for each pad
|
||||
if self.game.settings.ground_start_trucks_roadbase:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_fuel"),
|
||||
_type=tanker_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=ammo_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 10),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
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(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
),
|
||||
heading=pad.heading + 270,
|
||||
)
|
||||
self.m.static_group(
|
||||
country=country,
|
||||
name=(name + "_ammo"),
|
||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 10),
|
||||
heading=pad.heading + 180,
|
||||
)
|
||||
if self.game.settings.ground_start_ground_power_trucks_roadbase:
|
||||
self.m.vehicle_group(
|
||||
country=country,
|
||||
name=(name + "_power"),
|
||||
_type=power_truck_type,
|
||||
position=pad.position.point_from_heading(
|
||||
ground_spawn[0].heading.degrees + 90, 35
|
||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 20),
|
||||
group_size=1,
|
||||
heading=pad.heading + 315,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
def generate(self) -> None:
|
||||
try:
|
||||
@ -1069,6 +1114,14 @@ class GroundSpawnLargeGenerator:
|
||||
country.id
|
||||
)
|
||||
|
||||
warehouse = Airport(
|
||||
pad.position,
|
||||
self.m.terrain,
|
||||
).dict()
|
||||
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
|
||||
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||
|
||||
# Generate a FARP Ammo and Fuel stack for each pad
|
||||
if self.game.settings.ground_start_trucks:
|
||||
self.m.vehicle_group(
|
||||
@ -1186,61 +1239,88 @@ class GroundSpawnGenerator:
|
||||
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, 35
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
|
||||
self.cp, Airfield
|
||||
):
|
||||
cull_farp_statics = True
|
||||
elif self.game.position_culled(vtol_pad[0]):
|
||||
cull_farp_statics = True
|
||||
if self.cp.coalition.player:
|
||||
for package in self.cp.coalition.ato.packages:
|
||||
for flight in package.flights:
|
||||
if flight.squadron.location == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
elif flight.divert and flight.divert == self.cp:
|
||||
cull_farp_statics = False
|
||||
break
|
||||
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, 45
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
cull_farp_statics = False
|
||||
|
||||
if not cull_farp_statics:
|
||||
warehouse = Airport(
|
||||
pad.position,
|
||||
self.m.terrain,
|
||||
).dict()
|
||||
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
|
||||
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||
|
||||
# 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, 35
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
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, 45
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
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, 35
|
||||
),
|
||||
group_size=1,
|
||||
heading=pad.heading + 45,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
def generate(self) -> None:
|
||||
try:
|
||||
|
||||
@ -154,6 +154,10 @@ def save_dir() -> Path:
|
||||
return base_path() / "Retribution" / "Saves"
|
||||
|
||||
|
||||
def pre_pretense_backups_dir() -> Path:
|
||||
return save_dir() / "PrePretenseBackups"
|
||||
|
||||
|
||||
def server_port() -> int:
|
||||
global _server_port
|
||||
return _server_port
|
||||
|
||||
1073
game/pretense/pretenseaircraftgenerator.py
Normal file
1073
game/pretense/pretenseaircraftgenerator.py
Normal file
File diff suppressed because it is too large
Load Diff
155
game/pretense/pretenseflightgroupconfigurator.py
Normal file
155
game/pretense/pretenseflightgroupconfigurator.py
Normal file
@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
from dcs import Mission, Point
|
||||
from dcs.flyingunit import FlyingUnit
|
||||
from dcs.unitgroup import FlyingGroup
|
||||
|
||||
from game.ato import Flight, FlightType
|
||||
from game.ato.flightmember import FlightMember
|
||||
from game.data.weapons import Pylon
|
||||
from game.lasercodes.lasercoderegistry import LaserCodeRegistry
|
||||
from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior
|
||||
from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter
|
||||
from game.missiongenerator.aircraft.bingoestimator import BingoEstimator
|
||||
from game.missiongenerator.aircraft.flightdata import FlightData
|
||||
from game.missiongenerator.aircraft.flightgroupconfigurator import (
|
||||
FlightGroupConfigurator,
|
||||
)
|
||||
from game.missiongenerator.aircraft.waypoints import WaypointGenerator
|
||||
from game.missiongenerator.missiondata import MissionData
|
||||
from game.radio.radios import RadioRegistry
|
||||
from game.radio.tacan import (
|
||||
TacanRegistry,
|
||||
)
|
||||
from game.runways import RunwayData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
class PretenseFlightGroupConfigurator(FlightGroupConfigurator):
|
||||
def __init__(
|
||||
self,
|
||||
flight: Flight,
|
||||
group: FlyingGroup[Any],
|
||||
game: Game,
|
||||
mission: Mission,
|
||||
time: datetime,
|
||||
radio_registry: RadioRegistry,
|
||||
tacan_registry: TacanRegistry,
|
||||
mission_data: MissionData,
|
||||
dynamic_runways: dict[str, RunwayData],
|
||||
use_client: bool,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
flight,
|
||||
group,
|
||||
game,
|
||||
mission,
|
||||
time,
|
||||
radio_registry,
|
||||
tacan_registry,
|
||||
mission_data,
|
||||
dynamic_runways,
|
||||
use_client,
|
||||
)
|
||||
|
||||
self.flight = flight
|
||||
self.group = group
|
||||
self.game = game
|
||||
self.mission = mission
|
||||
self.time = time
|
||||
self.radio_registry = radio_registry
|
||||
self.tacan_registry = tacan_registry
|
||||
self.mission_data = mission_data
|
||||
self.dynamic_runways = dynamic_runways
|
||||
self.use_client = use_client
|
||||
|
||||
def configure(self) -> FlightData:
|
||||
AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group)
|
||||
AircraftPainter(self.flight, self.group).apply_livery()
|
||||
self.setup_props()
|
||||
self.setup_payloads()
|
||||
self.setup_fuel()
|
||||
flight_channel = self.setup_radios()
|
||||
|
||||
laser_codes: list[Optional[int]] = []
|
||||
for unit, pilot in zip(self.group.units, self.flight.roster.members):
|
||||
self.configure_flight_member(unit, pilot, laser_codes)
|
||||
|
||||
divert = None
|
||||
if self.flight.divert is not None:
|
||||
divert = self.flight.divert.active_runway(
|
||||
self.game.theater, self.game.conditions, self.dynamic_runways
|
||||
)
|
||||
|
||||
mission_start_time, waypoints = WaypointGenerator(
|
||||
self.flight,
|
||||
self.group,
|
||||
self.mission,
|
||||
self.time,
|
||||
self.game.settings,
|
||||
self.mission_data,
|
||||
).create_waypoints()
|
||||
|
||||
divert_position: Point | None = None
|
||||
if self.flight.divert is not None:
|
||||
divert_position = self.flight.divert.position
|
||||
bingo_estimator = BingoEstimator(
|
||||
self.flight.unit_type.fuel_consumption,
|
||||
self.flight.arrival.position,
|
||||
divert_position,
|
||||
self.flight.flight_plan.waypoints,
|
||||
)
|
||||
|
||||
self.group.uncontrolled = False
|
||||
|
||||
return FlightData(
|
||||
package=self.flight.package,
|
||||
aircraft_type=self.flight.unit_type,
|
||||
squadron=self.flight.squadron,
|
||||
flight_type=self.flight.flight_type,
|
||||
units=self.group.units,
|
||||
size=len(self.group.units),
|
||||
friendly=self.flight.departure.captured,
|
||||
departure_delay=mission_start_time,
|
||||
departure=self.flight.departure.active_runway(
|
||||
self.game.theater, self.game.conditions, self.dynamic_runways
|
||||
),
|
||||
arrival=self.flight.arrival.active_runway(
|
||||
self.game.theater, self.game.conditions, self.dynamic_runways
|
||||
),
|
||||
divert=divert,
|
||||
waypoints=waypoints,
|
||||
intra_flight_channel=flight_channel,
|
||||
bingo_fuel=bingo_estimator.estimate_bingo(),
|
||||
joker_fuel=bingo_estimator.estimate_joker(),
|
||||
custom_name=self.flight.custom_name,
|
||||
laser_codes=laser_codes,
|
||||
)
|
||||
|
||||
def setup_payloads(self) -> None:
|
||||
for unit, member in zip(self.group.units, self.flight.iter_members()):
|
||||
self.setup_payload(unit, member)
|
||||
|
||||
def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None:
|
||||
unit.pylons.clear()
|
||||
|
||||
loadout = member.loadout
|
||||
|
||||
if self.flight.flight_type == FlightType.SEAD:
|
||||
loadout = member.loadout.default_for_task_and_aircraft(
|
||||
FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type
|
||||
)
|
||||
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date)
|
||||
|
||||
for pylon_number, weapon in loadout.pylons.items():
|
||||
if weapon is None:
|
||||
continue
|
||||
pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number)
|
||||
pylon.equip(unit, weapon)
|
||||
261
game/pretense/pretenseflightgroupspawner.py
Normal file
261
game/pretense/pretenseflightgroupspawner.py
Normal file
@ -0,0 +1,261 @@
|
||||
import logging
|
||||
import random
|
||||
from typing import Any, Tuple
|
||||
|
||||
from dcs import Mission
|
||||
from dcs.country import Country
|
||||
from dcs.mapping import Vector2, Point
|
||||
from dcs.terrain import NoParkingSlotError, TheChannel, Falklands
|
||||
from dcs.terrain.falklands.airports import San_Carlos_FOB, Goose_Green, Gull_Point
|
||||
from dcs.terrain.thechannel.airports import Manston
|
||||
from dcs.unitgroup import (
|
||||
FlyingGroup,
|
||||
ShipGroup,
|
||||
StaticGroup,
|
||||
)
|
||||
|
||||
from game.ato import Flight
|
||||
from game.ato.flightstate import InFlight
|
||||
from game.ato.starttype import StartType
|
||||
from game.missiongenerator.aircraft.flightgroupspawner import (
|
||||
FlightGroupSpawner,
|
||||
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL,
|
||||
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL,
|
||||
STACK_SEPARATION,
|
||||
)
|
||||
from game.missiongenerator.missiondata import MissionData
|
||||
from game.naming import NameGenerator
|
||||
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint
|
||||
|
||||
|
||||
class PretenseNameGenerator(NameGenerator):
|
||||
@classmethod
|
||||
def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str:
|
||||
cls.aircraft_number += 1
|
||||
cp_name_trimmed = cls.pretense_trimmed_cp_name(cp.name)
|
||||
return "{}-{}-{}".format(
|
||||
cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def pretense_trimmed_cp_name(cls, cp_name: str) -> str:
|
||||
cp_name_alnum = "".join([i for i in cp_name.lower() if i.isalnum()])
|
||||
cp_name_trimmed = cp_name_alnum.lstrip("1 2 3 4 5 6 7 8 9 0")
|
||||
cp_name_trimmed = cp_name_trimmed.replace("ä", "a")
|
||||
cp_name_trimmed = cp_name_trimmed.replace("ö", "o")
|
||||
cp_name_trimmed = cp_name_trimmed.replace("ø", "o")
|
||||
return cp_name_trimmed
|
||||
|
||||
|
||||
namegen = PretenseNameGenerator
|
||||
# Air-start AI aircraft which are faster than this on WWII terrains
|
||||
# 1000 km/h is just above the max speed of the Harrier and Su-25,
|
||||
# so they will still start normally from grass and dirt strips
|
||||
WW2_TERRAIN_SUPERSONIC_AI_AIRSTART_SPEED = 1000
|
||||
|
||||
|
||||
class PretenseFlightGroupSpawner(FlightGroupSpawner):
|
||||
def __init__(
|
||||
self,
|
||||
flight: Flight,
|
||||
country: Country,
|
||||
mission: Mission,
|
||||
helipads: dict[ControlPoint, list[StaticGroup]],
|
||||
ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns_large: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]],
|
||||
mission_data: MissionData,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
flight,
|
||||
country,
|
||||
mission,
|
||||
helipads,
|
||||
ground_spawns_roadbase,
|
||||
ground_spawns_large,
|
||||
ground_spawns,
|
||||
mission_data,
|
||||
)
|
||||
|
||||
self.flight = flight
|
||||
self.country = country
|
||||
self.mission = mission
|
||||
self.helipads = helipads
|
||||
self.ground_spawns_roadbase = ground_spawns_roadbase
|
||||
self.ground_spawns = ground_spawns
|
||||
self.mission_data = mission_data
|
||||
|
||||
def insert_into_pretense(self, name: str) -> None:
|
||||
cp = self.flight.departure
|
||||
is_player = True
|
||||
cp_side = (
|
||||
2
|
||||
if self.flight.coalition
|
||||
== self.flight.coalition.game.coalition_for(is_player)
|
||||
else 1
|
||||
)
|
||||
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name)
|
||||
|
||||
if self.flight.client_count == 0:
|
||||
self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][
|
||||
self.flight.flight_type
|
||||
].append(name)
|
||||
try:
|
||||
self.flight.coalition.game.pretense_air_groups[name] = self.flight
|
||||
except AttributeError:
|
||||
self.flight.coalition.game.pretense_air_groups = {}
|
||||
self.flight.coalition.game.pretense_air_groups[name] = self.flight
|
||||
|
||||
def generate_flight_at_departure(self) -> FlyingGroup[Any]:
|
||||
cp = self.flight.departure
|
||||
name = namegen.next_pretense_aircraft_name(cp, self.flight)
|
||||
|
||||
try:
|
||||
if self.start_type is StartType.IN_FLIGHT:
|
||||
self.insert_into_pretense(name)
|
||||
group = self._generate_over_departure(name, cp)
|
||||
return group
|
||||
elif isinstance(cp, NavalControlPoint):
|
||||
group_name = cp.get_carrier_group_name()
|
||||
carrier_group = self.mission.find_group(group_name)
|
||||
if not isinstance(carrier_group, ShipGroup):
|
||||
raise RuntimeError(
|
||||
f"Carrier group {carrier_group} is a "
|
||||
f"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
||||
)
|
||||
self.insert_into_pretense(name)
|
||||
return self._generate_at_group(name, carrier_group)
|
||||
elif isinstance(cp, Fob):
|
||||
is_heli = self.flight.squadron.aircraft.helicopter
|
||||
is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable
|
||||
if not is_heli and not is_vtol and not cp.has_ground_spawns:
|
||||
raise RuntimeError(
|
||||
f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots."
|
||||
)
|
||||
pilot_count = len(self.flight.roster.members)
|
||||
if (
|
||||
not is_heli
|
||||
and self.flight.roster.player_count != pilot_count
|
||||
and not self.flight.coalition.game.settings.ground_start_ai_planes
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Fixed-wing aircraft at {cp} must be piloted by humans exclusively because"
|
||||
f' the "AI fixed-wing aircraft can use roadbases / bases with only ground'
|
||||
f' spawns" setting is currently disabled.'
|
||||
)
|
||||
if cp.has_helipads and (is_heli or is_vtol):
|
||||
self.insert_into_pretense(name)
|
||||
pad_group = self._generate_at_cp_helipad(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli):
|
||||
self.insert_into_pretense(name)
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
raise NoParkingSlotError
|
||||
elif isinstance(cp, Airfield):
|
||||
is_heli = self.flight.squadron.aircraft.helicopter
|
||||
is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable
|
||||
if cp.has_helipads and is_heli:
|
||||
self.insert_into_pretense(name)
|
||||
pad_group = self._generate_at_cp_helipad(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
# Air-start supersonic AI aircraft if the campaign is being flown in a WWII terrain
|
||||
# This will improve these terrains' use in cold war campaigns
|
||||
if isinstance(cp.theater.terrain, TheChannel) and not isinstance(
|
||||
cp.dcs_airport, Manston
|
||||
):
|
||||
if (
|
||||
self.flight.client_count == 0
|
||||
and self.flight.squadron.aircraft.max_speed.speed_in_kph
|
||||
> WW2_TERRAIN_SUPERSONIC_AI_AIRSTART_SPEED
|
||||
):
|
||||
self.insert_into_pretense(name)
|
||||
return self._generate_over_departure(name, cp)
|
||||
# Air-start AI fixed wing (non-VTOL) aircraft if the campaign is being flown in the South Atlantic terrain and
|
||||
# the airfield is one of the Harrier-only ones in East Falklands.
|
||||
# This will help avoid AI aircraft from smashing into the end of the runway and exploding.
|
||||
if isinstance(cp.theater.terrain, Falklands) and (
|
||||
isinstance(cp.dcs_airport, San_Carlos_FOB)
|
||||
or isinstance(cp.dcs_airport, Goose_Green)
|
||||
or isinstance(cp.dcs_airport, Gull_Point)
|
||||
):
|
||||
if self.flight.client_count == 0 and is_vtol:
|
||||
self.insert_into_pretense(name)
|
||||
return self._generate_over_departure(name, cp)
|
||||
if (
|
||||
cp.has_ground_spawns
|
||||
and len(self.ground_spawns[cp])
|
||||
+ len(self.ground_spawns_roadbase[cp])
|
||||
>= self.flight.count
|
||||
and (self.flight.client_count > 0 or is_heli)
|
||||
):
|
||||
self.insert_into_pretense(name)
|
||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||
if pad_group is not None:
|
||||
return pad_group
|
||||
self.insert_into_pretense(name)
|
||||
return self._generate_at_airfield(name, cp)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})"
|
||||
)
|
||||
except NoParkingSlotError:
|
||||
# Generated when there is no place on Runway or on Parking Slots
|
||||
if self.flight.client_count > 0:
|
||||
# Don't generate player airstarts
|
||||
logging.warning(
|
||||
"No room on runway or parking slots. Not generating a player air-start."
|
||||
)
|
||||
raise NoParkingSlotError
|
||||
else:
|
||||
logging.warning(
|
||||
"No room on runway or parking slots. Starting from the air."
|
||||
)
|
||||
self.flight.start_type = StartType.IN_FLIGHT
|
||||
self.insert_into_pretense(name)
|
||||
group = self._generate_over_departure(name, cp)
|
||||
return group
|
||||
|
||||
def generate_mid_mission(self) -> FlyingGroup[Any]:
|
||||
assert isinstance(self.flight.state, InFlight)
|
||||
name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight)
|
||||
|
||||
speed = self.flight.state.estimate_speed()
|
||||
pos = self.flight.state.estimate_position()
|
||||
pos += Vector2(random.randint(100, 1000), random.randint(100, 1000))
|
||||
alt, alt_type = self.flight.state.estimate_altitude()
|
||||
cp = self.flight.squadron.location.id
|
||||
|
||||
if cp not in self.mission_data.cp_stack:
|
||||
self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
|
||||
|
||||
# We don't know where the ground is, so just make sure that any aircraft
|
||||
# spawning at an MSL altitude is spawned at some minimum altitude.
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/1941
|
||||
if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL:
|
||||
alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL
|
||||
|
||||
# Set a minimum AGL value for 'alt' if needed,
|
||||
# otherwise planes might crash in trees and stuff.
|
||||
if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]:
|
||||
alt = self.mission_data.cp_stack[cp]
|
||||
self.mission_data.cp_stack[cp] += STACK_SEPARATION
|
||||
|
||||
self.insert_into_pretense(name)
|
||||
group = self.mission.flight_group(
|
||||
country=self.country,
|
||||
name=name,
|
||||
aircraft_type=self.flight.unit_type.dcs_unit_type,
|
||||
airport=None,
|
||||
position=pos,
|
||||
altitude=alt.meters,
|
||||
speed=speed.kph,
|
||||
maintask=None,
|
||||
group_size=self.flight.count,
|
||||
)
|
||||
|
||||
group.points[0].alt_type = alt_type
|
||||
return group
|
||||
2059
game/pretense/pretenseluagenerator.py
Normal file
2059
game/pretense/pretenseluagenerator.py
Normal file
File diff suppressed because it is too large
Load Diff
296
game/pretense/pretensemissiongenerator.py
Normal file
296
game/pretense/pretensemissiongenerator.py
Normal file
@ -0,0 +1,296 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import pickle
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import dcs.lua
|
||||
from dcs import Mission, Point
|
||||
from dcs.coalition import Coalition
|
||||
from dcs.countries import (
|
||||
country_dict,
|
||||
CombinedJointTaskForcesBlue,
|
||||
CombinedJointTaskForcesRed,
|
||||
)
|
||||
from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAction
|
||||
|
||||
from game.lasercodes.lasercoderegistry import LaserCodeRegistry
|
||||
from game.missiongenerator.convoygenerator import ConvoyGenerator
|
||||
from game.missiongenerator.environmentgenerator import EnvironmentGenerator
|
||||
from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator
|
||||
from game.missiongenerator.frontlineconflictdescription import (
|
||||
FrontLineConflictDescription,
|
||||
)
|
||||
from game.missiongenerator.missiondata import MissionData, JtacInfo
|
||||
from game.missiongenerator.tgogenerator import TgoGenerator
|
||||
from game.missiongenerator.visualsgenerator import VisualsGenerator
|
||||
from game.naming import namegen
|
||||
from game.persistency import pre_pretense_backups_dir
|
||||
from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator
|
||||
from game.radio.radios import RadioRegistry
|
||||
from game.radio.tacan import TacanRegistry
|
||||
from game.theater.bullseye import Bullseye
|
||||
from game.unitmap import UnitMap
|
||||
from .pretenseluagenerator import PretenseLuaGenerator
|
||||
from .pretensetgogenerator import PretenseTgoGenerator
|
||||
from .pretensetriggergenerator import PretenseTriggerGenerator
|
||||
from ..ato.airtaaskingorder import AirTaskingOrder
|
||||
from ..callsigns import callsign_for_support_unit
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..missiongenerator import MissionGenerator
|
||||
from ..theater import Airfield
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
class PretenseMissionGenerator(MissionGenerator):
|
||||
def __init__(self, game: Game, time: datetime) -> None:
|
||||
super().__init__(game, time)
|
||||
self.game = game
|
||||
self.time = time
|
||||
self.mission = Mission(game.theater.terrain)
|
||||
self.unit_map = UnitMap()
|
||||
|
||||
self.mission_data = MissionData()
|
||||
|
||||
self.laser_code_registry = LaserCodeRegistry()
|
||||
self.radio_registry = RadioRegistry()
|
||||
self.tacan_registry = TacanRegistry()
|
||||
|
||||
self.generation_started = False
|
||||
|
||||
self.p_country = country_dict[self.game.blue.faction.country.id]()
|
||||
self.e_country = country_dict[self.game.red.faction.country.id]()
|
||||
|
||||
with open("resources/default_options.lua", "r", encoding="utf-8") as f:
|
||||
options = dcs.lua.loads(f.read())["options"]
|
||||
ext_view = game.settings.external_views_allowed
|
||||
options["miscellaneous"]["f11_free_camera"] = ext_view
|
||||
options["difficulty"]["spectatorExternalViews"] = ext_view
|
||||
self.mission.options.load_from_dict(options)
|
||||
|
||||
def generate_miz(self, output: Path) -> UnitMap:
|
||||
game_backup_pickle = pickle.dumps(self.game)
|
||||
path = pre_pretense_backups_dir()
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
path /= f".pre-pretense-backup.retribution"
|
||||
try:
|
||||
with open(path, "wb") as f:
|
||||
pickle.dump(self.game, f)
|
||||
except:
|
||||
logging.error(f"Unable to save Pretense pre-generation backup to {path}")
|
||||
|
||||
if self.generation_started:
|
||||
raise RuntimeError(
|
||||
"Mission has already begun generating. To reset, create a new "
|
||||
"MissionSimulation."
|
||||
)
|
||||
self.generation_started = True
|
||||
|
||||
self.game.pretense_ground_supply = {1: {}, 2: {}}
|
||||
self.game.pretense_ground_assault = {1: {}, 2: {}}
|
||||
self.game.pretense_air = {1: {}, 2: {}}
|
||||
|
||||
self.setup_mission_coalitions()
|
||||
self.add_airfields_to_unit_map()
|
||||
self.initialize_registries()
|
||||
|
||||
EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate()
|
||||
|
||||
tgo_generator = PretenseTgoGenerator(
|
||||
self.mission,
|
||||
self.game,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.unit_map,
|
||||
self.mission_data,
|
||||
)
|
||||
tgo_generator.generate()
|
||||
|
||||
ConvoyGenerator(self.mission, self.game, self.unit_map).generate()
|
||||
|
||||
# Generate ground conflicts first so the JTACs get the first laser code (1688)
|
||||
# rather than the first player flight with a TGP.
|
||||
self.generate_ground_conflicts()
|
||||
self.generate_air_units(tgo_generator)
|
||||
|
||||
for cp in self.game.theater.controlpoints:
|
||||
if (
|
||||
self.game.settings.ground_start_airbase_statics_farps_remove
|
||||
and isinstance(cp, Airfield)
|
||||
):
|
||||
while len(tgo_generator.ground_spawns[cp]) > 0:
|
||||
ground_spawn = tgo_generator.ground_spawns[cp].pop()
|
||||
# Remove invisible FARPs from airfields because they are unnecessary
|
||||
neutral_country = self.mission.country(
|
||||
cp.coalition.game.neutral_country.name
|
||||
)
|
||||
neutral_country.remove_static_group(ground_spawn[0])
|
||||
while len(tgo_generator.ground_spawns_roadbase[cp]) > 0:
|
||||
ground_spawn = tgo_generator.ground_spawns_roadbase[cp].pop()
|
||||
# Remove invisible FARPs from airfields because they are unnecessary
|
||||
neutral_country = self.mission.country(
|
||||
cp.coalition.game.neutral_country.name
|
||||
)
|
||||
neutral_country.remove_static_group(ground_spawn[0])
|
||||
|
||||
self.mission.triggerrules.triggers.clear()
|
||||
PretenseTriggerGenerator(self.mission, self.game).generate()
|
||||
ForcedOptionsGenerator(self.mission, self.game).generate()
|
||||
VisualsGenerator(self.mission, self.game).generate()
|
||||
PretenseLuaGenerator(self.game, self.mission, self.mission_data).generate()
|
||||
|
||||
self.setup_combined_arms()
|
||||
|
||||
self.notify_info_generators()
|
||||
|
||||
# TODO: Shouldn't this be first?
|
||||
namegen.reset_numbers()
|
||||
self.mission.save(output)
|
||||
|
||||
print(
|
||||
f"Loading pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}"
|
||||
)
|
||||
self.game = pickle.loads(game_backup_pickle)
|
||||
print(
|
||||
f"Loaded pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}"
|
||||
)
|
||||
self.game.on_load()
|
||||
|
||||
return self.unit_map
|
||||
|
||||
def setup_mission_coalitions(self) -> None:
|
||||
self.mission.coalition["blue"] = Coalition(
|
||||
"blue", bullseye=self.game.blue.bullseye.to_pydcs()
|
||||
)
|
||||
self.mission.coalition["red"] = Coalition(
|
||||
"red", bullseye=self.game.red.bullseye.to_pydcs()
|
||||
)
|
||||
self.mission.coalition["neutrals"] = Coalition(
|
||||
"neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs()
|
||||
)
|
||||
|
||||
self.mission.coalition["blue"].add_country(self.p_country)
|
||||
self.mission.coalition["red"].add_country(self.e_country)
|
||||
|
||||
# Add CJTF factions to the coalitions, if they're not being used in the campaign
|
||||
if CombinedJointTaskForcesBlue.id not in {self.p_country.id, self.e_country.id}:
|
||||
self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue())
|
||||
if CombinedJointTaskForcesRed.id not in {self.p_country.id, self.e_country.id}:
|
||||
self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed())
|
||||
|
||||
belligerents = {self.p_country.id, self.e_country.id}
|
||||
for country_id in country_dict.keys():
|
||||
if country_id not in belligerents:
|
||||
c = country_dict[country_id]()
|
||||
self.mission.coalition["neutrals"].add_country(c)
|
||||
|
||||
def generate_ground_conflicts(self) -> None:
|
||||
"""Generate FLOTs and JTACs for each active front line."""
|
||||
for front_line in self.game.theater.conflicts():
|
||||
player_cp = front_line.blue_cp
|
||||
enemy_cp = front_line.red_cp
|
||||
|
||||
# Add JTAC
|
||||
if self.game.blue.faction.has_jtac:
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
# If the option fc3LaserCode is enabled, force all JTAC
|
||||
# laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs.
|
||||
# Otherwise use 1688 for the first JTAC, 1687 for the second etc.
|
||||
if self.game.settings.plugins.get("ctld.fc3LaserCode"):
|
||||
code = self.game.laser_code_registry.fc3_code
|
||||
else:
|
||||
code = front_line.laser_code
|
||||
|
||||
utype = self.game.blue.faction.jtac_unit
|
||||
if utype is None:
|
||||
utype = AircraftType.named("MQ-9 Reaper")
|
||||
|
||||
country = self.mission.country(self.game.blue.faction.country.name)
|
||||
position = FrontLineConflictDescription.frontline_position(
|
||||
front_line, self.game.theater, self.game.settings
|
||||
)
|
||||
jtac = self.mission.flight_group(
|
||||
country=country,
|
||||
name=namegen.next_jtac_name(),
|
||||
aircraft_type=utype.dcs_unit_type,
|
||||
position=position[0],
|
||||
airport=None,
|
||||
altitude=5000,
|
||||
maintask=AFAC,
|
||||
)
|
||||
jtac.points[0].tasks.append(
|
||||
FAC(
|
||||
callsign=len(self.mission_data.jtacs) + 1,
|
||||
frequency=int(freq.mhz),
|
||||
modulation=freq.modulation,
|
||||
)
|
||||
)
|
||||
jtac.points[0].tasks.append(SetInvisibleCommand(True))
|
||||
jtac.points[0].tasks.append(SetImmortalCommand(True))
|
||||
jtac.points[0].tasks.append(
|
||||
OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle)
|
||||
)
|
||||
frontline = f"Frontline {player_cp.name}/{enemy_cp.name}"
|
||||
# Note: Will need to change if we ever add ground based JTAC.
|
||||
callsign = callsign_for_support_unit(jtac)
|
||||
self.mission_data.jtacs.append(
|
||||
JtacInfo(
|
||||
group_name=jtac.name,
|
||||
unit_name=jtac.units[0].name,
|
||||
callsign=callsign,
|
||||
region=frontline,
|
||||
code=str(code),
|
||||
blue=True,
|
||||
freq=freq,
|
||||
)
|
||||
)
|
||||
|
||||
def generate_air_units(self, tgo_generator: TgoGenerator) -> None:
|
||||
"""Generate the air units for the Operation"""
|
||||
|
||||
# Generate Aircraft Activity on the map
|
||||
aircraft_generator = PretenseAircraftGenerator(
|
||||
self.mission,
|
||||
self.game.settings,
|
||||
self.game,
|
||||
self.time,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.laser_code_registry,
|
||||
self.unit_map,
|
||||
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,
|
||||
)
|
||||
|
||||
# Clear parking slots and ATOs
|
||||
aircraft_generator.clear_parking_slots()
|
||||
self.game.blue.ato.clear()
|
||||
self.game.red.ato.clear()
|
||||
|
||||
for cp in self.game.theater.controlpoints:
|
||||
for country in (self.p_country, self.e_country):
|
||||
ato = AirTaskingOrder()
|
||||
aircraft_generator.generate_flights(
|
||||
country,
|
||||
cp,
|
||||
ato,
|
||||
)
|
||||
aircraft_generator.generate_packages(
|
||||
country,
|
||||
ato,
|
||||
tgo_generator.runways,
|
||||
)
|
||||
|
||||
self.mission_data.flights = aircraft_generator.flights
|
||||
|
||||
for flight in aircraft_generator.flights:
|
||||
if not flight.client_units:
|
||||
continue
|
||||
flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data)
|
||||
955
game/pretense/pretensetgogenerator.py
Normal file
955
game/pretense/pretensetgogenerator.py
Normal file
@ -0,0 +1,955 @@
|
||||
"""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())
|
||||
475
game/pretense/pretensetriggergenerator.py
Normal file
475
game/pretense/pretensetriggergenerator.py
Normal file
@ -0,0 +1,475 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from dcs import Point
|
||||
from dcs.action import (
|
||||
ClearFlag,
|
||||
DoScript,
|
||||
MarkToAll,
|
||||
SetFlag,
|
||||
RemoveSceneObjects,
|
||||
RemoveSceneObjectsMask,
|
||||
SceneryDestructionZone,
|
||||
Smoke,
|
||||
)
|
||||
from dcs.condition import (
|
||||
AllOfCoalitionOutsideZone,
|
||||
FlagIsFalse,
|
||||
FlagIsTrue,
|
||||
PartOfCoalitionInZone,
|
||||
TimeAfter,
|
||||
TimeSinceFlag,
|
||||
)
|
||||
from dcs.mission import Mission
|
||||
from dcs.task import Option
|
||||
from dcs.terrain.caucasus.airports import Krasnodar_Pashkovsky
|
||||
from dcs.terrain.syria.airports import Damascus, Khalkhalah
|
||||
from dcs.translation import String
|
||||
from dcs.triggers import Event, TriggerCondition, TriggerOnce
|
||||
from dcs.unit import Skill
|
||||
from numpy import cross, einsum, arctan2
|
||||
from shapely import MultiPolygon, Point as ShapelyPoint
|
||||
|
||||
from game.naming import ALPHA_MILITARY
|
||||
from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator
|
||||
from game.theater import Airfield
|
||||
from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.game import Game
|
||||
|
||||
PUSH_TRIGGER_SIZE = 3000
|
||||
PUSH_TRIGGER_ACTIVATION_AGL = 25
|
||||
|
||||
REGROUP_ZONE_DISTANCE = 12000
|
||||
REGROUP_ALT = 5000
|
||||
|
||||
TRIGGER_WAYPOINT_OFFSET = 2
|
||||
TRIGGER_MIN_DISTANCE_FROM_START = 10000
|
||||
# modified since we now have advanced SAM units
|
||||
TRIGGER_RADIUS_MINIMUM = 3000000
|
||||
|
||||
TRIGGER_RADIUS_SMALL = 50000
|
||||
TRIGGER_RADIUS_MEDIUM = 100000
|
||||
TRIGGER_RADIUS_LARGE = 150000
|
||||
TRIGGER_RADIUS_ALL_MAP = 3000000
|
||||
TRIGGER_RADIUS_CLEAR_SCENERY = 1000
|
||||
TRIGGER_RADIUS_PRETENSE_TGO = 500
|
||||
TRIGGER_RADIUS_PRETENSE_SUPPLY = 500
|
||||
TRIGGER_RADIUS_PRETENSE_HELI = 1000
|
||||
TRIGGER_RADIUS_PRETENSE_HELI_BUFFER = 500
|
||||
TRIGGER_RADIUS_PRETENSE_CARRIER = 20000
|
||||
TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL = 3000
|
||||
TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER = 25000
|
||||
TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500
|
||||
TRIGGER_RUNWAY_WIDTH_PRETENSE = 400
|
||||
|
||||
SIMPLIFY_RUNS_PRETENSE_CARRIER = 10000
|
||||
|
||||
|
||||
class Silence(Option):
|
||||
Key = 7
|
||||
|
||||
|
||||
class PretenseTriggerGenerator:
|
||||
capture_zone_types = (Fob, Airfield)
|
||||
capture_zone_flag = 600
|
||||
|
||||
def __init__(self, mission: Mission, game: Game) -> None:
|
||||
self.mission = mission
|
||||
self.game = game
|
||||
|
||||
def _set_allegiances(self, player_coalition: str, enemy_coalition: str) -> None:
|
||||
"""
|
||||
Set airbase initial coalition
|
||||
"""
|
||||
|
||||
# Empty neutrals airports
|
||||
airfields = [
|
||||
cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield)
|
||||
]
|
||||
airport_ids = {cp.airport.id for cp in airfields}
|
||||
for airport in self.mission.terrain.airport_list():
|
||||
if airport.id not in airport_ids:
|
||||
airport.unlimited_fuel = False
|
||||
airport.unlimited_munitions = False
|
||||
airport.unlimited_aircrafts = False
|
||||
airport.gasoline_init = 0
|
||||
airport.methanol_mixture_init = 0
|
||||
airport.diesel_init = 0
|
||||
airport.jet_init = 0
|
||||
airport.operating_level_air = 0
|
||||
airport.operating_level_equipment = 0
|
||||
airport.operating_level_fuel = 0
|
||||
|
||||
for airport in self.mission.terrain.airport_list():
|
||||
if airport.id not in airport_ids:
|
||||
airport.unlimited_fuel = True
|
||||
airport.unlimited_munitions = True
|
||||
airport.unlimited_aircrafts = True
|
||||
|
||||
for airfield in airfields:
|
||||
cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id)
|
||||
if cp_airport is None:
|
||||
raise RuntimeError(
|
||||
f"Could not find {airfield.airport.name} in the mission"
|
||||
)
|
||||
cp_airport.set_coalition(
|
||||
airfield.captured and player_coalition or enemy_coalition
|
||||
)
|
||||
|
||||
def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None:
|
||||
"""
|
||||
Set skill level for all aircraft in the mission
|
||||
"""
|
||||
for coalition_name, coalition in self.mission.coalition.items():
|
||||
if coalition_name == player_coalition:
|
||||
skill_level = Skill(self.game.settings.player_skill)
|
||||
elif coalition_name == enemy_coalition:
|
||||
skill_level = Skill(self.game.settings.enemy_vehicle_skill)
|
||||
else:
|
||||
continue
|
||||
|
||||
for country in coalition.countries.values():
|
||||
for vehicle_group in country.vehicle_group:
|
||||
vehicle_group.set_skill(skill_level)
|
||||
|
||||
def _gen_markers(self) -> None:
|
||||
"""
|
||||
Generate markers on F10 map for each existing objective
|
||||
"""
|
||||
if self.game.settings.generate_marks:
|
||||
mark_trigger = TriggerOnce(Event.NoEvent, "Marks generator")
|
||||
mark_trigger.add_condition(TimeAfter(1))
|
||||
v = 10
|
||||
for cp in self.game.theater.controlpoints:
|
||||
seen = set()
|
||||
for ground_object in cp.ground_objects:
|
||||
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(
|
||||
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 += 1
|
||||
self.mission.triggerrules.triggers.append(mark_trigger)
|
||||
|
||||
def _generate_capture_triggers(
|
||||
self, player_coalition: str, enemy_coalition: str
|
||||
) -> None:
|
||||
"""Creates a pair of triggers for each control point of `cls.capture_zone_types`.
|
||||
One for the initial capture of a control point, and one if it is recaptured.
|
||||
Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua`
|
||||
"""
|
||||
for cp in self.game.theater.controlpoints:
|
||||
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
|
||||
if cp.captured:
|
||||
attacking_coalition = enemy_coalition
|
||||
attack_coalition_int = 1 # 1 is the Event int for Red
|
||||
defending_coalition = player_coalition
|
||||
defend_coalition_int = 2 # 2 is the Event int for Blue
|
||||
else:
|
||||
attacking_coalition = player_coalition
|
||||
attack_coalition_int = 2
|
||||
defending_coalition = enemy_coalition
|
||||
defend_coalition_int = 1
|
||||
|
||||
trigger_zone = self.mission.triggers.add_triggerzone(
|
||||
cp.position,
|
||||
radius=TRIGGER_RADIUS_CAPTURE,
|
||||
hidden=False,
|
||||
name="CAPTURE",
|
||||
)
|
||||
flag = self.get_capture_zone_flag()
|
||||
capture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
|
||||
capture_trigger.add_condition(
|
||||
AllOfCoalitionOutsideZone(
|
||||
defending_coalition, trigger_zone.id, unit_type="GROUND"
|
||||
)
|
||||
)
|
||||
capture_trigger.add_condition(
|
||||
PartOfCoalitionInZone(
|
||||
attacking_coalition, trigger_zone.id, unit_type="GROUND"
|
||||
)
|
||||
)
|
||||
capture_trigger.add_condition(FlagIsFalse(flag=flag))
|
||||
script_string = String(
|
||||
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"'
|
||||
)
|
||||
capture_trigger.add_action(DoScript(script_string))
|
||||
capture_trigger.add_action(SetFlag(flag=flag))
|
||||
self.mission.triggerrules.triggers.append(capture_trigger)
|
||||
|
||||
recapture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
|
||||
recapture_trigger.add_condition(
|
||||
AllOfCoalitionOutsideZone(
|
||||
attacking_coalition, trigger_zone.id, unit_type="GROUND"
|
||||
)
|
||||
)
|
||||
recapture_trigger.add_condition(
|
||||
PartOfCoalitionInZone(
|
||||
defending_coalition, trigger_zone.id, unit_type="GROUND"
|
||||
)
|
||||
)
|
||||
recapture_trigger.add_condition(FlagIsTrue(flag=flag))
|
||||
script_string = String(
|
||||
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"'
|
||||
)
|
||||
recapture_trigger.add_action(DoScript(script_string))
|
||||
recapture_trigger.add_action(ClearFlag(flag=flag))
|
||||
self.mission.triggerrules.triggers.append(recapture_trigger)
|
||||
|
||||
def _generate_pretense_zone_triggers(self) -> None:
|
||||
"""Creates triggger zones for the Pretense campaign. These include:
|
||||
- Carrier zones for friendly forces, generated from the navmesh / sea zone intersection
|
||||
- Carrier zones for opposing forces
|
||||
- Airfield and FARP zones
|
||||
- Airfield and FARP spawn points / helicopter spawn points / ground object positions
|
||||
"""
|
||||
|
||||
# First generate carrier zones for friendly forces
|
||||
use_blue_navmesh = (
|
||||
self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh"
|
||||
)
|
||||
sea_zones_landmap = self.game.coalition_for(
|
||||
player=False
|
||||
).nav_mesh.theater.landmap
|
||||
if (
|
||||
self.game.settings.pretense_controllable_carrier
|
||||
and sea_zones_landmap is not None
|
||||
):
|
||||
navmesh_number = 0
|
||||
for navmesh_poly in self.game.coalition_for(
|
||||
player=use_blue_navmesh
|
||||
).nav_mesh.polys:
|
||||
navmesh_number += 1
|
||||
if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly):
|
||||
# Get the intersection between the navmesh zone and the sea zone
|
||||
navmesh_sea_intersection = sea_zones_landmap.sea_zones.intersection(
|
||||
navmesh_poly.poly
|
||||
)
|
||||
navmesh_zone_verticies = navmesh_sea_intersection
|
||||
|
||||
# Simplify it to get a quadrangle
|
||||
for simplify_run in range(SIMPLIFY_RUNS_PRETENSE_CARRIER):
|
||||
navmesh_zone_verticies = navmesh_sea_intersection.simplify(
|
||||
float(simplify_run * 10), preserve_topology=False
|
||||
)
|
||||
if isinstance(navmesh_zone_verticies, MultiPolygon):
|
||||
break
|
||||
if len(navmesh_zone_verticies.exterior.coords) <= 4:
|
||||
break
|
||||
if isinstance(navmesh_zone_verticies, MultiPolygon):
|
||||
continue
|
||||
trigger_zone_verticies = []
|
||||
terrain = self.game.theater.terrain
|
||||
alpha = random.choice(ALPHA_MILITARY)
|
||||
|
||||
# Generate the quadrangle zone and four points inside it for carrier navigation
|
||||
if len(navmesh_zone_verticies.exterior.coords) == 4:
|
||||
zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15}
|
||||
corner_point_num = 0
|
||||
for point_coord in navmesh_zone_verticies.exterior.coords:
|
||||
corner_point = Point(
|
||||
x=point_coord[0], y=point_coord[1], terrain=terrain
|
||||
)
|
||||
nav_point = corner_point.point_from_heading(
|
||||
corner_point.heading_between_point(
|
||||
navmesh_sea_intersection.centroid
|
||||
),
|
||||
TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER,
|
||||
)
|
||||
corner_point_num += 1
|
||||
|
||||
zone_name = f"{alpha}-{navmesh_number}-{corner_point_num}"
|
||||
if sea_zones_landmap.sea_zones.contains(
|
||||
ShapelyPoint(nav_point.x, nav_point.y)
|
||||
):
|
||||
self.mission.triggers.add_triggerzone(
|
||||
nav_point,
|
||||
radius=TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL,
|
||||
hidden=False,
|
||||
name=zone_name,
|
||||
color=zone_color,
|
||||
)
|
||||
|
||||
trigger_zone_verticies.append(corner_point)
|
||||
|
||||
zone_name = f"{alpha}-{navmesh_number}"
|
||||
trigger_zone = self.mission.triggers.add_triggerzone_quad(
|
||||
navmesh_sea_intersection.centroid,
|
||||
trigger_zone_verticies,
|
||||
hidden=False,
|
||||
name=zone_name,
|
||||
color=zone_color,
|
||||
)
|
||||
try:
|
||||
if len(self.game.pretense_carrier_zones) == 0:
|
||||
self.game.pretense_carrier_zones = []
|
||||
except AttributeError:
|
||||
self.game.pretense_carrier_zones = []
|
||||
self.game.pretense_carrier_zones.append(zone_name)
|
||||
|
||||
for cp in self.game.theater.controlpoints:
|
||||
if (
|
||||
cp.is_fleet
|
||||
and self.game.settings.pretense_controllable_carrier
|
||||
and cp.captured
|
||||
):
|
||||
# Friendly carrier zones are generated above
|
||||
continue
|
||||
elif cp.is_fleet:
|
||||
trigger_radius = float(TRIGGER_RADIUS_PRETENSE_CARRIER)
|
||||
elif isinstance(cp, Fob) and cp.has_helipads:
|
||||
trigger_radius = TRIGGER_RADIUS_PRETENSE_HELI
|
||||
for helipad in list(
|
||||
cp.helipads + cp.helipads_quad + cp.helipads_invisible
|
||||
):
|
||||
if cp.position.distance_to_point(helipad) > trigger_radius:
|
||||
trigger_radius = cp.position.distance_to_point(helipad)
|
||||
for ground_spawn, ground_spawn_wp in list(
|
||||
cp.ground_spawns + cp.ground_spawns_roadbase
|
||||
):
|
||||
if cp.position.distance_to_point(ground_spawn) > trigger_radius:
|
||||
trigger_radius = cp.position.distance_to_point(ground_spawn)
|
||||
trigger_radius += TRIGGER_RADIUS_PRETENSE_HELI_BUFFER
|
||||
else:
|
||||
if cp.dcs_airport is not None and (
|
||||
isinstance(cp.dcs_airport, Damascus)
|
||||
or isinstance(cp.dcs_airport, Khalkhalah)
|
||||
or isinstance(cp.dcs_airport, Krasnodar_Pashkovsky)
|
||||
):
|
||||
# Increase the size of Pretense zones at Damascus, Khalkhalah and Krasnodar-Pashkovsky
|
||||
# (which are quite spread out) so the zone would encompass the entire airfield.
|
||||
trigger_radius = int(TRIGGER_RADIUS_CAPTURE * 1.8)
|
||||
else:
|
||||
trigger_radius = TRIGGER_RADIUS_CAPTURE
|
||||
cp_name = "".join(
|
||||
[i for i in cp.name if i.isalnum() or i.isspace() or i == "-"]
|
||||
)
|
||||
cp_name = cp_name.replace("Ä", "A")
|
||||
cp_name = cp_name.replace("Ö", "O")
|
||||
cp_name = cp_name.replace("Ø", "O")
|
||||
cp_name = cp_name.replace("ä", "a")
|
||||
cp_name = cp_name.replace("ö", "o")
|
||||
cp_name = cp_name.replace("ø", "o")
|
||||
if not isinstance(cp, OffMapSpawn):
|
||||
zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15}
|
||||
self.mission.triggers.add_triggerzone(
|
||||
cp.position,
|
||||
radius=trigger_radius,
|
||||
hidden=False,
|
||||
name=cp_name,
|
||||
color=zone_color,
|
||||
)
|
||||
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name)
|
||||
tgo_num = 0
|
||||
for tgo in cp.ground_objects:
|
||||
if cp.is_fleet or tgo.sea_object:
|
||||
continue
|
||||
tgo_num += 1
|
||||
zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15}
|
||||
self.mission.triggers.add_triggerzone(
|
||||
tgo.position,
|
||||
radius=TRIGGER_RADIUS_PRETENSE_TGO,
|
||||
hidden=False,
|
||||
name=f"{cp_name_trimmed}-{tgo_num}",
|
||||
color=zone_color,
|
||||
)
|
||||
for helipad in cp.helipads + cp.helipads_invisible + cp.helipads_quad:
|
||||
zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15}
|
||||
self.mission.triggers.add_triggerzone(
|
||||
position=helipad,
|
||||
radius=TRIGGER_RADIUS_PRETENSE_HELI,
|
||||
hidden=False,
|
||||
name=f"{cp_name_trimmed}-hsp",
|
||||
color=zone_color,
|
||||
)
|
||||
break
|
||||
for supply_route in cp.convoy_routes.values():
|
||||
tgo_num += 1
|
||||
zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15}
|
||||
origin_position = supply_route[0]
|
||||
next_position = supply_route[1]
|
||||
convoy_heading = origin_position.heading_between_point(next_position)
|
||||
supply_position = origin_position.point_from_heading(
|
||||
convoy_heading, 300
|
||||
)
|
||||
self.mission.triggers.add_triggerzone(
|
||||
supply_position,
|
||||
radius=TRIGGER_RADIUS_PRETENSE_TGO,
|
||||
hidden=False,
|
||||
name=f"{cp_name_trimmed}-sp",
|
||||
color=zone_color,
|
||||
)
|
||||
break
|
||||
airfields = [
|
||||
cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield)
|
||||
]
|
||||
for airfield in airfields:
|
||||
cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id)
|
||||
if cp_airport is None:
|
||||
continue
|
||||
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
|
||||
cp_airport.name
|
||||
)
|
||||
zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15}
|
||||
if cp_airport is None:
|
||||
raise RuntimeError(
|
||||
f"Could not find {airfield.airport.name} in the mission"
|
||||
)
|
||||
for runway in cp_airport.runways:
|
||||
runway_end_1 = cp_airport.position.point_from_heading(
|
||||
runway.heading, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2
|
||||
)
|
||||
runway_end_2 = cp_airport.position.point_from_heading(
|
||||
runway.heading + 180, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2
|
||||
)
|
||||
runway_verticies = [
|
||||
runway_end_1.point_from_heading(
|
||||
runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2
|
||||
),
|
||||
runway_end_1.point_from_heading(
|
||||
runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2
|
||||
),
|
||||
runway_end_2.point_from_heading(
|
||||
runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2
|
||||
),
|
||||
runway_end_2.point_from_heading(
|
||||
runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2
|
||||
),
|
||||
]
|
||||
trigger_zone = self.mission.triggers.add_triggerzone_quad(
|
||||
cp_airport.position,
|
||||
runway_verticies,
|
||||
hidden=False,
|
||||
name=f"{cp_name_trimmed}-runway-{runway.id}",
|
||||
color=zone_color,
|
||||
)
|
||||
break
|
||||
|
||||
def generate(self) -> None:
|
||||
player_coalition = "blue"
|
||||
enemy_coalition = "red"
|
||||
|
||||
self._set_skill(player_coalition, enemy_coalition)
|
||||
self._set_allegiances(player_coalition, enemy_coalition)
|
||||
self._generate_pretense_zone_triggers()
|
||||
self._generate_capture_triggers(player_coalition, enemy_coalition)
|
||||
|
||||
@classmethod
|
||||
def get_capture_zone_flag(cls) -> int:
|
||||
flag = cls.capture_zone_flag
|
||||
cls.capture_zone_flag += 1
|
||||
return flag
|
||||
@ -416,15 +416,19 @@ class RadioRegistry:
|
||||
already allocated.
|
||||
"""
|
||||
try:
|
||||
while_count = 0
|
||||
while (channel := random_frequency(radio)) in self.allocated_channels:
|
||||
while_count += 1
|
||||
if while_count > 1000:
|
||||
raise StopIteration
|
||||
pass
|
||||
self.reserve(channel)
|
||||
return channel
|
||||
except StopIteration:
|
||||
# In the event of too many channel users, fail gracefully by reusing
|
||||
# the last channel.
|
||||
# a channel.
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/598
|
||||
channel = radio.last_channel
|
||||
channel = random_frequency(radio)
|
||||
logging.warning(
|
||||
f"No more free channels for {radio.name}. Reusing {channel}."
|
||||
)
|
||||
|
||||
@ -49,6 +49,8 @@ FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation"
|
||||
CAMPAIGN_DOCTRINE_PAGE = "Campaign Doctrine"
|
||||
DOCTRINE_DISTANCES_SECTION = "Doctrine distances"
|
||||
|
||||
PRETENSE_PAGE = "Pretense"
|
||||
|
||||
MISSION_GENERATOR_PAGE = "Mission Generator"
|
||||
|
||||
GAMEPLAY_SECTION = "Gameplay"
|
||||
@ -156,6 +158,7 @@ class Settings:
|
||||
MISSION_RESTRICTIONS_SECTION,
|
||||
default=True,
|
||||
)
|
||||
|
||||
easy_communication: Optional[bool] = choices_option(
|
||||
"Easy Communication",
|
||||
page=DIFFICULTY_PAGE,
|
||||
@ -174,6 +177,20 @@ class Settings:
|
||||
|
||||
# Campaign management
|
||||
# General
|
||||
squadron_random_chance: int = bounded_int_option(
|
||||
"Percentage of randomly selected aircraft types (only for generated squadrons)",
|
||||
page=CAMPAIGN_MANAGEMENT_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=50,
|
||||
min=0,
|
||||
max=100,
|
||||
detail=(
|
||||
"<p>Aircraft type selection is governed by the campaign and the squadron definitions available to "
|
||||
"Retribution. Squadrons are generated by Retribution if the faction does not have access to the campaign "
|
||||
"designer's squadron/aircraft definitions. Use the above to increase/decrease aircraft variety by making "
|
||||
"some selections random instead of picking aircraft types from a priority list.</p>"
|
||||
),
|
||||
)
|
||||
restrict_weapons_by_date: bool = boolean_option(
|
||||
"Restrict weapons by date (WIP)",
|
||||
page=CAMPAIGN_MANAGEMENT_PAGE,
|
||||
@ -906,6 +923,17 @@ class Settings:
|
||||
"Needed to cold-start some aircraft types. Might have a performance impact."
|
||||
),
|
||||
)
|
||||
ground_start_airbase_statics_farps_remove: bool = boolean_option(
|
||||
"Remove ground spawn statics, including invisible FARPs, at airbases",
|
||||
MISSION_GENERATOR_PAGE,
|
||||
GAMEPLAY_SECTION,
|
||||
default=True,
|
||||
detail=(
|
||||
"Ammo and fuel statics and invisible FARPs should be unnecessary when creating "
|
||||
"additional spawns for players at airbases. This setting will disable them and "
|
||||
"potentially grant a marginal performance benefit."
|
||||
),
|
||||
)
|
||||
ai_unlimited_fuel: bool = boolean_option(
|
||||
"AI flights have unlimited fuel",
|
||||
MISSION_GENERATOR_PAGE,
|
||||
@ -1085,6 +1113,140 @@ class Settings:
|
||||
"if the start-up type was manually changed to 'In-Flight'."
|
||||
),
|
||||
)
|
||||
pretense_maxdistfromfront_distance: int = bounded_int_option(
|
||||
"Max distance from front (km)",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=130,
|
||||
min=10,
|
||||
max=10000,
|
||||
detail=(
|
||||
"Zones farther away than this from the front line are switched "
|
||||
"into low activity state, but will still be there as functional "
|
||||
"parts of the economy. Use this to adjust performance."
|
||||
),
|
||||
)
|
||||
pretense_controllable_carrier: bool = boolean_option(
|
||||
"Controllable carrier",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=True,
|
||||
detail=(
|
||||
"This can be used to enable or disable the native carrier support in Pretense. The Pretense carrier "
|
||||
"can be controlled through the communication menu (if the Pretense character has enough rank/CMD points) "
|
||||
"and the player can call in AI aerial and cruise missile missions using it."
|
||||
"The controllable carriers in Pretense do not build and deploy AI missions autonomously, so if you prefer "
|
||||
"to have both sides deploy carrier aviation autonomously, you might want to disable this option. "
|
||||
"When this option is disabled, moving the carrier can only be done with the Retribution interface."
|
||||
),
|
||||
)
|
||||
pretense_carrier_steams_into_wind: bool = boolean_option(
|
||||
"Carriers steam into wind",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=True,
|
||||
detail=(
|
||||
"This setting controls whether carriers and their escorts will steam into wind. Disable to "
|
||||
"to ensure that the carriers stay within the carrier zone in Pretense, but note that "
|
||||
"doing so might limit carrier operations, takeoff weights and landings."
|
||||
),
|
||||
)
|
||||
pretense_carrier_zones_navmesh: str = choices_option(
|
||||
"Navmesh to use for Pretense carrier zones",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
choices=["Blue navmesh", "Red navmesh"],
|
||||
default="Blue navmesh",
|
||||
detail=(
|
||||
"Use the Retribution map interface options to compare the blue navmesh and the red navmesh."
|
||||
"You can select which navmesh to use when generating the zones in which the controllable carrier(s) "
|
||||
"move and operate."
|
||||
),
|
||||
)
|
||||
pretense_extra_zone_connections: int = bounded_int_option(
|
||||
"Extra friendly zone connections",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=2,
|
||||
min=0,
|
||||
max=10,
|
||||
detail=(
|
||||
"Add connections from each zone to this many closest friendly zones,"
|
||||
"which don't have an existing supply route defined in the campaign."
|
||||
),
|
||||
)
|
||||
pretense_num_of_cargo_planes: int = bounded_int_option(
|
||||
"Number of cargo planes per side",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=2,
|
||||
min=1,
|
||||
max=100,
|
||||
)
|
||||
pretense_sead_flights_per_cp: int = bounded_int_option(
|
||||
"Number of AI SEAD flights per control point / zone",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_cas_flights_per_cp: int = bounded_int_option(
|
||||
"Number of AI CAS flights per control point / zone",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_bai_flights_per_cp: int = bounded_int_option(
|
||||
"Number of AI BAI flights per control point / zone",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_strike_flights_per_cp: int = bounded_int_option(
|
||||
"Number of AI Strike flights per control point / zone",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_barcap_flights_per_cp: int = bounded_int_option(
|
||||
"Number of AI BARCAP flights per control point / zone",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_ai_aircraft_per_flight: int = bounded_int_option(
|
||||
"Number of AI aircraft per flight",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=2,
|
||||
min=1,
|
||||
max=4,
|
||||
)
|
||||
pretense_player_flights_per_type: int = bounded_int_option(
|
||||
"Number of player flights per aircraft type at each base",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=1,
|
||||
min=1,
|
||||
max=10,
|
||||
)
|
||||
pretense_ai_cargo_planes_per_side: int = bounded_int_option(
|
||||
"Number of AI cargo planes per side",
|
||||
page=PRETENSE_PAGE,
|
||||
section=GENERAL_SECTION,
|
||||
default=2,
|
||||
min=1,
|
||||
max=20,
|
||||
)
|
||||
|
||||
# Cheating. Not using auto settings because the same page also has buttons which do
|
||||
# not alter settings.
|
||||
|
||||
@ -202,6 +202,29 @@ class ConflictTheater:
|
||||
assert closest_red is not None
|
||||
return closest_blue, closest_red
|
||||
|
||||
def closest_friendly_control_points_to(
|
||||
self, cp: ControlPoint
|
||||
) -> List[ControlPoint]:
|
||||
"""
|
||||
Returns a list of the friendly ControlPoints in theater to ControlPoint cp, sorted closest to farthest.
|
||||
"""
|
||||
closest_cps = list()
|
||||
distances_to_cp = dict()
|
||||
if cp.captured:
|
||||
control_points = self.player_points()
|
||||
else:
|
||||
control_points = self.enemy_points()
|
||||
for other_cp in control_points:
|
||||
if cp == other_cp:
|
||||
continue
|
||||
|
||||
dist = other_cp.position.distance_to_point(cp.position)
|
||||
distances_to_cp[dist] = other_cp
|
||||
for i in sorted(distances_to_cp.keys()):
|
||||
closest_cps.append(distances_to_cp[i])
|
||||
|
||||
return closest_cps
|
||||
|
||||
def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint:
|
||||
for i in self.controlpoints:
|
||||
if i.id == cp_id:
|
||||
|
||||
@ -32,6 +32,9 @@ def load_icons():
|
||||
"./resources/ui/misc/" + get_theme_icons() + "/github.png"
|
||||
)
|
||||
ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png")
|
||||
ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png")
|
||||
ICONS["Pretense_discord"] = QPixmap("./resources/ui/misc/pretense_discord.png")
|
||||
ICONS["Pretense_generate"] = QPixmap("./resources/ui/misc/pretense_generate.png")
|
||||
|
||||
ICONS["Control Points"] = QPixmap(
|
||||
"./resources/ui/misc/" + get_theme_icons() + "/circle.png"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import traceback
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@ -21,6 +22,8 @@ from game import Game, VERSION, persistency, Migrator
|
||||
from game.debriefing import Debriefing
|
||||
from game.game import TurnState
|
||||
from game.layout import LAYOUTS
|
||||
from game.persistency import pre_pretense_backups_dir
|
||||
from game.pretense.pretensemissiongenerator import PretenseMissionGenerator
|
||||
from game.server import EventStream, GameContext
|
||||
from game.server.dependencies import QtCallbacks, QtContext
|
||||
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||
@ -193,6 +196,20 @@ class QLiberationWindow(QMainWindow):
|
||||
lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/")
|
||||
)
|
||||
|
||||
self.pretenseLinkAction = QAction("&DCS: Pretense", self)
|
||||
self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense_discord"]))
|
||||
self.pretenseLinkAction.triggered.connect(
|
||||
lambda: webbrowser.open_new_tab(
|
||||
"https://" + "discord.gg" + "/" + "PtPsb9Mpk6"
|
||||
)
|
||||
)
|
||||
|
||||
self.newPretenseAction = QAction(
|
||||
"&Generate a Pretense Campaign from the running campaign", self
|
||||
)
|
||||
self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense_generate"]))
|
||||
self.newPretenseAction.triggered.connect(self.newPretenseCampaign)
|
||||
|
||||
self.openLogsAction = QAction("Show &logs", self)
|
||||
self.openLogsAction.triggered.connect(self.showLogsDialog)
|
||||
|
||||
@ -234,6 +251,8 @@ class QLiberationWindow(QMainWindow):
|
||||
self.links_bar.addAction(self.openDiscordAction)
|
||||
self.links_bar.addAction(self.openGithubAction)
|
||||
self.links_bar.addAction(self.ukraineAction)
|
||||
self.links_bar.addAction(self.pretenseLinkAction)
|
||||
self.links_bar.addAction(self.newPretenseAction)
|
||||
|
||||
self.actions_bar = self.addToolBar("Actions")
|
||||
self.actions_bar.addAction(self.openSettingsAction)
|
||||
@ -303,6 +322,29 @@ class QLiberationWindow(QMainWindow):
|
||||
wizard.show()
|
||||
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
|
||||
|
||||
def newPretenseCampaign(self):
|
||||
output = persistency.mission_path_for("pretense_campaign.miz")
|
||||
try:
|
||||
PretenseMissionGenerator(
|
||||
self.game, self.game.conditions.start_time
|
||||
).generate_miz(output)
|
||||
except Exception as e:
|
||||
now = datetime.now()
|
||||
date_time = now.strftime("%Y-%d-%mT%H_%M_%S")
|
||||
path = pre_pretense_backups_dir()
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
tgt = path / f"pre-pretense-backup_{date_time}.retribution"
|
||||
path /= f".pre-pretense-backup.retribution"
|
||||
if path.exists():
|
||||
with open(path, "rb") as source:
|
||||
with open(tgt, "wb") as target:
|
||||
target.write(source.read())
|
||||
raise e
|
||||
|
||||
title = "Pretense campaign generated"
|
||||
msg = f"A Pretense campaign mission has been successfully generated in {output}"
|
||||
QMessageBox.information(QApplication.focusWidget(), title, msg, QMessageBox.Ok)
|
||||
|
||||
def openFile(self):
|
||||
if self.game is not None and self.game.savepath:
|
||||
save_dir = self.game.savepath
|
||||
|
||||
BIN
resources/campaigns/afghanistan_full.miz
Normal file
BIN
resources/campaigns/afghanistan_full.miz
Normal file
Binary file not shown.
104
resources/campaigns/afghanistan_full.yaml
Normal file
104
resources/campaigns/afghanistan_full.yaml
Normal file
@ -0,0 +1,104 @@
|
||||
---
|
||||
name: Afghanistan - [Pretense] - Full map
|
||||
theater: Afghanistan
|
||||
authors: Colonel Akir Nakesh
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: Insurgents
|
||||
description:
|
||||
<p>An Afghanistan full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such may be unbalanced for a standard campaign.</p>
|
||||
miz: afghanistan_full.miz
|
||||
performance: 3
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Herat
|
||||
1:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
# Kandahar
|
||||
7:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
BIN
resources/campaigns/marianas_full.miz
Normal file
BIN
resources/campaigns/marianas_full.miz
Normal file
Binary file not shown.
126
resources/campaigns/marianas_full.yaml
Normal file
126
resources/campaigns/marianas_full.yaml
Normal file
@ -0,0 +1,126 @@
|
||||
---
|
||||
name: Marianas - [Pretense] - Full map
|
||||
theater: MarianaIslands
|
||||
authors: Colonel Akir Nakesh
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: China 2010
|
||||
description:
|
||||
<p>A Marianas full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign.</p>
|
||||
miz: marianas_full.miz
|
||||
performance: 3
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
#Andersen
|
||||
6:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
#Saipan
|
||||
2:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
Blue CVBG:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-33 Flanker-D
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2D Advanced Hawkeye
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
Red CVBG:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-33 Flanker-D
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2D Advanced Hawkeye
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
BIN
resources/campaigns/nevada_full.miz
Normal file
BIN
resources/campaigns/nevada_full.miz
Normal file
Binary file not shown.
104
resources/campaigns/nevada_full.yaml
Normal file
104
resources/campaigns/nevada_full.yaml
Normal file
@ -0,0 +1,104 @@
|
||||
---
|
||||
name: Nevada - [Pretense] - Full map
|
||||
theater: Nevada
|
||||
authors: Colonel Akir Nakesh
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: USAF Aggressors
|
||||
description:
|
||||
<p>A Nevada full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such may be unbalanced for a standard campaign.</p>
|
||||
miz: nevada_full.miz
|
||||
performance: 3
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Nellis
|
||||
4:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
# Tonopah Test Range
|
||||
18:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
BIN
resources/campaigns/persian_gulf_full.miz
Normal file
BIN
resources/campaigns/persian_gulf_full.miz
Normal file
Binary file not shown.
132
resources/campaigns/persian_gulf_full.yaml
Normal file
132
resources/campaigns/persian_gulf_full.yaml
Normal file
@ -0,0 +1,132 @@
|
||||
---
|
||||
name: Persian Gulf - [Pretense] - Full map
|
||||
theater: Persian Gulf
|
||||
authors: Colonel Akir Nakesh
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: Iran 2015
|
||||
description:
|
||||
<p>A Persian Gulf full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign. Recommended settings for campaign generation: disable frontline smoke, disable CTLD, disable Skynet, set CVBG navmesh to Redfor.</p>
|
||||
miz: persian_gulf_full.miz
|
||||
performance: 3
|
||||
version: "10.7"
|
||||
settings:
|
||||
pretense_carrier_zones_navmesh: "Red navmesh"
|
||||
perf_smoke_gen: false
|
||||
plugins:
|
||||
ctld: false
|
||||
skynetiads: false
|
||||
squadrons:
|
||||
#Al Dhafra AFB
|
||||
4:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
#Shiraz Intl
|
||||
19:
|
||||
- primary: Air Assault
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- Mi-8MTV2 Hip
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
- Ka-50 Hokum III
|
||||
- Ka-50 Hokum
|
||||
- Mi-24P Hind-F
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- A-10A Thunderbolt II
|
||||
- Su-25 Frogfoot
|
||||
- L-39ZA Albatros
|
||||
- primary: SEAD
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-25T Frogfoot
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- J-11A Flanker-L
|
||||
- MiG-29S Fulcrum-C
|
||||
- Su-27 Flanker-B
|
||||
- Su-33 Flanker-D
|
||||
- MiG-29A Fulcrum-A
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
- A-50
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- IL-78M
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
- An-26B
|
||||
Blue CVBG:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-33 Flanker-D
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2D Advanced Hawkeye
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
Red CVBG:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Su-33 Flanker-D
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2D Advanced Hawkeye
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
519
resources/plugins/pretense/init_body_1.lua
Normal file
519
resources/plugins/pretense/init_body_1.lua
Normal file
@ -0,0 +1,519 @@
|
||||
|
||||
end
|
||||
|
||||
presets = {
|
||||
upgrades = {
|
||||
basic = {
|
||||
tent = Preset:new({
|
||||
display = 'Tent',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "tent"
|
||||
}),
|
||||
comPost = Preset:new({
|
||||
display = 'Barracks',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "barracks"
|
||||
}),
|
||||
outpost = Preset:new({
|
||||
display = 'Outpost',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "outpost"
|
||||
}),
|
||||
artyBunker = Preset:new({
|
||||
display = 'Artillery Bunker',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
template = "ammo-depot"
|
||||
})
|
||||
},
|
||||
attack = {
|
||||
ammoCache = Preset:new({
|
||||
display = 'Ammo Cache',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "ammo-cache"
|
||||
}),
|
||||
ammoDepot = Preset:new({
|
||||
display = 'Ammo Depot',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
template = "ammo-depot"
|
||||
}),
|
||||
chemTank = Preset:new({
|
||||
display='Chemical Tank',
|
||||
cost = 2000,
|
||||
type ='upgrade',
|
||||
template = "chem-tank"
|
||||
}),
|
||||
},
|
||||
supply = {
|
||||
fuelCache = Preset:new({
|
||||
display = 'Fuel Cache',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "fuel-cache"
|
||||
}),
|
||||
fuelTank = Preset:new({
|
||||
display = 'Fuel Tank',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "fuel-tank-big"
|
||||
}),
|
||||
fuelTankFarp = Preset:new({
|
||||
display = 'Fuel Tank',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "fuel-tank-small"
|
||||
}),
|
||||
factory1 = Preset:new({
|
||||
display='Factory',
|
||||
cost = 2000,
|
||||
type ='upgrade',
|
||||
income = 20,
|
||||
template = "factory-1"
|
||||
}),
|
||||
factory2 = Preset:new({
|
||||
display='Factory',
|
||||
cost = 2000,
|
||||
type ='upgrade',
|
||||
income = 20,
|
||||
template = "factory-2"
|
||||
}),
|
||||
factoryTank = Preset:new({
|
||||
display='Storage Tank',
|
||||
cost = 1500,
|
||||
type ='upgrade',
|
||||
income = 10,
|
||||
template = "chem-tank"
|
||||
}),
|
||||
ammoDepot = Preset:new({
|
||||
display = 'Ammo Depot',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
income = 40,
|
||||
template = "ammo-depot"
|
||||
}),
|
||||
oilPump = Preset:new({
|
||||
display = 'Oil Pump',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
income = 20,
|
||||
template = "oil-pump"
|
||||
}),
|
||||
hangar = Preset:new({
|
||||
display = 'Hangar',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
income = 30,
|
||||
template = "hangar"
|
||||
}),
|
||||
excavator = Preset:new({
|
||||
display = 'Excavator',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
income = 20,
|
||||
template = "excavator"
|
||||
}),
|
||||
farm1 = Preset:new({
|
||||
display = 'Farm House',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
income = 40,
|
||||
template = "farm-house-1"
|
||||
}),
|
||||
farm2 = Preset:new({
|
||||
display = 'Farm House',
|
||||
cost = 2000,
|
||||
type = 'upgrade',
|
||||
income = 40,
|
||||
template = "farm-house-2"
|
||||
}),
|
||||
refinery1 = Preset:new({
|
||||
display='Refinery',
|
||||
cost = 2000,
|
||||
type ='upgrade',
|
||||
income = 100,
|
||||
template = "factory-1"
|
||||
}),
|
||||
powerplant1 = Preset:new({
|
||||
display='Power Plant',
|
||||
cost = 1500,
|
||||
type ='upgrade',
|
||||
income = 25,
|
||||
template = "factory-1"
|
||||
}),
|
||||
powerplant2 = Preset:new({
|
||||
display='Power Plant',
|
||||
cost = 1500,
|
||||
type ='upgrade',
|
||||
income = 25,
|
||||
template = "factory-2"
|
||||
}),
|
||||
antenna = Preset:new({
|
||||
display='Antenna',
|
||||
cost = 1000,
|
||||
type ='upgrade',
|
||||
income = 10,
|
||||
template = "antenna"
|
||||
}),
|
||||
hq = Preset:new({
|
||||
display='HQ Building',
|
||||
cost = 2000,
|
||||
type ='upgrade',
|
||||
income = 50,
|
||||
template = "tv-tower"
|
||||
}),
|
||||
},
|
||||
airdef = {
|
||||
bunker = Preset:new({
|
||||
display = 'Excavator',
|
||||
cost = 1500,
|
||||
type = 'upgrade',
|
||||
template = "excavator"
|
||||
}),
|
||||
comCenter = Preset:new({
|
||||
display = 'Command Center',
|
||||
cost = 12500,
|
||||
type = 'upgrade',
|
||||
template = "command-center"
|
||||
})
|
||||
}
|
||||
},
|
||||
defenses = {
|
||||
red = {
|
||||
infantry = Preset:new({
|
||||
display = 'Infantry',
|
||||
cost=2000,
|
||||
type='defense',
|
||||
template='infantry-red',
|
||||
}),
|
||||
artillery = Preset:new({
|
||||
display = 'Artillery',
|
||||
cost=2500,
|
||||
type='defense',
|
||||
template='artillery-red',
|
||||
}),
|
||||
shorad = Preset:new({
|
||||
display = 'SHORAD',
|
||||
cost=2500,
|
||||
type='defense',
|
||||
template='shorad-red',
|
||||
}),
|
||||
sa2 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='sa2-red',
|
||||
}),
|
||||
sa10 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='sa10-red',
|
||||
}),
|
||||
sa5 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=20000,
|
||||
type='defense',
|
||||
template='sa5-red',
|
||||
}),
|
||||
sa3 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=4000,
|
||||
type='defense',
|
||||
template='sa3-red',
|
||||
}),
|
||||
sa6 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=6000,
|
||||
type='defense',
|
||||
template='sa6-red',
|
||||
}),
|
||||
sa11 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=10000,
|
||||
type='defense',
|
||||
template='sa11-red',
|
||||
}),
|
||||
hawk = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=6000,
|
||||
type='defense',
|
||||
template='hawk-red',
|
||||
}),
|
||||
patriot = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='patriot-red',
|
||||
}),
|
||||
nasamsb = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='nasamsb-red',
|
||||
}),
|
||||
nasamsc = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='nasamsc-red',
|
||||
}),
|
||||
rapier = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='rapier-red',
|
||||
}),
|
||||
roland = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='roland-red',
|
||||
}),
|
||||
irondome = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=20000,
|
||||
type='defense',
|
||||
template='irondome-red',
|
||||
}),
|
||||
davidsling = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='davidsling-red',
|
||||
}),
|
||||
hq7 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='hq7-red',
|
||||
})
|
||||
},
|
||||
blue = {
|
||||
infantry = Preset:new({
|
||||
display = 'Infantry',
|
||||
cost=2000,
|
||||
type='defense',
|
||||
template='infantry-blue',
|
||||
}),
|
||||
artillery = Preset:new({
|
||||
display = 'Artillery',
|
||||
cost=2500,
|
||||
type='defense',
|
||||
template='artillery-blue',
|
||||
}),
|
||||
shorad = Preset:new({
|
||||
display = 'SHORAD',
|
||||
cost=2500,
|
||||
type='defense',
|
||||
template='shorad-blue',
|
||||
}),
|
||||
sa2 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='sa2-blue',
|
||||
}),
|
||||
sa10 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='sa10-blue',
|
||||
}),
|
||||
sa5 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=20000,
|
||||
type='defense',
|
||||
template='sa5-blue',
|
||||
}),
|
||||
sa3 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=4000,
|
||||
type='defense',
|
||||
template='sa3-blue',
|
||||
}),
|
||||
sa6 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=6000,
|
||||
type='defense',
|
||||
template='sa6-blue',
|
||||
}),
|
||||
sa11 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=10000,
|
||||
type='defense',
|
||||
template='sa11-blue',
|
||||
}),
|
||||
hawk = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=6000,
|
||||
type='defense',
|
||||
template='hawk-blue',
|
||||
}),
|
||||
patriot = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='patriot-blue',
|
||||
}),
|
||||
nasamsb = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='nasamsb-blue',
|
||||
}),
|
||||
nasamsc = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='nasamsc-blue',
|
||||
}),
|
||||
rapier = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='rapier-blue',
|
||||
}),
|
||||
roland = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='roland-blue',
|
||||
}),
|
||||
irondome = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=20000,
|
||||
type='defense',
|
||||
template='irondome-blue',
|
||||
}),
|
||||
davidsling = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=30000,
|
||||
type='defense',
|
||||
template='davidsling-blue',
|
||||
}),
|
||||
hq7 = Preset:new({
|
||||
display = 'SAM',
|
||||
cost=3000,
|
||||
type='defense',
|
||||
template='hq7-blue',
|
||||
})
|
||||
}
|
||||
},
|
||||
missions = {
|
||||
supply = {
|
||||
convoy = Preset:new({
|
||||
display = 'Supply convoy',
|
||||
cost = 4000,
|
||||
type = 'mission',
|
||||
missionType = ZoneCommand.missionTypes.supply_convoy
|
||||
}),
|
||||
convoy_escorted = Preset:new({
|
||||
display = 'Supply convoy',
|
||||
cost = 3000,
|
||||
type = 'mission',
|
||||
missionType = ZoneCommand.missionTypes.supply_convoy
|
||||
}),
|
||||
helo = Preset:new({
|
||||
display = 'Supply helicopter',
|
||||
cost = 2500,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.supply_air
|
||||
}),
|
||||
transfer = Preset:new({
|
||||
display = 'Supply transfer',
|
||||
cost = 1000,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.supply_transfer
|
||||
})
|
||||
},
|
||||
attack = {
|
||||
surface = Preset:new({
|
||||
display = 'Ground assault',
|
||||
cost = 100,
|
||||
type = 'mission',
|
||||
missionType = ZoneCommand.missionTypes.assault,
|
||||
}),
|
||||
cas = Preset:new({
|
||||
display = 'CAS',
|
||||
cost = 200,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.cas
|
||||
}),
|
||||
bai = Preset:new({
|
||||
display = 'BAI',
|
||||
cost = 200,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.bai
|
||||
}),
|
||||
strike = Preset:new({
|
||||
display = 'Strike',
|
||||
cost = 300,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.strike
|
||||
}),
|
||||
sead = Preset:new({
|
||||
display = 'SEAD',
|
||||
cost = 200,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.sead
|
||||
}),
|
||||
helo = Preset:new({
|
||||
display = 'CAS',
|
||||
cost = 100,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.cas_helo
|
||||
})
|
||||
},
|
||||
patrol={
|
||||
aircraft = Preset:new({
|
||||
display= "Patrol",
|
||||
cost = 100,
|
||||
type='mission',
|
||||
missionType = ZoneCommand.missionTypes.patrol
|
||||
})
|
||||
},
|
||||
support ={
|
||||
awacs = Preset:new({
|
||||
display= "AWACS",
|
||||
cost = 300,
|
||||
type='mission',
|
||||
bias='5',
|
||||
missionType = ZoneCommand.missionTypes.awacs
|
||||
}),
|
||||
tanker = Preset:new({
|
||||
display= "Tanker",
|
||||
cost = 200,
|
||||
type='mission',
|
||||
bias='2',
|
||||
missionType = ZoneCommand.missionTypes.tanker
|
||||
})
|
||||
}
|
||||
},
|
||||
special = {
|
||||
red = {
|
||||
infantry = Preset:new({
|
||||
display = 'Infantry',
|
||||
cost=-1,
|
||||
type='defense',
|
||||
template='defense-red',
|
||||
}),
|
||||
},
|
||||
blue = {
|
||||
infantry = Preset:new({
|
||||
display = 'Infantry',
|
||||
cost=-1,
|
||||
type='defense',
|
||||
template='defense-blue',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zones = {}
|
||||
do
|
||||
|
||||
32
resources/plugins/pretense/init_body_2.lua
Normal file
32
resources/plugins/pretense/init_body_2.lua
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
end
|
||||
|
||||
ZoneCommand.setNeighbours(cm)
|
||||
|
||||
bm = BattlefieldManager:new()
|
||||
|
||||
mc = MarkerCommands:new()
|
||||
|
||||
pt = PlayerTracker:new(mc)
|
||||
|
||||
mt = MissionTracker:new(pt, mc)
|
||||
|
||||
st = SquadTracker:new()
|
||||
|
||||
ct = CSARTracker:new()
|
||||
|
||||
pl = PlayerLogistics:new(mt, pt, st, ct)
|
||||
|
||||
gci = GCI:new(2)
|
||||
|
||||
gm = GroupMonitor:new(cm)
|
||||
ZoneCommand.groupMonitor = gm
|
||||
|
||||
-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1)
|
||||
pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8)
|
||||
62
resources/plugins/pretense/init_body_3.lua
Normal file
62
resources/plugins/pretense/init_body_3.lua
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
pm = PersistenceManager:new(savefile, gm, st, ct, pl)
|
||||
pm:load()
|
||||
|
||||
if pm:canRestore() then
|
||||
pm:restoreZones()
|
||||
pm:restoreAIMissions()
|
||||
pm:restoreBattlefield()
|
||||
pm:restoreCsar()
|
||||
pm:restoreSquads()
|
||||
else
|
||||
--initial states
|
||||
Starter.start(zones)
|
||||
end
|
||||
|
||||
timer.scheduleFunction(function(param, time)
|
||||
pm:save()
|
||||
env.info("Mission state saved")
|
||||
return time+60
|
||||
end, zones, timer.getTime()+60)
|
||||
|
||||
|
||||
--make sure support units are present where needed
|
||||
ensureSpawn = {
|
||||
['golf-farp-suport'] = zones.golf,
|
||||
['november-farp-suport'] = zones.november,
|
||||
['tango-farp-suport'] = zones.tango,
|
||||
['sierra-farp-suport'] = zones.sierra,
|
||||
['cherkessk-farp-suport'] = zones.cherkessk,
|
||||
['unal-farp-suport'] = zones.unal,
|
||||
['tyrnyauz-farp-suport'] = zones.tyrnyauz
|
||||
}
|
||||
|
||||
for grname, zn in pairs(ensureSpawn) do
|
||||
local g = Group.getByName(grname)
|
||||
if g then g:destroy() end
|
||||
end
|
||||
|
||||
timer.scheduleFunction(function(param, time)
|
||||
|
||||
for grname, zn in pairs(ensureSpawn) do
|
||||
local g = Group.getByName(grname)
|
||||
if zn.side == 2 then
|
||||
if not g then
|
||||
local err, msg = pcall(mist.respawnGroup,grname,true)
|
||||
if not err then
|
||||
env.info("ERROR spawning "..grname)
|
||||
env.info(msg)
|
||||
end
|
||||
end
|
||||
else
|
||||
if g then g:destroy() end
|
||||
end
|
||||
end
|
||||
|
||||
return time+30
|
||||
end, {}, timer.getTime()+30)
|
||||
|
||||
|
||||
--supply injection
|
||||
|
||||
|
||||
141
resources/plugins/pretense/init_footer.lua
Normal file
141
resources/plugins/pretense/init_footer.lua
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
supplyPointRegistry = {
|
||||
blue = {},
|
||||
red = {}
|
||||
}
|
||||
|
||||
for i,v in ipairs(blueSupply) do
|
||||
local g = Group.getByName(v)
|
||||
if g then
|
||||
supplyPointRegistry.blue[v] = g:getUnit(1):getPoint()
|
||||
end
|
||||
end
|
||||
|
||||
for i,v in ipairs(redSupply) do
|
||||
local g = Group.getByName(v)
|
||||
if g then
|
||||
supplyPointRegistry.red[v] = g:getUnit(1):getPoint()
|
||||
end
|
||||
end
|
||||
|
||||
offmapSupplyRegistry = {}
|
||||
timer.scheduleFunction(function(param, time)
|
||||
local availableBlue = {}
|
||||
for i,v in ipairs(param.blue) do
|
||||
if offmapSupplyRegistry[v] == nil then
|
||||
table.insert(availableBlue, v)
|
||||
end
|
||||
end
|
||||
|
||||
local availableRed = {}
|
||||
for i,v in ipairs(param.red) do
|
||||
if offmapSupplyRegistry[v] == nil then
|
||||
table.insert(availableRed, v)
|
||||
end
|
||||
end
|
||||
|
||||
local redtargets = {}
|
||||
local bluetargets = {}
|
||||
for _, zn in ipairs(param.offmapZones) do
|
||||
if zn:needsSupplies(3000) then
|
||||
local isOnRoute = false
|
||||
for _,data in pairs(offmapSupplyRegistry) do
|
||||
if data.zone.name == zn.name then
|
||||
isOnRoute = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not isOnRoute then
|
||||
if zn.side == 1 then
|
||||
table.insert(redtargets, zn)
|
||||
elseif zn.side == 2 then
|
||||
table.insert(bluetargets, zn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #availableRed > 0 and #redtargets > 0 then
|
||||
local zn = redtargets[math.random(1,#redtargets)]
|
||||
|
||||
local red = nil
|
||||
local minD = 999999999
|
||||
for i,v in ipairs(availableRed) do
|
||||
local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v])
|
||||
if d < minD then
|
||||
red = v
|
||||
minD = d
|
||||
end
|
||||
end
|
||||
|
||||
if not red then red = availableRed[math.random(1,#availableRed)] end
|
||||
|
||||
local gr = red
|
||||
red = nil
|
||||
mist.respawnGroup(gr, true)
|
||||
offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()}
|
||||
env.info(gr..' was deployed')
|
||||
timer.scheduleFunction(function(param,time)
|
||||
local g = Group.getByName(param.group)
|
||||
TaskExtensions.landAtAirfield(g, param.target.zone.point)
|
||||
env.info(param.group..' going to '..param.target.name)
|
||||
end, {group=gr, target=zn}, timer.getTime()+2)
|
||||
end
|
||||
|
||||
if #availableBlue > 0 and #bluetargets>0 then
|
||||
local zn = bluetargets[math.random(1,#bluetargets)]
|
||||
|
||||
local blue = nil
|
||||
local minD = 999999999
|
||||
for i,v in ipairs(availableBlue) do
|
||||
local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v])
|
||||
if d < minD then
|
||||
blue = v
|
||||
minD = d
|
||||
end
|
||||
end
|
||||
|
||||
if not blue then blue = availableBlue[math.random(1,#availableBlue)] end
|
||||
|
||||
local gr = blue
|
||||
blue = nil
|
||||
mist.respawnGroup(gr, true)
|
||||
offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()}
|
||||
env.info(gr..' was deployed')
|
||||
timer.scheduleFunction(function(param,time)
|
||||
local g = Group.getByName(param.group)
|
||||
TaskExtensions.landAtAirfield(g, param.target.zone.point)
|
||||
env.info(param.group..' going to '..param.target.name)
|
||||
end, {group=gr, target=zn}, timer.getTime()+2)
|
||||
end
|
||||
|
||||
return time+(60*5)
|
||||
end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60)
|
||||
|
||||
|
||||
|
||||
timer.scheduleFunction(function(param, time)
|
||||
|
||||
for groupname,data in pairs(offmapSupplyRegistry) do
|
||||
local gr = Group.getByName(groupname)
|
||||
if not gr then
|
||||
offmapSupplyRegistry[groupname] = nil
|
||||
env.info(groupname..' was destroyed')
|
||||
end
|
||||
|
||||
if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then
|
||||
gr:destroy()
|
||||
offmapSupplyRegistry[groupname] = nil
|
||||
env.info(groupname..' despawned due to being alive for too long')
|
||||
end
|
||||
|
||||
if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then
|
||||
data.zone:addResource(15000)
|
||||
gr:destroy()
|
||||
offmapSupplyRegistry[groupname] = nil
|
||||
env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources')
|
||||
end
|
||||
end
|
||||
|
||||
return time+180
|
||||
end, {}, timer.getTime()+180)
|
||||
12
resources/plugins/pretense/init_header.lua
Normal file
12
resources/plugins/pretense/init_header.lua
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
if lfs then
|
||||
local dir = lfs.writedir()..'Missions/Saves/'
|
||||
lfs.mkdir(dir)
|
||||
savefile = dir..savefile
|
||||
env.info('Pretense - Save file path: '..savefile)
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
|
||||
15862
resources/plugins/pretense/pretense_compiled.lua
Normal file
15862
resources/plugins/pretense/pretense_compiled.lua
Normal file
File diff suppressed because it is too large
Load Diff
BIN
resources/ui/misc/pretense.png
Normal file
BIN
resources/ui/misc/pretense.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
resources/ui/misc/pretense_discord.png
Normal file
BIN
resources/ui/misc/pretense_discord.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
resources/ui/misc/pretense_generate.png
Normal file
BIN
resources/ui/misc/pretense_generate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -1,4 +1,5 @@
|
||||
class: Logistics
|
||||
price: 2
|
||||
variants:
|
||||
LARC-V: null
|
||||
class: Logistics
|
||||
price: 3
|
||||
variants:
|
||||
LARC-V Amphibious Cargo Vehicle: null
|
||||
LARC-V: null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user