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
|
||||
|
||||
# Features/Improvements
|
||||
## Features/Improvements
|
||||
* **[Campaign Map]** Overhauled the campaign model
|
||||
* **[Campaign Map]** Possible to add FOB as control points
|
||||
* **[Campaign Map]** Added off-map spawn locations
|
||||
* **[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"
|
||||
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
|
||||
* **[Mission Generator]** Infantry squads on frontline can have manpads
|
||||
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
|
||||
* **[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 BAI missions.
|
||||
* **[Flight Planner]** Added anti-ship missions.
|
||||
@@ -21,6 +49,7 @@
|
||||
* **[QOL]** On liberation startup, your latest save game is loaded automatically
|
||||
* **[Units]** Reduced starting fuel load for C101
|
||||
* **[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.
|
||||
|
||||
## Fixes :
|
||||
@@ -34,7 +63,7 @@
|
||||
|
||||
# 2.2.1
|
||||
|
||||
# Features/Improvements
|
||||
## Features/Improvements
|
||||
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
|
||||
* **[Factions]** Added map Persian Gulf full by Plob
|
||||
* **[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.APC_MTLB: 4,
|
||||
Armor.FDDM_Grad: 5,
|
||||
Armor.FDDM_Grad: 4,
|
||||
Armor.ARV_BRDM_2: 6,
|
||||
Armor.ARV_BTR_RD: 8,
|
||||
Armor.ARV_BTR_RD: 6,
|
||||
Armor.APC_BTR_80: 8,
|
||||
Armor.APC_BTR_82A: 10,
|
||||
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_90: 30,
|
||||
Armor.IFV_BMD_1: 8,
|
||||
@@ -481,11 +483,12 @@ PRICES = {
|
||||
AirDefence.SAM_Stinger_comm_dsr: 4,
|
||||
AirDefence.SAM_Stinger_comm: 4,
|
||||
AirDefence.SPAAA_ZSU_23_4_Shilka: 10,
|
||||
AirDefence.AAA_ZSU_57_2: 12,
|
||||
AirDefence.AAA_ZU_23_Closed: 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_on_Ural_375: 8,
|
||||
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 7,
|
||||
AirDefence.AAA_ZU_23_Insurgent: 6,
|
||||
AirDefence.SAM_SA_18_Igla_MANPADS: 10,
|
||||
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_82A,
|
||||
Armor.APC_BTR_82A,
|
||||
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_72B,
|
||||
Armor.MBT_T_72B,
|
||||
Armor.MBT_T_72B3,
|
||||
Armor.MBT_T_72B3,
|
||||
Armor.MBT_T_80U,
|
||||
Armor.MBT_T_80U,
|
||||
Armor.MBT_T_90,
|
||||
|
||||
15
game/game.py
15
game/game.py
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import logging
|
||||
import random
|
||||
import sys
|
||||
@@ -19,6 +20,7 @@ from gen.ato import AirTaskingOrder
|
||||
from gen.conflictgen import Conflict
|
||||
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from gen.flights.flight import FlightType
|
||||
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
||||
from . import persistency
|
||||
from .debriefing import Debriefing
|
||||
@@ -391,9 +393,16 @@ class Game:
|
||||
if cpoint is not None:
|
||||
points.append(cpoint)
|
||||
|
||||
for package in self.blue_ato.packages:
|
||||
points.append(package.target.position)
|
||||
for package in self.red_ato.packages:
|
||||
packages = itertools.chain(self.blue_ato.packages,
|
||||
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)
|
||||
|
||||
# Else 0,0, since we need a default value
|
||||
|
||||
@@ -199,10 +199,14 @@ class Operation:
|
||||
|
||||
@classmethod
|
||||
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_radio_registry(unique_map_frequencies)
|
||||
|
||||
assert cls.radio_registry is not None
|
||||
for frequency in unique_map_frequencies:
|
||||
cls.radio_registry.reserve(frequency)
|
||||
|
||||
@classmethod
|
||||
def assign_channels_to_flights(cls, flights: List[FlightData],
|
||||
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_am)
|
||||
unique_map_frequencies.add(data.atc.uhf)
|
||||
# No need to reserve ILS or TACAN because those are in the
|
||||
# beacon list.
|
||||
# No need to reserve ILS or TACAN because those are in the
|
||||
# beacon list.
|
||||
|
||||
@classmethod
|
||||
def _generate_ground_units(cls):
|
||||
|
||||
@@ -487,11 +487,11 @@ class ConflictTheater:
|
||||
for inclusion_zone in self.landmap[0]:
|
||||
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
||||
nearest_points.append(nearest_pair[1])
|
||||
min_distance = None # type: Optional[geometry.Point]
|
||||
nearest_point = None # type: Optional[geometry.Point]
|
||||
for pt in nearest_points:
|
||||
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
||||
nearest_point = nearest_points[0] # type: geometry.Point
|
||||
for pt in nearest_points[1:]:
|
||||
distance = point.distance(pt)
|
||||
if not min_distance or distance < min_distance:
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
nearest_point = pt
|
||||
assert isinstance(nearest_point, geometry.Point)
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
def _build_version_string() -> str:
|
||||
components = ["2.3.0"]
|
||||
components = ["2.3.2"]
|
||||
build_number_path = Path("resources/buildnumber")
|
||||
if build_number_path.exists():
|
||||
with build_number_path.open("r") as build_number_file:
|
||||
|
||||
@@ -20,20 +20,20 @@ from dcs.planes import (
|
||||
B_17G,
|
||||
B_52H,
|
||||
Bf_109K_4,
|
||||
C_101EB,
|
||||
C_101CC,
|
||||
C_101EB,
|
||||
FW_190A8,
|
||||
FW_190D9,
|
||||
F_14B,
|
||||
I_16,
|
||||
JF_17,
|
||||
Ju_88A4,
|
||||
PlaneType,
|
||||
P_47D_30,
|
||||
P_47D_30bl1,
|
||||
P_47D_40,
|
||||
P_51D,
|
||||
P_51D_30_NA,
|
||||
PlaneType,
|
||||
SpitfireLFMkIX,
|
||||
SpitfireLFMkIXCW,
|
||||
Su_33,
|
||||
@@ -59,16 +59,15 @@ from dcs.task import (
|
||||
OptReactOnThreat,
|
||||
OptRestrictJettison,
|
||||
OrbitAction,
|
||||
PinpointStrike,
|
||||
RunwayAttack,
|
||||
SEAD,
|
||||
StartCommand,
|
||||
Targets,
|
||||
Task,
|
||||
WeaponType,
|
||||
PinpointStrike,
|
||||
)
|
||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||
from dcs.translation import String
|
||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
|
||||
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.runways import RunwayData
|
||||
from .conflictgen import Conflict
|
||||
from .flights.flightplan import (
|
||||
CasFlightPlan,
|
||||
LoiterFlightPlan,
|
||||
@@ -108,7 +106,6 @@ from .flights.flightplan import (
|
||||
)
|
||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
||||
from .naming import namegen
|
||||
from .runways import RunwayAssigner
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -1349,7 +1346,7 @@ class AircraftConflictGenerator:
|
||||
|
||||
# And setting *our* waypoint TOT causes the takeoff time to show up in
|
||||
# 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
|
||||
# the briefing.
|
||||
self.flights[-1].departure_delay = start_time
|
||||
@@ -1756,15 +1753,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
||||
f"{flight_plan_type} does not define a patrol.")
|
||||
return waypoint
|
||||
|
||||
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)
|
||||
# NB: It's important that the engage task comes before the orbit task.
|
||||
# Though they're on the same waypoint, if the orbit task comes first it
|
||||
# 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
|
||||
# engage targets if available and orbit if they find nothing to shoot.
|
||||
|
||||
# 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
|
||||
@@ -1775,6 +1768,16 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
||||
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -73,10 +73,17 @@ class FlightPlan:
|
||||
"""Iterates over all waypoints in the flight plan, in order."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def edges(self) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
|
||||
def edges(
|
||||
self, until: Optional[FlightWaypoint] = None
|
||||
) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
|
||||
"""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,
|
||||
b: FlightWaypoint) -> int:
|
||||
@@ -137,7 +144,6 @@ class FlightPlan:
|
||||
"""Joker fuel value for the FlightPlan
|
||||
"""
|
||||
return self.bingo_fuel + 1000
|
||||
|
||||
|
||||
def max_distance_from(self, cp: ControlPoint) -> int:
|
||||
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
|
||||
@@ -156,26 +162,18 @@ class FlightPlan:
|
||||
"""
|
||||
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(
|
||||
self, destination: FlightWaypoint) -> timedelta:
|
||||
total = timedelta()
|
||||
for previous_waypoint, waypoint in self.edges:
|
||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
||||
waypoint)
|
||||
if waypoint == destination:
|
||||
break
|
||||
else:
|
||||
|
||||
if destination not in self.waypoints:
|
||||
raise PlanningError(
|
||||
f"Did not find destination waypoint {destination} in "
|
||||
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
|
||||
|
||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||
@@ -196,10 +194,59 @@ class FlightPlan:
|
||||
def dismiss_escort_at(self) -> Optional[FlightWaypoint]:
|
||||
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)
|
||||
class LoiterFlightPlan(FlightPlan):
|
||||
hold: FlightWaypoint
|
||||
hold_duration: timedelta
|
||||
|
||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||
raise NotImplementedError
|
||||
@@ -221,6 +268,17 @@ class LoiterFlightPlan(FlightPlan):
|
||||
return self.push_time
|
||||
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)
|
||||
class FormationFlightPlan(LoiterFlightPlan):
|
||||
@@ -254,7 +312,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
||||
all of its formation waypoints.
|
||||
"""
|
||||
speeds = []
|
||||
for previous_waypoint, waypoint in self.edges:
|
||||
for previous_waypoint, waypoint in self.edges():
|
||||
if waypoint in self.package_speed_waypoints:
|
||||
speeds.append(self.best_speed_between_waypoints(
|
||||
previous_waypoint, waypoint))
|
||||
@@ -486,7 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
"""The estimated time between the first waypoint and the target."""
|
||||
destination = self.tot_waypoint
|
||||
total = timedelta()
|
||||
for previous_waypoint, waypoint in self.edges:
|
||||
for previous_waypoint, waypoint in self.edges():
|
||||
if waypoint == self.tot_waypoint:
|
||||
# For anything strike-like the TOT waypoint is the *flight's*
|
||||
# mission target, but to synchronize with the rest of the
|
||||
@@ -846,6 +904,7 @@ class FlightPlanBuilder:
|
||||
lead_time=timedelta(minutes=5),
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=builder.hold(self._hold_point(flight)),
|
||||
hold_duration=timedelta(minutes=5),
|
||||
sweep_start=start,
|
||||
sweep_end=end,
|
||||
land=builder.land(flight.arrival),
|
||||
@@ -1050,6 +1109,7 @@ class FlightPlanBuilder:
|
||||
flight=flight,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=builder.hold(self._hold_point(flight)),
|
||||
hold_duration=timedelta(minutes=5),
|
||||
join=builder.join(self.package.waypoints.join),
|
||||
ingress=ingress,
|
||||
targets=[target],
|
||||
@@ -1196,6 +1256,7 @@ class FlightPlanBuilder:
|
||||
flight=flight,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=builder.hold(self._hold_point(flight)),
|
||||
hold_duration=timedelta(minutes=5),
|
||||
join=builder.join(self.package.waypoints.join),
|
||||
ingress=builder.ingress(ingress_type,
|
||||
self.package.waypoints.ingress, location),
|
||||
|
||||
@@ -89,68 +89,23 @@ class TravelTime:
|
||||
|
||||
# TODO: Most if not all of this should move into FlightPlan.
|
||||
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:
|
||||
self.package = package
|
||||
|
||||
def mission_start_time(self, flight: Flight) -> timedelta:
|
||||
takeoff_time = self.takeoff_time_for_flight(flight)
|
||||
if takeoff_time is None:
|
||||
@staticmethod
|
||||
def mission_start_time(flight: Flight) -> timedelta:
|
||||
startup_time = flight.flight_plan.startup_time()
|
||||
if startup_time is None:
|
||||
# Could not determine takeoff time, probably due to a custom flight
|
||||
# plan. Start immediately.
|
||||
return timedelta()
|
||||
|
||||
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
|
||||
return startup_time
|
||||
|
||||
def earliest_tot(self) -> timedelta:
|
||||
earliest_tot = max((
|
||||
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,
|
||||
# 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.
|
||||
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.
|
||||
|
||||
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
|
||||
if an ingress point cannot be found.
|
||||
"""
|
||||
time_to_target = self.travel_time_to_target(flight)
|
||||
if time_to_target is None:
|
||||
# Clear the TOT, calculate the startup time. Negating the result gives
|
||||
# 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}")
|
||||
# Return 0 so this flight's travel time does not affect the rest
|
||||
# of the package.
|
||||
return timedelta()
|
||||
# Account for TOT offsets for the flight plan. An offset of -2 minutes
|
||||
# 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
|
||||
return -time
|
||||
|
||||
@@ -12,6 +12,7 @@ from gen.ground_forces.combat_stance import CombatStance
|
||||
TYPE_TANKS = [
|
||||
Armor.MBT_T_55,
|
||||
Armor.MBT_T_72B,
|
||||
Armor.MBT_T_72B3,
|
||||
Armor.MBT_T_80U,
|
||||
Armor.MBT_T_90,
|
||||
Armor.MBT_Leopard_2,
|
||||
@@ -96,6 +97,7 @@ TYPE_APC = [
|
||||
Armor.APC_M1126_Stryker_ICV,
|
||||
Armor.APC_M113,
|
||||
Armor.APC_BTR_80,
|
||||
Armor.APC_BTR_82A,
|
||||
Armor.APC_MTLB,
|
||||
Armor.APC_M2A1,
|
||||
Armor.APC_Cobra,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Radio frequency types and allocators."""
|
||||
import itertools
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Iterator, List, Set
|
||||
|
||||
@@ -71,12 +72,9 @@ class Radio:
|
||||
self.minimum.hertz, self.maximum.hertz, self.step.hertz
|
||||
))
|
||||
|
||||
|
||||
class OutOfChannelsError(RuntimeError):
|
||||
"""Raised when all channels usable by this radio have been allocated."""
|
||||
|
||||
def __init__(self, radio: Radio) -> None:
|
||||
super().__init__(f"No available channels for {radio}")
|
||||
@property
|
||||
def last_channel(self) -> RadioFrequency:
|
||||
return RadioFrequency(self.maximum.hertz - self.step.hertz)
|
||||
|
||||
|
||||
class ChannelInUseError(RuntimeError):
|
||||
@@ -215,7 +213,13 @@ class RadioRegistry:
|
||||
self.reserve(channel)
|
||||
return channel
|
||||
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:
|
||||
"""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_flak18 import Flak18Generator
|
||||
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.airdefensegroupgenerator import (
|
||||
AirDefenseGroupGenerator,
|
||||
@@ -98,7 +99,8 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
|
||||
"ColdWarFlakGenerator": ColdWarFlakGenerator,
|
||||
"EarlyColdWarFlakGenerator": EarlyColdWarFlakGenerator,
|
||||
"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>" + \
|
||||
"<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>" + \
|
||||
"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>" \
|
||||
"<b>rp-</b> <i>for the pydcs 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.cp.unclaimed_parking(self.game_model.game) <= 0:
|
||||
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
|
||||
|
||||
super().buy(unit_type)
|
||||
|
||||
@@ -7,7 +7,6 @@ from PySide2.QtWidgets import QHeaderView, QTableView
|
||||
from game.utils import meter_to_feet
|
||||
from gen.ato import Package
|
||||
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
||||
from gen.flights.traveltime import TotEstimator
|
||||
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \
|
||||
QWaypointItem
|
||||
|
||||
@@ -74,9 +73,9 @@ class QFlightWaypointList(QTableView):
|
||||
time = timedelta(seconds=int(time.total_seconds()))
|
||||
return f"{prefix}T+{time}"
|
||||
|
||||
def takeoff_text(self, flight: Flight) -> str:
|
||||
estimator = TotEstimator(self.package)
|
||||
takeoff_time = estimator.takeoff_time_for_flight(flight)
|
||||
@staticmethod
|
||||
def takeoff_text(flight: Flight) -> str:
|
||||
takeoff_time = flight.flight_plan.takeoff_time()
|
||||
# Handle custom flight plans where we can't estimate the takeoff time.
|
||||
if takeoff_time is None:
|
||||
takeoff_time = timedelta()
|
||||
|
||||
@@ -116,7 +116,8 @@ class QFlightWaypointTab(QFrame):
|
||||
if not waypoints:
|
||||
return
|
||||
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.on_change()
|
||||
|
||||
@@ -124,7 +125,8 @@ class QFlightWaypointTab(QFrame):
|
||||
rtb = self.planner.generate_rtb_waypoint(self.flight,
|
||||
self.flight.from_cp)
|
||||
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.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": "",
|
||||
"callsign": "IBND",
|
||||
"beacon_type": 14,
|
||||
"hertz": 333800000,
|
||||
"hertz": 109900000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "IBND",
|
||||
"beacon_type": 15,
|
||||
"hertz": 333800000,
|
||||
"hertz": 109900000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
@@ -77,12 +77,19 @@
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
"name": "BandarEJask",
|
||||
"name": "JASK",
|
||||
"callsign": "JSK",
|
||||
"beacon_type": 9,
|
||||
"hertz": 349000000,
|
||||
"hertz": 349000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "JSK",
|
||||
"beacon_type": 5,
|
||||
"hertz": null,
|
||||
"channel": 110
|
||||
},
|
||||
{
|
||||
"name": "BandarLengeh",
|
||||
"callsign": "LEN",
|
||||
@@ -101,8 +108,8 @@
|
||||
"name": "",
|
||||
"callsign": "MMA",
|
||||
"beacon_type": 15,
|
||||
"hertz": 111100000,
|
||||
"channel": 48
|
||||
"hertz": 109100000,
|
||||
"channel": 28
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
@@ -115,28 +122,28 @@
|
||||
"name": "",
|
||||
"callsign": "IMA",
|
||||
"beacon_type": 15,
|
||||
"hertz": 109100000,
|
||||
"channel": 28
|
||||
"hertz": 111100000,
|
||||
"channel": 48
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "RMA",
|
||||
"beacon_type": 15,
|
||||
"hertz": 114900000,
|
||||
"hertz": 108700000,
|
||||
"channel": 24
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "MMA",
|
||||
"beacon_type": 14,
|
||||
"hertz": 111100000,
|
||||
"channel": 48
|
||||
"hertz": 109100000,
|
||||
"channel": 28
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "RMA",
|
||||
"beacon_type": 14,
|
||||
"hertz": 114900000,
|
||||
"hertz": 108700000,
|
||||
"channel": 24
|
||||
},
|
||||
{
|
||||
@@ -150,8 +157,8 @@
|
||||
"name": "",
|
||||
"callsign": "IMA",
|
||||
"beacon_type": 14,
|
||||
"hertz": 109100000,
|
||||
"channel": 28
|
||||
"hertz": 111100000,
|
||||
"channel": 48
|
||||
},
|
||||
{
|
||||
"name": "AlDhafra",
|
||||
@@ -332,7 +339,7 @@
|
||||
"name": "KishIsland",
|
||||
"callsign": "KIH",
|
||||
"beacon_type": 9,
|
||||
"hertz": 201000000,
|
||||
"hertz": 201000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
@@ -367,14 +374,14 @@
|
||||
"name": "LavanIsland",
|
||||
"callsign": "LVA",
|
||||
"beacon_type": 9,
|
||||
"hertz": 310000000,
|
||||
"hertz": 310000,
|
||||
"channel": 0
|
||||
},
|
||||
{
|
||||
"name": "LiwaAirbase",
|
||||
"callsign": "\u00c4\u00bc",
|
||||
"beacon_type": 7,
|
||||
"hertz": null,
|
||||
"callsign": "OMLW",
|
||||
"beacon_type": 6,
|
||||
"hertz": 117400000,
|
||||
"channel": 121
|
||||
},
|
||||
{
|
||||
@@ -433,13 +440,6 @@
|
||||
"hertz": 113600000,
|
||||
"channel": 83
|
||||
},
|
||||
{
|
||||
"name": "SasAlNakheelAirport",
|
||||
"callsign": "SAS",
|
||||
"beacon_type": 10,
|
||||
"hertz": 128925,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
"name": "SasAlNakheel",
|
||||
"callsign": "SAS",
|
||||
@@ -500,14 +500,14 @@
|
||||
"name": "",
|
||||
"callsign": "ISYZ",
|
||||
"beacon_type": 15,
|
||||
"hertz": 109900000,
|
||||
"hertz": 108500000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"callsign": "ISYZ",
|
||||
"beacon_type": 14,
|
||||
"hertz": 109900000,
|
||||
"hertz": 108500000,
|
||||
"channel": null
|
||||
},
|
||||
{
|
||||
@@ -556,7 +556,7 @@
|
||||
"name": "DezfulAirport",
|
||||
"callsign": "DZF",
|
||||
"beacon_type": 9,
|
||||
"hertz": 293000000,
|
||||
"hertz": 293000,
|
||||
"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",
|
||||
"Tier2SA10Generator",
|
||||
"ZSU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23Generator",
|
||||
"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",
|
||||
"SA13Generator",
|
||||
"ZU23Generator",
|
||||
"ZSU23Generator"
|
||||
"ZSU23Generator",
|
||||
"ZSU57Generator"
|
||||
],
|
||||
"ewrs": [
|
||||
"BoxSpringGenerator",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"SA2Generator",
|
||||
"SA3Generator",
|
||||
"ZSU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23Generator",
|
||||
"ZU23UralGenerator"
|
||||
],
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"SA11Generator",
|
||||
"ColdWarFlakGenerator",
|
||||
"ZSU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23Generator",
|
||||
"ZU23UralGenerator"
|
||||
],
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"SA9Generator",
|
||||
"SA13Generator",
|
||||
"ZSU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23Generator",
|
||||
"ZU23UralGenerator"
|
||||
],
|
||||
|
||||
@@ -31,9 +31,10 @@
|
||||
"IFV_BMP_2",
|
||||
"IFV_BMP_3",
|
||||
"APC_BTR_80",
|
||||
"APC_BTR_82A",
|
||||
"MBT_T_90",
|
||||
"MBT_T_80U",
|
||||
"MBT_T_72B"
|
||||
"MBT_T_72B3"
|
||||
],
|
||||
"artillery_units": [
|
||||
"MLRS_9K57_Uragan_BM_27",
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"SA3Generator",
|
||||
"ZSU23Generator",
|
||||
"ZU23Generator",
|
||||
"ZU23UralGenerator"
|
||||
"ZU23UralGenerator",
|
||||
"ZSU57Generator"
|
||||
],
|
||||
"ewrs": [
|
||||
"FlatFaceGenerator"
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"SA3Generator",
|
||||
"ZSU23Generator",
|
||||
"ZU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23UralGenerator"
|
||||
],
|
||||
"ewrs": [
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"SA9Generator",
|
||||
"ZSU23Generator",
|
||||
"ZU23Generator",
|
||||
"ZSU57Generator",
|
||||
"ZU23UralGenerator"
|
||||
],
|
||||
"ewrs": [
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
"SA13Generator",
|
||||
"ZSU23Generator",
|
||||
"ZU23Generator",
|
||||
"ZU23UralGenerator"
|
||||
"ZU23UralGenerator",
|
||||
"ZSU57Generator"
|
||||
],
|
||||
"ewrs": [
|
||||
"BoxSpringGenerator"
|
||||
|
||||
@@ -24,6 +24,7 @@ import argparse
|
||||
from contextlib import contextmanager
|
||||
import dataclasses
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
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]:
|
||||
logging.info(f"Loading terrain data from {path}")
|
||||
# TODO: Fix case-sensitive issues.
|
||||
# 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
|
||||
@@ -84,13 +86,20 @@ def beacons_from_terrain(dcs_path: Path, path: Path) -> Iterable[Beacon]:
|
||||
end
|
||||
|
||||
"""))
|
||||
translator = gettext.translation(
|
||||
"messages", path / "l10n", languages=["en"])
|
||||
|
||||
def translate(message_name: str) -> str:
|
||||
if not message_name:
|
||||
try:
|
||||
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 translator.gettext(message_name)
|
||||
|
||||
bind_gettext(translate)
|
||||
|
||||
src = beacons_lua.read_text()
|
||||
@@ -148,7 +157,6 @@ class Importer:
|
||||
], indent=True))
|
||||
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
"""Parses and returns command line arguments."""
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -175,6 +183,7 @@ def parse_args() -> argparse.Namespace:
|
||||
|
||||
def main() -> None:
|
||||
"""Program entry point."""
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
args = parse_args()
|
||||
Importer(args.dcs_path, args.export_to).run()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user