Compare commits

...

31 Commits
2.3.0 ... 2.3.2

Author SHA1 Message Date
Dan Albert
3260260dce Move more SAMs off runways in Syria Full.
(cherry picked from commit e5bca224e9)
2020-12-19 12:30:38 -08:00
Dan Albert
70c1290993 Note fixed SAM placement in changelog. 2020-12-19 12:30:38 -08:00
Dan Albert
6bae60c51e Note Iraq 1991 faction in changelog.
(cherry picked from commit 8447c563ea)
2020-12-19 12:30:38 -08:00
Emanuele Garofalo
a45adb6b3a New Faction Iraq 1991
(cherry picked from commit fd61a4b23a)
2020-12-19 12:30:38 -08:00
Dan Albert
476aaf5d3e Update changelog for #616.
(cherry picked from commit 3e4bb88089)
2020-12-19 12:30:38 -08:00
Dan Albert
58187b6969 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616

(cherry picked from commit 2f3f53a978)
2020-12-19 12:30:38 -08:00
Dan Albert
f3a3d81d96 Update beacon data.
(cherry picked from commit 89755b1005)
2020-12-19 12:30:38 -08:00
Dan Albert
7a40b54153 Update beacon importer to handle TheChannel.
TheChannel doesn't have message catalogs for English.

(cherry picked from commit a7203ea90a)
2020-12-19 12:30:38 -08:00
Dan Albert
9dd62d3538 Improve TOT planning.
Moves all TOT planning into the FlightPlan to clean up specialized
behavior and improve planning characteristics.

The important part of this change is that flights are now planning to
the mission time for their flight rather than the package as a whole.
For example, a TARCAP is planned based on the time from startup to the
patrol point, a sweep is planned based on the time from startup to the
sween end point, and a strike flight is planned based on the time from
startup to the target location. TOT offsets can be handled within the
flight plan.

As another benefit of theis cleanup, flights without hold points no
longer account for the hold time in their planning, so those flights are
planned to reach their targets sooner.

As a follow up TotEstimator can be removed, but I want to keep this low
impact for 2.3.2.

Fixes https://github.com/Khopa/dcs_liberation/issues/593

(cherry picked from commit 745dfc71bc)
2020-12-19 12:30:38 -08:00
walterroach
76e4a6ed83 Fix bad air defense location #617 2020-12-19 12:30:38 -08:00
Dan Albert
7a9eb06677 Add missing 2.3.2 change to changelog.
(cherry picked from commit 4eac743812)
2020-12-19 12:30:38 -08:00
Dan Albert
26f54e7619 Fix adding custom waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/604

(cherry picked from commit 296e6e8e8f)
2020-12-19 12:30:38 -08:00
Khopa
117b7ae414 Changelog update 2020-12-19 12:30:38 -08:00
Khopa
baeac324d6 Updated version string 2020-12-19 12:30:38 -08:00
Khopa
0db0f003dc Added ZSU-57 sites 2020-12-19 12:30:38 -08:00
Khopa
2d4f341710 T72B3 and BTR-82A support 2020-12-19 12:30:38 -08:00
Khopa
b8a41dc937 pydcs version update to include new data export 2020-12-19 12:30:38 -08:00
walterroach
2f2bb0de4f Fix Ground units ... don't move forward #601 2020-12-19 12:30:38 -08:00
Dan Albert
3b76d7f47e Fail gracefully when out of radio channels.
Fixes https://github.com/Khopa/dcs_liberation/issues/598

(cherry picked from commit 498af28efb)
2020-12-19 12:30:38 -08:00
Dan Albert
10b74e507f Don't exclude BARCAP targets from culling.
Fixes https://github.com/Khopa/dcs_liberation/issues/597

(cherry picked from commit b9ade2295e)
2020-12-19 12:30:38 -08:00
C. Perreau
44b5f5a919 Merge pull request #596 from Khopa/develop_2_3_x
Release 2.3.1
2020-12-16 21:45:30 +01:00
Khopa
17d37494c2 2.3.1 changelog 2020-12-16 21:14:25 +01:00
Khopa
f0d81e98a0 About dialog update 2020-12-16 21:12:01 +01:00
Khopa
e3b13f7b4a UX : Display a warning message when attempting to buy more aircraft at an already full airfield. 2020-12-16 21:08:48 +01:00
Khopa
ba2686630a Updated version number 2020-12-16 20:51:36 +01:00
Emanuele Garofalo
e195cfa6a0 Replaced previous Syria full map by the new version by Hawkmoon 2020-12-16 20:49:03 +01:00
Emanuele Garofalo
b9fbd1906f Add files via upload
Fixed NATO DESERT STORM FACTIO
2020-12-16 20:47:04 +01:00
Emanuele Garofalo
1f611bafef Add files via upload
New Syria Full map Remastered
2020-12-16 20:46:54 +01:00
walterroach
69096b15ae Fix broken Full Caucasus Map frontline 2020-12-16 09:06:51 -06:00
Dan Albert
a075e62bad Fix easy going CAPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/592

(cherry picked from commit 1ebe367e07)
2020-12-15 22:48:21 -08:00
Khopa
db229f25bf Changelog update 2020-12-16 00:28:59 +01:00
37 changed files with 675 additions and 583 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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:

View 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

View File

@@ -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),

View File

@@ -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

View File

@@ -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,

View File

@@ -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
View 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

View File

@@ -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

Submodule pydcs updated: c9751f54e0...edc87fab1d

View File

@@ -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/>"\

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"
]
]
}

View 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"
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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
},
{

View 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"
}

View File

@@ -48,6 +48,7 @@
"SA13Generator",
"Tier2SA10Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View 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"
]
}

View File

@@ -48,7 +48,8 @@
"SA9Generator",
"SA13Generator",
"ZU23Generator",
"ZSU23Generator"
"ZSU23Generator",
"ZSU57Generator"
],
"ewrs": [
"BoxSpringGenerator",

View File

@@ -39,6 +39,7 @@
"SA2Generator",
"SA3Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -38,6 +38,7 @@
"SA11Generator",
"ColdWarFlakGenerator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -49,6 +49,7 @@
"SA9Generator",
"SA13Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -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",

View File

@@ -38,7 +38,8 @@
"SA3Generator",
"ZSU23Generator",
"ZU23Generator",
"ZU23UralGenerator"
"ZU23UralGenerator",
"ZSU57Generator"
],
"ewrs": [
"FlatFaceGenerator"

View File

@@ -42,6 +42,7 @@
"SA3Generator",
"ZSU23Generator",
"ZU23Generator",
"ZSU57Generator",
"ZU23UralGenerator"
],
"ewrs": [

View File

@@ -42,6 +42,7 @@
"SA9Generator",
"ZSU23Generator",
"ZU23Generator",
"ZSU57Generator",
"ZU23UralGenerator"
],
"ewrs": [

View File

@@ -46,7 +46,8 @@
"SA13Generator",
"ZSU23Generator",
"ZU23Generator",
"ZU23UralGenerator"
"ZU23UralGenerator",
"ZSU57Generator"
],
"ewrs": [
"BoxSpringGenerator"

View File

@@ -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()