mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3260260dce | ||
|
|
70c1290993 | ||
|
|
6bae60c51e | ||
|
|
a45adb6b3a | ||
|
|
476aaf5d3e | ||
|
|
58187b6969 | ||
|
|
f3a3d81d96 | ||
|
|
7a40b54153 | ||
|
|
9dd62d3538 | ||
|
|
76e4a6ed83 | ||
|
|
7a9eb06677 | ||
|
|
26f54e7619 | ||
|
|
117b7ae414 | ||
|
|
baeac324d6 | ||
|
|
0db0f003dc | ||
|
|
2d4f341710 | ||
|
|
b8a41dc937 | ||
|
|
2f2bb0de4f | ||
|
|
3b76d7f47e | ||
|
|
10b74e507f | ||
|
|
44b5f5a919 | ||
|
|
17d37494c2 | ||
|
|
f0d81e98a0 | ||
|
|
e3b13f7b4a | ||
|
|
ba2686630a | ||
|
|
e195cfa6a0 | ||
|
|
b9fbd1906f | ||
|
|
1f611bafef | ||
|
|
69096b15ae | ||
|
|
a075e62bad | ||
|
|
db229f25bf |
35
changelog.md
35
changelog.md
@@ -1,16 +1,44 @@
|
|||||||
|
# 2.3.2
|
||||||
|
|
||||||
|
## Features/Improvements
|
||||||
|
* **[Units]** Support for newly added BTR-82A, T-72B3
|
||||||
|
* **[Units]** Added ZSU-57 AAA sites
|
||||||
|
* **[Culling]** BARCAP missions no longer create culling exclusion zones.
|
||||||
|
* **[Flight Planner]** Improved TOT planning. Negative start times no longer occur with TARCAPs and hold times no longer affect planning for flight plans without hold points.
|
||||||
|
* **[Factions]** Added Iraq 1991 faction (thanks again to Hawkmoon!)
|
||||||
|
|
||||||
|
## Fixes:
|
||||||
|
* **[Mission Generator]** Fix mission generation error when there are too many radio frequency to setup for the Mig-21
|
||||||
|
* **[Mission Generator]** Fix ground units not moving forward
|
||||||
|
* **[Mission Generator]** Fixed assigned radio channels overlapping with beacons.
|
||||||
|
* **[Flight Planner]** Fix creation of custom waypoints.
|
||||||
|
* **[Campaigns]** Fixed many cases of SAMs spawning on the runways/taxiways in Syria Full.
|
||||||
|
|
||||||
|
# 2.3.1
|
||||||
|
|
||||||
|
## Features/Improvements
|
||||||
|
* **[UX]** Added a warning message when the player is attempting to buy more planes at an already full airbase.
|
||||||
|
* **[Campaigns]** Migrated Syria full map to new format. (Thanks to Hawkmoon)
|
||||||
|
* **[Faction]** Added NATO desert Storm faction (Thanks to Hawkmoon)
|
||||||
|
|
||||||
|
## Fixes:
|
||||||
|
* **[AI]** CAP flights will engage enemies again.
|
||||||
|
* **[Campaigns]** Fixed a missing path on the Caucasus Full Map campaign
|
||||||
|
|
||||||
# 2.3.0
|
# 2.3.0
|
||||||
|
|
||||||
# Features/Improvements
|
## Features/Improvements
|
||||||
* **[Campaign Map]** Overhauled the campaign model
|
* **[Campaign Map]** Overhauled the campaign model
|
||||||
* **[Campaign Map]** Possible to add FOB as control points
|
* **[Campaign Map]** Possible to add FOB as control points
|
||||||
* **[Campaign Map]** Added off-map spawn locations
|
* **[Campaign Map]** Added off-map spawn locations
|
||||||
* **[Campaign AI]** Overhauled AI recruiting behaviour
|
* **[Campaign AI]** Overhauled AI recruiting behaviour
|
||||||
* **[Campaign AI]** Added AI proucurement for Blue
|
* **[Campaign AI]** Added AI procurement for Blue
|
||||||
* **[Campaign]** New Campaign: "Black Sea"
|
* **[Campaign]** New Campaign: "Black Sea"
|
||||||
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
|
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
|
||||||
* **[Mission Generator]** Infantry squads on frontline can have manpads
|
* **[Mission Generator]** Infantry squads on frontline can have manpads
|
||||||
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
|
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
|
||||||
* **[Mission Generator]** Opfor now obeys parking limits
|
* **[Mission Generator]** Opfor now obeys parking limits
|
||||||
|
* **[Mission Generator]** Support for Anubis C-130 Hercules mod
|
||||||
* **[Flight Planner]** Added fighter sweep missions.
|
* **[Flight Planner]** Added fighter sweep missions.
|
||||||
* **[Flight Planner]** Added BAI missions.
|
* **[Flight Planner]** Added BAI missions.
|
||||||
* **[Flight Planner]** Added anti-ship missions.
|
* **[Flight Planner]** Added anti-ship missions.
|
||||||
@@ -21,6 +49,7 @@
|
|||||||
* **[QOL]** On liberation startup, your latest save game is loaded automatically
|
* **[QOL]** On liberation startup, your latest save game is loaded automatically
|
||||||
* **[Units]** Reduced starting fuel load for C101
|
* **[Units]** Reduced starting fuel load for C101
|
||||||
* **[UI]** Inform the user of the weather
|
* **[UI]** Inform the user of the weather
|
||||||
|
* **[UI]** Added toolbar buttons to change map display settings
|
||||||
* **[Game]** Added new Economy options for adjusting income multipliers and starting budgets.
|
* **[Game]** Added new Economy options for adjusting income multipliers and starting budgets.
|
||||||
|
|
||||||
## Fixes :
|
## Fixes :
|
||||||
@@ -34,7 +63,7 @@
|
|||||||
|
|
||||||
# 2.2.1
|
# 2.2.1
|
||||||
|
|
||||||
# Features/Improvements
|
## Features/Improvements
|
||||||
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
|
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
|
||||||
* **[Factions]** Added map Persian Gulf full by Plob
|
* **[Factions]** Added map Persian Gulf full by Plob
|
||||||
* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately.
|
* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately.
|
||||||
|
|||||||
17
game/db.py
17
game/db.py
@@ -362,12 +362,14 @@ PRICES = {
|
|||||||
|
|
||||||
# armor
|
# armor
|
||||||
Armor.APC_MTLB: 4,
|
Armor.APC_MTLB: 4,
|
||||||
Armor.FDDM_Grad: 5,
|
Armor.FDDM_Grad: 4,
|
||||||
Armor.ARV_BRDM_2: 6,
|
Armor.ARV_BRDM_2: 6,
|
||||||
Armor.ARV_BTR_RD: 8,
|
Armor.ARV_BTR_RD: 6,
|
||||||
Armor.APC_BTR_80: 8,
|
Armor.APC_BTR_80: 8,
|
||||||
|
Armor.APC_BTR_82A: 10,
|
||||||
Armor.MBT_T_55: 18,
|
Armor.MBT_T_55: 18,
|
||||||
Armor.MBT_T_72B: 22,
|
Armor.MBT_T_72B: 20,
|
||||||
|
Armor.MBT_T_72B3: 25,
|
||||||
Armor.MBT_T_80U: 25,
|
Armor.MBT_T_80U: 25,
|
||||||
Armor.MBT_T_90: 30,
|
Armor.MBT_T_90: 30,
|
||||||
Armor.IFV_BMD_1: 8,
|
Armor.IFV_BMD_1: 8,
|
||||||
@@ -481,11 +483,12 @@ PRICES = {
|
|||||||
AirDefence.SAM_Stinger_comm_dsr: 4,
|
AirDefence.SAM_Stinger_comm_dsr: 4,
|
||||||
AirDefence.SAM_Stinger_comm: 4,
|
AirDefence.SAM_Stinger_comm: 4,
|
||||||
AirDefence.SPAAA_ZSU_23_4_Shilka: 10,
|
AirDefence.SPAAA_ZSU_23_4_Shilka: 10,
|
||||||
|
AirDefence.AAA_ZSU_57_2: 12,
|
||||||
AirDefence.AAA_ZU_23_Closed: 6,
|
AirDefence.AAA_ZU_23_Closed: 6,
|
||||||
AirDefence.AAA_ZU_23_Emplacement: 6,
|
AirDefence.AAA_ZU_23_Emplacement: 6,
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375: 8,
|
AirDefence.AAA_ZU_23_on_Ural_375: 7,
|
||||||
AirDefence.AAA_ZU_23_Insurgent_Closed: 6,
|
AirDefence.AAA_ZU_23_Insurgent_Closed: 6,
|
||||||
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 8,
|
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 7,
|
||||||
AirDefence.AAA_ZU_23_Insurgent: 6,
|
AirDefence.AAA_ZU_23_Insurgent: 6,
|
||||||
AirDefence.SAM_SA_18_Igla_MANPADS: 10,
|
AirDefence.SAM_SA_18_Igla_MANPADS: 10,
|
||||||
AirDefence.SAM_SA_18_Igla_comm: 8,
|
AirDefence.SAM_SA_18_Igla_comm: 8,
|
||||||
@@ -704,6 +707,8 @@ UNIT_BY_TASK = {
|
|||||||
Armor.APC_BTR_80,
|
Armor.APC_BTR_80,
|
||||||
Armor.APC_BTR_80,
|
Armor.APC_BTR_80,
|
||||||
Armor.APC_BTR_80,
|
Armor.APC_BTR_80,
|
||||||
|
Armor.APC_BTR_82A,
|
||||||
|
Armor.APC_BTR_82A,
|
||||||
Armor.IFV_BMP_1,
|
Armor.IFV_BMP_1,
|
||||||
Armor.IFV_BMP_1,
|
Armor.IFV_BMP_1,
|
||||||
Armor.IFV_BMP_1,
|
Armor.IFV_BMP_1,
|
||||||
@@ -720,6 +725,8 @@ UNIT_BY_TASK = {
|
|||||||
Armor.MBT_T_55,
|
Armor.MBT_T_55,
|
||||||
Armor.MBT_T_72B,
|
Armor.MBT_T_72B,
|
||||||
Armor.MBT_T_72B,
|
Armor.MBT_T_72B,
|
||||||
|
Armor.MBT_T_72B3,
|
||||||
|
Armor.MBT_T_72B3,
|
||||||
Armor.MBT_T_80U,
|
Armor.MBT_T_80U,
|
||||||
Armor.MBT_T_80U,
|
Armor.MBT_T_80U,
|
||||||
Armor.MBT_T_90,
|
Armor.MBT_T_90,
|
||||||
|
|||||||
15
game/game.py
15
game/game.py
@@ -1,3 +1,4 @@
|
|||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import sys
|
import sys
|
||||||
@@ -19,6 +20,7 @@ from gen.ato import AirTaskingOrder
|
|||||||
from gen.conflictgen import Conflict
|
from gen.conflictgen import Conflict
|
||||||
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
||||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||||
|
from gen.flights.flight import FlightType
|
||||||
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
||||||
from . import persistency
|
from . import persistency
|
||||||
from .debriefing import Debriefing
|
from .debriefing import Debriefing
|
||||||
@@ -391,9 +393,16 @@ class Game:
|
|||||||
if cpoint is not None:
|
if cpoint is not None:
|
||||||
points.append(cpoint)
|
points.append(cpoint)
|
||||||
|
|
||||||
for package in self.blue_ato.packages:
|
packages = itertools.chain(self.blue_ato.packages,
|
||||||
points.append(package.target.position)
|
self.red_ato.packages)
|
||||||
for package in self.red_ato.packages:
|
for package in packages:
|
||||||
|
if package.primary_task is FlightType.BARCAP:
|
||||||
|
# BARCAPs will be planned at most locations on smaller theaters,
|
||||||
|
# rendering culling fairly useless. BARCAP packages don't really
|
||||||
|
# need the ground detail since they're defensive. SAMs nearby
|
||||||
|
# are only interesting if there are enemies in the area, and if
|
||||||
|
# there are they won't be culled because of the enemy's mission.
|
||||||
|
continue
|
||||||
points.append(package.target.position)
|
points.append(package.target.position)
|
||||||
|
|
||||||
# Else 0,0, since we need a default value
|
# Else 0,0, since we need a default value
|
||||||
|
|||||||
@@ -199,10 +199,14 @@ class Operation:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_radio_registries(cls) -> None:
|
def create_radio_registries(cls) -> None:
|
||||||
unique_map_frequencies = set() # type: Set[RadioFrequency]
|
unique_map_frequencies: Set[RadioFrequency] = set()
|
||||||
cls._create_tacan_registry(unique_map_frequencies)
|
cls._create_tacan_registry(unique_map_frequencies)
|
||||||
cls._create_radio_registry(unique_map_frequencies)
|
cls._create_radio_registry(unique_map_frequencies)
|
||||||
|
|
||||||
|
assert cls.radio_registry is not None
|
||||||
|
for frequency in unique_map_frequencies:
|
||||||
|
cls.radio_registry.reserve(frequency)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_channels_to_flights(cls, flights: List[FlightData],
|
def assign_channels_to_flights(cls, flights: List[FlightData],
|
||||||
air_support: AirSupport) -> None:
|
air_support: AirSupport) -> None:
|
||||||
@@ -256,8 +260,8 @@ class Operation:
|
|||||||
unique_map_frequencies.add(data.atc.vhf_fm)
|
unique_map_frequencies.add(data.atc.vhf_fm)
|
||||||
unique_map_frequencies.add(data.atc.vhf_am)
|
unique_map_frequencies.add(data.atc.vhf_am)
|
||||||
unique_map_frequencies.add(data.atc.uhf)
|
unique_map_frequencies.add(data.atc.uhf)
|
||||||
# No need to reserve ILS or TACAN because those are in the
|
# No need to reserve ILS or TACAN because those are in the
|
||||||
# beacon list.
|
# beacon list.
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_ground_units(cls):
|
def _generate_ground_units(cls):
|
||||||
|
|||||||
@@ -487,11 +487,11 @@ class ConflictTheater:
|
|||||||
for inclusion_zone in self.landmap[0]:
|
for inclusion_zone in self.landmap[0]:
|
||||||
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
||||||
nearest_points.append(nearest_pair[1])
|
nearest_points.append(nearest_pair[1])
|
||||||
min_distance = None # type: Optional[geometry.Point]
|
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
||||||
nearest_point = None # type: Optional[geometry.Point]
|
nearest_point = nearest_points[0] # type: geometry.Point
|
||||||
for pt in nearest_points:
|
for pt in nearest_points[1:]:
|
||||||
distance = point.distance(pt)
|
distance = point.distance(pt)
|
||||||
if not min_distance or distance < min_distance:
|
if distance < min_distance:
|
||||||
min_distance = distance
|
min_distance = distance
|
||||||
nearest_point = pt
|
nearest_point = pt
|
||||||
assert isinstance(nearest_point, geometry.Point)
|
assert isinstance(nearest_point, geometry.Point)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
def _build_version_string() -> str:
|
def _build_version_string() -> str:
|
||||||
components = ["2.3.0"]
|
components = ["2.3.2"]
|
||||||
build_number_path = Path("resources/buildnumber")
|
build_number_path = Path("resources/buildnumber")
|
||||||
if build_number_path.exists():
|
if build_number_path.exists():
|
||||||
with build_number_path.open("r") as build_number_file:
|
with build_number_path.open("r") as build_number_file:
|
||||||
|
|||||||
@@ -20,20 +20,20 @@ from dcs.planes import (
|
|||||||
B_17G,
|
B_17G,
|
||||||
B_52H,
|
B_52H,
|
||||||
Bf_109K_4,
|
Bf_109K_4,
|
||||||
C_101EB,
|
|
||||||
C_101CC,
|
C_101CC,
|
||||||
|
C_101EB,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
FW_190D9,
|
FW_190D9,
|
||||||
F_14B,
|
F_14B,
|
||||||
I_16,
|
I_16,
|
||||||
JF_17,
|
JF_17,
|
||||||
Ju_88A4,
|
Ju_88A4,
|
||||||
PlaneType,
|
|
||||||
P_47D_30,
|
P_47D_30,
|
||||||
P_47D_30bl1,
|
P_47D_30bl1,
|
||||||
P_47D_40,
|
P_47D_40,
|
||||||
P_51D,
|
P_51D,
|
||||||
P_51D_30_NA,
|
P_51D_30_NA,
|
||||||
|
PlaneType,
|
||||||
SpitfireLFMkIX,
|
SpitfireLFMkIX,
|
||||||
SpitfireLFMkIXCW,
|
SpitfireLFMkIXCW,
|
||||||
Su_33,
|
Su_33,
|
||||||
@@ -59,16 +59,15 @@ from dcs.task import (
|
|||||||
OptReactOnThreat,
|
OptReactOnThreat,
|
||||||
OptRestrictJettison,
|
OptRestrictJettison,
|
||||||
OrbitAction,
|
OrbitAction,
|
||||||
|
PinpointStrike,
|
||||||
RunwayAttack,
|
RunwayAttack,
|
||||||
SEAD,
|
SEAD,
|
||||||
StartCommand,
|
StartCommand,
|
||||||
Targets,
|
Targets,
|
||||||
Task,
|
Task,
|
||||||
WeaponType,
|
WeaponType,
|
||||||
PinpointStrike,
|
|
||||||
)
|
)
|
||||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||||
from dcs.translation import String
|
|
||||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
||||||
from dcs.unittype import FlyingType, UnitType
|
from dcs.unittype import FlyingType, UnitType
|
||||||
@@ -99,7 +98,6 @@ from gen.flights.flight import (
|
|||||||
)
|
)
|
||||||
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
||||||
from gen.runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
from .conflictgen import Conflict
|
|
||||||
from .flights.flightplan import (
|
from .flights.flightplan import (
|
||||||
CasFlightPlan,
|
CasFlightPlan,
|
||||||
LoiterFlightPlan,
|
LoiterFlightPlan,
|
||||||
@@ -108,7 +106,6 @@ from .flights.flightplan import (
|
|||||||
)
|
)
|
||||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
from .flights.traveltime import GroundSpeed, TotEstimator
|
||||||
from .naming import namegen
|
from .naming import namegen
|
||||||
from .runways import RunwayAssigner
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@@ -1349,7 +1346,7 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
# And setting *our* waypoint TOT causes the takeoff time to show up in
|
# And setting *our* waypoint TOT causes the takeoff time to show up in
|
||||||
# the player's kneeboard.
|
# the player's kneeboard.
|
||||||
waypoint.tot = estimator.takeoff_time_for_flight(flight)
|
waypoint.tot = flight.flight_plan.takeoff_time()
|
||||||
# And finally assign it to the FlightData info so it shows correctly in
|
# And finally assign it to the FlightData info so it shows correctly in
|
||||||
# the briefing.
|
# the briefing.
|
||||||
self.flights[-1].departure_delay = start_time
|
self.flights[-1].departure_delay = start_time
|
||||||
@@ -1756,15 +1753,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
f"{flight_plan_type} does not define a patrol.")
|
f"{flight_plan_type} does not define a patrol.")
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
racetrack = ControlledTask(OrbitAction(
|
# NB: It's important that the engage task comes before the orbit task.
|
||||||
altitude=waypoint.alt,
|
# Though they're on the same waypoint, if the orbit task comes first it
|
||||||
pattern=OrbitAction.OrbitPattern.RaceTrack
|
# is their first priority and they will not engage any targets because
|
||||||
))
|
# they're fully focused on orbiting. If the STE task is first, they will
|
||||||
self.set_waypoint_tot(
|
# engage targets if available and orbit if they find nothing to shoot.
|
||||||
waypoint, self.flight.flight_plan.patrol_start_time)
|
|
||||||
racetrack.stop_after_time(
|
|
||||||
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
|
|
||||||
waypoint.add_task(racetrack)
|
|
||||||
|
|
||||||
# TODO: Move the properties of this task into the flight plan?
|
# TODO: Move the properties of this task into the flight plan?
|
||||||
# CAP is the only current user of this so it's not a big deal, but might
|
# CAP is the only current user of this so it's not a big deal, but might
|
||||||
@@ -1775,6 +1768,16 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
||||||
targets=[Targets.All.Air]))
|
targets=[Targets.All.Air]))
|
||||||
|
|
||||||
|
racetrack = ControlledTask(OrbitAction(
|
||||||
|
altitude=waypoint.alt,
|
||||||
|
pattern=OrbitAction.OrbitPattern.RaceTrack
|
||||||
|
))
|
||||||
|
self.set_waypoint_tot(
|
||||||
|
waypoint, self.flight.flight_plan.patrol_start_time)
|
||||||
|
racetrack.stop_after_time(
|
||||||
|
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
|
||||||
|
waypoint.add_task(racetrack)
|
||||||
|
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,10 +73,17 @@ class FlightPlan:
|
|||||||
"""Iterates over all waypoints in the flight plan, in order."""
|
"""Iterates over all waypoints in the flight plan, in order."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
def edges(
|
||||||
def edges(self) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
|
self, until: Optional[FlightWaypoint] = None
|
||||||
|
) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
|
||||||
"""A list of all paths between waypoints, in order."""
|
"""A list of all paths between waypoints, in order."""
|
||||||
return zip(self.waypoints, self.waypoints[1:])
|
waypoints = self.waypoints
|
||||||
|
if until is None:
|
||||||
|
last_index = len(waypoints)
|
||||||
|
else:
|
||||||
|
last_index = waypoints.index(until) + 1
|
||||||
|
|
||||||
|
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
|
||||||
|
|
||||||
def best_speed_between_waypoints(self, a: FlightWaypoint,
|
def best_speed_between_waypoints(self, a: FlightWaypoint,
|
||||||
b: FlightWaypoint) -> int:
|
b: FlightWaypoint) -> int:
|
||||||
@@ -137,7 +144,6 @@ class FlightPlan:
|
|||||||
"""Joker fuel value for the FlightPlan
|
"""Joker fuel value for the FlightPlan
|
||||||
"""
|
"""
|
||||||
return self.bingo_fuel + 1000
|
return self.bingo_fuel + 1000
|
||||||
|
|
||||||
|
|
||||||
def max_distance_from(self, cp: ControlPoint) -> int:
|
def max_distance_from(self, cp: ControlPoint) -> int:
|
||||||
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
|
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
|
||||||
@@ -156,26 +162,18 @@ class FlightPlan:
|
|||||||
"""
|
"""
|
||||||
return timedelta()
|
return timedelta()
|
||||||
|
|
||||||
# Not cached because changes to the package might alter the formation speed.
|
|
||||||
@property
|
|
||||||
def travel_time_to_target(self) -> Optional[timedelta]:
|
|
||||||
"""The estimated time between the first waypoint and the target."""
|
|
||||||
if self.tot_waypoint is None:
|
|
||||||
return None
|
|
||||||
return self._travel_time_to_waypoint(self.tot_waypoint)
|
|
||||||
|
|
||||||
def _travel_time_to_waypoint(
|
def _travel_time_to_waypoint(
|
||||||
self, destination: FlightWaypoint) -> timedelta:
|
self, destination: FlightWaypoint) -> timedelta:
|
||||||
total = timedelta()
|
total = timedelta()
|
||||||
for previous_waypoint, waypoint in self.edges:
|
|
||||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
if destination not in self.waypoints:
|
||||||
waypoint)
|
|
||||||
if waypoint == destination:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"Did not find destination waypoint {destination} in "
|
f"Did not find destination waypoint {destination} in "
|
||||||
f"waypoints for {self.flight}")
|
f"waypoints for {self.flight}")
|
||||||
|
|
||||||
|
for previous_waypoint, waypoint in self.edges(until=destination):
|
||||||
|
total += self.travel_time_between_waypoints(previous_waypoint,
|
||||||
|
waypoint)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||||
@@ -196,10 +194,59 @@ class FlightPlan:
|
|||||||
def dismiss_escort_at(self) -> Optional[FlightWaypoint]:
|
def dismiss_escort_at(self) -> Optional[FlightWaypoint]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def takeoff_time(self) -> Optional[timedelta]:
|
||||||
|
tot_waypoint = self.tot_waypoint
|
||||||
|
if tot_waypoint is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
time = self.tot_for_waypoint(tot_waypoint)
|
||||||
|
if time is None:
|
||||||
|
return None
|
||||||
|
time += self.tot_offset
|
||||||
|
return time - self._travel_time_to_waypoint(tot_waypoint)
|
||||||
|
|
||||||
|
def startup_time(self) -> Optional[timedelta]:
|
||||||
|
takeoff_time = self.takeoff_time()
|
||||||
|
if takeoff_time is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
start_time = (takeoff_time - self.estimate_startup() -
|
||||||
|
self.estimate_ground_ops())
|
||||||
|
|
||||||
|
# In case FP math has given us some barely below zero time, round to
|
||||||
|
# zero.
|
||||||
|
if math.isclose(start_time.total_seconds(), 0):
|
||||||
|
return timedelta()
|
||||||
|
|
||||||
|
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
||||||
|
# and they're not interesting from a mission planning perspective so we
|
||||||
|
# don't want them in the UI.
|
||||||
|
#
|
||||||
|
# Round down so *barely* above zero start times are just zero.
|
||||||
|
return timedelta(seconds=math.floor(start_time.total_seconds()))
|
||||||
|
|
||||||
|
def estimate_startup(self) -> timedelta:
|
||||||
|
if self.flight.start_type == "Cold":
|
||||||
|
if self.flight.client_count:
|
||||||
|
return timedelta(minutes=10)
|
||||||
|
else:
|
||||||
|
# The AI doesn't seem to have a real startup procedure.
|
||||||
|
return timedelta(minutes=2)
|
||||||
|
return timedelta()
|
||||||
|
|
||||||
|
def estimate_ground_ops(self) -> timedelta:
|
||||||
|
if self.flight.start_type in ("Runway", "In Flight"):
|
||||||
|
return timedelta()
|
||||||
|
if self.flight.from_cp.is_fleet:
|
||||||
|
return timedelta(minutes=2)
|
||||||
|
else:
|
||||||
|
return timedelta(minutes=5)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class LoiterFlightPlan(FlightPlan):
|
class LoiterFlightPlan(FlightPlan):
|
||||||
hold: FlightWaypoint
|
hold: FlightWaypoint
|
||||||
|
hold_duration: timedelta
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -221,6 +268,17 @@ class LoiterFlightPlan(FlightPlan):
|
|||||||
return self.push_time
|
return self.push_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||||
|
b: FlightWaypoint) -> timedelta:
|
||||||
|
travel_time = super().travel_time_between_waypoints(a, b)
|
||||||
|
if a != self.hold:
|
||||||
|
return travel_time
|
||||||
|
try:
|
||||||
|
return travel_time + self.hold_duration
|
||||||
|
except AttributeError:
|
||||||
|
# Save compat for 2.3.
|
||||||
|
return travel_time + timedelta(minutes=5)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FormationFlightPlan(LoiterFlightPlan):
|
class FormationFlightPlan(LoiterFlightPlan):
|
||||||
@@ -254,7 +312,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
|||||||
all of its formation waypoints.
|
all of its formation waypoints.
|
||||||
"""
|
"""
|
||||||
speeds = []
|
speeds = []
|
||||||
for previous_waypoint, waypoint in self.edges:
|
for previous_waypoint, waypoint in self.edges():
|
||||||
if waypoint in self.package_speed_waypoints:
|
if waypoint in self.package_speed_waypoints:
|
||||||
speeds.append(self.best_speed_between_waypoints(
|
speeds.append(self.best_speed_between_waypoints(
|
||||||
previous_waypoint, waypoint))
|
previous_waypoint, waypoint))
|
||||||
@@ -486,7 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
"""The estimated time between the first waypoint and the target."""
|
"""The estimated time between the first waypoint and the target."""
|
||||||
destination = self.tot_waypoint
|
destination = self.tot_waypoint
|
||||||
total = timedelta()
|
total = timedelta()
|
||||||
for previous_waypoint, waypoint in self.edges:
|
for previous_waypoint, waypoint in self.edges():
|
||||||
if waypoint == self.tot_waypoint:
|
if waypoint == self.tot_waypoint:
|
||||||
# For anything strike-like the TOT waypoint is the *flight's*
|
# For anything strike-like the TOT waypoint is the *flight's*
|
||||||
# mission target, but to synchronize with the rest of the
|
# mission target, but to synchronize with the rest of the
|
||||||
@@ -846,6 +904,7 @@ class FlightPlanBuilder:
|
|||||||
lead_time=timedelta(minutes=5),
|
lead_time=timedelta(minutes=5),
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=builder.hold(self._hold_point(flight)),
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
|
hold_duration=timedelta(minutes=5),
|
||||||
sweep_start=start,
|
sweep_start=start,
|
||||||
sweep_end=end,
|
sweep_end=end,
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
@@ -1050,6 +1109,7 @@ class FlightPlanBuilder:
|
|||||||
flight=flight,
|
flight=flight,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=builder.hold(self._hold_point(flight)),
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
|
hold_duration=timedelta(minutes=5),
|
||||||
join=builder.join(self.package.waypoints.join),
|
join=builder.join(self.package.waypoints.join),
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
targets=[target],
|
targets=[target],
|
||||||
@@ -1196,6 +1256,7 @@ class FlightPlanBuilder:
|
|||||||
flight=flight,
|
flight=flight,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=builder.hold(self._hold_point(flight)),
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
|
hold_duration=timedelta(minutes=5),
|
||||||
join=builder.join(self.package.waypoints.join),
|
join=builder.join(self.package.waypoints.join),
|
||||||
ingress=builder.ingress(ingress_type,
|
ingress=builder.ingress(ingress_type,
|
||||||
self.package.waypoints.ingress, location),
|
self.package.waypoints.ingress, location),
|
||||||
|
|||||||
@@ -89,68 +89,23 @@ class TravelTime:
|
|||||||
|
|
||||||
# TODO: Most if not all of this should move into FlightPlan.
|
# TODO: Most if not all of this should move into FlightPlan.
|
||||||
class TotEstimator:
|
class TotEstimator:
|
||||||
# An extra five minutes given as wiggle room. Expected to be spent at the
|
|
||||||
# hold point performing any last minute configuration.
|
|
||||||
HOLD_TIME = timedelta(minutes=5)
|
|
||||||
|
|
||||||
def __init__(self, package: Package) -> None:
|
def __init__(self, package: Package) -> None:
|
||||||
self.package = package
|
self.package = package
|
||||||
|
|
||||||
def mission_start_time(self, flight: Flight) -> timedelta:
|
@staticmethod
|
||||||
takeoff_time = self.takeoff_time_for_flight(flight)
|
def mission_start_time(flight: Flight) -> timedelta:
|
||||||
if takeoff_time is None:
|
startup_time = flight.flight_plan.startup_time()
|
||||||
|
if startup_time is None:
|
||||||
# Could not determine takeoff time, probably due to a custom flight
|
# Could not determine takeoff time, probably due to a custom flight
|
||||||
# plan. Start immediately.
|
# plan. Start immediately.
|
||||||
return timedelta()
|
return timedelta()
|
||||||
|
return startup_time
|
||||||
startup_time = self.estimate_startup(flight)
|
|
||||||
ground_ops_time = self.estimate_ground_ops(flight)
|
|
||||||
start_time = takeoff_time - startup_time - ground_ops_time
|
|
||||||
# In case FP math has given us some barely below zero time, round to
|
|
||||||
# zero.
|
|
||||||
if math.isclose(start_time.total_seconds(), 0):
|
|
||||||
return timedelta()
|
|
||||||
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
|
||||||
# and they're not interesting from a mission planning perspective so we
|
|
||||||
# don't want them in the UI.
|
|
||||||
#
|
|
||||||
# Round down so *barely* above zero start times are just zero.
|
|
||||||
return timedelta(seconds=math.floor(start_time.total_seconds()))
|
|
||||||
|
|
||||||
def takeoff_time_for_flight(self, flight: Flight) -> Optional[timedelta]:
|
|
||||||
travel_time = self.travel_time_to_rendezvous_or_target(flight)
|
|
||||||
if travel_time is None:
|
|
||||||
from gen.flights.flightplan import CustomFlightPlan
|
|
||||||
if not isinstance(flight.flight_plan, CustomFlightPlan):
|
|
||||||
logging.warning(
|
|
||||||
"Found no rendezvous or target point. Cannot estimate "
|
|
||||||
f"takeoff time takeoff time for {flight}.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
from gen.flights.flightplan import FormationFlightPlan
|
|
||||||
if isinstance(flight.flight_plan, FormationFlightPlan):
|
|
||||||
tot = flight.flight_plan.tot_for_waypoint(
|
|
||||||
flight.flight_plan.join)
|
|
||||||
if tot is None:
|
|
||||||
logging.warning(
|
|
||||||
"Could not determine the TOT of the join point. Takeoff "
|
|
||||||
f"time for {flight} will be immediate.")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
tot_waypoint = flight.flight_plan.tot_waypoint
|
|
||||||
if tot_waypoint is None:
|
|
||||||
tot = self.package.time_over_target
|
|
||||||
else:
|
|
||||||
tot = flight.flight_plan.tot_for_waypoint(tot_waypoint)
|
|
||||||
if tot is None:
|
|
||||||
logging.error(f"TOT waypoint for {flight} has no TOT")
|
|
||||||
tot = self.package.time_over_target
|
|
||||||
return tot - travel_time - self.HOLD_TIME
|
|
||||||
|
|
||||||
def earliest_tot(self) -> timedelta:
|
def earliest_tot(self) -> timedelta:
|
||||||
earliest_tot = max((
|
earliest_tot = max((
|
||||||
self.earliest_tot_for_flight(f) for f in self.package.flights
|
self.earliest_tot_for_flight(f) for f in self.package.flights
|
||||||
)) + self.HOLD_TIME
|
))
|
||||||
|
|
||||||
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
||||||
# and they're not interesting from a mission planning perspective so we
|
# and they're not interesting from a mission planning perspective so we
|
||||||
@@ -159,7 +114,8 @@ class TotEstimator:
|
|||||||
# Round up so we don't get negative start times.
|
# Round up so we don't get negative start times.
|
||||||
return timedelta(seconds=math.ceil(earliest_tot.total_seconds()))
|
return timedelta(seconds=math.ceil(earliest_tot.total_seconds()))
|
||||||
|
|
||||||
def earliest_tot_for_flight(self, flight: Flight) -> timedelta:
|
@staticmethod
|
||||||
|
def earliest_tot_for_flight(flight: Flight) -> timedelta:
|
||||||
"""Estimate fastest time from mission start to the target position.
|
"""Estimate fastest time from mission start to the target position.
|
||||||
|
|
||||||
For BARCAP flights, this is time to race track start. This ensures that
|
For BARCAP flights, this is time to race track start. This ensures that
|
||||||
@@ -175,51 +131,18 @@ class TotEstimator:
|
|||||||
The earliest possible TOT for the given flight in seconds. Returns 0
|
The earliest possible TOT for the given flight in seconds. Returns 0
|
||||||
if an ingress point cannot be found.
|
if an ingress point cannot be found.
|
||||||
"""
|
"""
|
||||||
time_to_target = self.travel_time_to_target(flight)
|
# Clear the TOT, calculate the startup time. Negating the result gives
|
||||||
if time_to_target is None:
|
# the earliest possible start time.
|
||||||
|
orig_tot = flight.package.time_over_target
|
||||||
|
try:
|
||||||
|
flight.package.time_over_target = timedelta()
|
||||||
|
time = flight.flight_plan.startup_time()
|
||||||
|
finally:
|
||||||
|
flight.package.time_over_target = orig_tot
|
||||||
|
|
||||||
|
if time is None:
|
||||||
logging.warning(f"Cannot estimate TOT for {flight}")
|
logging.warning(f"Cannot estimate TOT for {flight}")
|
||||||
# Return 0 so this flight's travel time does not affect the rest
|
# Return 0 so this flight's travel time does not affect the rest
|
||||||
# of the package.
|
# of the package.
|
||||||
return timedelta()
|
return timedelta()
|
||||||
# Account for TOT offsets for the flight plan. An offset of -2 minutes
|
return -time
|
||||||
# means the flight's TOT is 2 minutes ahead of the package's so it needs
|
|
||||||
# an extra two minutes.
|
|
||||||
offset = -flight.flight_plan.tot_offset
|
|
||||||
startup = self.estimate_startup(flight)
|
|
||||||
ground_ops = self.estimate_ground_ops(flight)
|
|
||||||
return startup + ground_ops + time_to_target + offset
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def estimate_startup(flight: Flight) -> timedelta:
|
|
||||||
if flight.start_type == "Cold":
|
|
||||||
if flight.client_count:
|
|
||||||
return timedelta(minutes=10)
|
|
||||||
else:
|
|
||||||
# The AI doesn't seem to have a real startup procedure.
|
|
||||||
return timedelta(minutes=2)
|
|
||||||
return timedelta()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def estimate_ground_ops(flight: Flight) -> timedelta:
|
|
||||||
if flight.start_type in ("Runway", "In Flight"):
|
|
||||||
return timedelta()
|
|
||||||
if flight.from_cp.is_fleet:
|
|
||||||
return timedelta(minutes=2)
|
|
||||||
else:
|
|
||||||
return timedelta(minutes=5)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def travel_time_to_target(flight: Flight) -> Optional[timedelta]:
|
|
||||||
if flight.flight_plan is None:
|
|
||||||
return None
|
|
||||||
return flight.flight_plan.travel_time_to_target
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def travel_time_to_rendezvous_or_target(
|
|
||||||
flight: Flight) -> Optional[timedelta]:
|
|
||||||
if flight.flight_plan is None:
|
|
||||||
return None
|
|
||||||
from gen.flights.flightplan import FormationFlightPlan
|
|
||||||
if isinstance(flight.flight_plan, FormationFlightPlan):
|
|
||||||
return flight.flight_plan.travel_time_to_rendezvous
|
|
||||||
return flight.flight_plan.travel_time_to_target
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from gen.ground_forces.combat_stance import CombatStance
|
|||||||
TYPE_TANKS = [
|
TYPE_TANKS = [
|
||||||
Armor.MBT_T_55,
|
Armor.MBT_T_55,
|
||||||
Armor.MBT_T_72B,
|
Armor.MBT_T_72B,
|
||||||
|
Armor.MBT_T_72B3,
|
||||||
Armor.MBT_T_80U,
|
Armor.MBT_T_80U,
|
||||||
Armor.MBT_T_90,
|
Armor.MBT_T_90,
|
||||||
Armor.MBT_Leopard_2,
|
Armor.MBT_Leopard_2,
|
||||||
@@ -96,6 +97,7 @@ TYPE_APC = [
|
|||||||
Armor.APC_M1126_Stryker_ICV,
|
Armor.APC_M1126_Stryker_ICV,
|
||||||
Armor.APC_M113,
|
Armor.APC_M113,
|
||||||
Armor.APC_BTR_80,
|
Armor.APC_BTR_80,
|
||||||
|
Armor.APC_BTR_82A,
|
||||||
Armor.APC_MTLB,
|
Armor.APC_MTLB,
|
||||||
Armor.APC_M2A1,
|
Armor.APC_M2A1,
|
||||||
Armor.APC_Cobra,
|
Armor.APC_Cobra,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Radio frequency types and allocators."""
|
"""Radio frequency types and allocators."""
|
||||||
import itertools
|
import itertools
|
||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, Iterator, List, Set
|
from typing import Dict, Iterator, List, Set
|
||||||
|
|
||||||
@@ -71,12 +72,9 @@ class Radio:
|
|||||||
self.minimum.hertz, self.maximum.hertz, self.step.hertz
|
self.minimum.hertz, self.maximum.hertz, self.step.hertz
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@property
|
||||||
class OutOfChannelsError(RuntimeError):
|
def last_channel(self) -> RadioFrequency:
|
||||||
"""Raised when all channels usable by this radio have been allocated."""
|
return RadioFrequency(self.maximum.hertz - self.step.hertz)
|
||||||
|
|
||||||
def __init__(self, radio: Radio) -> None:
|
|
||||||
super().__init__(f"No available channels for {radio}")
|
|
||||||
|
|
||||||
|
|
||||||
class ChannelInUseError(RuntimeError):
|
class ChannelInUseError(RuntimeError):
|
||||||
@@ -215,7 +213,13 @@ class RadioRegistry:
|
|||||||
self.reserve(channel)
|
self.reserve(channel)
|
||||||
return channel
|
return channel
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise OutOfChannelsError(radio)
|
# In the event of too many channel users, fail gracefully by reusing
|
||||||
|
# the last channel.
|
||||||
|
# https://github.com/Khopa/dcs_liberation/issues/598
|
||||||
|
channel = radio.last_channel
|
||||||
|
logging.warning(
|
||||||
|
f"No more free channels for {radio.name}. Reusing {channel}.")
|
||||||
|
return channel
|
||||||
|
|
||||||
def alloc_uhf(self) -> RadioFrequency:
|
def alloc_uhf(self) -> RadioFrequency:
|
||||||
"""Allocates a UHF radio channel suitable for inter-flight comms.
|
"""Allocates a UHF radio channel suitable for inter-flight comms.
|
||||||
|
|||||||
25
gen/sam/aaa_zsu57.py
Normal file
25
gen/sam/aaa_zsu57.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
|
AirDefenseRange,
|
||||||
|
AirDefenseGroupGenerator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ZSU57Generator(AirDefenseGroupGenerator):
|
||||||
|
"""
|
||||||
|
This generate a Zsu 57 group
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "ZSU-57-2 Group"
|
||||||
|
price = 60
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
num_launchers = 5
|
||||||
|
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=360)
|
||||||
|
for i, position in enumerate(positions):
|
||||||
|
self.add_unit(AirDefence.AAA_ZSU_57_2, "SPAA#" + str(i), position[0], position[1], position[2])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def range(cls) -> AirDefenseRange:
|
||||||
|
return AirDefenseRange.Short
|
||||||
@@ -12,6 +12,7 @@ from gen.sam.aaa_bofors import BoforsGenerator
|
|||||||
from gen.sam.aaa_flak import FlakGenerator
|
from gen.sam.aaa_flak import FlakGenerator
|
||||||
from gen.sam.aaa_flak18 import Flak18Generator
|
from gen.sam.aaa_flak18 import Flak18Generator
|
||||||
from gen.sam.aaa_ww2_ally_flak import AllyWW2FlakGenerator
|
from gen.sam.aaa_ww2_ally_flak import AllyWW2FlakGenerator
|
||||||
|
from gen.sam.aaa_zsu57 import ZSU57Generator
|
||||||
from gen.sam.aaa_zu23_insurgent import ZU23InsurgentGenerator
|
from gen.sam.aaa_zu23_insurgent import ZU23InsurgentGenerator
|
||||||
from gen.sam.airdefensegroupgenerator import (
|
from gen.sam.airdefensegroupgenerator import (
|
||||||
AirDefenseGroupGenerator,
|
AirDefenseGroupGenerator,
|
||||||
@@ -98,7 +99,8 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
|
|||||||
"ColdWarFlakGenerator": ColdWarFlakGenerator,
|
"ColdWarFlakGenerator": ColdWarFlakGenerator,
|
||||||
"EarlyColdWarFlakGenerator": EarlyColdWarFlakGenerator,
|
"EarlyColdWarFlakGenerator": EarlyColdWarFlakGenerator,
|
||||||
"FreyaGenerator": FreyaGenerator,
|
"FreyaGenerator": FreyaGenerator,
|
||||||
"AllyWW2FlakGenerator": AllyWW2FlakGenerator
|
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
|
||||||
|
"ZSU57Generator": ZSU57Generator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
pydcs
2
pydcs
Submodule pydcs updated: c9751f54e0...edc87fab1d
@@ -284,7 +284,7 @@ class QLiberationWindow(QMainWindow):
|
|||||||
"<h4>Authors</h4>" + \
|
"<h4>Authors</h4>" + \
|
||||||
"<p>DCS Liberation was originally developed by <b>shdwp</b>, DCS Liberation 2.0 is a partial rewrite based on this work by <b>Khopa</b>." \
|
"<p>DCS Liberation was originally developed by <b>shdwp</b>, DCS Liberation 2.0 is a partial rewrite based on this work by <b>Khopa</b>." \
|
||||||
"<h4>Contributors</h4>" + \
|
"<h4>Contributors</h4>" + \
|
||||||
"shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob" + \
|
"shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob, Hawkmoon" + \
|
||||||
"<h4>Special Thanks :</h4>" \
|
"<h4>Special Thanks :</h4>" \
|
||||||
"<b>rp-</b> <i>for the pydcs framework</i><br/>"\
|
"<b>rp-</b> <i>for the pydcs framework</i><br/>"\
|
||||||
"<b>Grimes (mrSkortch)</b> & <b>Speed</b> <i>for the MIST framework</i><br/>"\
|
"<b>Grimes (mrSkortch)</b> & <b>Speed</b> <i>for the MIST framework</i><br/>"\
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
|||||||
if self.maximum_units > 0:
|
if self.maximum_units > 0:
|
||||||
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
|
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
|
||||||
logging.debug(f"No space for additional aircraft at {self.cp}.")
|
logging.debug(f"No space for additional aircraft at {self.cp}.")
|
||||||
|
QMessageBox.warning(
|
||||||
|
self, "No space for additional aircraft",
|
||||||
|
f"There is no parking space left at {self.cp.name} to accommodate another plane.", QMessageBox.Ok)
|
||||||
return
|
return
|
||||||
|
|
||||||
super().buy(unit_type)
|
super().buy(unit_type)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from PySide2.QtWidgets import QHeaderView, QTableView
|
|||||||
from game.utils import meter_to_feet
|
from game.utils import meter_to_feet
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
||||||
from gen.flights.traveltime import TotEstimator
|
|
||||||
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \
|
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \
|
||||||
QWaypointItem
|
QWaypointItem
|
||||||
|
|
||||||
@@ -74,9 +73,9 @@ class QFlightWaypointList(QTableView):
|
|||||||
time = timedelta(seconds=int(time.total_seconds()))
|
time = timedelta(seconds=int(time.total_seconds()))
|
||||||
return f"{prefix}T+{time}"
|
return f"{prefix}T+{time}"
|
||||||
|
|
||||||
def takeoff_text(self, flight: Flight) -> str:
|
@staticmethod
|
||||||
estimator = TotEstimator(self.package)
|
def takeoff_text(flight: Flight) -> str:
|
||||||
takeoff_time = estimator.takeoff_time_for_flight(flight)
|
takeoff_time = flight.flight_plan.takeoff_time()
|
||||||
# Handle custom flight plans where we can't estimate the takeoff time.
|
# Handle custom flight plans where we can't estimate the takeoff time.
|
||||||
if takeoff_time is None:
|
if takeoff_time is None:
|
||||||
takeoff_time = timedelta()
|
takeoff_time = timedelta()
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ class QFlightWaypointTab(QFrame):
|
|||||||
if not waypoints:
|
if not waypoints:
|
||||||
return
|
return
|
||||||
self.degrade_to_custom_flight_plan()
|
self.degrade_to_custom_flight_plan()
|
||||||
self.flight.flight_plan.waypoints.extend(waypoints)
|
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
||||||
|
self.flight.flight_plan.custom_waypoints.extend(waypoints)
|
||||||
self.flight_waypoint_list.update_list()
|
self.flight_waypoint_list.update_list()
|
||||||
self.on_change()
|
self.on_change()
|
||||||
|
|
||||||
@@ -124,7 +125,8 @@ class QFlightWaypointTab(QFrame):
|
|||||||
rtb = self.planner.generate_rtb_waypoint(self.flight,
|
rtb = self.planner.generate_rtb_waypoint(self.flight,
|
||||||
self.flight.from_cp)
|
self.flight.from_cp)
|
||||||
self.degrade_to_custom_flight_plan()
|
self.degrade_to_custom_flight_plan()
|
||||||
self.flight.flight_plan.waypoints.append(rtb)
|
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
||||||
|
self.flight.flight_plan.custom_waypoints.append(rtb)
|
||||||
self.flight_waypoint_list.update_list()
|
self.flight_waypoint_list.update_list()
|
||||||
self.on_change()
|
self.on_change()
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,185 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Syria - Full Map",
|
|
||||||
"theater": "Syria",
|
|
||||||
"authors": "Khopa",
|
|
||||||
"description": "<p>Full map of Syria</p><p><strong>Note:</strong> This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.</p>",
|
|
||||||
"player_points": [
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Ramat David",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "carrier",
|
|
||||||
"id": 1001,
|
|
||||||
"x": -151000,
|
|
||||||
"y": -106000,
|
|
||||||
"captured_invert": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "lha",
|
|
||||||
"id": 1002,
|
|
||||||
"x": -131000,
|
|
||||||
"y": -161000,
|
|
||||||
"captured_invert": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"enemy_points": [
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "King Hussein Air College",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Khalkhalah",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Al-Dumayr",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Al Qusayr",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Rene Mouawad",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Hama",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Bassel Al-Assad",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Palmyra",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Tabqa",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Jirah",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Aleppo",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Minakh",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Hatay",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "airbase",
|
|
||||||
"id": "Incirlik",
|
|
||||||
"size": 1000,
|
|
||||||
"importance": 1.4,
|
|
||||||
"captured_invert": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
[
|
|
||||||
"King Hussein Air College",
|
|
||||||
"Ramat David"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Khalkhalah",
|
|
||||||
"King Hussein Air College"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Al-Dumayr",
|
|
||||||
"Khalkhalah"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Al Qusayr",
|
|
||||||
"Al-Dumayr"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Al Qusayr",
|
|
||||||
"Hama"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Al Qusayr",
|
|
||||||
"Palmyra"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Al Qusayr",
|
|
||||||
"Rene Mouawad"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Bassel Al-Assad",
|
|
||||||
"Rene Mouawad"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Aleppo",
|
|
||||||
"Hama"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Bassel Al-Assad",
|
|
||||||
"Hama"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Bassel Al-Assad",
|
|
||||||
"Hatay"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Palmyra",
|
|
||||||
"Tabqa"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Jirah",
|
|
||||||
"Tabqa"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Aleppo",
|
|
||||||
"Jirah"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Aleppo",
|
|
||||||
"Minakh"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Hatay",
|
|
||||||
"Minakh"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Incirlik",
|
|
||||||
"Minakh"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
7
resources/campaigns/syria_full_map_remastered.json
Normal file
7
resources/campaigns/syria_full_map_remastered.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "Syria - Full Map",
|
||||||
|
"theater": "Syria",
|
||||||
|
"authors": "Hawkmoon",
|
||||||
|
"description": "<p>Full map of Syria</p><p><strong>Note:</strong>For a better early game experience is suggested to give the AI an high amount of starting money This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.</p>",
|
||||||
|
"miz": "syria_full_map_remastered.miz"
|
||||||
|
}
|
||||||
BIN
resources/campaigns/syria_full_map_remastered.miz
Normal file
BIN
resources/campaigns/syria_full_map_remastered.miz
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -52,14 +52,14 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "IBND",
|
"callsign": "IBND",
|
||||||
"beacon_type": 14,
|
"beacon_type": 14,
|
||||||
"hertz": 333800000,
|
"hertz": 109900000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "IBND",
|
"callsign": "IBND",
|
||||||
"beacon_type": 15,
|
"beacon_type": 15,
|
||||||
"hertz": 333800000,
|
"hertz": 109900000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,12 +77,19 @@
|
|||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "BandarEJask",
|
"name": "JASK",
|
||||||
"callsign": "JSK",
|
"callsign": "JSK",
|
||||||
"beacon_type": 9,
|
"beacon_type": 9,
|
||||||
"hertz": 349000000,
|
"hertz": 349000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"callsign": "JSK",
|
||||||
|
"beacon_type": 5,
|
||||||
|
"hertz": null,
|
||||||
|
"channel": 110
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "BandarLengeh",
|
"name": "BandarLengeh",
|
||||||
"callsign": "LEN",
|
"callsign": "LEN",
|
||||||
@@ -101,8 +108,8 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "MMA",
|
"callsign": "MMA",
|
||||||
"beacon_type": 15,
|
"beacon_type": 15,
|
||||||
"hertz": 111100000,
|
"hertz": 109100000,
|
||||||
"channel": 48
|
"channel": 28
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -115,28 +122,28 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "IMA",
|
"callsign": "IMA",
|
||||||
"beacon_type": 15,
|
"beacon_type": 15,
|
||||||
"hertz": 109100000,
|
"hertz": 111100000,
|
||||||
"channel": 28
|
"channel": 48
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "RMA",
|
"callsign": "RMA",
|
||||||
"beacon_type": 15,
|
"beacon_type": 15,
|
||||||
"hertz": 114900000,
|
"hertz": 108700000,
|
||||||
"channel": 24
|
"channel": 24
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "MMA",
|
"callsign": "MMA",
|
||||||
"beacon_type": 14,
|
"beacon_type": 14,
|
||||||
"hertz": 111100000,
|
"hertz": 109100000,
|
||||||
"channel": 48
|
"channel": 28
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "RMA",
|
"callsign": "RMA",
|
||||||
"beacon_type": 14,
|
"beacon_type": 14,
|
||||||
"hertz": 114900000,
|
"hertz": 108700000,
|
||||||
"channel": 24
|
"channel": 24
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -150,8 +157,8 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "IMA",
|
"callsign": "IMA",
|
||||||
"beacon_type": 14,
|
"beacon_type": 14,
|
||||||
"hertz": 109100000,
|
"hertz": 111100000,
|
||||||
"channel": 28
|
"channel": 48
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AlDhafra",
|
"name": "AlDhafra",
|
||||||
@@ -332,7 +339,7 @@
|
|||||||
"name": "KishIsland",
|
"name": "KishIsland",
|
||||||
"callsign": "KIH",
|
"callsign": "KIH",
|
||||||
"beacon_type": 9,
|
"beacon_type": 9,
|
||||||
"hertz": 201000000,
|
"hertz": 201000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -367,14 +374,14 @@
|
|||||||
"name": "LavanIsland",
|
"name": "LavanIsland",
|
||||||
"callsign": "LVA",
|
"callsign": "LVA",
|
||||||
"beacon_type": 9,
|
"beacon_type": 9,
|
||||||
"hertz": 310000000,
|
"hertz": 310000,
|
||||||
"channel": 0
|
"channel": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "LiwaAirbase",
|
"name": "LiwaAirbase",
|
||||||
"callsign": "\u00c4\u00bc",
|
"callsign": "OMLW",
|
||||||
"beacon_type": 7,
|
"beacon_type": 6,
|
||||||
"hertz": null,
|
"hertz": 117400000,
|
||||||
"channel": 121
|
"channel": 121
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -433,13 +440,6 @@
|
|||||||
"hertz": 113600000,
|
"hertz": 113600000,
|
||||||
"channel": 83
|
"channel": 83
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "SasAlNakheelAirport",
|
|
||||||
"callsign": "SAS",
|
|
||||||
"beacon_type": 10,
|
|
||||||
"hertz": 128925,
|
|
||||||
"channel": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "SasAlNakheel",
|
"name": "SasAlNakheel",
|
||||||
"callsign": "SAS",
|
"callsign": "SAS",
|
||||||
@@ -500,14 +500,14 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "ISYZ",
|
"callsign": "ISYZ",
|
||||||
"beacon_type": 15,
|
"beacon_type": 15,
|
||||||
"hertz": 109900000,
|
"hertz": 108500000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"callsign": "ISYZ",
|
"callsign": "ISYZ",
|
||||||
"beacon_type": 14,
|
"beacon_type": 14,
|
||||||
"hertz": 109900000,
|
"hertz": 108500000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -556,7 +556,7 @@
|
|||||||
"name": "DezfulAirport",
|
"name": "DezfulAirport",
|
||||||
"callsign": "DZF",
|
"callsign": "DZF",
|
||||||
"beacon_type": 9,
|
"beacon_type": 9,
|
||||||
"hertz": 293000000,
|
"hertz": 293000,
|
||||||
"channel": null
|
"channel": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
100
resources/factions/NATO_Desert_Storm.json
Normal file
100
resources/factions/NATO_Desert_Storm.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"country": "Combined Joint Task Forces Blue",
|
||||||
|
"name": "NATO Desert Storm",
|
||||||
|
"authors": "Hawkmoon",
|
||||||
|
"description": "<p>A faction to recreate the actual unit lineup during Desert Storm as closely as possible</p>",
|
||||||
|
"aircrafts": [
|
||||||
|
"F_15C",
|
||||||
|
"F_14A",
|
||||||
|
"F_15E",
|
||||||
|
"F_16C_50",
|
||||||
|
"FA_18C_hornet",
|
||||||
|
"A_10A",
|
||||||
|
"AV8BNA",
|
||||||
|
"UH_1H",
|
||||||
|
"AH_64A",
|
||||||
|
"B_52H",
|
||||||
|
"B_1B",
|
||||||
|
"Tornado_IDS",
|
||||||
|
"F_111F",
|
||||||
|
"F_4E",
|
||||||
|
"F_117A",
|
||||||
|
"M_2000C",
|
||||||
|
"S_3B",
|
||||||
|
"SA342M",
|
||||||
|
"SA342L",
|
||||||
|
"SA342Mistral",
|
||||||
|
"OH_58D"
|
||||||
|
],
|
||||||
|
"awacs": [
|
||||||
|
"E_3A",
|
||||||
|
"E_2C"
|
||||||
|
],
|
||||||
|
"tankers": [
|
||||||
|
"KC_135",
|
||||||
|
"KC135MPRS"
|
||||||
|
],
|
||||||
|
"frontline_units": [
|
||||||
|
"MBT_M1A2_Abrams",
|
||||||
|
"ATGM_M1134_Stryker",
|
||||||
|
"IFV_M2A2_Bradley",
|
||||||
|
"APC_M1126_Stryker_ICV",
|
||||||
|
"IFV_LAV_25",
|
||||||
|
"APC_M1043_HMMWV_Armament",
|
||||||
|
"ATGM_M1045_HMMWV_TOW",
|
||||||
|
"TPz_Fuchs",
|
||||||
|
"IFV_MCV_80",
|
||||||
|
"MBT_Challenger_II",
|
||||||
|
"MBT_M60A3_Patton",
|
||||||
|
"SPG_M1128_Stryker_MGS"
|
||||||
|
],
|
||||||
|
"artillery_units": [
|
||||||
|
"MLRS_M270",
|
||||||
|
"SPH_M109_Paladin"
|
||||||
|
],
|
||||||
|
"logistics_units": [
|
||||||
|
"Transport_M818"
|
||||||
|
],
|
||||||
|
"infantry_units": [
|
||||||
|
"Infantry_M4",
|
||||||
|
"Soldier_M249",
|
||||||
|
"Stinger_MANPADS"
|
||||||
|
],
|
||||||
|
"air_defenses": [
|
||||||
|
"AvengerGenerator",
|
||||||
|
"ChaparralGenerator",
|
||||||
|
"VulcanGenerator",
|
||||||
|
"RolandGenerator",
|
||||||
|
"HawkGenerator",
|
||||||
|
"PatriotGenerator",
|
||||||
|
"RapierGenerator"
|
||||||
|
],
|
||||||
|
"ewrs": [
|
||||||
|
"PatriotEwrGenerator"
|
||||||
|
],
|
||||||
|
"aircraft_carrier": [
|
||||||
|
"CVN_74_John_C__Stennis"
|
||||||
|
],
|
||||||
|
"helicopter_carrier": [
|
||||||
|
"LHA_1_Tarawa"
|
||||||
|
],
|
||||||
|
"destroyers": [
|
||||||
|
"OliverHazardPerryGroupGenerator"
|
||||||
|
],
|
||||||
|
"cruisers": [
|
||||||
|
"Ticonderoga_class"
|
||||||
|
],
|
||||||
|
"requirements": {},
|
||||||
|
"carrier_names": [
|
||||||
|
"CVN-71 Theodore Roosevelt"
|
||||||
|
],
|
||||||
|
"helicopter_carrier_names": [
|
||||||
|
"LHA-1 Tarawa",
|
||||||
|
"LHA-4 Nassau"
|
||||||
|
],
|
||||||
|
"navy_generators": [
|
||||||
|
"OliverHazardPerryGroupGenerator"
|
||||||
|
],
|
||||||
|
"has_jtac": true,
|
||||||
|
"jtac_unit": "MQ_9_Reaper"
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
"SA13Generator",
|
"SA13Generator",
|
||||||
"Tier2SA10Generator",
|
"Tier2SA10Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
|
|||||||
85
resources/factions/iraq_1991.json
Normal file
85
resources/factions/iraq_1991.json
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"country": "Iraq",
|
||||||
|
"name": "Iraq 1991",
|
||||||
|
"authors": "Hawkmoon",
|
||||||
|
"description": "<p>Iraq forces during desert Storm</p>",
|
||||||
|
"aircrafts": [
|
||||||
|
"MiG_19P",
|
||||||
|
"MiG_21Bis",
|
||||||
|
"MiG_23MLD",
|
||||||
|
"MiG_25PD",
|
||||||
|
"Su_17M4",
|
||||||
|
"Mi_8MT",
|
||||||
|
"Su-25",
|
||||||
|
"Su-24M",
|
||||||
|
"MiG_25PD",
|
||||||
|
"Tu_22M3",
|
||||||
|
"L_39C",
|
||||||
|
"L_39ZA",
|
||||||
|
"Mi_24V"
|
||||||
|
],
|
||||||
|
"awacs": [
|
||||||
|
"A_50"
|
||||||
|
],
|
||||||
|
"tankers": [
|
||||||
|
"IL_78M"
|
||||||
|
],
|
||||||
|
"frontline_units": [
|
||||||
|
"IFV_BMP_1",
|
||||||
|
"APC_MTLB",
|
||||||
|
"MBT_T_55",
|
||||||
|
"MBT_T_72B",
|
||||||
|
"APC_BTR_80",
|
||||||
|
"ARV_BRDM_2",
|
||||||
|
"SPH_2S1_Gvozdika"
|
||||||
|
],
|
||||||
|
"artillery_units": [
|
||||||
|
"MLRS_BM_21_Grad"
|
||||||
|
],
|
||||||
|
"logistics_units": [
|
||||||
|
"Transport_Ural_375",
|
||||||
|
"Transport_UAZ_469"
|
||||||
|
],
|
||||||
|
"infantry_units": [
|
||||||
|
"Paratrooper_AKS",
|
||||||
|
"Infantry_Soldier_Rus",
|
||||||
|
"Paratrooper_RPG_16",
|
||||||
|
"SAM_SA_18_Igla_MANPADS"
|
||||||
|
],
|
||||||
|
"air_defenses": [
|
||||||
|
"ColdWarFlakGenerator",
|
||||||
|
"EarlyColdWarFlakGenerator",
|
||||||
|
"SA2Generator",
|
||||||
|
"SA3Generator",
|
||||||
|
"SA6Generator",
|
||||||
|
"SA8Generator",
|
||||||
|
"SA9Generator",
|
||||||
|
"SA13Generator",
|
||||||
|
"ZSU23Generator",
|
||||||
|
"ZU23Generator",
|
||||||
|
"ZU23UralGenerator"
|
||||||
|
],
|
||||||
|
"ewrs": [
|
||||||
|
"BoxSpringGenerator"
|
||||||
|
],
|
||||||
|
"missiles": [
|
||||||
|
"ScudGenerator"
|
||||||
|
],
|
||||||
|
"missiles_group_count": 1,
|
||||||
|
"aircraft_carrier": [
|
||||||
|
],
|
||||||
|
"helicopter_carrier": [
|
||||||
|
],
|
||||||
|
"helicopter_carrier_names": [
|
||||||
|
],
|
||||||
|
"destroyers": [
|
||||||
|
],
|
||||||
|
"cruisers": [
|
||||||
|
],
|
||||||
|
"requirements": {},
|
||||||
|
"carrier_names": [
|
||||||
|
],
|
||||||
|
"navy_generators": [
|
||||||
|
"GrishaGroupGenerator"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -48,7 +48,8 @@
|
|||||||
"SA9Generator",
|
"SA9Generator",
|
||||||
"SA13Generator",
|
"SA13Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZSU23Generator"
|
"ZSU23Generator",
|
||||||
|
"ZSU57Generator"
|
||||||
],
|
],
|
||||||
"ewrs": [
|
"ewrs": [
|
||||||
"BoxSpringGenerator",
|
"BoxSpringGenerator",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"SA2Generator",
|
"SA2Generator",
|
||||||
"SA3Generator",
|
"SA3Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"SA11Generator",
|
"SA11Generator",
|
||||||
"ColdWarFlakGenerator",
|
"ColdWarFlakGenerator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
"SA9Generator",
|
"SA9Generator",
|
||||||
"SA13Generator",
|
"SA13Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -31,9 +31,10 @@
|
|||||||
"IFV_BMP_2",
|
"IFV_BMP_2",
|
||||||
"IFV_BMP_3",
|
"IFV_BMP_3",
|
||||||
"APC_BTR_80",
|
"APC_BTR_80",
|
||||||
|
"APC_BTR_82A",
|
||||||
"MBT_T_90",
|
"MBT_T_90",
|
||||||
"MBT_T_80U",
|
"MBT_T_80U",
|
||||||
"MBT_T_72B"
|
"MBT_T_72B3"
|
||||||
],
|
],
|
||||||
"artillery_units": [
|
"artillery_units": [
|
||||||
"MLRS_9K57_Uragan_BM_27",
|
"MLRS_9K57_Uragan_BM_27",
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
"SA3Generator",
|
"SA3Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator",
|
||||||
|
"ZSU57Generator"
|
||||||
],
|
],
|
||||||
"ewrs": [
|
"ewrs": [
|
||||||
"FlatFaceGenerator"
|
"FlatFaceGenerator"
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"SA3Generator",
|
"SA3Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
"ewrs": [
|
"ewrs": [
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"SA9Generator",
|
"SA9Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
|
"ZSU57Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator"
|
||||||
],
|
],
|
||||||
"ewrs": [
|
"ewrs": [
|
||||||
|
|||||||
@@ -46,7 +46,8 @@
|
|||||||
"SA13Generator",
|
"SA13Generator",
|
||||||
"ZSU23Generator",
|
"ZSU23Generator",
|
||||||
"ZU23Generator",
|
"ZU23Generator",
|
||||||
"ZU23UralGenerator"
|
"ZU23UralGenerator",
|
||||||
|
"ZSU57Generator"
|
||||||
],
|
],
|
||||||
"ewrs": [
|
"ewrs": [
|
||||||
"BoxSpringGenerator"
|
"BoxSpringGenerator"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import argparse
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import gettext
|
import gettext
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import textwrap
|
import textwrap
|
||||||
@@ -60,6 +61,7 @@ def convert_lua_frequency(raw: Union[float, int]) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def beacons_from_terrain(dcs_path: Path, path: Path) -> Iterable[Beacon]:
|
def beacons_from_terrain(dcs_path: Path, path: Path) -> Iterable[Beacon]:
|
||||||
|
logging.info(f"Loading terrain data from {path}")
|
||||||
# TODO: Fix case-sensitive issues.
|
# TODO: Fix case-sensitive issues.
|
||||||
# The beacons.lua file differs by case in some terrains. Will need to be
|
# The beacons.lua file differs by case in some terrains. Will need to be
|
||||||
# fixed if the tool is to be run on Linux, but presumably the server
|
# fixed if the tool is to be run on Linux, but presumably the server
|
||||||
@@ -84,13 +86,20 @@ def beacons_from_terrain(dcs_path: Path, path: Path) -> Iterable[Beacon]:
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""))
|
"""))
|
||||||
translator = gettext.translation(
|
|
||||||
"messages", path / "l10n", languages=["en"])
|
|
||||||
|
|
||||||
def translate(message_name: str) -> str:
|
try:
|
||||||
if not message_name:
|
translator = gettext.translation(
|
||||||
|
"messages", path / "l10n", languages=["en"])
|
||||||
|
|
||||||
|
def translate(message_name: str) -> str:
|
||||||
|
if not message_name:
|
||||||
|
return message_name
|
||||||
|
return translator.gettext(message_name)
|
||||||
|
except FileNotFoundError:
|
||||||
|
# TheChannel has no locale data for English.
|
||||||
|
def translate(message_name: str) -> str:
|
||||||
return message_name
|
return message_name
|
||||||
return translator.gettext(message_name)
|
|
||||||
bind_gettext(translate)
|
bind_gettext(translate)
|
||||||
|
|
||||||
src = beacons_lua.read_text()
|
src = beacons_lua.read_text()
|
||||||
@@ -148,7 +157,6 @@ class Importer:
|
|||||||
], indent=True))
|
], indent=True))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
"""Parses and returns command line arguments."""
|
"""Parses and returns command line arguments."""
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -175,6 +183,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Program entry point."""
|
"""Program entry point."""
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
Importer(args.dcs_path, args.export_to).run()
|
Importer(args.dcs_path, args.export_to).run()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user