1 Commits

Author SHA1 Message Date
Raffson
0a32d13201 Avoid appending triggers without actions
Resolves #67

The problem turned out to be split-triggers with no actions, which occurs when a STRIKE flight has no escorts in its package. Added a guard for this so the trigger isn't pushed to the mission in such a case.
2023-01-05 18:46:59 +01:00
427 changed files with 14565 additions and 27632 deletions

View File

@@ -3,7 +3,7 @@ name: Bug report
description: >
Use for any bug that happens after campaign generation. If the New Game wizard
failed, use the "New Game wizard failed" template instead.
labels: ["Bug", "Priority Medium"]
labels: bug
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@
name: Campaign update submission
about: Submit an update to a campaign you maintain.
title: 'Update for <campaign name>'
labels: Campaign Update Submission
labels: campaign-update-submission
assignees: ''
---

View File

@@ -2,7 +2,7 @@
name: Code Architecture
description: >
Suggest a refactor, or other issues related to the codebase.
labels: ["Refactor", "Python", "Priority Low"]
labels: ["refactor", "python"]
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@
name: Feature Request
description: >
Suggest an idea for this project.
labels: ["Enhancement", "Priority Medium"]
labels: enhancement
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@
name: Mod support request
about: Request Retribution support for new mods, or updates to existing mods
title: Add/update <mod name>
labels: Mod Request
labels: mod support
assignees: ''
---

View File

@@ -3,7 +3,7 @@ name: New Game wizard failed
description: >
Use for bugs that prevent the "New Game" wizard from completing successfully.
If the wizard completes without issue, use the normal bug report template.
labels: ["Bug", "Priority High"]
labels: bug
body:
- type: markdown
attributes:

View File

@@ -49,15 +49,4 @@ jobs:
include_image: true
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
username: GitHub Build Notifier
if: startsWith(github.ref_name, 'test')
- name: Send status to Discord
uses: stegzilla/discord-notify@v2
with:
webhook_url: ${{ secrets.discord_webhook_alphas }}
title: "Build #${{ github.run_number }} available!"
message: "**Branch:** ${{ github.ref_name }}\n**Download link:\n** https://github.com/dcs-retribution/dcs-retribution/actions/runs/${{ github.run_id }}"
include_image: true
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
username: GitHub Build Notifier
if: github.ref_name == 'dev'
if: startsWith(github.ref_name, 'test')

View File

@@ -1,63 +1,3 @@
# Retribution 1.1.1 (hotfix)
## Features/Improvements
* **[Modding]** Support for IDF Mod Project F-16I Sufa & F-16D v3.6 mod
* **[Modding]** Support for JAS-39 Gripen v1.8.5-beta mod
## Fixes
* **[Plugins]** Fix bug where changes to plugin options doesn't do anything.
* **[Campaign Management]** Fix bug in procurement when no squadrons are present.
* **[Layouts]** Fix edge-case bug layout's group size.
* **[Campaign Design]** Preset groups assigned to specific TGOs not working as intended.
# Retribution 1.1.0
## Features/Improvements
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
* **[Mission Generation]** Add option to switch ATFLIR to LITENING automatically for ground based F/A-18C flights
* **[Mission Generation]** Add option to configure OPFOR autoplanner aggressiveness and have the AI take risks and plan missions against defended targets
* **[Mission Generation]** Add option to configure the desired tanker on-station time in settings
* **[Mission Generation]** Reserve GUARD frequency on VHF/UHF
* **[Mission Generation]** Randomization in radio frequency allocation
* **[Mission Generation]** Configurable number of Combined Arms slots
* **[Mission Generation]** Enable spectating & F11 free camera when the "Allow external views" option is selected
* **[Cheat Menu]** Option to instantly transfer squadrons across bases.
* **[Modding]** Support for IDF Mod Project F-16I Sufa & F-16D v3.2 mod
* **[Modding]** Support for F/A-18E/F/G mod version 2.1
* **[Modding]** Support for Swedish Military Assets for DCS by Currenthill Version 1.10
* **[UI]** Add selectable units in faction overview during campaign generation.
* **[UI]** Add button to rename pilots in Air Wing's Squadron dialog.
* **[UI]** Add clone buttons for flights & packages.
* **[UI]** Editing of flight's custom name.
* **[UI]** Introduce custom names for packages (purely for organizational purposes).
* **[UI]** Configurable UHF frequency (225-400MHz) for Packages, Carriers, LHAs, FOBs & FARPs.
* **[UI]** Configurable Intra-Flight frequency for Flights.
* **[UI]** Configurable TACAN for Carriers, LHAs & Tankers.
* **[UI]** Configurable ICLS for capable Carriers & LHAs.
* **[UI]** Configurable LINK4 for Carriers.
* **[Kneeboard]** Show package information in Support page
* **[Kneeboard]** Show extra weather information on 'Mission info' page
* **[Kneeboard]** Show BRC in 'RWY' column for aircraft carriers on 'Mission info' page
* **[Campaign Design]** Ability to define designated CTLD zones for Control Points (Airbases & FOBs/FARPs)
* **[Campaign Design]** Ability to define preset groups for specific TGOs, given the preset group is accessible for the faction and the task matches.
* **[Campaign Management]** Additional options for automated budget management.
* **[Campaign Management]** New options to allow more control of randomized flight sizes (applicable for BARCAP/CAS/OCA/ANTI-SHIP).
* **[Plugins]** Updated Splash Damage script to v2.0 by RotorOps.
* **[Mission Generation]** Improvements to DEAD & STRIKE flights, allowing AI to handle a larger variety of weapons.
## Fixes
* **[UI]** Removed deprecated options
* **[UI]** Add missing icon & banner for C130 Hercules mod
* **[Mission Generation]** Avoid aircraft from being assigned to helicopter parking spots, resulting into air starts that usually crash.
* **[Mission Generation]** Use stacking algorithm to create vertical separation between flights spawning mid-mission over their departure, usually resulting into mid-air collisions.
* **[Mission Generation]** Fixed all callsigns being "Enfield 1-1" on dedicated servers.
* **[Mission Generation]** Fixed AI ferry flights for helicopters when transferring to a FOB/FARP.
* **[Mission Generation]** Fixed 'Uninitialized flight' exception when adding flights after aborted take-off.
* **[Modding]** Fixed conflicts caused by HDS units
* **[UX]** Gracefully handle corrupted preferences file.
* **[Mission Generation]** Aircraft not using decoys during SEAD.
* **[Campaign]** New campaign (1968 Yankee Station) by Adecarcer
# Retribution 1.0.1 (hotfix)
* **[Mission Generation]** Fix serialization issue when STRIKE flight has no escorts
@@ -107,25 +47,8 @@ Saves from 6.x are not compatible with 7.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.8.3.37556.
* **[Engine]** Saved games are now a zip file of save assets for easier bug reporting. The new extension is .liberation.zip. Drag and drop that file into bug reports.
* **[Flight Planning]** Package TOT and composition can be modified after advancing time in Liberation.
* **[Mission Generation]** Units on the front line are now hidden on MFDs.
* **[Mission Generation]** Preset radio channels will now be configured for both A-10C modules.
* **[Mission Generation]** Both A-10C modules now use separate radios for inter- and intra-flight comms (similar to other modern aircraft).
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[Modding]** Add support for VSN F-4B and F-4C mod.
## Fixes
# 6.1.1
## Fixes
* **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect.
* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix.
* **[Mission Generation]** Using heliports (airports without any runways) will no longer cause mission generation to fail.
# 6.1.0
Saves from 6.0.0 are compatible with 6.1.0

View File

@@ -1,6 +1,6 @@
# DCS Retribution Client
# DCS Liberation Client
This is a React app for the front-end of DCS Retribution. It is a work in
This is a React app for the front-end of DCS Liberation. It is a work in
progress that just barely implements the map. This is not useful for players
yet.

19645
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -135,16 +135,14 @@ from pydcs_extensions.f16i_idf.f16i_idf import (
F_16D_52,
F_16D_50_NS,
F_16D_52_NS,
F_16D_Barak_30,
F_16D_Barak_40,
F_16D_Barak,
F_16I,
)
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.f4b.f4b import VSN_F4B, VSN_F4C
from pydcs_extensions.f4b.f4b import VSN_F4B
from pydcs_extensions.f84g.f84g import VSN_F84G
from pydcs_extensions.fa18efg.fa18efg import FA_18E, FA_18F, EA_18G
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_BVR, JAS39Gripen_AG
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.su30.su30 import Su_30MKA, Su_30MKI, Su_30MKM, Su_30SM
from pydcs_extensions.su57.su57 import Su_57
from pydcs_extensions.ov10a.ov10a import Bronco_OV_10A
@@ -177,22 +175,17 @@ ESCORT_CAPABLE = [
MiG_29S,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
JF_17,
JAS39Gripen_BVR,
JAS39Gripen,
F_16A_MLU,
F_16A,
F_4E,
VSN_F4C,
VSN_F4B,
MiG_31,
MiG_25PD,
@@ -256,14 +249,11 @@ CAS_CAPABLE = [
F_15E,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_GR4,
Tornado_IDS,
@@ -342,14 +332,13 @@ CAS_CAPABLE = [
MQ_9_Reaper,
RQ_1A_Predator,
VSN_A6A,
VSN_F4C,
VSN_F4B,
VSN_F100,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F4B,
VSN_F84G,
]
@@ -359,15 +348,11 @@ SEAD_ESCORT_CAPABLE = [
JF_17,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
EA_18G,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_IDS,
Su_25T,
@@ -447,14 +432,11 @@ STRIKE_CAPABLE = [
Tornado_GR4,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
@@ -483,8 +465,6 @@ STRIKE_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
@@ -526,6 +506,7 @@ STRIKE_CAPABLE = [
VSN_F104C,
VSN_F105G,
VSN_F105D,
VSN_F4B,
VSN_F84G,
]
@@ -535,8 +516,6 @@ ANTISHIP_CAPABLE = [
Tu_142,
Tu_22M3,
H_6J,
FA_18E,
FA_18F,
FA_18C_hornet,
JAS39Gripen_AG,
F_16A_MLU,
@@ -586,14 +565,11 @@ RUNWAY_ATTACK_CAPABLE = [
AJS37,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
@@ -622,8 +598,6 @@ RUNWAY_ATTACK_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
@@ -657,6 +631,7 @@ RUNWAY_ATTACK_CAPABLE = [
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F4B,
]
# For any aircraft that isn't necessarily directly involved in strike
@@ -692,6 +667,7 @@ AIR_ASSAULT_CAPABLE = [
Mi_24P,
Mi_24V,
Hercules,
C_47,
]
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]

View File

@@ -5,17 +5,12 @@ from datetime import datetime, timedelta
from typing import Any, List, Optional, TYPE_CHECKING
from dcs import Point
from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
from dcs.planes import C_101CC, C_101EB, Su_33
from pydcs_extensions.hercules.hercules import Hercules
from .flightroster import FlightRoster
from .flightstate import FlightState, Navigating, Uninitialized
from .flightstate.killed import Killed
from .loadouts import Loadout, Weapon
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.TacanContainer import TacanContainer
from ..radio.radios import RadioFrequency
from ..radio.tacan import TacanChannel
from .loadouts import Loadout
from ..sidc import (
Entity,
SidcDescribable,
@@ -23,9 +18,9 @@ from ..sidc import (
Status,
SymbolSet,
)
from game.dcs.aircrafttype import AircraftType
if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType
from game.sim.gameupdateevents import GameUpdateEvents
from game.sim.simulationresults import SimulationResults
from game.squadrons import Squadron, Pilot
@@ -37,10 +32,8 @@ if TYPE_CHECKING:
from .package import Package
from .starttype import StartType
F18_TGP_PYLON: int = 4
class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
class Flight(SidcDescribable):
def __init__(
self,
package: Package,
@@ -53,9 +46,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
custom_name: Optional[str] = None,
cargo: Optional[TransferOrder] = None,
roster: Optional[FlightRoster] = None,
frequency: Optional[RadioFrequency] = None,
channel: Optional[TacanChannel] = None,
callsign: Optional[str] = None,
) -> None:
self.id = uuid.uuid4()
self.package = package
@@ -75,11 +65,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
self.custom_name = custom_name
self.group_id: int = 0
self.frequency = frequency
if self.unit_type.dcs_unit_type.tacan:
self.tacan = channel
self.tcn_name = callsign
# Only used by transport missions.
self.cargo = cargo
@@ -102,15 +87,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
self._flight_plan_builder = FlightPlanBuilderTypes.for_flight(self)(self)
is_f18 = self.squadron.aircraft.dcs_unit_type.id == FA_18C_hornet.id
on_land = not self.squadron.location.is_fleet
if on_land and is_f18 and self.coalition.game.settings.atflir_autoswap:
self.loadout.pylons[F18_TGP_PYLON] = Weapon.with_clsid(
str(
FA_18C_hornet.Pylon4.AN_AAQ_28_LITENING___Targeting_Pod_[1]["clsid"]
)
)
@property
def flight_plan(self) -> FlightPlan[Any]:
return self._flight_plan_builder.get_or_build()
@@ -174,10 +150,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def is_helo(self) -> bool:
return self.unit_type.dcs_unit_type.helicopter
@property
def is_hercules(self) -> bool:
return self.unit_type == AircraftType.named("C-130J-30 Super Hercules")
@property
def from_cp(self) -> ControlPoint:
return self.departure
@@ -214,18 +186,17 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
return Su_33.fuel_max * 0.8
elif unit_type in {C_101EB, C_101CC}:
return unit_type.fuel_max * 0.5
elif unit_type == Hercules:
return unit_type.fuel_max * 0.75
return None
def __repr__(self) -> str:
return self.__str__()
if self.custom_name:
return f"{self.custom_name} {self.count} x {self.unit_type}"
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
def __str__(self) -> str:
string = f"[{self.flight_type}] {self.count} x {self.unit_type}"
if self.custom_name:
return f"{self.custom_name} - {string}"
return string
return f"{self.custom_name} {self.count} x {self.unit_type}"
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
def abort(self) -> None:
from .flightplans.rtb import RtbFlightPlan
@@ -283,15 +254,3 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def recreate_flight_plan(self) -> None:
self._flight_plan_builder.regenerate()
@staticmethod
def clone_flight(flight: Flight) -> Flight:
return Flight(
flight.package,
flight.country,
flight.squadron,
flight.count,
flight.flight_type,
flight.start_type,
flight.divert,
)

View File

@@ -1,20 +0,0 @@
from __future__ import annotations
import random
from typing import Union, TYPE_CHECKING, Tuple
from game.theater.interfaces.CTLD import CTLD
from game.theater import ControlPoint
if TYPE_CHECKING:
from dcs import Point
def generate_random_ctld_point(cp: Union[ControlPoint, CTLD]) -> Point:
if isinstance(cp, CTLD) and cp.ctld_zones:
zone: Tuple[Point, float] = random.choice(cp.ctld_zones)
pos, radius = zone
return pos.random_point_within(radius)
elif isinstance(cp, CTLD) and isinstance(cp, ControlPoint):
return cp.position.random_point_within(2000, 200)
raise RuntimeError("Could not generate CTLD point")

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget
@@ -14,10 +13,8 @@ from .planningerror import PlanningError
from .uizonedisplay import UiZone, UiZoneDisplay
from .waypointbuilder import WaypointBuilder
from ..flightwaypoint import FlightWaypointType
from ...theater.interfaces.CTLD import CTLD
if TYPE_CHECKING:
from dcs import Point
from ..flightwaypoint import FlightWaypoint
@@ -28,7 +25,7 @@ class AirAssaultLayout(StandardLayout):
pickup: FlightWaypoint | None
nav_to_ingress: list[FlightWaypoint]
ingress: FlightWaypoint
drop_off: FlightWaypoint | None
drop_off: FlightWaypoint
# This is an implementation detail used by CTLD. The aircraft will not go to this
# waypoint. It is used by CTLD as the destination for unloaded troops.
target: FlightWaypoint
@@ -40,8 +37,7 @@ class AirAssaultLayout(StandardLayout):
yield self.pickup
yield from self.nav_to_ingress
yield self.ingress
if self.drop_off is not None:
yield self.drop_off
yield self.drop_off
yield self.target
yield from self.nav_to_home
yield self.arrival
@@ -57,9 +53,7 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
@property
def tot_waypoint(self) -> FlightWaypoint:
if self.flight.is_helo and self.layout.drop_off is not None:
return self.layout.drop_off
return self.layout.target
return self.layout.drop_off
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.tot_waypoint:
@@ -86,10 +80,8 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def layout(self) -> AirAssaultLayout:
if not self.flight.is_helo and not self.flight.is_hercules:
raise PlanningError(
"Air assault is only usable by helicopters and Anubis' C-130 mod"
)
if not self.flight.is_helo:
raise PlanningError("Air assault is only usable by helicopters")
assert self.package.waypoints is not None
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
@@ -97,7 +89,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
builder = WaypointBuilder(self.flight, self.coalition)
if self.flight.is_hercules or self.flight.departure.cptype in [
if self.flight.departure.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
ControlPointType.OFF_MAP,
@@ -107,25 +99,27 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
pickup = None
pickup_position = self.flight.departure.position
else:
# TODO The calculation of the Pickup LZ is currently randomized. This
# leads to the problem that we can not gurantee that the LZ is clear of
# obstacles. This has to be improved in the future so that the Mission can
# be autoplanned. In the current state the User has to check the created
# Waypoints for the Pickup and Dropoff LZs are free of obstacles.
# Create a special pickup zone for Helos from Airbase / FOB
pickup = builder.pickup_zone(
MissionTarget(
"Pickup Zone",
self._generate_ctld_pickup(),
self.flight.departure.position.random_point_within(1200, 600),
)
)
pickup_position = pickup.position
assault_area = builder.assault_area(self.package.target)
heading = self.package.target.position.heading_between_point(pickup_position)
if self.flight.is_hercules:
assault_area.only_for_player = False
assault_area.alt = feet(1000)
# TODO: define CTLD dropoff zones in campaign miz?
# TODO we can not gurantee a safe LZ for DropOff. See comment above.
drop_off_zone = MissionTarget(
"Dropoff zone",
self.package.target.position.point_from_heading(heading, 1200),
)
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
return AirAssaultLayout(
departure=builder.takeoff(self.flight.departure),
@@ -141,7 +135,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
self.package.waypoints.ingress,
self.package.target,
),
drop_off=dz,
drop_off=builder.dropoff_zone(drop_off_zone),
target=assault_area,
nav_to_home=builder.nav_path(
drop_off_zone.position,
@@ -156,7 +150,3 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def build(self) -> AirAssaultFlightPlan:
return AirAssaultFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:
assert isinstance(self.flight.departure, CTLD)
return generate_random_ctld_point(self.flight.departure)

View File

@@ -5,18 +5,15 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.theater.missiontarget import MissionTarget
from game.utils import feet
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .standard import StandardFlightPlan, StandardLayout
from .waypointbuilder import WaypointBuilder
from ...theater.interfaces.CTLD import CTLD
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
from dcs import Point
@dataclass(frozen=True)
@@ -109,10 +106,15 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
if self.flight.is_helo:
# Create CTLD Zones for Helo flights
pickup_zone = builder.pickup_zone(
MissionTarget("Pickup Zone", self._generate_ctld_pickup())
MissionTarget(
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
)
)
drop_off_zone = builder.dropoff_zone(
MissionTarget("Dropoff zone", self._generate_ctld_dropoff())
MissionTarget(
"Dropoff zone",
cargo.next_stop.position.random_point_within(1000, 200),
)
)
# Show the zone waypoints only to the player
pickup_zone.only_for_player = True
@@ -151,15 +153,3 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
def build(self) -> AirliftFlightPlan:
return AirliftFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:
cargo = self.flight.cargo
if cargo and cargo.origin and isinstance(cargo.origin, CTLD):
return generate_random_ctld_point(cargo.origin)
raise RuntimeError("Could not generate CTLD pickup")
def _generate_ctld_dropoff(self) -> Point:
cargo = self.flight.cargo
if cargo and cargo.transport and isinstance(cargo.transport.destination, CTLD):
return generate_random_ctld_point(cargo.transport.destination)
raise RuntimeError("Could not generate CTLD dropoff")

View File

@@ -8,6 +8,8 @@ from .formationattack import (
FormationAttackLayout,
)
from .waypointbuilder import WaypointBuilder
from .. import FlightType
from ...utils import Distance
class EscortFlightPlan(FormationAttackFlightPlan):
@@ -30,9 +32,11 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = builder.refuel(self.package.waypoints.refuel)
initial = builder.escort_hold(
self.package.waypoints.initial, self.doctrine.ingress_altitude
)
initial = None
if self.package.primary_task == FlightType.STRIKE:
initial = builder.escort_hold(
self.package.waypoints.initial, Distance.from_feet(20000)
)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),

View File

@@ -72,15 +72,6 @@ class FlightPlan(ABC, Generic[LayoutT]):
"""A list of all waypoints in the flight plan, in order."""
return list(self.iter_waypoints())
def get_index_of_wpt_by_type(self, wpt_type: FlightWaypointType) -> int:
index = 0
for wpt in self.waypoints:
if wpt and not wpt.only_for_player:
index += 1
if wpt.waypoint_type == wpt_type:
return index
return -1
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
"""Iterates over all waypoints in the flight plan, in order."""
yield from self.layout.iter_waypoints()

View File

@@ -26,13 +26,12 @@ from .theaterrefueling import TheaterRefuelingFlightPlan
if TYPE_CHECKING:
from game.ato import Flight
from game.theater import FrontLine
class FlightPlanBuilderTypes:
@staticmethod
def for_flight(flight: Flight) -> Type[IBuilder[Any, Any]]:
from game.theater import FrontLine
if flight.flight_type is FlightType.REFUELING:
if flight.package.target.is_friendly(flight.squadron.player) or isinstance(
flight.package.target, FrontLine

View File

@@ -17,7 +17,7 @@ class TheaterRefuelingFlightPlan(RefuelingFlightPlan):
@property
def patrol_duration(self) -> timedelta:
return self.flight.coalition.game.settings.desired_tanker_on_station_time
return timedelta(hours=1)
class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):

View File

@@ -14,7 +14,6 @@ from typing import (
from dcs.mapping import Point, Vector2
from game.ato.flightplans._common_ctld import generate_random_ctld_point
from game.ato.flightwaypoint import AltitudeReference, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.theater import (
@@ -24,8 +23,7 @@ from game.theater import (
TheaterGroundObject,
TheaterUnit,
)
from game.theater.interfaces.CTLD import CTLD
from game.utils import Distance, meters, nautical_miles, feet
from game.utils import Distance, meters, nautical_miles
if TYPE_CHECKING:
from game.coalition import Coalition
@@ -129,9 +127,10 @@ class WaypointBuilder:
position = divert.position
altitude_type: AltitudeReference
if isinstance(divert, OffMapSpawn):
altitude = (
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude
)
if self.is_helo:
altitude = meters(500)
else:
altitude = self.doctrine.rendezvous_altitude
altitude_type = "BARO"
else:
altitude = meters(0)
@@ -226,17 +225,15 @@ class WaypointBuilder:
position: Point,
objective: MissionTarget,
) -> FlightWaypoint:
alt = self.doctrine.ingress_altitude
alt_type: AltitudeReference = "BARO"
if self.is_helo or self.flight.is_hercules:
if self.is_helo:
alt_type = "RADIO"
alt = meters(60) if self.is_helo else feet(1000)
return FlightWaypoint(
"INGRESS",
ingress_type,
position,
alt,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
alt_type,
description=f"INGRESS on {objective.name}",
pretty_name=f"INGRESS on {objective.name}",
@@ -558,14 +555,10 @@ class WaypointBuilder:
"""Creates a cargo stop waypoint.
This waypoint is used by AirLift as a landing and stopover waypoint
"""
if isinstance(control_point, CTLD) and control_point.ctld_zones:
pos = generate_random_ctld_point(control_point)
else:
pos = control_point.position
return FlightWaypoint(
"CARGOSTOP",
FlightWaypointType.CARGO_STOP,
pos,
control_point.position,
meters(0),
"RADIO",
description=f"Stop for cargo at {control_point.name}",

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import datetime
import logging
from collections.abc import Iterable
from typing import Iterator, Optional, TYPE_CHECKING, Type, Dict
from typing import Iterator, Mapping, Optional, TYPE_CHECKING, Type, Dict
from dcs.unittype import FlyingType
@@ -19,14 +19,14 @@ class Loadout:
def __init__(
self,
name: str,
pylons: Dict[int, Optional[Weapon]],
pylons: Mapping[int, Optional[Weapon]],
date: Optional[datetime.date],
is_custom: bool = False,
) -> None:
self.name = name
# We clear unused pylon entries on initialization, but UI actions can still
# cause a pylon to be emptied, so make the optional type explicit.
self.pylons: Dict[int, Optional[Weapon]] = {
self.pylons: Mapping[int, Optional[Weapon]] = {
k: v for k, v in pylons.items() if v is not None
}
self.date = date

View File

@@ -13,28 +13,19 @@ from .flightplans.formation import FormationFlightPlan
from .flighttype import FlightType
from .packagewaypoints import PackageWaypoints
from .traveltime import TotEstimator
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.radios import RadioFrequency
if TYPE_CHECKING:
from game.theater import ControlPoint, MissionTarget
class Package(RadioFrequencyContainer):
class Package:
"""A mission package."""
def __init__(
self,
target: MissionTarget,
db: Database[Flight],
auto_asap: bool = False,
custom_name: str | None = None,
frequency: RadioFrequency | None = None,
self, target: MissionTarget, db: Database[Flight], auto_asap: bool = False
) -> None:
self.target = target
self._db = db
self.frequency = frequency
self.custom_name = custom_name
# True if the package ToT should be reset to ASAP whenever the player makes a
# change. This is really a UI property rather than a game property, but we want
@@ -223,12 +214,3 @@ class Package(RadioFrequencyContainer):
if flight.departure == airfield:
return airfield
raise RuntimeError("Could not find any airfield assigned to this package")
@staticmethod
def clone_package(package: Package) -> Package:
clone = Package(package.target, package._db, package.auto_asap)
clone.time_over_target = package.time_over_target
for f in package.flights:
cf = Flight.clone_flight(f)
clone.add_flight(cf)
return clone

View File

@@ -15,7 +15,7 @@ def callsign_for_support_unit(group: FlyingGroup[Any]) -> str:
try:
return f"Flight {int(raw_callsign)}"
except ValueError:
return f"{raw_callsign[:-2]} {raw_callsign[-2]}"
return raw_callsign.rstrip("1234567890")
def create_group_callsign_from_unit(lead: FlyingUnit) -> str:

View File

@@ -19,7 +19,6 @@ from game.theater.iadsnetwork.iadsnetwork import IadsNetwork
from game.theater.theaterloader import TheaterLoader
from game.version import CAMPAIGN_FORMAT_VERSION
from .campaignairwingconfig import CampaignAirWingConfig
from .campaigngroundconfig import TgoConfig
from .mizcampaignloader import MizCampaignLoader
PERF_FRIENDLY = 0
@@ -33,7 +32,6 @@ DEFAULT_BUDGET = 2000
class Campaign:
name: str
menu_thumbnail_dcs_relative_path: Path
fallback_icon_path: Path
authors: str
description: str
@@ -92,7 +90,6 @@ class Campaign:
return cls(
data["name"],
TheaterLoader(data["theater"].lower()).menu_thumbnail_dcs_relative_path,
TheaterLoader(data["theater"].lower()).icon_path,
data.get("authors", "???"),
data.get("description", ""),
(version.major, version.minor),
@@ -138,13 +135,6 @@ class Campaign:
return CampaignAirWingConfig({})
return CampaignAirWingConfig.from_campaign_data(squadron_data, theater)
def load_ground_forces_config(self) -> TgoConfig:
ground_forces = self.data.get("ground_forces", {})
if not ground_forces:
logging.warning(f"Campaign {self.name} does not define any ground_forces")
return TgoConfig({})
return TgoConfig.from_campaign_data(ground_forces)
@property
def is_out_of_date(self) -> bool:
"""Returns True if this campaign is not up to date with the latest format.
@@ -181,7 +171,7 @@ class Campaign:
@classmethod
def iter_campaign_defs(cls) -> Iterator[Path]:
yield from cls.iter_campaigns_in_dir(
Path(persistency.base_path()) / "Retribution/Campaigns"
Path(persistency.base_path()) / "Liberation/Campaigns"
)
yield from cls.iter_campaigns_in_dir(Path("resources/campaigns"))

View File

@@ -1,25 +0,0 @@
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from typing import Iterator, Optional
from game.armedforces.forcegroup import ForceGroup
@dataclass(frozen=True)
class TgoConfig:
by_original_name: dict[str, ForceGroup]
def __iter__(self) -> Iterator[str]:
return self.by_original_name.__iter__()
def __getitem__(self, name: str) -> Optional[ForceGroup]:
return self.by_original_name.get(name)
@classmethod
def from_campaign_data(cls, data: dict[str, str]) -> TgoConfig:
by_original_name: dict[str, ForceGroup] = defaultdict()
for tgo_name, force_group in data.items():
by_original_name[tgo_name] = ForceGroup.from_preset_group(force_group)
return TgoConfig(by_original_name)

View File

@@ -29,6 +29,7 @@ class DefaultSquadronAssigner:
self.coalition.player
):
for squadron_config in self.config.by_location[control_point]:
squadron_def = self.override_squadron_defaults(
self.find_squadron_for(squadron_config, control_point),
squadron_config,
@@ -52,6 +53,7 @@ class DefaultSquadronAssigner:
def find_squadron_for(
self, config: SquadronConfig, control_point: ControlPoint
) -> Optional[SquadronDef]:
for preferred_aircraft in config.aircraft:
squadron_def = self.find_preferred_squadron(
preferred_aircraft, config.primary, control_point
@@ -92,9 +94,8 @@ class DefaultSquadronAssigner:
if aircraft not in self.coalition.faction.aircrafts:
return None
lo = self.coalition.faction.liveries_overrides
squadron_def = self.find_squadron_for_airframe(aircraft, task, control_point)
if squadron_def is not None and lo.get(aircraft) is None:
if squadron_def is not None:
return squadron_def
# No premade squadron available for this aircraft that meets the requirements,
@@ -152,6 +153,7 @@ class DefaultSquadronAssigner:
def override_squadron_defaults(
squadron_def: Optional[SquadronDef], config: SquadronConfig
) -> Optional[SquadronDef]:
if squadron_def is None:
return None

View File

@@ -32,7 +32,6 @@ from game.utils import Distance, meters
if TYPE_CHECKING:
from game.theater.conflicttheater import ConflictTheater
from dcs import Point
class MizCampaignLoader:
@@ -108,12 +107,8 @@ class MizCampaignLoader:
if self.mission.country(self.RED_COUNTRY.name) is None:
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
def control_point_from_airport(
self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
) -> ControlPoint:
cp = Airfield(
airport, self.theater, starts_blue=airport.is_blue(), ctld_zones=ctld_zones
)
def control_point_from_airport(self, airport: Airport) -> ControlPoint:
cp = Airfield(airport, self.theater, starts_blue=airport.is_blue())
# Use the unlimited aircraft option to determine if an airfield should
# be owned by the player when the campaign is "inverted".
@@ -250,8 +245,7 @@ class MizCampaignLoader:
control_points = {}
for airport in self.mission.terrain.airport_list():
if airport.is_blue() or airport.is_red():
ctld_zones = self.get_ctld_zones(airport.name)
control_point = self.control_point_from_airport(airport, ctld_zones)
control_point = self.control_point_from_airport(airport)
control_points[control_point.id] = control_point
for blue in (False, True):
@@ -274,13 +268,8 @@ class MizCampaignLoader:
control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point
for fob in self.fobs(blue):
ctld_zones = self.get_ctld_zones(fob.name)
control_point = Fob(
str(fob.name),
fob.position,
self.theater,
starts_blue=blue,
ctld_zones=ctld_zones,
str(fob.name), fob.position, self.theater, starts_blue=blue
)
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
@@ -471,9 +460,3 @@ class MizCampaignLoader:
self.add_preset_locations()
self.add_supply_routes()
self.add_shipping_lanes()
def get_ctld_zones(self, prefix: str) -> List[Tuple[Point, float]]:
zones = [t for t in self.mission.triggers.zones() if prefix + " CTLD" in t.name]
for z in zones:
self.mission.triggers.zones().remove(z)
return [(z.position, z.radius) for z in zones]

View File

@@ -5,7 +5,6 @@ import operator
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import IntEnum, auto, unique
import random
from typing import Generic, Iterator, Optional, TYPE_CHECKING, TypeVar, Union
from game.ato.flighttype import FlightType
@@ -171,24 +170,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
if not ignore_iads:
for iads_threat in self.iter_iads_threats(state):
# Only consider blue faction flights threatened.
# Red might still plan missions into hostile territory,
# depending on the aggressiveness setting.
if (
state.context.coalition.player
or random.randint(1, 100)
> state.context.coalition.game.settings.opfor_autoplanner_aggressiveness
):
threatened = True
threatened = True
if iads_threat not in state.threatening_air_defenses:
state.threatening_air_defenses.append(iads_threat)
return not threatened
def get_flight_size(self) -> int:
settings = self.target.coalition.game.settings
weights = [
settings.fpa_2ship_weight,
settings.fpa_3ship_weight,
settings.fpa_4ship_weight,
]
return random.choices([2, 3, 4], weights, k=1)[0]

View File

@@ -23,6 +23,5 @@ class PlanAntiShip(PackagePlanningTask[NavalGroundObject]):
state.eliminate_ship(self.target)
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.ANTISHIP, size)
self.propose_flight(FlightType.ANTISHIP, randint(2, 4))
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@@ -22,6 +22,5 @@ class PlanAntiShipping(PackagePlanningTask[CargoShip]):
state.enemy_shipping.remove(self.target)
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.ANTISHIP, size)
self.propose_flight(FlightType.ANTISHIP, randint(2, 4))
self.propose_common_escorts()

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from random import randint
@@ -23,8 +22,7 @@ class PlanBarcap(PackagePlanningTask[ControlPoint]):
state.barcaps_needed[self.target] -= 1
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.BARCAP, size)
self.propose_flight(FlightType.BARCAP, randint(2, 4))
@property
def purchase_multiplier(self) -> int:

View File

@@ -30,6 +30,5 @@ class PlanCas(PackagePlanningTask[FrontLine]):
state.vulnerable_front_lines.remove(self.target)
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.CAS, size)
self.propose_flight(FlightType.CAS, randint(2, 4))
self.propose_flight(FlightType.TARCAP, 2)

View File

@@ -24,8 +24,7 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
state.oca_targets.remove(self.target)
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.OCA_RUNWAY, size)
self.propose_flight(FlightType.OCA_RUNWAY, randint(2, 4))
if self.aircraft_cold_start:
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()

View File

@@ -19,18 +19,6 @@ PydcsWeapon = Any
PydcsWeaponAssignment = tuple[int, PydcsWeapon]
def weapons_migrator(name: str) -> str:
migration_map = {
"AGM-88C HARM - High Speed Anti-Radiation Missile": "AGM-88C HARM",
"Kh-25MPU (Updated AS-12 Kegler) - 320kg, ARM, IN & Pas Rdr": "Kh-25MPU",
"Kh-31P (AS-17 Krypton) - 600kg, ARM, IN & Pas Rdr": "Kh-31P",
"Kh-58U (AS-11 Kilter) - 640kg, ARM, IN & Pas Rdr": "Kh-58U",
}
while name in migration_map:
name = migration_map[name]
return name
@dataclass(frozen=True)
class Weapon:
"""Wrapper for DCS weapons."""
@@ -151,7 +139,7 @@ class WeaponGroup:
def __setstate__(self, state: dict[str, Any]) -> None:
# Update any existing models with new data on load.
updated = WeaponGroup.named(weapons_migrator(state["name"]))
updated = WeaponGroup.named(state["name"])
state.update(updated.__dict__)
self.__dict__.update(state)
@@ -173,21 +161,16 @@ class WeaponGroup:
@classmethod
def _each_weapon_group(cls) -> Iterator[WeaponGroup]:
names = []
links = []
for group_file_path in Path("resources/weapons").glob("**/*.yaml"):
with group_file_path.open(encoding="utf8") as group_file:
data = yaml.safe_load(group_file)
name = data["name"]
names.append(name)
try:
weapon_type = WeaponType(data["type"])
except KeyError:
weapon_type = WeaponType.UNKNOWN
year = data.get("year")
fallback_name = data.get("fallback")
if fallback_name:
links.append((name, fallback_name))
group = WeaponGroup(name, weapon_type, year, fallback_name)
for clsid in data["clsids"]:
weapon = Weapon(clsid, group)
@@ -195,17 +178,6 @@ class WeaponGroup:
group.weapons.append(weapon)
yield group
for name, fb in links:
if fb not in names:
sn = "Unknown"
for n in names:
if fb in n:
sn = n
break
logging.error(
f"Weapon '{name}' has invalid fallback '{fb}': suggested = {sn}"
)
@classmethod
def register_clean_pylon(cls) -> None:
group = WeaponGroup(

View File

@@ -348,11 +348,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
@classmethod
def _each_variant_of(cls, aircraft: Type[FlyingType]) -> Iterator[AircraftType]:
# Replace slashes with underscores because slashes are not allowed in filenames
aircraft_id = aircraft.id.replace("/", "_")
data_path = Path("resources/units/aircraft") / f"{aircraft_id}.yaml"
data_path = Path("resources/units/aircraft") / f"{aircraft.id}.yaml"
if not data_path.exists():
logging.warning(f"No data for {aircraft_id}; it will not be available")
logging.warning(f"No data for {aircraft.id}; it will not be available")
return
with data_path.open(encoding="utf-8") as data_file:
@@ -373,7 +371,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
nautical_miles(50) if aircraft.helicopter else nautical_miles(150)
)
logging.warning(
f"{aircraft_id} does not specify a max_range. Defaulting to "
f"{aircraft.id} does not specify a max_range. Defaulting to "
f"{mission_range.nautical_miles}NM"
)

View File

@@ -60,7 +60,7 @@ class Faction:
description: str = field(default="")
# Available aircraft
aircraft: List[AircraftType] = field(default_factory=list)
aircrafts: List[AircraftType] = field(default_factory=list)
# Available awacs aircraft
awacs: List[AircraftType] = field(default_factory=list)
@@ -178,10 +178,6 @@ class Faction:
)
return sorted(air_defenses)
@cached_property
def aircrafts(self) -> list[UnitType[Any]]:
return list(self.aircraft + self.awacs + self.tankers)
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
faction = Faction(locales=json.get("locales"))
@@ -210,10 +206,14 @@ class Faction:
faction.authors = json.get("authors", "")
faction.description = json.get("description", "")
faction.aircraft = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.aircrafts = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
faction.aircrafts = list(
set(faction.aircrafts + faction.awacs + faction.tankers)
)
faction.frontline_units = [
GroundUnitType.named(n) for n in json.get("frontline_units", [])
]
@@ -335,9 +335,8 @@ class Faction:
if not mod_settings.uh_60l:
self.remove_aircraft("UH-60L")
self.remove_aircraft("KC130J")
if not mod_settings.f4bc_phantom:
if not mod_settings.f4b_phantom:
self.remove_aircraft("VSN_F4B")
self.remove_aircraft("VSN_F4C")
if not mod_settings.f15d_baz:
self.remove_aircraft("F-15D")
if not mod_settings.f_16_idf:
@@ -346,10 +345,10 @@ class Faction:
self.remove_aircraft("F_16D_50")
self.remove_aircraft("F_16D_50_NS")
self.remove_aircraft("F_16D_52_NS")
self.remove_aircraft("F-16D_Barak_30")
self.remove_aircraft("F-16D_Barak_40")
else:
inject_F16I()
# Remove the stock Viper because that DCS ID is now used by the Sufa
self.remove_aircraft_by_name("F-16CM Fighting Falcon (Block 50)")
if not mod_settings.f22_raptor:
self.remove_aircraft("F-22A")
if not mod_settings.f84g_thunderjet:
@@ -368,7 +367,6 @@ class Faction:
self.remove_aircraft("VSN_A6A")
if not mod_settings.jas39_gripen:
self.remove_aircraft("JAS39Gripen")
self.remove_aircraft("JAS39Gripen_BVR")
self.remove_aircraft("JAS39Gripen_AG")
if not mod_settings.su30_flanker_h:
self.remove_aircraft("Su-30MKA")
@@ -425,56 +423,13 @@ class Faction:
self.remove_preset("SA-20B/S-300PMU-2")
self.remove_preset("SA-23/S-300VM")
self.remove_preset("SA-17")
self.remove_preset("KS-19_HDS")
self.remove_preset("KS-19")
self.remove_preset("HQ-2")
self.remove_preset("SA-2/S-75 V-759/5V23")
self.remove_preset("SA-3/S-125 V-601P/5V27")
self.remove_vehicle("SAM SA-14 Strela-3 manpad")
self.remove_vehicle("SAM SA-24 Igla-S manpad")
self.remove_vehicle("Polyana-D4M1 C2 node")
if not mod_settings.fa_18efg:
self.remove_aircraft("FA-18E")
self.remove_aircraft("FA-18F")
self.remove_aircraft("EA-18G")
# swedish military assets pack
if not mod_settings.swedishmilitaryassetspack:
self.remove_vehicle("BV410_RBS70")
self.remove_vehicle("BV410_RBS90")
self.remove_vehicle("LvS_103_Lavett103_Rb103A")
self.remove_vehicle("LvS_103_Lavett103_Rb103B")
self.remove_vehicle("LvS_103_Lavett103_HX_Rb103A")
self.remove_vehicle("LvS_103_Lavett103_HX_Rb103B")
self.remove_vehicle("LvS_103_StriE103")
self.remove_vehicle("LvS_103_PM103")
self.remove_vehicle("LvS_103_PM103_HX")
self.remove_vehicle("LvS_103_Elverk103")
self.remove_vehicle("LvKv9040")
self.remove_vehicle("RBS_70")
self.remove_vehicle("RBS_90")
self.remove_vehicle("RBS_98")
self.remove_vehicle("UndE23")
self.remove_vehicle("BV410")
self.remove_vehicle("CV9040")
self.remove_vehicle("Strv103")
self.remove_vehicle("Strv121")
self.remove_vehicle("Strv122")
self.remove_vehicle("Strv2000")
self.remove_vehicle("Volvo740")
self.remove_vehicle("RBS_15KA")
self.remove_vehicle("AG_90")
self.remove_vehicle("SwedishinfantryAK4")
self.remove_vehicle("SwedishinfantryAK5")
self.remove_vehicle("SwedishinfantryAK5GT")
self.remove_vehicle("SwedishinfantryKSP90")
self.remove_vehicle("SwedishinfantryKSP58")
self.remove_vehicle("SwedishinfantryPskott86")
self.remove_vehicle("RBS_57")
self.remove_vehicle("RBS_58")
self.remove_vehicle("Artillerisystem08")
self.remove_vehicle("Grkpbv90")
self.remove_ship("HSwMS_Visby")
self.remove_ship("Strb90")
self.remove_aircraft("HKP15B")
def remove_aircraft(self, name: str) -> None:
for i in self.aircrafts:
@@ -496,11 +451,6 @@ class Faction:
if i.dcs_unit_type.id == name:
self.frontline_units.remove(i)
def remove_ship(self, name: str) -> None:
for i in self.naval_units:
if i.dcs_unit_type.id == name:
self.naval_units.remove(i)
def load_ship(name: str) -> Optional[Type[ShipType]]:
if (ship := getattr(dcs.ships, name, None)) is not None:

View File

@@ -31,7 +31,7 @@ class FactionLoader:
@classmethod
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
user_faction_path = Path(persistency.base_path()) / "Retribution/Factions"
user_faction_path = Path(persistency.base_path()) / "Liberation/Factions"
files = cls.find_faction_files_in(
FACTION_DIRECTORY
) + cls.find_faction_files_in(user_faction_path)

View File

@@ -89,6 +89,7 @@ class GroundPlanner:
self.reserve: List[CombatGroup] = []
def plan_groundwar(self) -> None:
ground_unit_limit = self.cp.frontline_unit_count_limit
remaining_available_frontline_units = ground_unit_limit
@@ -138,6 +139,7 @@ class GroundPlanner:
remaining_available_frontline_units -= available
while available > 0:
if role == CombatGroupRole.SHORAD:
count = 1
else:

View File

@@ -137,14 +137,11 @@ class TgoLayoutUnitGroup:
@property
def group_size(self) -> int:
"""
The amount of units to be generated. If unit_count is defined in the layout this will be
randomized accordingly. Otherwise this will be the maximum size.
"""
"""The amount of units to be generated. If unit_count is defined in the layout this will be randomized accordingly. Otherwise this will be the maximum size."""
if self.unit_count:
if len(self.unit_count) == 1:
return self.unit_count[0]
return random.choice(range(min(self.unit_count), max(self.unit_count) + 1))
return random.choice(range(min(self.unit_count), max(self.unit_count)))
return self.max_size
@property
@@ -241,6 +238,7 @@ class AntiAirLayout(TgoLayout):
location: PresetLocation,
control_point: ControlPoint,
) -> IadsGroundObject:
if GroupTask.EARLY_WARNING_RADAR in self.tasks:
return EwrGroundObject(name, location, control_point)
elif any(tasking in self.tasks for tasking in GroupRole.AIR_DEFENSE.tasks):

View File

@@ -132,6 +132,7 @@ class LayoutLoader:
temp_mis.country(country.name).ship_group,
temp_mis.country(country.name).static_group,
):
try:
g_id, u_id, group_name, group_mapping = mapping.group_for_name(
dcs_group.name

View File

@@ -19,4 +19,4 @@ def init_logging(version: str) -> None:
with log_config.open() as log_file:
logging.config.dictConfig(yaml.safe_load(log_file))
logging.info(f"DCS Retribution {version}")
logging.info(f"DCS Liberation {version}")

View File

@@ -1,19 +1,15 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
from game.ato.packagewaypoints import PackageWaypoints
from game.data.doctrine import MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
from game.utils import knots, feet, nautical_miles
if TYPE_CHECKING:
from game import Game
def try_set_attr(obj: Any, attr_name: str, val: Any = None) -> None:
if not hasattr(obj, attr_name):
setattr(obj, attr_name, val)
class Migrator:
def __init__(self, game: Game):
self.game = game
@@ -22,9 +18,6 @@ class Migrator:
def _migrate_game(self) -> None:
self._update_doctrine()
self._update_packagewaypoints()
self._update_package_attributes()
self._update_control_points()
self._update_flights()
def _update_doctrine(self) -> None:
doctrines = [
@@ -47,32 +40,5 @@ class Migrator:
def _update_packagewaypoints(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
if p.flights:
try_set_attr(p.waypoints, "initial", PackageWaypoints.create(p, c))
def _update_package_attributes(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
try_set_attr(p, "custom_name")
try_set_attr(p, "frequency")
def _update_control_points(self) -> None:
for cp in self.game.theater.controlpoints:
is_carrier = cp.is_carrier
is_lha = cp.is_lha
is_fob = cp.category == "fob"
radio_configurable = is_carrier or is_lha or is_fob
if radio_configurable:
try_set_attr(cp, "frequency")
if is_carrier or is_lha:
try_set_attr(cp, "tacan")
try_set_attr(cp, "tcn_name")
try_set_attr(cp, "icls_channel")
try_set_attr(cp, "icls_name")
try_set_attr(cp, "link4")
def _update_flights(self) -> None:
for f in self.game.db.flights.objects.values():
try_set_attr(f, "frequency")
try_set_attr(f, "tacan")
try_set_attr(f, "tcn_name")
if not hasattr(p.waypoints, "initial"):
p.waypoints = PackageWaypoints.create(p, c)

View File

@@ -29,7 +29,6 @@ from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType
from game.ato.flightplans.aewc import AewcFlightPlan
from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
from game.ato.flightwaypointtype import FlightWaypointType
class AircraftBehavior:
@@ -179,9 +178,9 @@ class AircraftBehavior:
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.OpenFire,
# Guided includes ARMs and TALDs (among other things, but those are the useful
# ASM includes ARMs and TALDs (among other things, but those are the useful
# weapons for SEAD).
rtb_winchester=OptRTBOnOutOfAmmo.Values.Guided,
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
restrict_jettison=True,
mission_uses_gun=False,
)
@@ -273,14 +272,8 @@ class AircraftBehavior:
# Search Then Engage task, which we have to use instead of the Escort
# task for the reasons explained in JoinPointBuilder.
group.task = Escort.name
if flight.flight_plan.is_formation(flight.flight_plan):
index = flight.flight_plan.get_index_of_wpt_by_type(
FlightWaypointType.SPLIT
)
if index > 0:
group.add_trigger_action(SwitchWaypoint(None, index))
else:
logging.warning(f"Couldn't determine SPLIT for {group.name}")
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
self.configure_behavior(
flight, group, roe=OptROE.Values.OpenFire, restrict_jettison=True
)
@@ -290,33 +283,26 @@ class AircraftBehavior:
# available aircraft, and F-14s are not able to be SEAD despite having TALDs.
# https://forums.eagle.ru/topic/272112-cannot-assign-f-14-to-sead/
group.task = SEAD.name
index = flight.flight_plan.get_index_of_wpt_by_type(FlightWaypointType.SPLIT)
if index > 0 and flight.flight_plan.is_formation(flight.flight_plan):
group.add_trigger_action(SwitchWaypoint(None, index))
if index < 1:
logging.warning(f"Couldn't determine SPLIT for {group.name}")
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
self.configure_behavior(
flight,
group,
roe=OptROE.Values.OpenFire,
# Guided includes ARMs and TALDs (among other things, but those are the useful
# ASM includes ARMs and TALDs (among other things, but those are the useful
# weapons for SEAD).
rtb_winchester=OptRTBOnOutOfAmmo.Values.Guided,
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
restrict_jettison=True,
mission_uses_gun=False,
)
def configure_transport(self, group: FlyingGroup[Any], flight: Flight) -> None:
group.task = Transport.name
roe = OptROE.Values.WeaponHold
if flight.is_hercules:
group.task = GroundAttack.name
roe = OptROE.Values.OpenFire
self.configure_behavior(
flight,
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=roe,
roe=OptROE.Values.WeaponHold,
restrict_jettison=True,
)

View File

@@ -29,7 +29,6 @@ from game.settings import Settings
from game.theater.controlpoint import (
Airfield,
ControlPoint,
Fob,
)
from game.unitmap import UnitMap
from .aircraftpainter import AircraftPainter
@@ -105,9 +104,7 @@ class AircraftGenerator:
ato: The ATO to spawn aircraft for.
dynamic_runways: Runway data for carriers and FARPs.
"""
self._reserve_frequencies_and_tacan(ato)
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
for package in ato.packages:
if not package.flights:
continue
for flight in package.flights:
@@ -118,10 +115,8 @@ class AircraftGenerator:
)
self.unit_map.add_aircraft(group, flight)
if (
package.primary_flight is not None
and package.primary_flight.flight_plan.is_formation(
package.primary_flight.flight_plan
)
package.primary_task == FlightType.STRIKE
and package.primary_flight is not None
):
splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}")
splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}"))
@@ -160,6 +155,9 @@ class AircraftGenerator:
def _spawn_unused_for(
self, squadron: Squadron, country: Country, faction: Faction
) -> None:
if self.game.settings.perf_disable_idle_aircraft:
return
assert isinstance(squadron.location, Airfield)
for _ in range(squadron.untasked_aircraft):
# Creating a flight even those this isn't a fragged mission lets us
@@ -204,27 +202,4 @@ class AircraftGenerator:
self.use_client,
).configure()
)
wpt = group.waypoint("LANDING")
if flight.is_helo and isinstance(flight.arrival, Fob) and wpt:
hpad = self.helipads[flight.arrival].units.pop(0)
wpt.helipad_id = hpad.id
wpt.link_unit = hpad.id
self.helipads[flight.arrival].units.append(hpad)
return group
def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None:
for package in ato.packages:
if package.frequency is None:
continue
if package.frequency not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(package.frequency)
for f in package.flights:
if (
f.frequency
and f.frequency not in self.radio_registry.allocated_channels
):
self.radio_registry.reserve(f.frequency)
if f.tacan and f.tacan not in self.tacan_registy.allocated_channels:
self.tacan_registy.mark_unavailable(f.tacan)

View File

@@ -133,20 +133,14 @@ class FlightGroupConfigurator:
laser_codes.append(None)
def setup_radios(self) -> RadioFrequency:
freq = self.flight.frequency
if freq is None and (freq := self.flight.package.frequency) is None:
freq = self.radio_registry.alloc_uhf()
self.flight.package.frequency = freq
if freq not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(freq)
if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}:
self.register_air_support(freq)
elif self.flight.frequency is None and self.flight.client_count:
freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry)
channel = self.radio_registry.alloc_uhf()
self.register_air_support(channel)
else:
channel = self.flight.unit_type.alloc_flight_radio(self.radio_registry)
self.group.set_frequency(freq.mhz)
return freq
self.group.set_frequency(channel.mhz)
return channel
def register_air_support(self, channel: RadioFrequency) -> None:
callsign = callsign_for_support_unit(self.group)
@@ -157,18 +151,13 @@ class FlightGroupConfigurator:
callsign=callsign,
freq=channel,
depature_location=self.flight.departure.name,
start_time=self.flight.flight_plan.patrol_start_time,
end_time=self.flight.flight_plan.patrol_end_time,
end_time=self.flight.flight_plan.mission_departure_time,
start_time=self.flight.flight_plan.takeoff_time(),
blue=self.flight.departure.captured,
)
)
elif isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan):
if self.flight.tacan is None:
tacan = self.tacan_registry.alloc_for_band(
TacanBand.Y, TacanUsage.AirToAir
)
else:
tacan = self.flight.tacan
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
self.mission_data.tankers.append(
TankerInfo(
group_name=str(self.group.name),

View File

@@ -135,6 +135,7 @@ class FlightGroupSpawner:
"No room on runway or parking slots. Starting from the air."
)
group = self._generate_over_departure(name, cp)
group.points[0].alt = 1500
return group
def generate_mid_mission(self) -> FlyingGroup[Any]:
@@ -207,11 +208,7 @@ class FlightGroupSpawner:
elif self.flight.unit_type.helicopter:
alt = WARM_START_HELI_ALT
else:
if origin.id not in self.mission_data.cp_stack:
min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
self.mission_data.cp_stack[origin.id] = min_alt
alt = self.mission_data.cp_stack[origin.id]
self.mission_data.cp_stack[origin.id] += STACK_SEPARATION
alt = WARM_START_ALTITUDE
speed = GroundSpeed.for_flight(self.flight, alt)
pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000))
@@ -247,18 +244,16 @@ class FlightGroupSpawner:
def _generate_at_cp_helipad(self, name: str, cp: ControlPoint) -> FlyingGroup[Any]:
try:
helipad = self.helipads[cp]
except IndexError:
except IndexError as ex:
raise NoParkingSlotError()
group = self._generate_at_group(name, helipad)
if self.start_type is StartType.WARM:
if self.start_type is not StartType.COLD:
group.points[0].type = "TakeOffParkingHot"
hpad = helipad.units[0]
for i in range(self.flight.count):
group.units[i].position = hpad.position
group.units[i].heading = hpad.heading
group.units[i].parking_id = str(i + 1)
group.units[i].position = helipad.units[i].position
group.units[i].heading = helipad.units[i].heading
return group
def dcs_start_type(self) -> DcsStartType:

View File

@@ -1,28 +0,0 @@
from dcs.point import MovingPoint
from dcs.task import Expend, WeaponType, CarpetBombing, OptROE
from game.ato.flightwaypointtype import FlightWaypointType
from game.utils import feet, knots
from pydcs_extensions.hercules.hercules import Hercules
from .pydcswaypointbuilder import PydcsWaypointBuilder
class AirAssaultIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
air_drop = self.group.units[0].unit_type in [Hercules]
if air_drop:
waypoint.speed = knots(230).meters_per_second
waypoint.speed_locked = True
waypoint.ETA_locked = False
tgt = self.flight.flight_plan.package.target.position
for wpt in self.flight.flight_plan.waypoints:
if wpt.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
tgt = wpt.position
break
bombing = CarpetBombing(
tgt,
weapon_type=WeaponType.Bombs,
expend=Expend.All,
carpet_length=feet(9000).meters,
)
waypoint.add_task(bombing)

View File

@@ -1,8 +1,7 @@
import logging
import math
from dcs.point import MovingPoint
from dcs.task import AttackGroup, OptECMUsing, WeaponType, Expend
from dcs.task import AttackGroup, OptECMUsing, WeaponType
from game.theater import TheaterGroundObject
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -29,24 +28,10 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
continue
task = AttackGroup(
miz_group.id,
weapon_type=WeaponType.Guided,
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
miz_group.id, weapon_type=WeaponType.Auto, group_attack=True
)
waypoint.tasks.append(task)
dir = target.position.heading_between_point(waypoint.position)
task = AttackGroup(
miz_group.id,
weapon_type=WeaponType.Unguided,
expend=Expend.All,
direction=math.radians(dir),
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
)
task.params["altitudeEnabled"] = False
waypoint.tasks.append(task)
# Preemptively use ECM to better avoid getting swatted.
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfDetectedLockByRadar)
waypoint.tasks.append(ecm_option)

View File

@@ -71,9 +71,10 @@ class JoinPointBuilder(PydcsWaypointBuilder):
max_dist: float = 30.0,
vertical_spacing: float = 2000.0,
) -> None:
rx = (random.random() + 0.1) * 333
ry = feet(vertical_spacing).meters
rz = (random.random() + 0.1) * 166 * random.choice([-1, 1])
rz = (random.random() + 0.1) * 50 * random.choice([-1, 1])
pos = {"x": rx, "y": ry, "z": rz}
group_id = None

View File

@@ -4,7 +4,7 @@ from datetime import timedelta
from typing import Any, Iterable, Union
from dcs import Mission
from dcs.planes import AJS37, F_14A_135_GR, F_14B, JF_17
from dcs.planes import AJS37, F_14B, JF_17
from dcs.point import MovingPoint, PointAction
from dcs.unitgroup import FlyingGroup
@@ -91,5 +91,5 @@ class PydcsWaypointBuilder:
for i, t in enumerate(targets):
if self.group.units[0].unit_type == JF_17 and i < 4:
self.group.add_nav_target_point(t.position, "PP" + str(i + 1))
if self.group.units[0].unit_type in [F_14B, F_14A_135_GR] and i == 0:
if self.group.units[0].unit_type == F_14B and i == 0:
self.group.add_nav_target_point(t.position, "ST")

View File

@@ -71,20 +71,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
if self.flight.unit_type.dcs_unit_type.tacan:
tanker_info = self.mission_data.tankers[-1]
tacan = tanker_info.tacan
if self.flight.tcn_name is None:
cs = tanker_info.callsign[:-2]
csn = tanker_info.callsign[-1]
tacan_callsign = {
"Texaco": "TX",
"Arco": "AC",
"Shell": "SH",
}.get(cs)
if tacan_callsign:
tacan_callsign = tacan_callsign + csn
else:
tacan_callsign = cs[0:2] + csn
else:
tacan_callsign = self.flight.tcn_name
tacan_callsign = {
"Texaco": "TEX",
"Arco": "ARC",
"Shell": "SHL",
}.get(tanker_info.callsign)
waypoint.add_task(
ActivateBeaconCommand(
@@ -94,6 +85,5 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
bearing=True,
unit_id=self.group.units[0].id,
aa=True,
tanker=True,
)
)

View File

@@ -49,10 +49,9 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
# All non ARM types like Decoys will use the normal AttackGroup Task
attack_task = AttackGroup(
miz_group.id,
weapon_type=DcsWeaponType.Decoy,
weapon_type=DcsWeaponType.Guided,
group_attack=True,
expend=Expend.All,
altitude=waypoint.alt,
)
waypoint.tasks.append(attack_task)

View File

@@ -6,6 +6,7 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
class SplitPointBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
if not self.flight.flight_type.is_air_to_air:
# Capture any non A/A type to avoid issues with SPJs that use the primary radar such as the F/A-18C.
# You can bully them with STT to not be able to fire radar guided missiles at you,

View File

@@ -3,9 +3,9 @@ import copy
from dcs import Point
from dcs.planes import B_17G, B_52H, Tu_22M3, B_1B
from dcs.point import MovingPoint
from dcs.task import Bombing, Expend, OptFormation, WeaponType, CarpetBombing
from dcs.task import Bombing, Expend, OptFormation, WeaponType
from game.utils import mach
from game.utils import mach, Distance
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -13,12 +13,15 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
bomber = self.group.units[0].unit_type in [B_17G, Tu_22M3]
bomber_guided = self.group.units[0].unit_type in [B_1B, B_52H]
waypoint.tasks.append(OptFormation.trail_open())
if bomber_guided or not bomber:
if bomber_guided:
self.add_strike_tasks(waypoint, WeaponType.Guided)
self.add_bombing_tasks(waypoint)
elif bomber:
self.add_bombing_tasks(waypoint)
else:
self.add_strike_tasks(waypoint)
waypoint.tasks.append(OptFormation.ww2_bomber_element_close())
self.add_bombing_tasks(waypoint)
waypoint.tasks.append(OptFormation.trail_open())
def add_bombing_tasks(self, waypoint: MovingPoint) -> None:
targets = self.waypoint.targets
@@ -29,16 +32,9 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
for target in targets[1:]:
center += target.position
center /= len(targets)
avg_spacing = 0.0
for t in targets:
avg_spacing += center.distance_to_point(t.position)
avg_spacing /= len(targets)
bombing = CarpetBombing(
center,
weapon_type=WeaponType.Bombs,
expend=Expend.All,
carpet_length=avg_spacing,
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
ga = True if self.flight.count > 1 else False
bombing = Bombing(
center, weapon_type=WeaponType.Bombs, expend=Expend.All, group_attack=ga
)
waypoint.tasks.append(bombing)
@@ -53,8 +49,7 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
bombing.params["expend"] = Expend.All.value
waypoint.tasks.append(bombing)
doctrine = self.flight.coalition.doctrine
waypoint.speed = mach(0.85, doctrine.ingress_altitude).meters_per_second
waypoint.speed = mach(0.85, Distance.from_feet(20000)).meters_per_second
# Register special waypoints
self.register_special_waypoints(self.waypoint.targets)

View File

@@ -20,7 +20,6 @@ from game.missiongenerator.aircraft.waypoints.cargostop import CargoStopBuilder
from game.missiongenerator.missiondata import MissionData
from game.settings import Settings
from game.utils import pairwise
from .airassaultingress import AirAssaultIngressBuilder
from .baiingress import BaiIngressBuilder
from .landingzone import LandingZoneBuilder
from .casingress import CasIngressBuilder
@@ -137,7 +136,6 @@ class WaypointGenerator:
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
FlightWaypointType.REFUEL: RefuelPointBuilder,
FlightWaypointType.CARGO_STOP: CargoStopBuilder,
FlightWaypointType.INGRESS_AIR_ASSAULT: AirAssaultIngressBuilder,
}
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
return builder(

View File

@@ -0,0 +1,210 @@
from __future__ import annotations
import logging
from typing import List, TYPE_CHECKING, Tuple, Type
from dcs.mission import Mission, StartType
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135, PlaneType
from dcs.task import (
AWACS,
ActivateBeaconCommand,
MainTask,
Refueling,
SetImmortalCommand,
SetInvisibleCommand,
)
from dcs.unittype import UnitType
from game.ato.ai_flight_planner_db import AEWC_CAPABLE
from game.callsigns import callsign_for_support_unit
from game.naming import namegen
from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
from game.utils import Heading
from .airconflictdescription import AirConflictDescription
from .missiondata import AwacsInfo, MissionData, TankerInfo
if TYPE_CHECKING:
from game import Game
TANKER_DISTANCE = 15000
TANKER_ALT = 4572
TANKER_HEADING_OFFSET = 45
AWACS_DISTANCE = 150000
AWACS_ALT = 13000
class AirSupportGenerator:
def __init__(
self,
mission: Mission,
conflict: AirConflictDescription,
game: Game,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
mission_data: MissionData,
) -> None:
self.mission = mission
self.conflict = conflict
self.game = game
self.radio_registry = radio_registry
self.tacan_registry = tacan_registry
self.mission_data = mission_data
@classmethod
def support_tasks(cls) -> List[Type[MainTask]]:
return [Refueling, AWACS]
@staticmethod
def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]:
if unit_type is KC130:
return TANKER_ALT - 500, 596
elif unit_type is KC_135:
return TANKER_ALT, 770
elif unit_type is KC135MPRS:
return TANKER_ALT + 500, 596
return TANKER_ALT, 574
def generate(self) -> None:
player_cp = (
self.conflict.blue_cp
if self.conflict.blue_cp.captured
else self.conflict.red_cp
)
country = self.mission.country(self.game.blue.country_name)
if not self.game.settings.disable_legacy_tanker:
fallback_tanker_number = 0
for i, tanker_unit_type in enumerate(
self.game.faction_for(player=True).tankers
):
unit_type = tanker_unit_type.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"Refueling aircraft {unit_type} must be a plane")
continue
# TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(
TacanBand.Y, TacanUsage.AirToAir
)
tanker_heading = Heading.from_degrees(
self.conflict.red_cp.position.heading_between_point(
self.conflict.blue_cp.position
)
+ TANKER_HEADING_OFFSET * i
)
tanker_position = player_cp.position.point_from_heading(
tanker_heading.degrees, TANKER_DISTANCE
)
tanker_group = self.mission.refuel_flight(
country=country,
name=namegen.next_tanker_name(country, tanker_unit_type),
airport=None,
plane_type=unit_type,
position=tanker_position,
altitude=alt,
race_distance=58000,
frequency=freq.mhz,
start_type=StartType.Warm,
speed=airspeed,
tacanchannel=str(tacan),
)
tanker_group.set_frequency(freq.mhz)
callsign = callsign_for_support_unit(tanker_group)
tacan_callsign = {
"Texaco": "TEX",
"Arco": "ARC",
"Shell": "SHL",
}.get(callsign)
if tacan_callsign is None:
# The dict above is all the callsigns currently in the game, but
# non-Western countries don't use the callsigns and instead just
# use numbers. It's possible that none of those nations have
# TACAN compatible refueling aircraft, but fallback just in
# case.
tacan_callsign = f"TK{fallback_tanker_number}"
fallback_tanker_number += 1
if tanker_unit_type != IL_78M:
# Override PyDCS tacan channel.
tanker_group.points[0].tasks.pop()
tanker_group.points[0].tasks.append(
ActivateBeaconCommand(
tacan.number,
tacan.band.value,
tacan_callsign,
True,
tanker_group.units[0].id,
True,
)
)
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
self.mission_data.tankers.append(
TankerInfo(
group_name=str(tanker_group.name),
callsign=callsign,
variant=tanker_unit_type.name,
freq=freq,
tacan=tacan,
start_time=None,
end_time=None,
blue=True,
)
)
if not self.game.settings.disable_legacy_aewc:
possible_awacs = [
a
for a in self.game.faction_for(player=True).aircrafts
if a in AEWC_CAPABLE
]
if not possible_awacs:
logging.warning("No AWACS for faction")
return
awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf()
unit_type = awacs_unit.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"AWACS aircraft {unit_type} must be a plane")
return
awacs_flight = self.mission.awacs_flight(
country=country,
name=namegen.next_awacs_name(country),
plane_type=unit_type,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.center.random_point_within(
AWACS_DISTANCE, AWACS_DISTANCE
),
frequency=freq.mhz,
start_type=StartType.Warm,
)
awacs_flight.set_frequency(freq.mhz)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.mission_data.awacs.append(
AwacsInfo(
group_name=str(awacs_flight.name),
callsign=callsign_for_support_unit(awacs_flight),
freq=freq,
depature_location=None,
start_time=None,
end_time=None,
blue=True,
)
)

View File

@@ -17,7 +17,7 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater import ControlPoint, FrontLine
from .aircraft.flightdata import FlightData
from .missiondata import AwacsInfo, TankerInfo
from .airsupportgenerator import AwacsInfo, TankerInfo
from .flotgenerator import JtacInfo
if TYPE_CHECKING:

View File

@@ -59,6 +59,7 @@ class DrawingsGenerator:
if destination in seen:
continue
else:
# Determine path color
if cp.captured and destination.captured:
color = BLUE_PATH_COLOR

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import logging
import math
import random
from datetime import timedelta
from typing import List, Optional, TYPE_CHECKING, Tuple
from dcs import Mission
@@ -16,6 +15,7 @@ from dcs.task import (
AFAC,
AttackGroup,
ControlledTask,
EPLRS,
FAC,
FireAtPoint,
GoToWaypoint,
@@ -47,7 +47,6 @@ from .frontlineconflictdescription import FrontLineConflictDescription
from .groundforcepainter import GroundForcePainter
from .lasercoderegistry import LaserCodeRegistry
from .missiondata import JtacInfo, MissionData
from ..ato import FlightType
if TYPE_CHECKING:
from game import Game
@@ -204,6 +203,7 @@ class FlotGenerator:
side: Country,
forward_heading: Heading,
) -> None:
infantry_position = self.conflict.find_ground_position(
group.points[0].position.random_point_within(250, 50),
500,
@@ -276,29 +276,8 @@ class FlotGenerator:
vehicle = vg.units[0]
GroundForcePainter(faction, vehicle).apply_livery()
def _earliest_tot_on_flot(self, player: bool) -> timedelta:
tots = [
x.time_over_target
for x in self.game.ato_for(player).packages
if x.primary_task == FlightType.CAS
]
return (
timedelta(seconds=random.randint(300, 1800))
if len(tots) == 0
else min(
[
x.time_over_target
for x in self.game.ato_for(player).packages
if x.primary_task == FlightType.CAS
]
)
)
def _set_reform_waypoint(
self,
dcs_group: VehicleGroup,
forward_heading: Heading,
hold_duration: timedelta = timedelta(),
self, dcs_group: VehicleGroup, forward_heading: Heading
) -> None:
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
rather than spin
@@ -306,10 +285,7 @@ class FlotGenerator:
reform_point = dcs_group.position.point_from_heading(
forward_heading.degrees, 50
)
rp = dcs_group.add_waypoint(reform_point)
hold = ControlledTask(Hold())
hold.stop_after_duration(hold_duration.seconds)
rp.add_task(hold)
dcs_group.add_waypoint(reform_point)
def _plan_artillery_action(
self,
@@ -343,10 +319,11 @@ class FlotGenerator:
# Artillery will fall back when under attack
if stance != CombatStance.RETREAT:
# Hold position
dcs_group.points[1].tasks.append(Hold())
retreat = self.find_retreat_point(
dcs_group, forward_heading, int(RETREAT_DISTANCE / 3)
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3)
)
dcs_group.add_waypoint(
dcs_group.position.point_from_heading(forward_heading.degrees, 1),
@@ -393,10 +370,7 @@ class FlotGenerator:
Handles adding the DCS tasks for tank and IFV groups for all combat stances.
Returns True if tasking was added, returns False if the stance was not a combat stance.
"""
duration = timedelta()
if stance != CombatStance.RETREAT:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
self._set_reform_waypoint(dcs_group, forward_heading, duration)
self._set_reform_waypoint(dcs_group, forward_heading)
if stance == CombatStance.AGGRESSIVE:
# Attack nearest enemy if any
# Then move forward OR Attack enemy base if it is not too far away
@@ -483,10 +457,7 @@ class FlotGenerator:
Handles adding the DCS tasks for APC and ATGM groups for all combat stances.
Returns True if tasking was added, returns False if the stance was not a combat stance.
"""
duration = timedelta()
if stance != CombatStance.RETREAT:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
self._set_reform_waypoint(dcs_group, forward_heading, duration)
self._set_reform_waypoint(dcs_group, forward_heading)
if stance in [
CombatStance.AGGRESSIVE,
CombatStance.BREAKTHROUGH,
@@ -520,10 +491,14 @@ class FlotGenerator:
from_cp: ControlPoint,
to_cp: ControlPoint,
) -> None:
if not self.game.settings.perf_moving_units:
return
for dcs_group, group in ally_groups:
if group.unit_type.eplrs_capable:
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
if group.role == CombatGroupRole.ARTILLERY:
if self.game.settings.perf_artillery:
target = self.get_artillery_target_in_range(
@@ -544,7 +519,7 @@ class FlotGenerator:
if stance == CombatStance.RETREAT:
# In retreat mode, the units will fall back
# If the allied base is close enough, the units will even regroup there
# If the ally base is close enough, the units will even regroup there
if (
from_cp.position.distance_to_point(dcs_group.points[0].position)
<= RETREAT_DISTANCE
@@ -562,7 +537,7 @@ class FlotGenerator:
self, dcs_group: VehicleGroup, forward_heading: Heading
) -> None:
"""
This adds a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS
This add a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS
"""
if len(dcs_group.units) == 1:
@@ -579,7 +554,7 @@ class FlotGenerator:
# We add a new retreat waypoint
dcs_group.add_waypoint(
self.find_retreat_point(
dcs_group, forward_heading, int(RETREAT_DISTANCE / 8)
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)
),
self.wpt_pointaction,
)
@@ -709,7 +684,7 @@ class FlotGenerator:
@staticmethod
def get_artilery_group_distance_from_frontline(group: CombatGroup) -> int:
"""
For artillery group, decide the distance from frontline with the range of the unit
For artilery group, decide the distance from frontline with the range of the unit
"""
rg = group.unit_type.dcs_unit_type.threat_range - 7500
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:

View File

@@ -43,10 +43,10 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater import TheaterGroundObject, TheaterUnit
from game.theater.bullseye import Bullseye
from game.utils import Distance, UnitSystem, meters, mps, pounds, knots, feet
from game.utils import Distance, UnitSystem, meters, mps, pounds
from game.weather import Weather
from .aircraft.flightdata import FlightData
from .missiondata import AwacsInfo, TankerInfo
from .airsupportgenerator import AwacsInfo, TankerInfo
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator
if TYPE_CHECKING:
@@ -191,6 +191,7 @@ class NumberedWaypoint:
class FlightPlanBuilder:
WAYPOINT_DESC_MAX_LEN = 25
def __init__(self, start_time: datetime.datetime, units: UnitSystem) -> None:
@@ -335,7 +336,7 @@ class BriefingPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name:
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -402,19 +403,6 @@ class BriefingPage(KneeboardPage):
writer.text(
f"Turbulence: {round(self.weather.atmospheric.turbulence_per_10cm)} per 10cm at ground level."
)
writer.text(
f"Wind: {self.weather.wind.at_0m.direction}°"
f" / {round(mps(self.weather.wind.at_0m.speed).knots)}kts (0ft)"
f" ; {self.weather.wind.at_2000m.direction}°"
f" / {round(mps(self.weather.wind.at_2000m.speed).knots)}kts (~6500ft)"
f" ; {self.weather.wind.at_8000m.direction}°"
f" / {round(mps(self.weather.wind.at_8000m.speed).knots)}kts (~26000ft)"
)
c = self.weather.clouds
writer.text(
f'Cloud base: {f"{int(round(meters(c.base).feet, -2))}ft" if c else "CAVOK"}'
f'{f", {c.preset.ui_name[:-2]}" if c and c.preset else ""}'
)
fl = self.flight
@@ -468,7 +456,7 @@ class BriefingPage(KneeboardPage):
ils = ""
return [
row_title,
"\n".join(textwrap.wrap(runway.airfield_name, width=17)),
"\n".join(textwrap.wrap(runway.airfield_name, width=24)),
atc,
tacan,
ils,
@@ -494,7 +482,6 @@ class SupportPage(KneeboardPage):
def __init__(
self,
flight: FlightData,
package_flights: List[FlightData],
comms: List[CommInfo],
awacs: List[AwacsInfo],
tankers: List[TankerInfo],
@@ -503,7 +490,6 @@ class SupportPage(KneeboardPage):
dark_kneeboard: bool,
) -> None:
self.flight = flight
self.package_flights = package_flights
self.comms = list(comms)
self.awacs = awacs
self.tankers = tankers
@@ -514,92 +500,60 @@ class SupportPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name:
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
writer.title(f"{self.flight.callsign} Support Info{custom_name_title}")
# Package Section
package = self.flight.package
custom = f' "{package.custom_name}"' if package.custom_name else ""
writer.heading(f"{package.package_description} Package{custom}")
freq = self.format_frequency(package.frequency).replace("\n", " - ")
writer.text(f" FREQ: {freq}", font=writer.table_font)
tot = self._format_time(package.time_over_target)
writer.text(f" TOT: {tot}", font=writer.table_font)
comm_ladder = []
for comm in self.comms:
comm_ladder.append(
[
comm.name,
"",
"",
str(len(self.flight.units)),
self.format_frequency(comm.freq),
]
)
for f in self.package_flights:
comm_ladder.append(
[
f.callsign,
str(f.flight_type),
KneeboardPageWriter.wrap_line(str(f.aircraft_type), 23),
str(len(f.units)),
self.format_frequency(f.intra_flight_channel),
]
)
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "#A/C", "FREQ"])
# AEW&C
writer.heading("AEW&C")
aewc_ladder = []
for single_aewc in self.awacs:
if single_aewc.depature_location is None:
tot = "-"
tos = "-"
dep = "-"
arr = "-"
else:
tot = self._format_time(single_aewc.start_time)
tos = self._format_duration(
single_aewc.end_time - single_aewc.start_time
)
dep = self._format_time(single_aewc.start_time)
arr = self._format_time(single_aewc.end_time)
aewc_ladder.append(
[
str(single_aewc.callsign),
self.format_frequency(single_aewc.freq),
str(single_aewc.depature_location),
"TOT: " + tot + "\n" + "TOS: " + tos,
str(dep),
str(arr),
]
)
writer.table(
aewc_ladder,
headers=["Callsign", "FREQ", "Departure", "TOT / TOS"],
headers=["Callsign", "FREQ", "Depature", "ETD", "ETA"],
)
# Package Section
writer.heading("Comm ladder")
comm_ladder = []
writer.heading("Tankers:")
for comm in self.comms:
comm_ladder.append(
[comm.name, "", "", "", self.format_frequency(comm.freq)]
)
for tanker in self.tankers:
tot = self._format_time(tanker.start_time)
tos = self._format_duration(tanker.end_time - tanker.start_time)
comm_ladder.append(
[
tanker.callsign,
"Tanker",
KneeboardPageWriter.wrap_line(tanker.variant, 21),
tanker.variant,
str(tanker.tacan),
self.format_frequency(tanker.freq),
"TOT: " + tot + "\n" + "TOS: " + tos,
]
)
writer.table(
comm_ladder,
headers=["Callsign", "Task", "Type", "TACAN", "FREQ", "TOT / TOS"],
)
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "TACAN", "FREQ"])
writer.heading("JTAC")
jtacs = []
@@ -619,9 +573,7 @@ class SupportPage(KneeboardPage):
writer.write(path)
def format_frequency(self, frequency: Optional[RadioFrequency]) -> str:
if frequency is None:
return ""
def format_frequency(self, frequency: RadioFrequency) -> str:
channel = self.flight.channel_for(frequency)
if channel is None:
return str(frequency)
@@ -637,13 +589,6 @@ class SupportPage(KneeboardPage):
local_time = self.start_time + time
return f"{local_time.strftime('%H:%M:%S')}{'Z' if local_time.tzinfo is not None else ''}"
@staticmethod
def _format_duration(time: Optional[datetime.timedelta]) -> str:
if time is None:
return ""
time -= datetime.timedelta(microseconds=time.microseconds)
return f"{time}"
class SeadTaskPage(KneeboardPage):
"""A kneeboard page containing SEAD/DEAD target information."""
@@ -666,7 +611,7 @@ class SeadTaskPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name:
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -708,7 +653,7 @@ class StrikeTaskPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name:
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -788,13 +733,8 @@ class KneeboardGenerator(MissionInfoGenerator):
for flight in self.flights:
if not flight.client_units:
continue
package_flights = [
f
for f in self.flights
if f.package is flight.package and f is not flight
]
all_flights[flight.aircraft_type].extend(
self.generate_flight_kneeboard(flight, package_flights)
self.generate_flight_kneeboard(flight)
)
return all_flights
@@ -805,9 +745,7 @@ class KneeboardGenerator(MissionInfoGenerator):
return StrikeTaskPage(flight, self.dark_kneeboard)
return None
def generate_flight_kneeboard(
self, flight: FlightData, package_flights: List[FlightData]
) -> List[KneeboardPage]:
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
"""Returns a list of kneeboard pages for the given flight."""
if flight.aircraft_type.utc_kneeboard:
@@ -827,7 +765,6 @@ class KneeboardGenerator(MissionInfoGenerator):
),
SupportPage(
flight,
package_flights,
self.comms,
self.awacs,
self.tankers,

View File

@@ -33,8 +33,8 @@ class AwacsInfo(GroupInfo):
"""AWACS information for the kneeboard."""
depature_location: Optional[str]
start_time: timedelta
end_time: timedelta
start_time: Optional[timedelta]
end_time: Optional[timedelta]
@dataclass
@@ -43,8 +43,8 @@ class TankerInfo(GroupInfo):
variant: str
tacan: TacanChannel
start_time: timedelta
end_time: timedelta
start_time: Optional[timedelta]
end_time: Optional[timedelta]
@dataclass

View File

@@ -17,11 +17,13 @@ from game.missiongenerator.aircraft.aircraftgenerator import (
AircraftGenerator,
)
from game.naming import namegen
from game.radio.radios import RadioFrequency, RadioRegistry, MHz
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanRegistry
from game.theater import Airfield
from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap
from .airconflictdescription import AirConflictDescription
from .airsupportgenerator import AirSupportGenerator
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
from .cargoshipgenerator import CargoShipGenerator
from .convoygenerator import ConvoyGenerator
@@ -37,12 +39,14 @@ from .missiondata import MissionData
from .tgogenerator import TgoGenerator
from .triggergenerator import TriggerGenerator
from .visualsgenerator import VisualsGenerator
from ..radio.TacanContainer import TacanContainer
if TYPE_CHECKING:
from game import Game
COMBINED_ARMS_SLOTS = 3
def country_id_from_name(name: str) -> int:
for k, v in country_dict.items():
if v.name == name:
@@ -66,11 +70,7 @@ class MissionGenerator:
self.generation_started = False
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)
self.mission.options.load_from_dict(dcs.lua.loads(f.read())["options"])
def generate_miz(self, output: Path) -> UnitMap:
if self.generation_started:
@@ -159,9 +159,6 @@ class MissionGenerator:
unique_map_frequencies: set[RadioFrequency] = set()
self.initialize_tacan_registry(unique_map_frequencies)
self.initialize_radio_registry(unique_map_frequencies)
# Allocate UHF/VHF Guard Freq first!
unique_map_frequencies.add(MHz(243))
unique_map_frequencies.add(MHz(121, 500))
for frequency in unique_map_frequencies:
self.radio_registry.reserve(frequency)
@@ -179,9 +176,6 @@ class MissionGenerator:
logging.warning(f"TACAN beacon has no channel: {beacon.callsign}")
else:
self.tacan_registry.mark_unavailable(beacon.tacan_channel)
for cp in self.game.theater.controlpoints:
if isinstance(cp, TacanContainer) and cp.tacan is not None:
self.tacan_registry.mark_unavailable(cp.tacan)
def initialize_radio_registry(
self, unique_map_frequencies: set[RadioFrequency]
@@ -226,6 +220,17 @@ class MissionGenerator:
def generate_air_units(self, tgo_generator: TgoGenerator) -> None:
"""Generate the air units for the Operation"""
# Air Support (Tanker & Awacs)
air_support_generator = AirSupportGenerator(
self.mission,
AirConflictDescription.for_theater(self.game.theater),
self.game,
self.radio_registry,
self.tacan_registry,
self.mission_data,
)
air_support_generator.generate()
# Generate Aircraft Activity on the map
aircraft_generator = AircraftGenerator(
self.mission,
@@ -236,7 +241,7 @@ class MissionGenerator:
self.tacan_registry,
self.laser_code_registry,
self.unit_map,
mission_data=self.mission_data,
mission_data=air_support_generator.mission_data,
helipads=tgo_generator.helipads,
)
@@ -252,18 +257,19 @@ class MissionGenerator:
self.game.red.ato,
tgo_generator.runways,
)
if not self.game.settings.perf_disable_idle_aircraft:
aircraft_generator.spawn_unused_aircraft(
self.mission.country(self.game.blue.country_name),
self.mission.country(self.game.red.country_name),
)
self.mission_data.flights = aircraft_generator.flights
aircraft_generator.spawn_unused_aircraft(
self.mission.country(self.game.blue.country_name),
self.mission.country(self.game.red.country_name),
)
for flight in aircraft_generator.flights:
if not flight.client_units:
continue
flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data)
flight.aircraft_type.assign_channels_for_flight(
flight, air_support_generator.mission_data
)
self.mission_data.flights = aircraft_generator.flights
def generate_destroyed_units(self) -> None:
"""Add destroyed units to the Mission"""
@@ -324,11 +330,12 @@ class MissionGenerator:
gen.generate()
def setup_combined_arms(self) -> None:
settings = self.game.settings
commanders = settings.tactical_commander_count
self.mission.groundControl.pilot_can_control_vehicles = commanders > 0
self.mission.groundControl.blue_game_masters = settings.game_masters_count
self.mission.groundControl.blue_tactical_commander = commanders
self.mission.groundControl.blue_jtac = settings.jtac_count
self.mission.groundControl.blue_observer = settings.observer_count
observers = 3
self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0
self.mission.groundControl.blue_game_masters = 1
self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS
self.mission.groundControl.blue_observer = observers
self.mission.groundControl.red_game_masters = 1
self.mission.groundControl.red_tactical_commander = COMBINED_ARMS_SLOTS
self.mission.groundControl.red_observer = observers
self.mission.groundControl.neutrals_observer = observers

View File

@@ -22,8 +22,6 @@ from dcs.ships import (
CVN_73,
CVN_75,
Stennis,
Forrestal,
LHA_Tarawa,
)
from dcs.statics import Fortification
from dcs.task import (
@@ -37,7 +35,7 @@ from dcs.task import (
)
from dcs.translation import String
from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone
from dcs.unit import Unit, InvisibleFARP, BaseFARP
from dcs.unit import Unit, InvisibleFARP
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import ShipType, VehicleType
from dcs.vehicles import vehicle_map
@@ -47,16 +45,10 @@ from game.missiongenerator.groundforcepainter import (
GroundForcePainter,
)
from game.missiongenerator.missiondata import CarrierInfo, MissionData
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
from game.runways import RunwayData
from game.theater import (
ControlPoint,
TheaterGroundObject,
TheaterUnit,
NavalControlPoint,
)
from game.theater import ControlPoint, TheaterGroundObject, TheaterUnit
from game.theater.theatergroundobject import (
CarrierGroundObject,
GenericCarrierGroundObject,
@@ -359,7 +351,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
def __init__(
self,
ground_object: GenericCarrierGroundObject,
control_point: NavalControlPoint,
control_point: ControlPoint,
country: Country,
game: Game,
mission: Mission,
@@ -380,12 +372,9 @@ class GenericCarrierGenerator(GroundObjectGenerator):
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()
# This can also be refactored as the general generation was updated
atc = self.radio_registry.alloc_uhf()
for g_id, group in enumerate(self.ground_object.groups):
if not group.units:
@@ -420,33 +409,15 @@ class GenericCarrierGenerator(GroundObjectGenerator):
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
tacan = self.tacan_registry.alloc_for_band(
TacanBand.X, TacanUsage.TransmitReceive
)
tacan_callsign = self.tacan_callsign()
icls = next(self.icls_alloc)
link4 = None
if carrier_type in [Stennis, CVN_71, CVN_72, CVN_73, CVN_75]:
link4 = self.radio_registry.alloc_uhf()
self.activate_beacons(ship_group, tacan, tacan_callsign, icls, link4)
self.add_runway_data(
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
)
@@ -490,8 +461,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
group: ShipGroup,
tacan: TacanChannel,
callsign: str,
icls: Optional[int] = None,
icls_name: Optional[str] = None,
icls: int,
link4: Optional[RadioFrequency] = None,
) -> None:
group.points[0].tasks.append(
@@ -503,14 +473,12 @@ class GenericCarrierGenerator(GroundObjectGenerator):
aa=False,
)
)
if icls is not None:
icls_name = "" if icls_name is None else icls_name
group.points[0].tasks.append(
ActivateICLSCommand(icls, group.units[0].id, icls_name)
)
group.points[0].tasks.append(
ActivateICLSCommand(icls, unit_id=group.units[0].id)
)
if link4 is not None:
group.points[0].tasks.append(
ActivateLink4Command(link4.hertz, group.units[0].id)
ActivateLink4Command(int(link4.mhz), group.units[0].id)
)
group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id))
@@ -520,8 +488,9 @@ class GenericCarrierGenerator(GroundObjectGenerator):
atc: RadioFrequency,
tacan: TacanChannel,
callsign: str,
icls: Optional[int],
icls: int,
) -> None:
# TODO: Make unit name usable.
# This relies on one control point mapping exactly
# to one LHA, carrier, or other usable "runway".
# This isn't wholly true, since the DD escorts of
@@ -529,10 +498,10 @@ class GenericCarrierGenerator(GroundObjectGenerator):
# they aren't exposed as such to the game. Should
# clean this up so that's possible. We can't use the
# unit name since it's an arbitrary ID.
self.runways[self.control_point.full_name] = RunwayData(
self.runways[self.control_point.name] = RunwayData(
self.control_point.name,
brc,
f"{brc.degrees:03}",
"N/A",
atc=atc,
tacan=tacan,
tacan_callsign=callsign,
@@ -621,13 +590,6 @@ class HelipadGenerator:
self.helipads.add_unit(
InvisibleFARP(self.m.terrain, self.m.next_unit_id(), name_i)
)
# Set FREQ
if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency:
for hp in self.helipads.units:
if isinstance(hp, BaseFARP):
hp.heliport_frequency = self.cp.frequency.mhz
pad = self.helipads.units[-1]
pad.position = helipad
pad.heading = heading
@@ -699,9 +661,7 @@ class TgoGenerator:
for ground_object in cp.ground_objects:
generator: GroundObjectGenerator
if isinstance(ground_object, CarrierGroundObject) and isinstance(
cp, NavalControlPoint
):
if isinstance(ground_object, CarrierGroundObject):
generator = CarrierGenerator(
ground_object,
cp,
@@ -715,9 +675,7 @@ class TgoGenerator:
self.unit_map,
self.mission_data,
)
elif isinstance(ground_object, LhaGroundObject) and isinstance(
cp, NavalControlPoint
):
elif isinstance(ground_object, LhaGroundObject):
generator = LhaGenerator(
ground_object,
cp,

View File

@@ -2,9 +2,8 @@ from typing import Type
from dcs.helicopters import HelicopterType, helicopter_map
from dcs.planes import PlaneType, plane_map
from dcs.unittype import VehicleType, ShipType
from dcs.unittype import VehicleType
from dcs.vehicles import vehicle_map
from dcs.ships import ship_map
def helicoptermod(helicopter: Type[HelicopterType]) -> Type[HelicopterType]:
@@ -20,8 +19,3 @@ def planemod(plane: Type[PlaneType]) -> Type[PlaneType]:
def vehiclemod(vehicle: Type[VehicleType]) -> Type[VehicleType]:
vehicle_map[vehicle.id] = vehicle
return vehicle
def shipmod(ship: Type[ShipType]) -> Type[ShipType]:
ship_map[ship.id] = ship
return ship

View File

@@ -5,7 +5,7 @@ import logging
import textwrap
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING, Any
from typing import List, Optional, TYPE_CHECKING
from game.settings import Settings
@@ -32,9 +32,9 @@ class LuaPluginWorkOrder:
class PluginSettings:
def __init__(self, identifier: str, value: Any) -> None:
def __init__(self, identifier: str, enabled_by_default: bool) -> None:
self.identifier = identifier
self.value = value
self.enabled_by_default = enabled_by_default
self.settings = Settings()
self.initialize_settings()
@@ -46,26 +46,20 @@ class PluginSettings:
# Plugin options are saved in the game's Settings, but it's possible for
# plugins to change across loads. If new plugins are added or new
# options added to those plugins, initialize the new settings.
self.settings.initialize_plugin_option(self.identifier, self.value)
self.settings.initialize_plugin_option(self.identifier, self.enabled_by_default)
@property
def get_value(self) -> Any:
def enabled(self) -> bool:
return self.settings.plugin_option(self.identifier)
def set_value(self, value: Any) -> None:
self.settings.set_plugin_option(self.identifier, value)
def set_enabled(self, enabled: bool) -> None:
self.settings.set_plugin_option(self.identifier, enabled)
class LuaPluginOption(PluginSettings):
def __init__(
self, identifier: str, name: str, min: Any, max: Any, value: Any
) -> None:
super().__init__(identifier, value)
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
super().__init__(identifier, enabled_by_default)
self.name = name
if type(value) == int or type(value) == float:
self.min, self.max = min, max
else:
self.min, self.max = None, None
@dataclass(frozen=True)
@@ -89,9 +83,7 @@ class LuaPluginDefinition:
LuaPluginOption(
identifier=f"{name}.{option_id}",
name=option.get("nameInUI", name),
min=option.get("minimumValue", 0),
max=option.get("maximumValue", 10000),
value=option.get("defaultValue"),
enabled_by_default=option.get("defaultValue"),
)
)
@@ -144,10 +136,6 @@ class LuaPlugin(PluginSettings):
def options(self) -> List[LuaPluginOption]:
return self.definition.options
@property
def enabled(self) -> bool:
return type(self.get_value) == bool and self.get_value
@classmethod
def from_json(cls, name: str, path: Path) -> Optional[LuaPlugin]:
try:
@@ -172,9 +160,9 @@ class LuaPlugin(PluginSettings):
if self.options:
option_decls = []
for option in self.options:
value = str(option.get_value).lower()
enabled = str(option.enabled).lower()
name = option.identifier
option_decls.append(f" dcsRetribution.plugins.{name} = {value}")
option_decls.append(f" dcsRetribution.plugins.{name} = {enabled}")
joined_options = "\n".join(option_decls)

View File

@@ -16,6 +16,8 @@ if TYPE_CHECKING:
from game.factions.faction import Faction
from game.squadrons import Squadron
FRONTLINE_RESERVES_FACTOR = 1.3
@dataclass(frozen=True)
class AircraftProcurementRequest:
@@ -39,6 +41,7 @@ class ProcurementAi:
manage_front_line: bool,
manage_aircraft: bool,
) -> None:
self.game = game
self.is_player = for_player
self.air_wing = game.air_wing_for(for_player)
@@ -59,8 +62,8 @@ class ProcurementAi:
):
return 0
# faction has no planes or no squadrons
if len(self.faction.aircrafts) == 0 or len(self.air_wing.squadrons) == 0:
# faction has no planes
if len(self.faction.aircrafts) == 0:
return 1
for cp in self.owned_points:
@@ -71,15 +74,13 @@ class ProcurementAi:
cp_aircraft = cp.allocated_aircraft()
aircraft_investment += cp_aircraft.total_value
air = self.game.settings.auto_procurement_balance / 100.0
ground = 1 - air
weighted_investment = aircraft_investment * air + armor_investment * ground
if weighted_investment == 0:
total_investment = aircraft_investment + armor_investment
if total_investment == 0:
# Turn 0 or all units were destroyed. Either way, split 30/70.
return min(0.3, ground)
return 0.3
# the more planes we have, the more ground units we want and vice versa
ground_unit_share = aircraft_investment * air / weighted_investment
ground_unit_share = aircraft_investment / total_investment
if ground_unit_share > 1.0:
raise ValueError
@@ -256,8 +257,7 @@ class ProcurementAi:
# No source of ground units, so can't buy anything.
continue
fr_factor = self.game.settings.frontline_reserves_factor / 100.0
purchase_target = cp.frontline_unit_count_limit * fr_factor
purchase_target = cp.frontline_unit_count_limit * FRONTLINE_RESERVES_FACTOR
allocated = cp.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)

View File

@@ -1,6 +0,0 @@
from typing import Optional
class ICLSContainer:
icls_channel: Optional[int] = None
icls_name: Optional[str] = None

View File

@@ -1,8 +0,0 @@
from typing import Optional
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency
class Link4Container(RadioFrequencyContainer):
link4: Optional[RadioFrequency] = None

View File

@@ -1,7 +0,0 @@
from typing import Optional
from game.radio.radios import RadioFrequency
class RadioFrequencyContainer:
frequency: Optional[RadioFrequency] = None

View File

@@ -1,8 +0,0 @@
from typing import Optional
from game.radio.tacan import TacanChannel
class TacanContainer:
tacan: Optional[TacanChannel] = None
tcn_name: Optional[str] = None

View File

@@ -76,9 +76,6 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
for jtac in mission_data.jtacs:
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
if (freq := flight.package.frequency) is not None:
flight.assign_channel(radio_id, next(channel_alloc), freq)
if flight.arrival != flight.departure and flight.arrival.atc is not None:
flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc)

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import itertools
import logging
import random
import re
from dataclasses import dataclass
from typing import Dict, FrozenSet, Iterator, List, Set, Tuple
@@ -157,61 +156,57 @@ class ChannelInUseError(RuntimeError):
# TODO: Figure out appropriate steps for each radio. These are just guesses.
#: List of all known radios used by aircraft in the game.
RADIOS: List[Radio] = [
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
Radio(
"AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)
),
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), kHz(25), Modulation.FM),)),
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), MHz(1), Modulation.FM),)),
Radio(
"AN/ARC-210",
(
RadioRange(
MHz(225),
MHz(400),
kHz(25),
MHz(1),
Modulation.AM,
frozenset((MHz(243),)),
),
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.AM),
RadioRange(MHz(156), MHz(174), kHz(25), Modulation.FM),
RadioRange(MHz(118), MHz(136), kHz(25), Modulation.AM),
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.FM),
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.AM),
RadioRange(MHz(156), MHz(174), MHz(1), Modulation.FM),
RadioRange(MHz(118), MHz(136), MHz(1), Modulation.AM),
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.FM),
# The AN/ARC-210 can also use 225-400 and 136-155 with FM Modulation
RadioRange(
MHz(225),
MHz(400),
kHz(25),
MHz(1),
Modulation.FM,
frozenset((MHz(243),)),
),
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.FM),
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.FM),
),
),
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)),
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
Radio("BC-1206", (RadioRange(kHz(200), kHz(400), kHz(10), Modulation.AM),)),
Radio(
"TRT ERA 7000 V/UHF",
(
RadioRange(MHz(118), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(118), MHz(150), MHz(1), Modulation.AM),
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
),
),
Radio(
"TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)
),
Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
# Tomcat radios
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
Radio(
"AN/ARC-182",
(
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(108), MHz(174), kHz(25), Modulation.AM),
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
RadioRange(MHz(108), MHz(174), MHz(1), Modulation.AM),
# The Range from 30-88MHz should be FM but its modeled as AM in dcs
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.AM),
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.AM),
),
),
Radio(
@@ -225,28 +220,18 @@ RADIOS: List[Radio] = [
# 4 preset channels (A/B/C/D)
Radio("SCR522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
# JF-17 Radios should use AM
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), kHz(25), Modulation.AM),)),
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), MHz(1), Modulation.AM),)),
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
# MiG-15bis
Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), kHz(25), Modulation.AM),)),
# MiG-19P
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),)),
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), MHz(1), Modulation.AM),)),
# MiG-21bis
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), kHz(25), Modulation.AM),)),
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), MHz(1), Modulation.AM),)),
# Ka-50
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
Radio("R-800L1", (RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),)),
Radio("R-828", (RadioRange(MHz(20), MHz(60), kHz(25), Modulation.FM),)),
# Mi-8 / Mi-24P
Radio(
"R-863",
(
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.FM),
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.FM),
),
),
# UH-1H
Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), kHz(50), Modulation.AM),)),
Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), kHz(50), Modulation.FM),)),
@@ -278,7 +263,7 @@ RADIOS: List[Radio] = [
RadioRange(
MHz(225),
MHz(400),
kHz(25),
MHz(1),
Modulation.AM,
frozenset((MHz(243),)),
),
@@ -290,28 +275,28 @@ RADIOS: List[Radio] = [
RadioRange(
MHz(30),
MHz(88),
kHz(25),
MHz(1),
Modulation.FM,
frozenset((MHz(40, 500),)),
),
RadioRange(
MHz(108),
MHz(156),
kHz(25),
MHz(1),
Modulation.AM,
frozenset((MHz(121, 500),)),
),
RadioRange(
MHz(156),
MHz(174),
kHz(25),
MHz(1),
Modulation.FM,
frozenset((MHz(156, 800),)),
),
RadioRange(
MHz(225),
MHz(400),
kHz(25),
MHz(1),
Modulation.AM, # Actually AM/FM, but we can't represent that.
frozenset((MHz(243),)),
),
@@ -338,13 +323,6 @@ def get_radio(name: str) -> Radio:
raise KeyError(f"Unknown radio: {name}")
def random_frequency(radio: Radio) -> RadioFrequency:
range: RadioRange = radio.ranges[0]
delta = round(random.random() * (range.maximum.hertz - range.minimum.hertz))
delta -= delta % range.step.hertz
return RadioFrequency(range.minimum.hertz + delta)
class RadioRegistry:
"""Manages allocation of radio channels.
@@ -390,8 +368,9 @@ class RadioRegistry:
OutOfChannelsError: All channels compatible with the given radio are
already allocated.
"""
allocator = self.radio_allocators[radio]
try:
while (channel := random_frequency(radio)) in self.allocated_channels:
while (channel := next(allocator)) in self.allocated_channels:
pass
self.reserve(channel)
return channel

View File

@@ -54,8 +54,8 @@ class TacanChannel:
if match is None:
raise ValueError(f"Could not parse TACAN from {text}")
number = int(match.group(1))
if not (0 < number <= 126):
raise ValueError("TACAN channel cannot be 0 or larger than 126")
if not number:
raise ValueError("TACAN channel cannot be 0")
return TacanChannel(number, TacanBand(match.group(2)))

View File

@@ -31,6 +31,7 @@ class SceneryGroup:
def __init__(
self, zone_def: TriggerZone, zones: Iterable[TriggerZone], category: str
) -> None:
self.zone_def = zone_def
self.zones = zones
self.position = zone_def.position

View File

@@ -50,6 +50,7 @@ class GameUpdateEventsJs(BaseModel):
def from_events(
cls, events: GameUpdateEvents, game: Game | None
) -> GameUpdateEventsJs:
# We still need to be able to send update events when there is no game loaded
# because we need to send the unload event.
new_combats = []

View File

@@ -36,7 +36,6 @@ CAMPAIGN_MANAGEMENT_PAGE = "Campaign Management"
GENERAL_SECTION = "General"
PILOTS_AND_SQUADRONS_SECTION = "Pilots and Squadrons"
HQ_AUTOMATION_SECTION = "HQ Automation"
FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation"
MISSION_GENERATOR_PAGE = "Mission Generator"
@@ -174,6 +173,30 @@ class Settings:
"extremely incomplete so does not affect all weapons."
),
)
disable_legacy_aewc: bool = boolean_option(
"Spawn invulnerable, always-available AEW&C aircraft (deprecated)",
page=CAMPAIGN_MANAGEMENT_PAGE,
section=GENERAL_SECTION,
default=True,
invert=True,
detail=(
"If checked, an invulnerable friendly AEW&C aircraft that begins the "
"mission on station will be be spawned. This behavior will be removed in a "
"future release."
),
)
disable_legacy_tanker: bool = boolean_option(
"Spawn invulnerable, always-available tanker aircraft (deprecated)",
page=CAMPAIGN_MANAGEMENT_PAGE,
section=GENERAL_SECTION,
default=True,
invert=True,
detail=(
"If checked, an invulnerable friendly tanker aircraft that begins the "
"mission on station will be be spawned. This behavior will be removed in a "
"future release."
),
)
# Pilots and Squadrons
ai_pilot_levelling: bool = boolean_option(
"Allow AI pilot leveling",
@@ -274,77 +297,7 @@ class Settings:
HQ_AUTOMATION_SECTION,
default=True,
)
auto_procurement_balance: int = bounded_int_option(
"AI ground unit procurement budget ratio (%)",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=100,
default=50,
detail=(
"Ratio (larger number -> more budget for ground units) "
"that indicates how the AI procurement planner should "
"spend its budget."
),
)
frontline_reserves_factor: int = bounded_int_option(
"AI ground unit front-line reserves factor (%)",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=1000,
default=130,
detail=(
"Factor to be multiplied with the control point's unit count limit "
"to calculate the procurement target for reserve troops at front-lines."
),
)
reserves_procurement_target: int = bounded_int_option(
"AI ground unit reserves procurement target",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=1000,
default=10,
detail=(
"The number of units that will be bought as reserves for applicable control points"
),
)
# Flight Planner Automation
#: The weight used for 2-ships.
fpa_2ship_weight: int = bounded_int_option(
"2-ship weight factor (WF2)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=50,
min=0,
max=100,
detail=(
"Used as a distribution to randomize 2/3/4-ships for BARCAP, CAS, OCA & ANTI-SHIP flights. "
"The weight W_i is calculated according to the following formula: &#10;&#13;"
"W_i = WF_i / (WF2 + WF3 + WF4)"
),
)
#: The weight used for 3-ships.
fpa_3ship_weight: int = bounded_int_option(
"3-ship weight factor (WF3)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=35,
min=0,
max=100,
detail="See 2-ship weight factor (WF2)",
)
fpa_4ship_weight: int = bounded_int_option(
"4-ship weight factor (WF4)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=15,
min=0,
max=100,
detail="See 2-ship weight factor (WF2)",
)
reserves_procurement_target: int = 10
# Mission Generator
# Gameplay
@@ -414,7 +367,7 @@ class Settings:
"Player flights ignore TOT and spawn immediately",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=True,
default=False,
detail=(
"Does not adjust package waypoint times. Should not be used if players "
"have runway or in-air starts."
@@ -426,16 +379,6 @@ class Settings:
"option only allows the player to wait on the ground.</strong>"
),
)
atflir_autoswap: bool = boolean_option(
"Auto-swap ATFLIR to LITENING",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=True,
detail=(
"Automatically swaps ATFLIR to LITENING pod for newly generated land-based F-18 flights "
"without having to change the payload. <u>Takes effect after current turn!</u>"
),
)
default_start_type: StartType = choices_option(
"Default start type for AI aircraft",
page=MISSION_GENERATOR_PAGE,
@@ -457,14 +400,6 @@ class Settings:
min=30,
max=150,
)
desired_tanker_on_station_time: timedelta = minutes_option(
"Desired tanker on-station time",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=timedelta(minutes=60),
min=30,
max=150,
)
# Mission specific
max_frontline_length: int = bounded_int_option(
"Maximum frontline length (km)",
@@ -474,67 +409,6 @@ class Settings:
min=1,
max=100,
)
opfor_autoplanner_aggressiveness: int = bounded_int_option(
"OPFOR autoplanner aggressiveness (%)",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=20,
min=0,
max=100,
detail=(
"Chance (larger number -> higher chance) that the OPFOR AI "
"autoplanner will take risks and plan flights against targets "
"within threatened airspace."
),
)
game_masters_count: int = bounded_int_option(
"Number of game masters",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=1,
min=0,
max=10,
detail=(
"The number of game master slots to generate for each side. "
"Game masters can see, control & direct all units in the mission."
),
)
tactical_commander_count: int = bounded_int_option(
"Number of tactical commands",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=3,
min=0,
max=10,
detail=(
"The number of tactical commander slots to generate for each side. "
"Tactical commanders can control & direct friendly units."
),
)
jtac_count: int = bounded_int_option(
"Number of JTAC controllers",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=3,
min=0,
max=10,
detail=(
"The number of JTAC controller slots to generate for each side. "
"JTAC operators can only control friendly units."
),
)
observer_count: int = bounded_int_option(
"Number of observers",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=0,
min=0,
max=10,
detail=(
"The number of observers slots to generate for each side. "
'Use this to allow spectators when disabling "Allow external views".'
),
)
# Performance
perf_smoke_gen: bool = boolean_option(
@@ -649,7 +523,6 @@ class Settings:
show_red_ato: bool = False
enable_frontline_cheats: bool = False
enable_base_capture_cheat: bool = False
enable_transfer_cheat: bool = False
# LUA Plugins system
plugins: Dict[str, bool] = field(default_factory=dict)
@@ -660,17 +533,17 @@ class Settings:
def plugin_settings_key(identifier: str) -> str:
return f"plugins.{identifier}"
def initialize_plugin_option(self, identifier: str, default_value: Any) -> None:
def initialize_plugin_option(self, identifier: str, default_value: bool) -> None:
try:
self.plugin_option(identifier)
except KeyError:
self.set_plugin_option(identifier, default_value)
def plugin_option(self, identifier: str) -> Any:
def plugin_option(self, identifier: str) -> bool:
return self.plugins[self.plugin_settings_key(identifier)]
def set_plugin_option(self, identifier: str, value: Any) -> None:
self.plugins[self.plugin_settings_key(identifier)] = value
def set_plugin_option(self, identifier: str, enabled: bool) -> None:
self.plugins[self.plugin_settings_key(identifier)] = enabled
def __setstate__(self, state: dict[str, Any]) -> None:
# __setstate__ is called with the dict of the object being unpickled. We

View File

@@ -235,6 +235,7 @@ class MissionResultsProcessor:
delta = DEFEAT_INFLUENCE
status_msg = f"Enemy casualties outnumber allied casualties along the {cp.name}-{enemy_cp.name} frontline. Allied forces claim a victory."
elif ally_casualties > enemy_casualties:
if (
ally_units_alive > 2 * enemy_units_alive
and player_aggresive

View File

@@ -22,7 +22,7 @@ class SquadronDefLoader:
def squadron_directories() -> Iterator[Path]:
from game import persistency
yield Path(persistency.base_path()) / "Retribution/Squadrons"
yield Path(persistency.base_path()) / "Liberation/Squadrons"
yield Path("resources/squadrons")
def load(self) -> dict[AircraftType, list[SquadronDef]]:

View File

@@ -22,7 +22,6 @@ from typing import (
TYPE_CHECKING,
Tuple,
Type,
Union,
)
from uuid import UUID
@@ -41,7 +40,6 @@ from dcs.ships import (
Hms_invincible,
)
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.triggers import TriggerZone
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import ShipType
@@ -63,7 +61,6 @@ from game.theater.presetlocation import PresetLocation
from game.utils import Distance, Heading, meters
from .base import Base
from .frontline import FrontLine
from .interfaces.CTLD import CTLD
from .missiontarget import MissionTarget
from .theatergroundobject import (
GenericCarrierGroundObject,
@@ -76,10 +73,6 @@ from ..data.units import UnitClass
from ..db import Database
from ..dcs.aircrafttype import AircraftType
from ..dcs.groundunittype import GroundUnitType
from ..radio.ICLSContainer import ICLSContainer
from ..radio.Link4Container import Link4Container
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.TacanContainer import TacanContainer
from ..utils import nautical_miles
from ..weather import Conditions
@@ -312,7 +305,7 @@ class ControlPointStatus(IntEnum):
Destroyed = auto()
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
StartingPosition = ShipGroup | StaticGroup | Airport | Point
class ControlPoint(MissionTarget, SidcDescribable, ABC):
@@ -877,11 +870,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
) -> RunwayData:
...
def stub_runway_data(self) -> RunwayData:
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def airdrome_id_for_landing(self) -> Optional[int]:
return None
@@ -1054,13 +1042,9 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
...
class Airfield(ControlPoint, CTLD):
class Airfield(ControlPoint):
def __init__(
self,
airport: Airport,
theater: ConflictTheater,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
self, airport: Airport, theater: ConflictTheater, starts_blue: bool
) -> None:
super().__init__(
airport.name,
@@ -1072,7 +1056,6 @@ class Airfield(ControlPoint, CTLD):
)
self.airport = airport
self._runway_status = RunwayStatus()
self.ctld_zones = ctld_zones
@property
def dcs_airport(self) -> Airport:
@@ -1145,14 +1128,6 @@ class Airfield(ControlPoint, CTLD):
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
if not self.airport.runways:
# Some airfields are heliports and don't have any runways. This isn't really
# the best fix, since we should still try to generate partial data for TACAN
# beacons, but it'll do for a bug fix, and the proper fix probably involves
# making heliports their own CP type.
# https://github.com/dcs-liberation/dcs_liberation/issues/2710
return self.stub_runway_data()
assigner = RunwayAssigner(conditions)
return assigner.get_preferred_runway(theater, self.airport)
@@ -1186,9 +1161,7 @@ class Airfield(ControlPoint, CTLD):
return ControlPointStatus.Functional
class NavalControlPoint(
ControlPoint, ABC, Link4Container, TacanContainer, ICLSContainer
):
class NavalControlPoint(ControlPoint, ABC):
@property
def is_fleet(self) -> bool:
return True
@@ -1246,7 +1219,7 @@ class NavalControlPoint(
fallback = RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
return dynamic_runways.get(self.full_name, fallback)
return dynamic_runways.get(self.name, fallback)
@property
def runway_status(self) -> RunwayStatus:
@@ -1291,7 +1264,7 @@ class Carrier(NavalControlPoint):
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato.flighttype import FlightType
from game.ato import FlightType
yield from super().mission_types(for_player)
if self.is_friendly(for_player):
@@ -1398,7 +1371,9 @@ class OffMapSpawn(ControlPoint):
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return self.stub_runway_data()
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def runway_status(self) -> RunwayStatus:
@@ -1417,20 +1392,14 @@ class OffMapSpawn(ControlPoint):
return ControlPointStatus.Functional
class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
class Fob(ControlPoint):
def __init__(
self,
name: str,
at: Point,
theater: ConflictTheater,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
) -> None:
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
):
super().__init__(
name, at, at, theater, starts_blue, cptype=ControlPointType.FOB
)
self.name = name
self.ctld_zones = ctld_zones
@property
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
@@ -1446,7 +1415,9 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: FOBs have no runways.")
return self.stub_runway_data()
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def runway_status(self) -> RunwayStatus:

View File

@@ -12,7 +12,7 @@ from ..utils import Heading, pairwise
if TYPE_CHECKING:
from game.ato import FlightType
from .controlpoint import ControlPoint, Coalition
from .controlpoint import ControlPoint
FRONTLINE_MIN_CP_DISTANCE = 5000
@@ -121,10 +121,6 @@ class FrontLine(MissionTarget):
"""Returns a tuple of the two control points."""
return self.blue_cp, self.red_cp
@property
def coalition(self) -> Coalition:
return self.blue_cp.coalition
@property
def route_length(self) -> float:
"""The total distance of all segments"""

View File

@@ -1,7 +0,0 @@
from typing import Optional, List, Tuple
from dcs import Point
class CTLD:
ctld_zones: Optional[List[Tuple[Point, float]]] = None

View File

@@ -6,7 +6,7 @@ from dcs.mapping import Point
if TYPE_CHECKING:
from game.ato.flighttype import FlightType
from game.theater import TheaterUnit, Coalition
from game.theater import TheaterUnit
class MissionTarget:
@@ -47,7 +47,3 @@ class MissionTarget:
@property
def strike_targets(self) -> list[TheaterUnit]:
return []
@property
def coalition(self) -> Coalition:
raise NotImplementedError

View File

@@ -4,7 +4,7 @@ import logging
import random
from dataclasses import dataclass
from datetime import datetime, time
from typing import List, Optional
from typing import List
import dcs.statics
@@ -30,7 +30,6 @@ from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
from ..armedforces.armedforces import ArmedForces
from ..armedforces.forcegroup import ForceGroup
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
from ..campaignloader.campaigngroundconfig import TgoConfig
from ..data.groups import GroupTask
from ..profiling import logged_duration
from ..settings import Settings
@@ -48,17 +47,15 @@ class GeneratorSettings:
no_lha: bool
no_player_navy: bool
no_enemy_navy: bool
tgo_config: TgoConfig
@dataclass
class ModSettings:
a4_skyhawk: bool = False
a6a_intruder: bool = False
f4bc_phantom: bool = False
f4b_phantom: bool = False
f15d_baz: bool = False
f_16_idf: bool = False
fa_18efg: bool = False
f22_raptor: bool = False
f84g_thunderjet: bool = False
f100_supersabre: bool = False
@@ -72,7 +69,6 @@ class ModSettings:
frenchpack: bool = False
high_digit_sams: bool = False
ov10a_bronco: bool = False
swedishmilitaryassetspack: bool = False
class GameGenerator:
@@ -299,38 +295,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
self.generate_missile_sites()
self.generate_coastal_sites()
def get_unit_group_for_task(
self, position: PresetLocation, task: GroupTask
) -> Optional[ForceGroup]:
tgo_config = self.generator_settings.tgo_config
fg = tgo_config[position.original_name]
valid_fg = (
fg
and task in fg.tasks
and all([u in self.faction.accessible_units for u in fg.units])
)
if valid_fg:
assert fg
for layout in fg.layouts:
for lg in layout.groups:
for ug in lg.unit_groups:
if not fg.has_unit_for_layout_group(ug) and ug.fill:
for g in self.faction.ground_units:
if g.unit_class in ug.unit_classes:
fg.units.append(g)
unit_group: Optional[ForceGroup] = fg
self.armed_forces.add_or_update_force_group(fg)
else:
if fg and not valid_fg:
logging.warning(
f"Override in ground_forces failed for {fg} at {position.original_name}"
)
unit_group = self.armed_forces.random_group_for_task(task)
return unit_group
def generate_armor_groups(self) -> None:
for position in self.control_point.preset_locations.armor_groups:
unit_group = self.get_unit_group_for_task(position, GroupTask.BASE_DEFENSE)
unit_group = self.armed_forces.random_group_for_task(GroupTask.BASE_DEFENSE)
if not unit_group:
logging.error(f"{self.faction_name} has no ForceGroup for Armor")
return
@@ -384,7 +351,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate_aa_at(self, location: PresetLocation, tasks: list[GroupTask]) -> None:
for task in tasks:
unit_group = self.get_unit_group_for_task(location, task)
unit_group = self.armed_forces.random_group_for_task(task)
if unit_group:
# Only take next (smaller) aa_range when no template available for the
# most requested range. Otherwise break the loop and continue

View File

@@ -27,7 +27,7 @@ if TYPE_CHECKING:
from game.ato.flighttype import FlightType
from game.threatzones import ThreatPoly
from .theatergroup import TheaterUnit, TheaterGroup
from .controlpoint import ControlPoint, Coalition
from .controlpoint import ControlPoint
NAME_BY_CATEGORY = {
@@ -275,10 +275,6 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
def is_iads(self) -> bool:
return False
@property
def coalition(self) -> Coalition:
return self.control_point.coalition
class BuildingGroundObject(TheaterGroundObject):
def __init__(

View File

@@ -195,8 +195,7 @@ class TheaterGroup:
def max_threat_range(self, radar_only: bool = False) -> Distance:
"""Calculate the maximum threat range of the TheaterGroup.
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter.
"""
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter."""
max_non_radar = meters(0)
max_telar_range = meters(0)
max_tel_range = meters(0)

View File

@@ -90,10 +90,6 @@ class TheaterLoader:
def landmap_path(self) -> Path:
return self.descriptor_path.with_name("landmap.p")
@property
def icon_path(self) -> Path:
return self.descriptor_path.with_name("icon.gif")
@property
def menu_thumbnail_dcs_relative_path(self) -> Path:
with self.descriptor_path.open() as descriptor_file:

View File

@@ -59,7 +59,6 @@ from game.utils import meters, nautical_miles
if TYPE_CHECKING:
from game import Game
from game.squadrons import Squadron
from game.theater import Coalition
class Transport:
@@ -415,8 +414,7 @@ class MultiGroupTransport(MissionTarget, Transport):
units: dict[GroundUnitType, int] = defaultdict(int)
for transfer in self.transfers:
for unit_type, count in transfer.units.items():
if count > 0:
units[unit_type] += count
units[unit_type] += count
return units
def iter_units(self) -> Iterator[GroundUnitType]:
@@ -434,10 +432,6 @@ class MultiGroupTransport(MissionTarget, Transport):
def description(self) -> str:
raise NotImplementedError
@property
def coalition(self) -> Coalition:
return self.origin.coalition
class Convoy(MultiGroupTransport):
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
@@ -621,20 +615,13 @@ class PendingTransfers:
raise ValueError
units = {}
to_delete = []
for unit_type, remaining in transfer.units.items():
take = min(remaining, size)
if not take:
if not remaining:
to_delete.append(unit_type)
continue
size -= take
transfer.units[unit_type] -= take
if not transfer.units[unit_type]:
to_delete.append(unit_type)
units[unit_type] = take
for td in to_delete:
del transfer.units[td]
if not size:
break
new_transfer = TransferOrder(transfer.origin, transfer.destination, units)
self.pending_transfers.append(new_transfer)
return new_transfer
@@ -730,6 +717,7 @@ class PendingTransfers:
self.order_airlift_assets_at(control_point)
def desired_airlift_capacity(self, control_point: ControlPoint) -> int:
if control_point.has_factory:
is_major_hub = control_point.total_aircraft_parking > 0
# Check if there is a CP which is only reachable via Airlift

View File

@@ -2,8 +2,8 @@ from pathlib import Path
MAJOR_VERSION = 1
MINOR_VERSION = 1
MICRO_VERSION = 1
MINOR_VERSION = 0
MICRO_VERSION = 0
def _optional_build_id_component(path: Path) -> str | None:
@@ -170,13 +170,6 @@ VERSION = _build_version_string()
#:
#: Version 10.5
#: * Support for scenery objectives defined by quad zones.
#: * Campaign designers can now define more settings:
#: `max_frontline_length: 25` (in km)
#: `culling_exclusion_radius: 35` (in km)
#:
#: Version 10.6
#: * Designated CTLD zones for ControlPoints (Airbases & FOBs/FARPs)
#: * 'ground_forces' in yaml file to specify preset groups for TGOs,
#: given the group is available for the faction and the task matches
CAMPAIGN_FORMAT_VERSION = (10, 6)
#: * Campaign designers can now define `max_frontline_length` (in km) setting:
#: `max_frontline_length: 25`
CAMPAIGN_FORMAT_VERSION = (10, 5)

View File

@@ -299,6 +299,7 @@ class Weather:
def interpolate_solar_activity(
time_of_day: TimeOfDay, high: float, low: float
) -> float:
scale: float = 0
match time_of_day:

BIN
installer/ISCC.exe Normal file

Binary file not shown.

View File

@@ -0,0 +1,51 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "DCS Liberation"
#define MyAppVersion "{{version}}"
#define MyAppPublisher "Khopa"
#define MyAppURL "https://github.com/dcs-liberation/dcs_liberation/wiki"
#define MyAppExeName "liberation_main.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{6753B352-D281-42CB-9AFA-5E93EB90AA5A}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=..\dist
OutputBaseFilename=dcs_liberation
SetupIconFile=..\resources\icon.ico
UninstallDisplayIcon={app}\liberation_main.exe
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "..\dist\dcs_liberation\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

View File

@@ -14,7 +14,6 @@ from .su30 import *
from .su57 import *
from .uh60l import *
from .ov10a import *
from .swedishmilitaryassetspack import *
def load_mods() -> None:

View File

@@ -12,6 +12,7 @@ from game.modsupport import planemod
class VSN_A6A(PlaneType):
id = "VSN_A6A"
flyable = True
group_size_max = 1
height = 4.57
width = 10.15
length = 17.98

View File

@@ -613,10 +613,7 @@ class VSN_F104S(PlaneType):
AIM_9M_Sidewinder_IR_AAM = (3, Weapons.AIM_9M_Sidewinder_IR_AAM)
AIM_9P_Sidewinder_IR_AAM = (3, Weapons.AIM_9P_Sidewinder_IR_AAM)
AIM_9P5_Sidewinder_IR_AAM = (3, Weapons.AIM_9P5_Sidewinder_IR_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
class Pylon4:
@@ -662,10 +659,7 @@ class VSN_F104S(PlaneType):
AIM_9M_Sidewinder_IR_AAM = (9, Weapons.AIM_9M_Sidewinder_IR_AAM)
AIM_9P_Sidewinder_IR_AAM = (9, Weapons.AIM_9P_Sidewinder_IR_AAM)
AIM_9P5_Sidewinder_IR_AAM = (9, Weapons.AIM_9P5_Sidewinder_IR_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
class Pylon10:

View File

@@ -106,10 +106,7 @@ class F_15D(PlaneType):
4,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
I_Derby_ER = (4, WeaponsF15D.I_Derby_ER)
class Pylon5:
@@ -124,10 +121,7 @@ class F_15D(PlaneType):
5,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
I_Derby_ER = (5, WeaponsF15D.I_Derby_ER)
ELL___8222SB = (5, WeaponsF15D.ELL___8222SB)
@@ -147,10 +141,7 @@ class F_15D(PlaneType):
7,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
I_Derby_ER = (7, WeaponsF15D.I_Derby_ER)
class Pylon8:
@@ -165,10 +156,7 @@ class F_15D(PlaneType):
8,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
I_Derby_ER = (8, WeaponsF15D.I_Derby_ER)
class Pylon9:

File diff suppressed because it is too large Load Diff

View File

@@ -7,17 +7,12 @@ from game.modsupport import planemod
from pydcs_extensions.weapon_injector import inject_weapons
class WeaponsF4BC:
class WeaponsF4B:
F4B_SUU_23_Gun_Pod = {
"clsid": "{VSN_F4B_Gunpod}",
"name": "F4B SUU-23 Gun Pod",
"weight": 112.35,
}
F4B_Gunpod_w_SAPHEI_T = {
"clsid": "{VSN_F4B_Equalizer}",
"name": "F4B Gunpod w/SAPHEI-T",
"weight": 244.46,
}
VSN_F4EC_PTB = {
"clsid": "VSN_F4EC_PTB",
"name": "Fuel tank 600 Gal",
@@ -33,11 +28,6 @@ class WeaponsF4BC:
"name": "Fuel tank 370 Gal",
"weight": 1240,
}
VSN_F4B_C2_PTB = {
"clsid": "VSN_F4B_C2_PTB",
"name": "Fuel tank Center 370 Gal",
"weight": 1240,
}
LAU_105_2_AIM_9J = {
"clsid": "{VSN_F4B_LAU105_AIM9J}",
"name": "LAU-105 2*AIM-9J",
@@ -50,7 +40,7 @@ class WeaponsF4BC:
}
inject_weapons(WeaponsF4BC)
inject_weapons(WeaponsF4B)
@planemod
@@ -108,6 +98,10 @@ class VSN_F4B(PlaneType):
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
2,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
Smokewinder___red = (2, Weapons.Smokewinder___red)
Smokewinder___green = (2, Weapons.Smokewinder___green)
Smokewinder___blue = (2, Weapons.Smokewinder___blue)
@@ -129,8 +123,7 @@ class VSN_F4B(PlaneType):
2,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (2, Weapons.BIN_200)
VSN_F4EL_PTB = (2, WeaponsF4BC.VSN_F4EL_PTB)
VSN_F4EL_PTB = (2, WeaponsF4B.VSN_F4EL_PTB)
class Pylon3:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -147,6 +140,18 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (3, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
3,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
@@ -163,6 +168,18 @@ class VSN_F4B(PlaneType):
3,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM = (
3,
Weapons.LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9L = (3, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
3,
@@ -173,13 +190,11 @@ class VSN_F4B(PlaneType):
3,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (3, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4BC.LAU_105_2_AIM_9JULI)
LAU_105_2_AIM_9J = (3, WeaponsF4B.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4B.LAU_105_2_AIM_9JULI)
AIM_120B_AMRAAM___Active_Rdr_AAM = (3, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
3,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
@@ -193,21 +208,18 @@ class VSN_F4B(PlaneType):
3,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (3, Weapons.BIN_200)
class Pylon4:
AIM_7M_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (4, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
class Pylon5:
AIM_7M_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (5, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
class Pylon6:
Smokewinder___red = (6, Weapons.Smokewinder___red)
@@ -228,23 +240,20 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
ALQ_131___ECM_Pod = (6, Weapons.ALQ_131___ECM_Pod)
F4B_Gunpod_w_SAPHEI_T = (6, WeaponsF4BC.F4B_Gunpod_w_SAPHEI_T)
VSN_F4EC_PTB = (6, WeaponsF4BC.VSN_F4EC_PTB)
VSN_F4B_C2_PTB = (6, WeaponsF4BC.VSN_F4B_C2_PTB)
F4B_SUU_23_Gun_Pod = (6, WeaponsF4B.F4B_SUU_23_Gun_Pod)
VSN_F4EC_PTB = (6, WeaponsF4B.VSN_F4EC_PTB)
class Pylon7:
AIM_7M_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (7, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
class Pylon8:
AIM_7M_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (8, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
class Pylon9:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -261,6 +270,18 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (9, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
9,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
@@ -277,6 +298,18 @@ class VSN_F4B(PlaneType):
9,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM = (
9,
Weapons.LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9L = (9, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
9,
@@ -287,13 +320,11 @@ class VSN_F4B(PlaneType):
9,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (9, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4BC.LAU_105_2_AIM_9JULI)
LAU_105_2_AIM_9J = (9, WeaponsF4B.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4B.LAU_105_2_AIM_9JULI)
AIM_120B_AMRAAM___Active_Rdr_AAM = (9, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
9,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
@@ -307,7 +338,6 @@ class VSN_F4B(PlaneType):
9,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (9, Weapons.BIN_200)
class Pylon10:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -339,331 +369,9 @@ class VSN_F4B(PlaneType):
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
Smokewinder___red = (10, Weapons.Smokewinder___red)
Smokewinder___green = (10, Weapons.Smokewinder___green)
Smokewinder___blue = (10, Weapons.Smokewinder___blue)
Smokewinder___white = (10, Weapons.Smokewinder___white)
Smokewinder___yellow = (10, Weapons.Smokewinder___yellow)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
10,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
10,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb = (
10,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (10, Weapons.BIN_200)
VSN_F4ER_PTB = (10, WeaponsF4BC.VSN_F4ER_PTB)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
tasks = [
task.CAP,
task.Escort,
task.FighterSweep,
task.Intercept,
task.Reconnaissance,
task.GroundAttack,
task.CAS,
task.AFAC,
task.RunwayAttack,
]
task_default = task.CAP
@planemod
class VSN_F4C(PlaneType):
id = "VSN_F4C"
flyable = True
height = 5.02
width = 11.71
length = 19.2
fuel_max = 6416
max_speed = 2545.2
chaff = 48
flare = 48
charge_total = 96
chaff_charge_size = 1
flare_charge_size = 1
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
radio_frequency = 127.5
livery_name = "VSN_F4C" # from type
Liveries = Liveries()[livery_name]
class Pylon1:
Smoke_Generator___red_ = (1, Weapons.Smoke_Generator___red_)
Smoke_Generator___green_ = (1, Weapons.Smoke_Generator___green_)
Smoke_Generator___blue_ = (1, Weapons.Smoke_Generator___blue_)
Smoke_Generator___white_ = (1, Weapons.Smoke_Generator___white_)
Smoke_Generator___yellow_ = (1, Weapons.Smoke_Generator___yellow_)
Smoke_Generator___orange_ = (1, Weapons.Smoke_Generator___orange_)
class Pylon2:
GBU_10___2000lb_Laser_Guided_Bomb = (
2,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (2, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
2,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
Mk_84___2000lb_GP_Bomb_LD = (2, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
2,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
Smokewinder___red = (2, Weapons.Smokewinder___red)
Smokewinder___green = (2, Weapons.Smokewinder___green)
Smokewinder___blue = (2, Weapons.Smokewinder___blue)
Smokewinder___white = (2, Weapons.Smokewinder___white)
Smokewinder___yellow = (2, Weapons.Smokewinder___yellow)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
2,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
2,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb = (
2,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (2, Weapons.BIN_200)
VSN_F4EL_PTB = (2, WeaponsF4BC.VSN_F4EL_PTB)
class Pylon3:
GBU_10___2000lb_Laser_Guided_Bomb = (
3,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (3, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
3,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
3,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (3, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
3,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
3,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_105_2_AIM_9L = (3, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
3,
Weapons.LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9P5 = (3, Weapons.LAU_105_2_AIM_9P5)
LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM = (
3,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (3, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
3,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
)
Mk_82___500lb_GP_Bomb_LD = (3, Weapons.Mk_82___500lb_GP_Bomb_LD)
TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD = (
3,
Weapons.TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD,
)
BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD = (
3,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (3, Weapons.BIN_200)
class Pylon4:
AIM_7F_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon5:
AIM_7F_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon6:
Smokewinder___red = (6, Weapons.Smokewinder___red)
Smokewinder___green = (6, Weapons.Smokewinder___green)
Smokewinder___blue = (6, Weapons.Smokewinder___blue)
Smokewinder___white = (6, Weapons.Smokewinder___white)
Smokewinder___yellow = (6, Weapons.Smokewinder___yellow)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
6,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
6,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
6,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
ALQ_131___ECM_Pod = (6, Weapons.ALQ_131___ECM_Pod)
F4B_Gunpod_w_SAPHEI_T = (6, WeaponsF4BC.F4B_Gunpod_w_SAPHEI_T)
VSN_F4EC_PTB = (6, WeaponsF4BC.VSN_F4EC_PTB)
VSN_F4ER_PTB = (6, WeaponsF4BC.VSN_F4ER_PTB)
class Pylon7:
AIM_7F_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon8:
AIM_7F_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon9:
GBU_10___2000lb_Laser_Guided_Bomb = (
9,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (9, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
9,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
9,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (9, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
9,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
9,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_105_2_AIM_9L = (9, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
9,
Weapons.LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9P5 = (9, Weapons.LAU_105_2_AIM_9P5)
LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM = (
9,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (9, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
9,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
)
Mk_82___500lb_GP_Bomb_LD = (9, Weapons.Mk_82___500lb_GP_Bomb_LD)
TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD = (
9,
Weapons.TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD,
)
BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD = (
9,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (9, Weapons.BIN_200)
class Pylon10:
GBU_10___2000lb_Laser_Guided_Bomb = (
10,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (
10,
Weapons.GBU_12___500lb_Laser_Guided_Bomb,
)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
10,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
Mk_84___2000lb_GP_Bomb_LD = (10, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
10,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
Smokewinder___red = (10, Weapons.Smokewinder___red)
Smokewinder___green = (10, Weapons.Smokewinder___green)
@@ -686,10 +394,13 @@ class VSN_F4C(PlaneType):
10,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (10, Weapons.BIN_200)
VSN_F4ER_PTB = (10, WeaponsF4BC.VSN_F4ER_PTB)
VSN_F4ER_PTB = (10, WeaponsF4B.VSN_F4ER_PTB)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
class Pylon11:
L005_Sorbtsiya_ECM_pod__left_ = (11, Weapons.L005_Sorbtsiya_ECM_pod__left_)
L_081_Fantasmagoria_ELINT_pod = (11, Weapons.L_081_Fantasmagoria_ELINT_pod)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
tasks = [
task.CAP,

File diff suppressed because it is too large Load Diff

View File

@@ -136,11 +136,6 @@ class JAS39GripenWeapons:
"name": "M70B HE Unguided rocket",
"weight": 372.2,
}
_4_x_M_71_120_kg_GP_Bomb_High_drag = {
"clsid": "{JAS39_M71HD}",
"name": "4 x M/71 120 kg GP Bomb High-drag",
"weight": 605,
}
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = {
"clsid": "{JAS39_M71LD}",
"name": "4 x M/71 120 kg GP Bomb Low-drag ",
@@ -256,26 +251,6 @@ class JAS39GripenWeapons:
"name": "KEPD 350 Long Range Anti-Radiation Cruise-missile",
"weight": 1400,
}
RBS_15_Mk4_AShM = {
"clsid": "{JAS39_RBS15}",
"name": "RBS-15 Mk4 AShM",
"weight": 650,
}
RBS_15_Mk4_AShM_for_AI = {
"clsid": "{JAS39_RBS15AI}",
"name": "RBS-15 Mk4 AShM for AI",
"weight": 650,
}
AGM_65H_TV_Guided_Missile = {
"clsid": "{JAS39_AGM_65H}",
"name": "AGM-65H TV Guided Missile",
"weight": 260,
}
AGM_65K_TV_Guided_Missile = {
"clsid": "{JAS39_AGM_65K}",
"name": "AGM-65K TV Guided Missile",
"weight": 349,
}
inject_weapons(JAS39GripenWeapons)
@@ -353,10 +328,6 @@ class JAS39Gripen(PlaneType):
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -397,10 +368,6 @@ class JAS39Gripen(PlaneType):
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
@@ -448,10 +415,6 @@ class JAS39Gripen(PlaneType):
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
@@ -491,267 +454,6 @@ class JAS39Gripen(PlaneType):
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
class Pylon8:
IRIS_T_IR_AAM = (8, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (8, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (8, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (8, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (8, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
Smokewinder___red = (8, Weapons.Smokewinder___red)
Smokewinder___green = (8, Weapons.Smokewinder___green)
Smokewinder___blue = (8, Weapons.Smokewinder___blue)
Smokewinder___white = (8, Weapons.Smokewinder___white)
Smokewinder___yellow = (8, Weapons.Smokewinder___yellow)
Smokewinder___orange = (8, Weapons.Smokewinder___orange)
class Pylon9:
Litening_III_Targeting_Pod_FLIR = (
9,
JAS39GripenWeapons.Litening_III_Targeting_Pod_FLIR,
)
class Pylon10:
Integrated_ELINT = (10, JAS39GripenWeapons.Integrated_ELINT)
class Pylon11:
EWS_39_Integrated_ECM = (11, JAS39GripenWeapons.EWS_39_Integrated_ECM)
pylons: Set[int] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
tasks = [
task.Intercept,
task.CAP,
task.Reconnaissance,
task.Escort,
task.FighterSweep,
]
task_default = task.FighterSweep
@planemod
class JAS39Gripen_BVR(PlaneType):
id = "JAS39Gripen_BVR"
flyable = True
height = 4.5
width = 8.4
length = 14.1
fuel_max = 2550
max_speed = 2649.996
chaff = 80
flare = 40
charge_total = 120
chaff_charge_size = 1
flare_charge_size = 1
eplrs = True
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
radio_frequency = 127.5
livery_name = "JAS39GRIPEN_BVR" # from type
Liveries = Liveries()[livery_name]
class Pylon1:
IRIS_T_IR_AAM = (1, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (1, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (1, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (1, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (1, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
Smokewinder___red = (1, Weapons.Smokewinder___red)
Smokewinder___green = (1, Weapons.Smokewinder___green)
Smokewinder___blue = (1, Weapons.Smokewinder___blue)
Smokewinder___white = (1, Weapons.Smokewinder___white)
Smokewinder___yellow = (1, Weapons.Smokewinder___yellow)
Smokewinder___orange = (1, Weapons.Smokewinder___orange)
class Pylon2:
IRIS_T_IR_AAM = (2, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (2, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (2, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (2, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (2, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (2, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (2, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
class Pylon3:
AIM_9L_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
IRIS_T_IR_AAM = (3, JAS39GripenWeapons.IRIS_T_IR_AAM)
A_Darter_IR_AAM = (3, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (3, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (3, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
Mk_84_2000_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_84_2000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (3, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_2_x_Mk_83_1000_lb_GP_Bomb = (3, JAS39GripenWeapons._2_x_Mk_83_1000_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon4:
Drop_tank_1100_litre = (4, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon5:
Litening_III_Targeting_Pod = (5, JAS39GripenWeapons.Litening_III_Targeting_Pod)
class Pylon6:
AIM_9L_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
IRIS_T_IR_AAM = (6, JAS39GripenWeapons.IRIS_T_IR_AAM)
A_Darter_IR_AAM = (6, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (6, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (6, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
Mk_84_2000_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_84_2000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (6, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_2_x_Mk_83_1000_lb_GP_Bomb = (6, JAS39GripenWeapons._2_x_Mk_83_1000_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon7:
IRIS_T_IR_AAM = (7, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (7, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (7, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (7, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (7, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (7, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (7, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -840,8 +542,14 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (2, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (2, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
RBS_15_Mk4_AShM = (2, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (2, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
2,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
2,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
MAR_1_High_Speed_Anti_Radiation_Missile = (
2,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -897,18 +605,17 @@ class JAS39Gripen_AG(PlaneType):
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
_3_x_Brimstone_Laser_Guided_Missile = (
2,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
)
AGM_65K_TV_Guided_Missile = (2, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (2, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
2,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (2, Weapons.LAU_117_AGM_65H)
_3_x_SPEAR_3_Anti_Radiation_Missile = (
2,
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
@@ -923,8 +630,11 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (3, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (3, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AGM_65K_TV_Guided_Missile = (3, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (3, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
3,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (3, Weapons.LAU_117_AGM_65H)
_3_x_Brimstone_Laser_Guided_Missile = (
3,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
@@ -934,8 +644,14 @@ class JAS39Gripen_AG(PlaneType):
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
)
_3_x_SPEAR_EW_Decoy = (3, JAS39GripenWeapons._3_x_SPEAR_EW_Decoy)
RBS_15_Mk4_AShM = (3, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (3, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
3,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
3,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
MAR_1_High_Speed_Anti_Radiation_Missile = (
3,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -1013,10 +729,6 @@ class JAS39Gripen_AG(PlaneType):
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -1030,7 +742,6 @@ class JAS39Gripen_AG(PlaneType):
)
class Pylon4:
DIS_LS_6_100_DUAL_L = (4, Weapons.DIS_LS_6_100_DUAL_L)
Drop_tank_1100_litre = (4, JAS39GripenWeapons.Drop_tank_1100_litre)
# ERRR {INV-SMOKE-RED}
@@ -1051,8 +762,11 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (6, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (6, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AGM_65K_TV_Guided_Missile = (6, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (6, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
6,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (6, Weapons.LAU_117_AGM_65H)
_3_x_Brimstone_Laser_Guided_Missile = (
6,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
@@ -1062,8 +776,14 @@ class JAS39Gripen_AG(PlaneType):
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
)
_3_x_SPEAR_EW_Decoy = (6, JAS39GripenWeapons._3_x_SPEAR_EW_Decoy)
RBS_15_Mk4_AShM = (6, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (6, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
6,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
6,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
MAR_1_High_Speed_Anti_Radiation_Missile = (
6,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -1141,10 +861,6 @@ class JAS39Gripen_AG(PlaneType):
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -1165,8 +881,14 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (7, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (7, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
RBS_15_Mk4_AShM = (7, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (7, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
7,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
7,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
MAR_1_High_Speed_Anti_Radiation_Missile = (
7,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -1222,18 +944,17 @@ class JAS39Gripen_AG(PlaneType):
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
_3_x_Brimstone_Laser_Guided_Missile = (
7,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
)
AGM_65K_TV_Guided_Missile = (7, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (7, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
7,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (7, Weapons.LAU_117_AGM_65H)
_3_x_SPEAR_3_Anti_Radiation_Missile = (
7,
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,

View File

@@ -1 +0,0 @@
from .swedishmilitaryassetspack import *

Some files were not shown because too many files have changed in this diff Show More