mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x
This commit is contained in:
commit
02ecfebb85
@ -4,11 +4,15 @@
|
||||
* **[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
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -2,7 +2,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
def _build_version_string() -> str:
|
||||
components = ["2.3.2"]
|
||||
components = ["2.3.3"]
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
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
|
||||
},
|
||||
{
|
||||
|
||||
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"
|
||||
]
|
||||
}
|
||||
@ -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()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user