mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Merge branch 'develop' into add-e2c-to-more-factions
This commit is contained in:
commit
ca7a86b6d7
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -28,7 +28,8 @@ We will usually need more information for debugging. Include as much of the foll
|
||||
|
||||
- DCS Liberation save file (the `.liberation` file you save from the DCS Liberation window). By default these are located in your DCS saved games directory (`%USERPROFILE%/Saved Games/DCS`).
|
||||
- The generated mission file (the `.miz` file that you load in DCS to play the turn). By default these are located in your missions directory (`%USERPROFILE%/Saved Games/DCS/Missions`).
|
||||
- A tacview track file, especially when demonstrating an issue with AI behavior. By default these are locaed in your Tacview tracks directory (`%USERPROFILE%/Documents/Tacview`).
|
||||
- A tacview track file, especially when demonstrating an issue with AI behavior. By default these are located in your Tacview tracks directory (`%USERPROFILE%/Documents/Tacview`).
|
||||
- The state.json file from the finished mission when the problem is related to results processing. By default these are located in your Liberation install directory.
|
||||
|
||||
**Version information (please complete the following information):**
|
||||
- DCS Liberation [e.g. 2.3.1]:
|
||||
|
||||
@ -8,6 +8,9 @@ Saves from 2.4 are not compatible with 2.5.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Flight Planner]** Front lines now project threat zones, so TARCAP/escorts will not be pruned for flights near the front. Packages may also route around the front line when practical.
|
||||
* **[Flight Planner]** Fixed error when planning BAI at SAMs with dead subgroups.
|
||||
|
||||
# 2.4.3
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
@ -180,6 +180,7 @@ from pydcs_extensions.su57.su57 import Su_57
|
||||
UNITINFOTEXT_PATH = Path("./resources/units/unit_info_text.json")
|
||||
|
||||
plane_map["A-4E-C"] = A_4E_C
|
||||
plane_map["F-22A"] = F_22A
|
||||
plane_map["MB-339PAN"] = MB_339PAN
|
||||
plane_map["Rafale_M"] = Rafale_M
|
||||
plane_map["Rafale_A_S"] = Rafale_A_S
|
||||
|
||||
@ -21,6 +21,10 @@ from game.threatzones import ThreatZones
|
||||
from game.utils import nautical_miles
|
||||
|
||||
|
||||
class NavMeshError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class NavMeshPoly:
|
||||
def __init__(self, ident: int, poly: Polygon, threatened: bool) -> None:
|
||||
self.ident = ident
|
||||
@ -125,7 +129,7 @@ class NavMesh:
|
||||
path.append(current.world_point)
|
||||
previous = came_from[current]
|
||||
if previous is None:
|
||||
raise RuntimeError(
|
||||
raise NavMeshError(
|
||||
f"Could not reconstruct path to {destination} from {origin}"
|
||||
)
|
||||
current = previous
|
||||
@ -140,10 +144,12 @@ class NavMesh:
|
||||
def shortest_path(self, origin: Point, destination: Point) -> List[Point]:
|
||||
origin_poly = self.localize(origin)
|
||||
if origin_poly is None:
|
||||
raise ValueError(f"Origin point {origin} is outside the navmesh")
|
||||
raise NavMeshError(f"Origin point {origin} is outside the navmesh")
|
||||
destination_poly = self.localize(destination)
|
||||
if destination_poly is None:
|
||||
raise ValueError(f"Origin point {destination} is outside the navmesh")
|
||||
raise NavMeshError(
|
||||
f"Destination point {destination} is outside the navmesh"
|
||||
)
|
||||
|
||||
return self._shortest_path(
|
||||
NavPoint(self.dcs_to_shapely_point(origin), origin_poly),
|
||||
@ -203,7 +209,7 @@ class NavMesh:
|
||||
# threatened airbases at the map edges have room to retreat from the
|
||||
# threat without running off the navmesh.
|
||||
return box(*LineString(points).bounds).buffer(
|
||||
nautical_miles(100).meters, resolution=1
|
||||
nautical_miles(200).meters, resolution=1
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -166,6 +166,7 @@ class Operation:
|
||||
airgen: AircraftConflictGenerator,
|
||||
):
|
||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
||||
|
||||
gens: List[MissionInfoGenerator] = [
|
||||
KneeboardGenerator(cls.current_mission, cls.game),
|
||||
BriefingGenerator(cls.current_mission, cls.game),
|
||||
@ -177,9 +178,8 @@ class Operation:
|
||||
for tanker in airsupportgen.air_support.tankers:
|
||||
gen.add_tanker(tanker)
|
||||
|
||||
if cls.player_awacs_enabled:
|
||||
for awacs in airsupportgen.air_support.awacs:
|
||||
gen.add_awacs(awacs)
|
||||
for aewc in airsupportgen.air_support.awacs:
|
||||
gen.add_awacs(aewc)
|
||||
|
||||
for jtac in jtacs:
|
||||
gen.add_jtac(jtac)
|
||||
@ -378,7 +378,9 @@ class Operation:
|
||||
cls.game,
|
||||
cls.radio_registry,
|
||||
cls.unit_map,
|
||||
air_support=cls.airsupportgen.air_support,
|
||||
)
|
||||
|
||||
cls.airgen.clear_parking_slots()
|
||||
|
||||
cls.airgen.generate_flights(
|
||||
|
||||
@ -31,6 +31,7 @@ class Settings:
|
||||
automate_aircraft_reinforcements: bool = False
|
||||
restrict_weapons_by_date: bool = False
|
||||
disable_legacy_aewc: bool = False
|
||||
generate_dark_kneeboard: bool = False
|
||||
|
||||
# Performance oriented
|
||||
perf_red_alert_state: bool = True
|
||||
|
||||
@ -15,6 +15,7 @@ from shapely.ops import nearest_points, unary_union
|
||||
|
||||
from game.theater import ControlPoint
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from gen import Conflict
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from gen.flights.flight import Flight
|
||||
|
||||
@ -131,7 +132,7 @@ class ThreatZones:
|
||||
zone belongs to the player, it is the zone that will be avoided by
|
||||
the enemy and vice versa.
|
||||
"""
|
||||
airbases = []
|
||||
air_threats = []
|
||||
air_defenses = []
|
||||
for control_point in game.theater.controlpoints:
|
||||
if control_point.captured != player:
|
||||
@ -139,7 +140,7 @@ class ThreatZones:
|
||||
if control_point.runway_is_operational():
|
||||
point = ShapelyPoint(control_point.position.x, control_point.position.y)
|
||||
cap_threat_range = cls.barcap_threat_range(game, control_point)
|
||||
airbases.append(point.buffer(cap_threat_range.meters))
|
||||
air_threats.append(point.buffer(cap_threat_range.meters))
|
||||
|
||||
for tgo in control_point.ground_objects:
|
||||
for group in tgo.groups:
|
||||
@ -151,8 +152,25 @@ class ThreatZones:
|
||||
threat_zone = point.buffer(threat_range.meters)
|
||||
air_defenses.append(threat_zone)
|
||||
|
||||
for front_line in game.theater.conflicts(player):
|
||||
vector = Conflict.frontline_vector(
|
||||
front_line.control_point_a, front_line.control_point_b, game.theater
|
||||
)
|
||||
|
||||
start = vector[0]
|
||||
end = vector[0].point_from_heading(vector[1], vector[2])
|
||||
|
||||
line = LineString(
|
||||
[
|
||||
ShapelyPoint(start.x, start.y),
|
||||
ShapelyPoint(end.x, end.y),
|
||||
]
|
||||
)
|
||||
doctrine = game.faction_for(player).doctrine
|
||||
air_threats.append(line.buffer(doctrine.cap_engagement_range.meters))
|
||||
|
||||
return cls(
|
||||
airbases=unary_union(airbases), air_defenses=unary_union(air_defenses)
|
||||
airbases=unary_union(air_threats), air_defenses=unary_union(air_defenses)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union
|
||||
@ -67,6 +67,8 @@ from dcs.task import (
|
||||
Targets,
|
||||
Task,
|
||||
WeaponType,
|
||||
AWACSTaskAction,
|
||||
SetFrequencyCommand,
|
||||
)
|
||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||
@ -88,7 +90,6 @@ from game.theater.controlpoint import (
|
||||
from game.theater.theatergroundobject import TheaterGroundObject
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from gen.airsupportgen import AirSupport
|
||||
from gen.ato import AirTaskingOrder, Package
|
||||
from gen.callsigns import create_group_callsign_from_unit
|
||||
from gen.flights.flight import (
|
||||
@ -104,9 +105,12 @@ from .flights.flightplan import (
|
||||
LoiterFlightPlan,
|
||||
PatrollingFlightPlan,
|
||||
SweepFlightPlan,
|
||||
AwacsFlightPlan,
|
||||
)
|
||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
||||
from .naming import namegen
|
||||
from .airsupportgen import AirSupport, AwacsInfo
|
||||
from .callsigns import callsign_for_support_unit
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@ -652,6 +656,12 @@ AIRCRAFT_DATA: Dict[str, AircraftData] = {
|
||||
),
|
||||
channel_namer=HueyChannelNamer,
|
||||
),
|
||||
"F-22A": AircraftData(
|
||||
inter_flight_radio=get_radio("SCR-522"),
|
||||
intra_flight_radio=get_radio("SCR-522"),
|
||||
channel_allocator=None,
|
||||
channel_namer=SCR522ChannelNamer,
|
||||
),
|
||||
}
|
||||
AIRCRAFT_DATA["A-10C_2"] = AIRCRAFT_DATA["A-10C"]
|
||||
AIRCRAFT_DATA["P-51D-30-NA"] = AIRCRAFT_DATA["P-51D"]
|
||||
@ -666,6 +676,7 @@ class AircraftConflictGenerator:
|
||||
game: Game,
|
||||
radio_registry: RadioRegistry,
|
||||
unit_map: UnitMap,
|
||||
air_support: AirSupport,
|
||||
) -> None:
|
||||
self.m = mission
|
||||
self.game = game
|
||||
@ -673,6 +684,7 @@ class AircraftConflictGenerator:
|
||||
self.radio_registry = radio_registry
|
||||
self.unit_map = unit_map
|
||||
self.flights: List[FlightData] = []
|
||||
self.air_support = air_support
|
||||
|
||||
@cached_property
|
||||
def use_client(self) -> bool:
|
||||
@ -787,7 +799,10 @@ class AircraftConflictGenerator:
|
||||
OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
|
||||
)
|
||||
|
||||
channel = self.get_intra_flight_channel(unit_type)
|
||||
if flight.flight_type == FlightType.AEWC:
|
||||
channel = self.radio_registry.alloc_uhf()
|
||||
else:
|
||||
channel = self.get_intra_flight_channel(unit_type)
|
||||
group.set_frequency(channel.mhz)
|
||||
|
||||
divert = None
|
||||
@ -824,6 +839,20 @@ class AircraftConflictGenerator:
|
||||
if unit_type in [Su_33, C_101EB, C_101CC]:
|
||||
self.set_reduced_fuel(flight, group, unit_type)
|
||||
|
||||
if isinstance(flight.flight_plan, AwacsFlightPlan):
|
||||
callsign = callsign_for_support_unit(group)
|
||||
|
||||
self.air_support.awacs.append(
|
||||
AwacsInfo(
|
||||
dcsGroupName=str(group.name),
|
||||
callsign=callsign,
|
||||
freq=channel,
|
||||
depature_location=flight.departure.name,
|
||||
end_time=flight.flight_plan.mission_departure_time,
|
||||
start_time=flight.flight_plan.mission_start_time,
|
||||
)
|
||||
)
|
||||
|
||||
def _generate_at_airport(
|
||||
self,
|
||||
name: str,
|
||||
@ -1356,7 +1385,16 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = AWACS.name
|
||||
|
||||
if not isinstance(flight.flight_plan, AwacsFlightPlan):
|
||||
logging.error(
|
||||
f"Cannot configure AEW&C tasks for {flight} because it does not have an AEW&C flight plan."
|
||||
)
|
||||
return
|
||||
|
||||
self._setup_group(group, AWACS, package, flight, dynamic_runways)
|
||||
|
||||
# Awacs task action
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1364,6 +1402,8 @@ class AircraftConflictGenerator:
|
||||
restrict_jettison=True,
|
||||
)
|
||||
|
||||
group.points[0].tasks.append(AWACSTaskAction())
|
||||
|
||||
def configure_escort(
|
||||
self,
|
||||
group: FlyingGroup,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Type, Tuple
|
||||
from datetime import timedelta
|
||||
from typing import List, Type, Tuple, Optional
|
||||
|
||||
from dcs.mission import Mission, StartType
|
||||
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135
|
||||
@ -37,6 +38,9 @@ class AwacsInfo:
|
||||
dcsGroupName: str
|
||||
callsign: str
|
||||
freq: RadioFrequency
|
||||
depature_location: Optional[str]
|
||||
start_time: Optional[timedelta]
|
||||
end_time: Optional[timedelta]
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -192,9 +196,12 @@ class AirSupportConflictGenerator:
|
||||
|
||||
self.air_support.awacs.append(
|
||||
AwacsInfo(
|
||||
str(awacs_flight.name),
|
||||
callsign_for_support_unit(awacs_flight),
|
||||
freq,
|
||||
dcsGroupName=str(awacs_flight.name),
|
||||
callsign=callsign_for_support_unit(awacs_flight),
|
||||
freq=freq,
|
||||
depature_location=None,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
||||
@ -20,6 +20,7 @@ from .ground_forces.combat_stance import CombatStance
|
||||
from .radios import RadioFrequency
|
||||
from .runways import RunwayData
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
@ -88,6 +88,7 @@ from dcs.planes import (
|
||||
Tu_22M3,
|
||||
Tu_95MS,
|
||||
WingLoong_I,
|
||||
I_16,
|
||||
)
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
|
||||
@ -713,6 +713,10 @@ class AwacsFlightPlan(LoiterFlightPlan):
|
||||
if self.divert is not None:
|
||||
yield self.divert
|
||||
|
||||
@property
|
||||
def mission_start_time(self) -> Optional[timedelta]:
|
||||
return self.takeoff_time()
|
||||
|
||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
if waypoint == self.hold:
|
||||
return self.package.time_over_target
|
||||
@ -796,7 +800,17 @@ class FlightPlanBuilder:
|
||||
raise RuntimeError("Flight must be a part of the package")
|
||||
if self.package.waypoints is None:
|
||||
self.regenerate_package_waypoints()
|
||||
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
|
||||
|
||||
from game.navmesh import NavMeshError
|
||||
|
||||
try:
|
||||
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
|
||||
except NavMeshError as ex:
|
||||
color = "blue" if self.is_player else "red"
|
||||
raise PlanningError(
|
||||
f"Could not plan {color} {flight.flight_type.value} from "
|
||||
f"{flight.departure} to {flight.package.target}"
|
||||
) from ex
|
||||
|
||||
def generate_flight_plan(
|
||||
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||
@ -1013,7 +1027,8 @@ class FlightPlanBuilder:
|
||||
|
||||
targets: List[StrikeTarget] = []
|
||||
for group in location.groups:
|
||||
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
if group.units:
|
||||
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||
|
||||
@ -14,7 +14,7 @@ from typing import (
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.unit import Unit
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
from dcs.unitgroup import Group, VehicleGroup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@ -32,7 +32,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType
|
||||
@dataclass(frozen=True)
|
||||
class StrikeTarget:
|
||||
name: str
|
||||
target: Union[VehicleGroup, TheaterGroundObject, Unit]
|
||||
target: Union[VehicleGroup, TheaterGroundObject, Unit, Group]
|
||||
|
||||
|
||||
class WaypointBuilder:
|
||||
|
||||
@ -41,6 +41,7 @@ from .flights.flight import FlightWaypoint, FlightWaypointType
|
||||
from .radios import RadioFrequency
|
||||
from .runways import RunwayData
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
@ -48,8 +49,16 @@ if TYPE_CHECKING:
|
||||
class KneeboardPageWriter:
|
||||
"""Creates kneeboard images."""
|
||||
|
||||
def __init__(self, page_margin: int = 24, line_spacing: int = 12) -> None:
|
||||
self.image = Image.new("RGB", (768, 1024), (0xFF, 0xFF, 0xFF))
|
||||
def __init__(
|
||||
self, page_margin: int = 24, line_spacing: int = 12, dark_theme: bool = False
|
||||
) -> None:
|
||||
if dark_theme:
|
||||
self.foreground_fill = (215, 200, 200)
|
||||
self.background_fill = (10, 5, 5)
|
||||
else:
|
||||
self.foreground_fill = (15, 15, 15)
|
||||
self.background_fill = (255, 252, 252)
|
||||
self.image = Image.new("RGB", (768, 1024), self.background_fill)
|
||||
# These font sizes create a relatively full page for current sorties. If
|
||||
# we start generating more complicated flight plans, or start including
|
||||
# more information in the comm ladder (the latter of which we should
|
||||
@ -79,10 +88,10 @@ class KneeboardPageWriter:
|
||||
self.y += height + self.line_spacing
|
||||
|
||||
def title(self, title: str) -> None:
|
||||
self.text(title, font=self.title_font)
|
||||
self.text(title, font=self.title_font, fill=self.foreground_fill)
|
||||
|
||||
def heading(self, text: str) -> None:
|
||||
self.text(text, font=self.heading_font)
|
||||
self.text(text, font=self.heading_font, fill=self.foreground_fill)
|
||||
|
||||
def table(
|
||||
self, cells: List[List[str]], headers: Optional[List[str]] = None
|
||||
@ -90,7 +99,7 @@ class KneeboardPageWriter:
|
||||
if headers is None:
|
||||
headers = []
|
||||
table = tabulate(cells, headers=headers, numalign="right")
|
||||
self.text(table, font=self.table_font)
|
||||
self.text(table, font=self.table_font, fill=self.foreground_fill)
|
||||
|
||||
def write(self, path: Path) -> None:
|
||||
self.image.save(path)
|
||||
@ -237,6 +246,7 @@ class BriefingPage(KneeboardPage):
|
||||
tankers: List[TankerInfo],
|
||||
jtacs: List[JtacInfo],
|
||||
start_time: datetime.datetime,
|
||||
dark_kneeboard: bool,
|
||||
) -> None:
|
||||
self.flight = flight
|
||||
self.comms = list(comms)
|
||||
@ -244,10 +254,11 @@ class BriefingPage(KneeboardPage):
|
||||
self.tankers = tankers
|
||||
self.jtacs = jtacs
|
||||
self.start_time = start_time
|
||||
self.dark_kneeboard = dark_kneeboard
|
||||
self.comms.append(CommInfo("Flight", self.flight.intra_flight_channel))
|
||||
|
||||
def write(self, path: Path) -> None:
|
||||
writer = KneeboardPageWriter()
|
||||
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
|
||||
if self.flight.custom_name is not None:
|
||||
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
|
||||
else:
|
||||
@ -285,6 +296,34 @@ class BriefingPage(KneeboardPage):
|
||||
["Bingo", "Joker"],
|
||||
)
|
||||
|
||||
# AEW&C
|
||||
writer.heading("AEW&C")
|
||||
aewc_ladder = []
|
||||
|
||||
for single_aewc in self.awacs:
|
||||
|
||||
if single_aewc.depature_location is None:
|
||||
dep = "-"
|
||||
arr = "-"
|
||||
else:
|
||||
dep = self._format_time(single_aewc.start_time)
|
||||
arr = self._format_time(single_aewc.end_time)
|
||||
|
||||
aewc_ladder.append(
|
||||
[
|
||||
str(single_aewc.callsign),
|
||||
str(single_aewc.freq),
|
||||
str(single_aewc.depature_location),
|
||||
str(dep),
|
||||
str(arr),
|
||||
]
|
||||
)
|
||||
|
||||
writer.table(
|
||||
aewc_ladder,
|
||||
headers=["Callsign", "FREQ", "Depature", "ETD", "ETA"],
|
||||
)
|
||||
|
||||
# Package Section
|
||||
writer.heading("Comm ladder")
|
||||
comm_ladder = []
|
||||
@ -293,10 +332,6 @@ class BriefingPage(KneeboardPage):
|
||||
[comm.name, "", "", "", self.format_frequency(comm.freq)]
|
||||
)
|
||||
|
||||
for a in self.awacs:
|
||||
comm_ladder.append(
|
||||
[a.callsign, "AWACS", "", "", self.format_frequency(a.freq)]
|
||||
)
|
||||
for tanker in self.tankers:
|
||||
comm_ladder.append(
|
||||
[
|
||||
@ -365,12 +400,21 @@ class BriefingPage(KneeboardPage):
|
||||
channel_name = namer.channel_name(channel.radio_id, channel.channel)
|
||||
return f"{channel_name} {frequency}"
|
||||
|
||||
def _format_time(self, time: Optional[datetime.timedelta]) -> str:
|
||||
if time is None:
|
||||
return ""
|
||||
local_time = self.start_time + time
|
||||
return local_time.strftime(f"%H:%M:%S")
|
||||
|
||||
|
||||
class KneeboardGenerator(MissionInfoGenerator):
|
||||
"""Creates kneeboard pages for each client flight in the mission."""
|
||||
|
||||
def __init__(self, mission: Mission, game: "Game") -> None:
|
||||
super().__init__(mission, game)
|
||||
self.dark_kneeboard = self.game.settings.generate_dark_kneeboard and (
|
||||
self.mission.start_time.hour > 19 or self.mission.start_time.hour < 7
|
||||
)
|
||||
|
||||
def generate(self) -> None:
|
||||
"""Generates a kneeboard per client flight."""
|
||||
@ -414,5 +458,6 @@ class KneeboardGenerator(MissionInfoGenerator):
|
||||
self.tankers,
|
||||
self.jtacs,
|
||||
self.mission.start_time,
|
||||
self.dark_kneeboard,
|
||||
),
|
||||
]
|
||||
|
||||
2
pydcs
2
pydcs
@ -1 +1 @@
|
||||
Subproject commit 5ffae3c76b99610ab5065c7317a8a5c72c7e4afb
|
||||
Subproject commit 42de2ec352903d592ca123950b4b12a15ffa6544
|
||||
@ -495,6 +495,7 @@ class QLiberationMap(QGraphicsView):
|
||||
package = Package(target)
|
||||
flight = Flight(
|
||||
package,
|
||||
self.game.player_country if player else self.game.enemy_country,
|
||||
F_16C_50,
|
||||
2,
|
||||
task,
|
||||
@ -914,35 +915,48 @@ class QLiberationMap(QGraphicsView):
|
||||
SMALL_LINE = 2
|
||||
|
||||
dist = self.distance_to_pixels(nautical_miles(scale_distance_nm))
|
||||
self.scene().addRect(
|
||||
POS_X,
|
||||
POS_Y - PADDING,
|
||||
PADDING * 2 + dist,
|
||||
BIG_LINE * 2 + 3 * PADDING,
|
||||
pen=CONST.COLORS["black"],
|
||||
brush=CONST.COLORS["black"],
|
||||
)
|
||||
l = self.scene().addLine(
|
||||
POS_X + PADDING,
|
||||
POS_Y + BIG_LINE * 2,
|
||||
POS_X + PADDING + dist,
|
||||
POS_Y + BIG_LINE * 2,
|
||||
)
|
||||
l.setPen(CONST.COLORS["black"])
|
||||
|
||||
lw = self.scene().addLine(
|
||||
POS_X + PADDING + 1,
|
||||
POS_Y + BIG_LINE * 2 + 1,
|
||||
POS_X + PADDING + dist + 1,
|
||||
POS_Y + BIG_LINE * 2 + 1,
|
||||
)
|
||||
lw.setPen(CONST.COLORS["white"])
|
||||
|
||||
text = self.scene().addText(
|
||||
"0nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False)
|
||||
)
|
||||
text.setPos(POS_X, POS_Y + BIG_LINE * 2)
|
||||
text.setDefaultTextColor(Qt.white)
|
||||
text.setDefaultTextColor(Qt.black)
|
||||
|
||||
text_white = self.scene().addText(
|
||||
"0nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False)
|
||||
)
|
||||
text_white.setPos(POS_X + 1, POS_Y + BIG_LINE * 2)
|
||||
text_white.setDefaultTextColor(Qt.white)
|
||||
|
||||
text2 = self.scene().addText(
|
||||
str(scale_distance_nm) + "nm",
|
||||
font=QFont("Trebuchet MS", 6, weight=5, italic=False),
|
||||
)
|
||||
text2.setPos(POS_X + dist, POS_Y + BIG_LINE * 2)
|
||||
text2.setDefaultTextColor(Qt.white)
|
||||
text2.setDefaultTextColor(Qt.black)
|
||||
|
||||
text2_white = self.scene().addText(
|
||||
str(scale_distance_nm) + "nm",
|
||||
font=QFont("Trebuchet MS", 6, weight=5, italic=False),
|
||||
)
|
||||
text2_white.setPos(POS_X + dist + 1, POS_Y + BIG_LINE * 2)
|
||||
text2_white.setDefaultTextColor(Qt.white)
|
||||
|
||||
l.setPen(CONST.COLORS["white"])
|
||||
for i in range(number_of_points + 1):
|
||||
d = float(i) / float(number_of_points)
|
||||
if i == 0 or i == number_of_points:
|
||||
@ -956,7 +970,15 @@ class QLiberationMap(QGraphicsView):
|
||||
POS_X + PADDING + d * dist,
|
||||
POS_Y + BIG_LINE - h,
|
||||
)
|
||||
l.setPen(CONST.COLORS["white"])
|
||||
l.setPen(CONST.COLORS["black"])
|
||||
|
||||
lw = self.scene().addLine(
|
||||
POS_X + PADDING + d * dist + 1,
|
||||
POS_Y + BIG_LINE * 2,
|
||||
POS_X + PADDING + d * dist + 1,
|
||||
POS_Y + BIG_LINE - h,
|
||||
)
|
||||
lw.setPen(CONST.COLORS["white"])
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent):
|
||||
if event.angleDelta().y() > 0:
|
||||
|
||||
@ -48,6 +48,9 @@ class QUnitInfoWindow(QDialog):
|
||||
|
||||
header = QLabel(self)
|
||||
header.setGeometry(0, 0, 720, 360)
|
||||
|
||||
pixmap = None
|
||||
|
||||
if (
|
||||
dcs.planes.plane_map.get(self.unit_type.id) is not None
|
||||
or dcs.helicopters.helicopter_map.get(self.unit_type.id) is not None
|
||||
|
||||
@ -81,6 +81,10 @@ class QPylonEditor(QComboBox):
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.setCurrentText(
|
||||
weapons_data.weapon_ids.get(pylon_default_weapon).get("name")
|
||||
)
|
||||
weapon = weapons_data.weapon_ids.get(pylon_default_weapon)
|
||||
if weapon is not None:
|
||||
self.setCurrentText(
|
||||
weapons_data.weapon_ids.get(pylon_default_weapon).get("name")
|
||||
)
|
||||
else:
|
||||
self.setCurrentText(pylon_default_weapon)
|
||||
|
||||
@ -422,6 +422,12 @@ class QSettingsWindow(QDialog):
|
||||
self.generate_marks.setChecked(self.game.settings.generate_marks)
|
||||
self.generate_marks.toggled.connect(self.applySettings)
|
||||
|
||||
self.generate_dark_kneeboard = QCheckBox()
|
||||
self.generate_dark_kneeboard.setChecked(
|
||||
self.game.settings.generate_dark_kneeboard
|
||||
)
|
||||
self.generate_dark_kneeboard.toggled.connect(self.applySettings)
|
||||
|
||||
self.never_delay_players = QCheckBox()
|
||||
self.never_delay_players.setChecked(
|
||||
self.game.settings.never_delay_player_flights
|
||||
@ -437,6 +443,14 @@ class QSettingsWindow(QDialog):
|
||||
self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0)
|
||||
self.gameplayLayout.addWidget(self.generate_marks, 1, 1, Qt.AlignRight)
|
||||
|
||||
dark_kneeboard_label = QLabel(
|
||||
"Generate Dark Kneeboard <br />"
|
||||
"<strong>Dark kneeboard for night missions.<br />"
|
||||
"This will likely make the kneeboard on the pilot leg unreadable.</strong>"
|
||||
)
|
||||
self.gameplayLayout.addWidget(dark_kneeboard_label, 2, 0)
|
||||
self.gameplayLayout.addWidget(self.generate_dark_kneeboard, 2, 1, Qt.AlignRight)
|
||||
|
||||
spawn_players_immediately_tooltip = (
|
||||
"Always spawns player aircraft immediately, even if their start time is "
|
||||
"more than 10 minutes after the start of the mission. <strong>This does "
|
||||
@ -449,8 +463,8 @@ class QSettingsWindow(QDialog):
|
||||
"Should not be used if players have runway or in-air starts.</strong>"
|
||||
)
|
||||
spawn_immediately_label.setToolTip(spawn_players_immediately_tooltip)
|
||||
self.gameplayLayout.addWidget(spawn_immediately_label, 2, 0)
|
||||
self.gameplayLayout.addWidget(self.never_delay_players, 2, 1, Qt.AlignRight)
|
||||
self.gameplayLayout.addWidget(spawn_immediately_label, 3, 0)
|
||||
self.gameplayLayout.addWidget(self.never_delay_players, 3, 1, Qt.AlignRight)
|
||||
|
||||
start_type_label = QLabel(
|
||||
"Default start type for AI aircraft<br /><strong>Warning: "
|
||||
@ -460,8 +474,8 @@ class QSettingsWindow(QDialog):
|
||||
start_type = StartTypeComboBox(self.game.settings)
|
||||
start_type.setCurrentText(self.game.settings.default_start_type)
|
||||
|
||||
self.gameplayLayout.addWidget(start_type_label, 3, 0)
|
||||
self.gameplayLayout.addWidget(start_type, 3, 1)
|
||||
self.gameplayLayout.addWidget(start_type_label, 4, 0)
|
||||
self.gameplayLayout.addWidget(start_type, 4, 1)
|
||||
|
||||
self.performance = QGroupBox("Performance")
|
||||
self.performanceLayout = QGridLayout()
|
||||
@ -629,6 +643,10 @@ class QSettingsWindow(QDialog):
|
||||
|
||||
self.game.settings.supercarrier = self.supercarrier.isChecked()
|
||||
|
||||
self.game.settings.generate_dark_kneeboard = (
|
||||
self.generate_dark_kneeboard.isChecked()
|
||||
)
|
||||
|
||||
self.game.settings.perf_red_alert_state = self.red_alert.isChecked()
|
||||
self.game.settings.perf_smoke_gen = self.smoke.isChecked()
|
||||
self.game.settings.perf_artillery = self.arti.isChecked()
|
||||
|
||||
@ -14,7 +14,7 @@ mypy-extensions==0.4.3
|
||||
nodeenv==1.5.0
|
||||
pathspec==0.8.1
|
||||
pefile==2019.4.18
|
||||
Pillow==7.2.0
|
||||
Pillow==8.1.1
|
||||
pre-commit==2.10.1
|
||||
PyInstaller==3.6
|
||||
PySide2==5.15.2
|
||||
|
||||
BIN
resources/ui/units/aircrafts/banners/F-22A_24.jpg
Normal file
BIN
resources/ui/units/aircrafts/banners/F-22A_24.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
resources/ui/units/aircrafts/icons/F-22A_24.jpg
Normal file
BIN
resources/ui/units/aircrafts/icons/F-22A_24.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
x
Reference in New Issue
Block a user