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/
|
/logs/
|
||||||
/resources/logging.yaml
|
/resources/logging.yaml
|
||||||
|
/resources/plugins/pretense/pretense_output.lua
|
||||||
|
|
||||||
*.psd
|
*.psd
|
||||||
|
|||||||
@ -19,6 +19,7 @@ from .ocaaircraft import OcaAircraftFlightPlan
|
|||||||
from .ocarunway import OcaRunwayFlightPlan
|
from .ocarunway import OcaRunwayFlightPlan
|
||||||
from .packagerefueling import PackageRefuelingFlightPlan
|
from .packagerefueling import PackageRefuelingFlightPlan
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
|
from .pretensecargo import PretenseCargoFlightPlan
|
||||||
from .sead import SeadFlightPlan
|
from .sead import SeadFlightPlan
|
||||||
from .seadsweep import SeadSweepFlightPlan
|
from .seadsweep import SeadSweepFlightPlan
|
||||||
from .strike import StrikeFlightPlan
|
from .strike import StrikeFlightPlan
|
||||||
@ -61,6 +62,7 @@ class FlightPlanBuilderTypes:
|
|||||||
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
FlightType.TRANSPORT: AirliftFlightPlan.builder_type(),
|
||||||
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
FlightType.FERRY: FerryFlightPlan.builder_type(),
|
||||||
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(),
|
||||||
|
FlightType.PRETENSE_CARGO: PretenseCargoFlightPlan.builder_type(),
|
||||||
FlightType.ARMED_RECON: ArmedReconFlightPlan.builder_type(),
|
FlightType.ARMED_RECON: ArmedReconFlightPlan.builder_type(),
|
||||||
}
|
}
|
||||||
try:
|
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"
|
FERRY = "Ferry"
|
||||||
AIR_ASSAULT = "Air Assault"
|
AIR_ASSAULT = "Air Assault"
|
||||||
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
|
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"
|
ARMED_RECON = "Armed Recon"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@ -124,5 +125,6 @@ class FlightType(Enum):
|
|||||||
FlightType.SWEEP: AirEntity.FIGHTER,
|
FlightType.SWEEP: AirEntity.FIGHTER,
|
||||||
FlightType.TARCAP: AirEntity.FIGHTER,
|
FlightType.TARCAP: AirEntity.FIGHTER,
|
||||||
FlightType.TRANSPORT: AirEntity.UTILITY,
|
FlightType.TRANSPORT: AirEntity.UTILITY,
|
||||||
|
FlightType.PRETENSE_CARGO: AirEntity.UTILITY,
|
||||||
FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING,
|
FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING,
|
||||||
}.get(self, AirEntity.UNSPECIFIED)
|
}.get(self, AirEntity.UNSPECIFIED)
|
||||||
|
|||||||
@ -23,6 +23,8 @@ class SquadronDefGenerator:
|
|||||||
def generate_for_task(
|
def generate_for_task(
|
||||||
self, task: FlightType, control_point: ControlPoint
|
self, task: FlightType, control_point: ControlPoint
|
||||||
) -> Optional[SquadronDef]:
|
) -> Optional[SquadronDef]:
|
||||||
|
settings = control_point.coalition.game.settings
|
||||||
|
squadron_random_chance = settings.squadron_random_chance
|
||||||
aircraft_choice: Optional[AircraftType] = None
|
aircraft_choice: Optional[AircraftType] = None
|
||||||
for aircraft in AircraftType.priority_list_for_task(task):
|
for aircraft in AircraftType.priority_list_for_task(task):
|
||||||
if aircraft not in self.faction.all_aircrafts:
|
if aircraft not in self.faction.all_aircrafts:
|
||||||
@ -30,9 +32,9 @@ class SquadronDefGenerator:
|
|||||||
if not control_point.can_operate(aircraft):
|
if not control_point.can_operate(aircraft):
|
||||||
continue
|
continue
|
||||||
aircraft_choice = aircraft
|
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.
|
# priority list to maintain some unit variety.
|
||||||
if random.choice([True, False]):
|
if squadron_random_chance >= random.randint(1, 100):
|
||||||
break
|
break
|
||||||
|
|
||||||
if aircraft_choice is None:
|
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.plugins import LuaPluginManager
|
||||||
from game.utils import Distance
|
from game.utils import Distance
|
||||||
from . import naming, persistency
|
from . import naming, persistency
|
||||||
|
from .ato import Flight
|
||||||
from .ato.flighttype import FlightType
|
from .ato.flighttype import FlightType
|
||||||
from .campaignloader import CampaignAirWingConfig
|
from .campaignloader import CampaignAirWingConfig
|
||||||
from .coalition import Coalition
|
from .coalition import Coalition
|
||||||
@ -148,6 +149,16 @@ class Game:
|
|||||||
self.blue.configure_default_air_wing(air_wing_config)
|
self.blue.configure_default_air_wing(air_wing_config)
|
||||||
self.red.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)
|
self.on_load(game_still_initializing=True)
|
||||||
|
|
||||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Optional, TYPE_CHECKING
|
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.logisticsgenerator import LogisticsGenerator
|
||||||
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
|
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
|
||||||
from game.radio.radios import RadioFrequency, RadioRegistry
|
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.runways import RunwayData
|
||||||
from game.squadrons import Pilot
|
from game.squadrons import Pilot
|
||||||
from .aircraftbehavior import AircraftBehavior
|
from .aircraftbehavior import AircraftBehavior
|
||||||
@ -210,9 +216,12 @@ class FlightGroupConfigurator:
|
|||||||
) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan):
|
) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan):
|
||||||
tacan = self.flight.tacan
|
tacan = self.flight.tacan
|
||||||
if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan:
|
if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan:
|
||||||
tacan = self.tacan_registry.alloc_for_band(
|
try:
|
||||||
TacanBand.Y, TacanUsage.AirToAir
|
tacan = self.tacan_registry.alloc_for_band(
|
||||||
)
|
TacanBand.Y, TacanUsage.AirToAir
|
||||||
|
)
|
||||||
|
except OutOfTacanChannelsError:
|
||||||
|
tacan = random.choice(list(self.tacan_registry.allocated_channels))
|
||||||
else:
|
else:
|
||||||
tacan = self.flight.tacan
|
tacan = self.flight.tacan
|
||||||
self.mission_data.tankers.append(
|
self.mission_data.tankers.append(
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from dcs.planes import (
|
|||||||
C_101CC,
|
C_101CC,
|
||||||
Su_33,
|
Su_33,
|
||||||
MiG_15bis,
|
MiG_15bis,
|
||||||
|
M_2000C,
|
||||||
)
|
)
|
||||||
from dcs.point import PointAction
|
from dcs.point import PointAction
|
||||||
from dcs.ships import KUZNECOW
|
from dcs.ships import KUZNECOW
|
||||||
@ -36,7 +37,7 @@ from game.missiongenerator.missiondata import MissionData
|
|||||||
from game.naming import namegen
|
from game.naming import namegen
|
||||||
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
|
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
|
||||||
from game.utils import feet, meters
|
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_HELI_ALT = meters(500)
|
||||||
WARM_START_ALTITUDE = meters(3000)
|
WARM_START_ALTITUDE = meters(3000)
|
||||||
@ -496,7 +497,7 @@ class FlightGroupSpawner:
|
|||||||
) -> Optional[FlyingGroup[Any]]:
|
) -> Optional[FlyingGroup[Any]]:
|
||||||
is_airbase = False
|
is_airbase = False
|
||||||
is_roadbase = 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:
|
if not is_large and len(self.ground_spawns_roadbase[cp]) > 0:
|
||||||
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
ground_spawn = self.ground_spawns_roadbase[cp].pop()
|
||||||
@ -519,6 +520,8 @@ class FlightGroupSpawner:
|
|||||||
group.points[0].type = "TakeOffGround"
|
group.points[0].type = "TakeOffGround"
|
||||||
group.units[0].heading = ground_spawn[0].units[0].heading
|
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
|
# Hot start aircraft which require ground power to start, when ground power
|
||||||
# trucks have been disabled for performance reasons
|
# trucks have been disabled for performance reasons
|
||||||
ground_power_available = (
|
ground_power_available = (
|
||||||
@ -529,10 +532,31 @@ class FlightGroupSpawner:
|
|||||||
and self.flight.coalition.game.settings.ground_start_ground_power_trucks_roadbase
|
and self.flight.coalition.game.settings.ground_start_ground_power_trucks_roadbase
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.start_type is not StartType.COLD or (
|
# Also hot start aircraft which require ground crew support (ground air or chock removal)
|
||||||
not ground_power_available
|
# which might not be available at roadbases
|
||||||
and self.flight.unit_type.dcs_unit_type
|
if (
|
||||||
in [A_4E_C, F_5E_3, F_86F_Sabre, MiG_15bis, F_14A_135_GR, F_14B, C_101CC]
|
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].action = PointAction.FromGroundAreaHot
|
||||||
group.points[0].type = "TakeOffGroundHot"
|
group.points[0].type = "TakeOffGroundHot"
|
||||||
@ -556,12 +580,33 @@ class FlightGroupSpawner:
|
|||||||
ground_spawn[0].x, ground_spawn[0].y, terrain=terrain
|
ground_spawn[0].x, ground_spawn[0].y, terrain=terrain
|
||||||
)
|
)
|
||||||
group.units[1 + i].heading = ground_spawn[0].units[0].heading
|
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:
|
except IndexError as ex:
|
||||||
raise RuntimeError(
|
raise NoParkingSlotError(
|
||||||
f"Not enough ground spawn slots available at {cp}"
|
f"Not enough STOL slots available at {cp}"
|
||||||
) from ex
|
) from ex
|
||||||
return group
|
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:
|
def dcs_start_type(self) -> DcsStartType:
|
||||||
if self.start_type is StartType.RUNWAY:
|
if self.start_type is StartType.RUNWAY:
|
||||||
return DcsStartType.Runway
|
return DcsStartType.Runway
|
||||||
|
|||||||
@ -52,6 +52,8 @@ class CarrierInfo(UnitInfo):
|
|||||||
"""Carrier information."""
|
"""Carrier information."""
|
||||||
|
|
||||||
tacan: TacanChannel
|
tacan: TacanChannel
|
||||||
|
icls_channel: int | None
|
||||||
|
link4_freq: RadioFrequency | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -67,6 +67,7 @@ from game.theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
TheaterUnit,
|
TheaterUnit,
|
||||||
NavalControlPoint,
|
NavalControlPoint,
|
||||||
|
Airfield,
|
||||||
)
|
)
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
CarrierGroundObject,
|
CarrierGroundObject,
|
||||||
@ -297,11 +298,9 @@ class GroundObjectGenerator:
|
|||||||
# All alive Ships
|
# All alive Ships
|
||||||
ship_units.append(unit)
|
ship_units.append(unit)
|
||||||
if vehicle_units:
|
if vehicle_units:
|
||||||
vg = self.create_vehicle_group(group.group_name, vehicle_units)
|
self.create_vehicle_group(group.group_name, vehicle_units)
|
||||||
vg.hidden_on_mfd = self.ground_object.hide_on_mfd
|
|
||||||
if ship_units:
|
if ship_units:
|
||||||
sg = self.create_ship_group(group.group_name, ship_units)
|
self.create_ship_group(group.group_name, ship_units)
|
||||||
sg.hidden_on_mfd = self.ground_object.hide_on_mfd
|
|
||||||
|
|
||||||
def create_vehicle_group(
|
def create_vehicle_group(
|
||||||
self, group_name: str, units: list[TheaterUnit]
|
self, group_name: str, units: list[TheaterUnit]
|
||||||
@ -333,6 +332,7 @@ class GroundObjectGenerator:
|
|||||||
self._register_theater_unit(unit, vehicle_group.units[-1])
|
self._register_theater_unit(unit, vehicle_group.units[-1])
|
||||||
if vehicle_group is None:
|
if vehicle_group is None:
|
||||||
raise RuntimeError(f"Error creating VehicleGroup for {group_name}")
|
raise RuntimeError(f"Error creating VehicleGroup for {group_name}")
|
||||||
|
vehicle_group.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||||
return vehicle_group
|
return vehicle_group
|
||||||
|
|
||||||
def create_ship_group(
|
def create_ship_group(
|
||||||
@ -369,6 +369,7 @@ class GroundObjectGenerator:
|
|||||||
self._register_theater_unit(unit, ship_group.units[-1])
|
self._register_theater_unit(unit, ship_group.units[-1])
|
||||||
if ship_group is None:
|
if ship_group is None:
|
||||||
raise RuntimeError(f"Error creating ShipGroup for {group_name}")
|
raise RuntimeError(f"Error creating ShipGroup for {group_name}")
|
||||||
|
ship_group.hidden_on_mfd = self.ground_object.hide_on_mfd
|
||||||
return ship_group
|
return ship_group
|
||||||
|
|
||||||
def create_static_group(self, unit: TheaterUnit) -> None:
|
def create_static_group(self, unit: TheaterUnit) -> None:
|
||||||
@ -645,6 +646,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
|||||||
callsign=tacan_callsign,
|
callsign=tacan_callsign,
|
||||||
freq=atc,
|
freq=atc,
|
||||||
tacan=tacan,
|
tacan=tacan,
|
||||||
|
icls_channel=icls,
|
||||||
|
link4_freq=link4,
|
||||||
blue=self.control_point.captured,
|
blue=self.control_point.captured,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -844,6 +847,20 @@ class HelipadGenerator:
|
|||||||
else:
|
else:
|
||||||
self.helipads.append(sg)
|
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(
|
warehouse = Airport(
|
||||||
pad.position,
|
pad.position,
|
||||||
self.m.terrain,
|
self.m.terrain,
|
||||||
@ -852,30 +869,31 @@ class HelipadGenerator:
|
|||||||
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||||
self.m.warehouses.warehouses[pad.id] = warehouse
|
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||||
|
|
||||||
# Generate a FARP Ammo and Fuel stack for each pad
|
if not cull_farp_statics:
|
||||||
self.m.static_group(
|
# Generate a FARP Ammo and Fuel stack for each pad
|
||||||
country=country,
|
self.m.static_group(
|
||||||
name=(name + "_fuel"),
|
country=country,
|
||||||
_type=Fortification.FARP_Fuel_Depot,
|
name=(name + "_fuel"),
|
||||||
position=pad.position.point_from_heading(helipad.heading.degrees, 35),
|
_type=Fortification.FARP_Fuel_Depot,
|
||||||
heading=pad.heading + 180,
|
position=pad.position.point_from_heading(helipad.heading.degrees, 35),
|
||||||
)
|
heading=pad.heading + 180,
|
||||||
self.m.static_group(
|
)
|
||||||
country=country,
|
self.m.static_group(
|
||||||
name=(name + "_ammo"),
|
country=country,
|
||||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
name=(name + "_ammo"),
|
||||||
position=pad.position.point_from_heading(
|
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||||
helipad.heading.degrees, 35
|
position=pad.position.point_from_heading(
|
||||||
).point_from_heading(helipad.heading.degrees + 90, 10),
|
helipad.heading.degrees, 35
|
||||||
heading=pad.heading + 90,
|
).point_from_heading(helipad.heading.degrees + 90, 10),
|
||||||
)
|
heading=pad.heading + 90,
|
||||||
self.m.static_group(
|
)
|
||||||
country=country,
|
self.m.static_group(
|
||||||
name=(name + "_ws"),
|
country=country,
|
||||||
_type=Fortification.Windsock,
|
name=(name + "_ws"),
|
||||||
position=helipad.point_from_heading(helipad.heading.degrees + 45, 35),
|
_type=Fortification.Windsock,
|
||||||
heading=pad.heading,
|
position=helipad.point_from_heading(helipad.heading.degrees + 45, 35),
|
||||||
)
|
heading=pad.heading,
|
||||||
|
)
|
||||||
|
|
||||||
def append_helipad(
|
def append_helipad(
|
||||||
self,
|
self,
|
||||||
@ -952,61 +970,88 @@ class GroundSpawnRoadbaseGenerator:
|
|||||||
country.id
|
country.id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate ammo truck/farp and fuel truck/stack for each pad
|
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
|
||||||
if self.game.settings.ground_start_trucks_roadbase:
|
self.cp, Airfield
|
||||||
self.m.vehicle_group(
|
):
|
||||||
country=country,
|
cull_farp_statics = True
|
||||||
name=(name + "_fuel"),
|
elif self.game.position_culled(ground_spawn[0]):
|
||||||
_type=tanker_type,
|
cull_farp_statics = True
|
||||||
position=pad.position.point_from_heading(
|
if self.cp.coalition.player:
|
||||||
ground_spawn[0].heading.degrees + 90, 35
|
for package in self.cp.coalition.ato.packages:
|
||||||
),
|
for flight in package.flights:
|
||||||
group_size=1,
|
if flight.squadron.location == self.cp:
|
||||||
heading=pad.heading + 315,
|
cull_farp_statics = False
|
||||||
move_formation=PointAction.OffRoad,
|
break
|
||||||
)
|
elif flight.divert and flight.divert == self.cp:
|
||||||
self.m.vehicle_group(
|
cull_farp_statics = False
|
||||||
country=country,
|
break
|
||||||
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:
|
else:
|
||||||
self.m.static_group(
|
cull_farp_statics = False
|
||||||
country=country,
|
|
||||||
name=(name + "_fuel"),
|
warehouse = Airport(
|
||||||
_type=Fortification.FARP_Fuel_Depot,
|
pad.position,
|
||||||
position=pad.position.point_from_heading(
|
self.m.terrain,
|
||||||
ground_spawn[0].heading.degrees + 90, 35
|
).dict()
|
||||||
),
|
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
|
||||||
heading=pad.heading + 270,
|
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||||
)
|
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||||
self.m.static_group(
|
|
||||||
country=country,
|
if not cull_farp_statics:
|
||||||
name=(name + "_ammo"),
|
# Generate ammo truck/farp and fuel truck/stack for each pad
|
||||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
if self.game.settings.ground_start_trucks_roadbase:
|
||||||
position=pad.position.point_from_heading(
|
self.m.vehicle_group(
|
||||||
ground_spawn[0].heading.degrees + 90, 35
|
country=country,
|
||||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 10),
|
name=(name + "_fuel"),
|
||||||
heading=pad.heading + 180,
|
_type=tanker_type,
|
||||||
)
|
position=pad.position.point_from_heading(
|
||||||
if self.game.settings.ground_start_ground_power_trucks_roadbase:
|
ground_spawn[0].heading.degrees + 90, 35
|
||||||
self.m.vehicle_group(
|
),
|
||||||
country=country,
|
group_size=1,
|
||||||
name=(name + "_power"),
|
heading=pad.heading + 315,
|
||||||
_type=power_truck_type,
|
move_formation=PointAction.OffRoad,
|
||||||
position=pad.position.point_from_heading(
|
)
|
||||||
ground_spawn[0].heading.degrees + 90, 35
|
self.m.vehicle_group(
|
||||||
).point_from_heading(ground_spawn[0].heading.degrees + 180, 20),
|
country=country,
|
||||||
group_size=1,
|
name=(name + "_ammo"),
|
||||||
heading=pad.heading + 315,
|
_type=ammo_truck_type,
|
||||||
move_formation=PointAction.OffRoad,
|
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:
|
def generate(self) -> None:
|
||||||
try:
|
try:
|
||||||
@ -1069,6 +1114,14 @@ class GroundSpawnLargeGenerator:
|
|||||||
country.id
|
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
|
# Generate a FARP Ammo and Fuel stack for each pad
|
||||||
if self.game.settings.ground_start_trucks:
|
if self.game.settings.ground_start_trucks:
|
||||||
self.m.vehicle_group(
|
self.m.vehicle_group(
|
||||||
@ -1186,61 +1239,88 @@ class GroundSpawnGenerator:
|
|||||||
country.id
|
country.id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate a FARP Ammo and Fuel stack for each pad
|
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
|
||||||
if self.game.settings.ground_start_trucks:
|
self.cp, Airfield
|
||||||
self.m.vehicle_group(
|
):
|
||||||
country=country,
|
cull_farp_statics = True
|
||||||
name=(name + "_fuel"),
|
elif self.game.position_culled(vtol_pad[0]):
|
||||||
_type=tanker_type,
|
cull_farp_statics = True
|
||||||
position=pad.position.point_from_heading(
|
if self.cp.coalition.player:
|
||||||
vtol_pad[0].heading.degrees - 175, 35
|
for package in self.cp.coalition.ato.packages:
|
||||||
),
|
for flight in package.flights:
|
||||||
group_size=1,
|
if flight.squadron.location == self.cp:
|
||||||
heading=pad.heading + 45,
|
cull_farp_statics = False
|
||||||
move_formation=PointAction.OffRoad,
|
break
|
||||||
)
|
elif flight.divert and flight.divert == self.cp:
|
||||||
self.m.vehicle_group(
|
cull_farp_statics = False
|
||||||
country=country,
|
break
|
||||||
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:
|
else:
|
||||||
self.m.static_group(
|
cull_farp_statics = False
|
||||||
country=country,
|
|
||||||
name=(name + "_fuel"),
|
if not cull_farp_statics:
|
||||||
_type=Fortification.FARP_Fuel_Depot,
|
warehouse = Airport(
|
||||||
position=pad.position.point_from_heading(
|
pad.position,
|
||||||
vtol_pad[0].heading.degrees - 180, 45
|
self.m.terrain,
|
||||||
),
|
).dict()
|
||||||
heading=pad.heading,
|
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
|
||||||
)
|
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
|
||||||
self.m.static_group(
|
self.m.warehouses.warehouses[pad.id] = warehouse
|
||||||
country=country,
|
|
||||||
name=(name + "_ammo"),
|
# Generate a FARP Ammo and Fuel stack for each pad
|
||||||
_type=Fortification.FARP_Ammo_Dump_Coating,
|
if self.game.settings.ground_start_trucks:
|
||||||
position=pad.position.point_from_heading(
|
self.m.vehicle_group(
|
||||||
vtol_pad[0].heading.degrees - 180, 35
|
country=country,
|
||||||
),
|
name=(name + "_fuel"),
|
||||||
heading=pad.heading + 270,
|
_type=tanker_type,
|
||||||
)
|
position=pad.position.point_from_heading(
|
||||||
if self.game.settings.ground_start_ground_power_trucks:
|
vtol_pad[0].heading.degrees - 175, 35
|
||||||
self.m.vehicle_group(
|
),
|
||||||
country=country,
|
group_size=1,
|
||||||
name=(name + "_power"),
|
heading=pad.heading + 45,
|
||||||
_type=power_truck_type,
|
move_formation=PointAction.OffRoad,
|
||||||
position=pad.position.point_from_heading(
|
)
|
||||||
vtol_pad[0].heading.degrees - 185, 35
|
self.m.vehicle_group(
|
||||||
),
|
country=country,
|
||||||
group_size=1,
|
name=(name + "_ammo"),
|
||||||
heading=pad.heading + 45,
|
_type=ammo_truck_type,
|
||||||
move_formation=PointAction.OffRoad,
|
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:
|
def generate(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -154,6 +154,10 @@ def save_dir() -> Path:
|
|||||||
return base_path() / "Retribution" / "Saves"
|
return base_path() / "Retribution" / "Saves"
|
||||||
|
|
||||||
|
|
||||||
|
def pre_pretense_backups_dir() -> Path:
|
||||||
|
return save_dir() / "PrePretenseBackups"
|
||||||
|
|
||||||
|
|
||||||
def server_port() -> int:
|
def server_port() -> int:
|
||||||
global _server_port
|
global _server_port
|
||||||
return _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.
|
already allocated.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
while_count = 0
|
||||||
while (channel := random_frequency(radio)) in self.allocated_channels:
|
while (channel := random_frequency(radio)) in self.allocated_channels:
|
||||||
|
while_count += 1
|
||||||
|
if while_count > 1000:
|
||||||
|
raise StopIteration
|
||||||
pass
|
pass
|
||||||
self.reserve(channel)
|
self.reserve(channel)
|
||||||
return channel
|
return channel
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
# In the event of too many channel users, fail gracefully by reusing
|
# 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
|
# https://github.com/dcs-liberation/dcs_liberation/issues/598
|
||||||
channel = radio.last_channel
|
channel = random_frequency(radio)
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"No more free channels for {radio.name}. Reusing {channel}."
|
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"
|
CAMPAIGN_DOCTRINE_PAGE = "Campaign Doctrine"
|
||||||
DOCTRINE_DISTANCES_SECTION = "Doctrine distances"
|
DOCTRINE_DISTANCES_SECTION = "Doctrine distances"
|
||||||
|
|
||||||
|
PRETENSE_PAGE = "Pretense"
|
||||||
|
|
||||||
MISSION_GENERATOR_PAGE = "Mission Generator"
|
MISSION_GENERATOR_PAGE = "Mission Generator"
|
||||||
|
|
||||||
GAMEPLAY_SECTION = "Gameplay"
|
GAMEPLAY_SECTION = "Gameplay"
|
||||||
@ -156,6 +158,7 @@ class Settings:
|
|||||||
MISSION_RESTRICTIONS_SECTION,
|
MISSION_RESTRICTIONS_SECTION,
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
easy_communication: Optional[bool] = choices_option(
|
easy_communication: Optional[bool] = choices_option(
|
||||||
"Easy Communication",
|
"Easy Communication",
|
||||||
page=DIFFICULTY_PAGE,
|
page=DIFFICULTY_PAGE,
|
||||||
@ -174,6 +177,20 @@ class Settings:
|
|||||||
|
|
||||||
# Campaign management
|
# Campaign management
|
||||||
# General
|
# 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: bool = boolean_option(
|
||||||
"Restrict weapons by date (WIP)",
|
"Restrict weapons by date (WIP)",
|
||||||
page=CAMPAIGN_MANAGEMENT_PAGE,
|
page=CAMPAIGN_MANAGEMENT_PAGE,
|
||||||
@ -906,6 +923,17 @@ class Settings:
|
|||||||
"Needed to cold-start some aircraft types. Might have a performance impact."
|
"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_unlimited_fuel: bool = boolean_option(
|
||||||
"AI flights have unlimited fuel",
|
"AI flights have unlimited fuel",
|
||||||
MISSION_GENERATOR_PAGE,
|
MISSION_GENERATOR_PAGE,
|
||||||
@ -1085,6 +1113,140 @@ class Settings:
|
|||||||
"if the start-up type was manually changed to 'In-Flight'."
|
"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
|
# Cheating. Not using auto settings because the same page also has buttons which do
|
||||||
# not alter settings.
|
# not alter settings.
|
||||||
|
|||||||
@ -202,6 +202,29 @@ class ConflictTheater:
|
|||||||
assert closest_red is not None
|
assert closest_red is not None
|
||||||
return closest_blue, closest_red
|
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:
|
def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint:
|
||||||
for i in self.controlpoints:
|
for i in self.controlpoints:
|
||||||
if i.id == cp_id:
|
if i.id == cp_id:
|
||||||
|
|||||||
@ -32,6 +32,9 @@ def load_icons():
|
|||||||
"./resources/ui/misc/" + get_theme_icons() + "/github.png"
|
"./resources/ui/misc/" + get_theme_icons() + "/github.png"
|
||||||
)
|
)
|
||||||
ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.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(
|
ICONS["Control Points"] = QPixmap(
|
||||||
"./resources/ui/misc/" + get_theme_icons() + "/circle.png"
|
"./resources/ui/misc/" + get_theme_icons() + "/circle.png"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@ -21,6 +22,8 @@ from game import Game, VERSION, persistency, Migrator
|
|||||||
from game.debriefing import Debriefing
|
from game.debriefing import Debriefing
|
||||||
from game.game import TurnState
|
from game.game import TurnState
|
||||||
from game.layout import LAYOUTS
|
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 import EventStream, GameContext
|
||||||
from game.server.dependencies import QtCallbacks, QtContext
|
from game.server.dependencies import QtCallbacks, QtContext
|
||||||
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
|
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||||
@ -193,6 +196,20 @@ class QLiberationWindow(QMainWindow):
|
|||||||
lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/")
|
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 = QAction("Show &logs", self)
|
||||||
self.openLogsAction.triggered.connect(self.showLogsDialog)
|
self.openLogsAction.triggered.connect(self.showLogsDialog)
|
||||||
|
|
||||||
@ -234,6 +251,8 @@ class QLiberationWindow(QMainWindow):
|
|||||||
self.links_bar.addAction(self.openDiscordAction)
|
self.links_bar.addAction(self.openDiscordAction)
|
||||||
self.links_bar.addAction(self.openGithubAction)
|
self.links_bar.addAction(self.openGithubAction)
|
||||||
self.links_bar.addAction(self.ukraineAction)
|
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 = self.addToolBar("Actions")
|
||||||
self.actions_bar.addAction(self.openSettingsAction)
|
self.actions_bar.addAction(self.openSettingsAction)
|
||||||
@ -303,6 +322,29 @@ class QLiberationWindow(QMainWindow):
|
|||||||
wizard.show()
|
wizard.show()
|
||||||
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
|
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):
|
def openFile(self):
|
||||||
if self.game is not None and self.game.savepath:
|
if self.game is not None and self.game.savepath:
|
||||||
save_dir = 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
|
class: Logistics
|
||||||
price: 2
|
price: 3
|
||||||
variants:
|
variants:
|
||||||
|
LARC-V Amphibious Cargo Vehicle: null
|
||||||
LARC-V: null
|
LARC-V: null
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user