mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +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`).
|
- 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`).
|
- 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):**
|
**Version information (please complete the following information):**
|
||||||
- DCS Liberation [e.g. 2.3.1]:
|
- DCS Liberation [e.g. 2.3.1]:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ Saves from 2.4 are not compatible with 2.5.
|
|||||||
|
|
||||||
## Fixes
|
## 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
|
# 2.4.3
|
||||||
|
|
||||||
## Features/Improvements
|
## Features/Improvements
|
||||||
|
|||||||
@ -180,6 +180,7 @@ from pydcs_extensions.su57.su57 import Su_57
|
|||||||
UNITINFOTEXT_PATH = Path("./resources/units/unit_info_text.json")
|
UNITINFOTEXT_PATH = Path("./resources/units/unit_info_text.json")
|
||||||
|
|
||||||
plane_map["A-4E-C"] = A_4E_C
|
plane_map["A-4E-C"] = A_4E_C
|
||||||
|
plane_map["F-22A"] = F_22A
|
||||||
plane_map["MB-339PAN"] = MB_339PAN
|
plane_map["MB-339PAN"] = MB_339PAN
|
||||||
plane_map["Rafale_M"] = Rafale_M
|
plane_map["Rafale_M"] = Rafale_M
|
||||||
plane_map["Rafale_A_S"] = Rafale_A_S
|
plane_map["Rafale_A_S"] = Rafale_A_S
|
||||||
|
|||||||
@ -21,6 +21,10 @@ from game.threatzones import ThreatZones
|
|||||||
from game.utils import nautical_miles
|
from game.utils import nautical_miles
|
||||||
|
|
||||||
|
|
||||||
|
class NavMeshError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NavMeshPoly:
|
class NavMeshPoly:
|
||||||
def __init__(self, ident: int, poly: Polygon, threatened: bool) -> None:
|
def __init__(self, ident: int, poly: Polygon, threatened: bool) -> None:
|
||||||
self.ident = ident
|
self.ident = ident
|
||||||
@ -125,7 +129,7 @@ class NavMesh:
|
|||||||
path.append(current.world_point)
|
path.append(current.world_point)
|
||||||
previous = came_from[current]
|
previous = came_from[current]
|
||||||
if previous is None:
|
if previous is None:
|
||||||
raise RuntimeError(
|
raise NavMeshError(
|
||||||
f"Could not reconstruct path to {destination} from {origin}"
|
f"Could not reconstruct path to {destination} from {origin}"
|
||||||
)
|
)
|
||||||
current = previous
|
current = previous
|
||||||
@ -140,10 +144,12 @@ class NavMesh:
|
|||||||
def shortest_path(self, origin: Point, destination: Point) -> List[Point]:
|
def shortest_path(self, origin: Point, destination: Point) -> List[Point]:
|
||||||
origin_poly = self.localize(origin)
|
origin_poly = self.localize(origin)
|
||||||
if origin_poly is None:
|
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)
|
destination_poly = self.localize(destination)
|
||||||
if destination_poly is None:
|
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(
|
return self._shortest_path(
|
||||||
NavPoint(self.dcs_to_shapely_point(origin), origin_poly),
|
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
|
# threatened airbases at the map edges have room to retreat from the
|
||||||
# threat without running off the navmesh.
|
# threat without running off the navmesh.
|
||||||
return box(*LineString(points).bounds).buffer(
|
return box(*LineString(points).bounds).buffer(
|
||||||
nautical_miles(100).meters, resolution=1
|
nautical_miles(200).meters, resolution=1
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -166,6 +166,7 @@ class Operation:
|
|||||||
airgen: AircraftConflictGenerator,
|
airgen: AircraftConflictGenerator,
|
||||||
):
|
):
|
||||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
||||||
|
|
||||||
gens: List[MissionInfoGenerator] = [
|
gens: List[MissionInfoGenerator] = [
|
||||||
KneeboardGenerator(cls.current_mission, cls.game),
|
KneeboardGenerator(cls.current_mission, cls.game),
|
||||||
BriefingGenerator(cls.current_mission, cls.game),
|
BriefingGenerator(cls.current_mission, cls.game),
|
||||||
@ -177,9 +178,8 @@ class Operation:
|
|||||||
for tanker in airsupportgen.air_support.tankers:
|
for tanker in airsupportgen.air_support.tankers:
|
||||||
gen.add_tanker(tanker)
|
gen.add_tanker(tanker)
|
||||||
|
|
||||||
if cls.player_awacs_enabled:
|
for aewc in airsupportgen.air_support.awacs:
|
||||||
for awacs in airsupportgen.air_support.awacs:
|
gen.add_awacs(aewc)
|
||||||
gen.add_awacs(awacs)
|
|
||||||
|
|
||||||
for jtac in jtacs:
|
for jtac in jtacs:
|
||||||
gen.add_jtac(jtac)
|
gen.add_jtac(jtac)
|
||||||
@ -378,7 +378,9 @@ class Operation:
|
|||||||
cls.game,
|
cls.game,
|
||||||
cls.radio_registry,
|
cls.radio_registry,
|
||||||
cls.unit_map,
|
cls.unit_map,
|
||||||
|
air_support=cls.airsupportgen.air_support,
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.airgen.clear_parking_slots()
|
cls.airgen.clear_parking_slots()
|
||||||
|
|
||||||
cls.airgen.generate_flights(
|
cls.airgen.generate_flights(
|
||||||
|
|||||||
@ -31,6 +31,7 @@ class Settings:
|
|||||||
automate_aircraft_reinforcements: bool = False
|
automate_aircraft_reinforcements: bool = False
|
||||||
restrict_weapons_by_date: bool = False
|
restrict_weapons_by_date: bool = False
|
||||||
disable_legacy_aewc: bool = False
|
disable_legacy_aewc: bool = False
|
||||||
|
generate_dark_kneeboard: bool = False
|
||||||
|
|
||||||
# Performance oriented
|
# Performance oriented
|
||||||
perf_red_alert_state: bool = True
|
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.theater import ControlPoint
|
||||||
from game.utils import Distance, meters, nautical_miles
|
from game.utils import Distance, meters, nautical_miles
|
||||||
|
from gen import Conflict
|
||||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||||
from gen.flights.flight import Flight
|
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
|
zone belongs to the player, it is the zone that will be avoided by
|
||||||
the enemy and vice versa.
|
the enemy and vice versa.
|
||||||
"""
|
"""
|
||||||
airbases = []
|
air_threats = []
|
||||||
air_defenses = []
|
air_defenses = []
|
||||||
for control_point in game.theater.controlpoints:
|
for control_point in game.theater.controlpoints:
|
||||||
if control_point.captured != player:
|
if control_point.captured != player:
|
||||||
@ -139,7 +140,7 @@ class ThreatZones:
|
|||||||
if control_point.runway_is_operational():
|
if control_point.runway_is_operational():
|
||||||
point = ShapelyPoint(control_point.position.x, control_point.position.y)
|
point = ShapelyPoint(control_point.position.x, control_point.position.y)
|
||||||
cap_threat_range = cls.barcap_threat_range(game, control_point)
|
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 tgo in control_point.ground_objects:
|
||||||
for group in tgo.groups:
|
for group in tgo.groups:
|
||||||
@ -151,8 +152,25 @@ class ThreatZones:
|
|||||||
threat_zone = point.buffer(threat_range.meters)
|
threat_zone = point.buffer(threat_range.meters)
|
||||||
air_defenses.append(threat_zone)
|
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(
|
return cls(
|
||||||
airbases=unary_union(airbases), air_defenses=unary_union(air_defenses)
|
airbases=unary_union(air_threats), air_defenses=unary_union(air_defenses)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union
|
from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
@ -67,6 +67,8 @@ from dcs.task import (
|
|||||||
Targets,
|
Targets,
|
||||||
Task,
|
Task,
|
||||||
WeaponType,
|
WeaponType,
|
||||||
|
AWACSTaskAction,
|
||||||
|
SetFrequencyCommand,
|
||||||
)
|
)
|
||||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||||
@ -88,7 +90,6 @@ from game.theater.controlpoint import (
|
|||||||
from game.theater.theatergroundobject import TheaterGroundObject
|
from game.theater.theatergroundobject import TheaterGroundObject
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from game.utils import Distance, meters, nautical_miles
|
from game.utils import Distance, meters, nautical_miles
|
||||||
from gen.airsupportgen import AirSupport
|
|
||||||
from gen.ato import AirTaskingOrder, Package
|
from gen.ato import AirTaskingOrder, Package
|
||||||
from gen.callsigns import create_group_callsign_from_unit
|
from gen.callsigns import create_group_callsign_from_unit
|
||||||
from gen.flights.flight import (
|
from gen.flights.flight import (
|
||||||
@ -104,9 +105,12 @@ from .flights.flightplan import (
|
|||||||
LoiterFlightPlan,
|
LoiterFlightPlan,
|
||||||
PatrollingFlightPlan,
|
PatrollingFlightPlan,
|
||||||
SweepFlightPlan,
|
SweepFlightPlan,
|
||||||
|
AwacsFlightPlan,
|
||||||
)
|
)
|
||||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
from .flights.traveltime import GroundSpeed, TotEstimator
|
||||||
from .naming import namegen
|
from .naming import namegen
|
||||||
|
from .airsupportgen import AirSupport, AwacsInfo
|
||||||
|
from .callsigns import callsign_for_support_unit
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -652,6 +656,12 @@ AIRCRAFT_DATA: Dict[str, AircraftData] = {
|
|||||||
),
|
),
|
||||||
channel_namer=HueyChannelNamer,
|
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["A-10C_2"] = AIRCRAFT_DATA["A-10C"]
|
||||||
AIRCRAFT_DATA["P-51D-30-NA"] = AIRCRAFT_DATA["P-51D"]
|
AIRCRAFT_DATA["P-51D-30-NA"] = AIRCRAFT_DATA["P-51D"]
|
||||||
@ -666,6 +676,7 @@ class AircraftConflictGenerator:
|
|||||||
game: Game,
|
game: Game,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
|
air_support: AirSupport,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.m = mission
|
self.m = mission
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -673,6 +684,7 @@ class AircraftConflictGenerator:
|
|||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
self.flights: List[FlightData] = []
|
self.flights: List[FlightData] = []
|
||||||
|
self.air_support = air_support
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def use_client(self) -> bool:
|
def use_client(self) -> bool:
|
||||||
@ -787,7 +799,10 @@ class AircraftConflictGenerator:
|
|||||||
OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
|
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)
|
group.set_frequency(channel.mhz)
|
||||||
|
|
||||||
divert = None
|
divert = None
|
||||||
@ -824,6 +839,20 @@ class AircraftConflictGenerator:
|
|||||||
if unit_type in [Su_33, C_101EB, C_101CC]:
|
if unit_type in [Su_33, C_101EB, C_101CC]:
|
||||||
self.set_reduced_fuel(flight, group, unit_type)
|
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(
|
def _generate_at_airport(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
@ -1356,7 +1385,16 @@ class AircraftConflictGenerator:
|
|||||||
dynamic_runways: Dict[str, RunwayData],
|
dynamic_runways: Dict[str, RunwayData],
|
||||||
) -> None:
|
) -> None:
|
||||||
group.task = AWACS.name
|
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)
|
self._setup_group(group, AWACS, package, flight, dynamic_runways)
|
||||||
|
|
||||||
|
# Awacs task action
|
||||||
self.configure_behavior(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
@ -1364,6 +1402,8 @@ class AircraftConflictGenerator:
|
|||||||
restrict_jettison=True,
|
restrict_jettison=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.points[0].tasks.append(AWACSTaskAction())
|
||||||
|
|
||||||
def configure_escort(
|
def configure_escort(
|
||||||
self,
|
self,
|
||||||
group: FlyingGroup,
|
group: FlyingGroup,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass, field
|
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.mission import Mission, StartType
|
||||||
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135
|
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135
|
||||||
@ -37,6 +38,9 @@ class AwacsInfo:
|
|||||||
dcsGroupName: str
|
dcsGroupName: str
|
||||||
callsign: str
|
callsign: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
|
depature_location: Optional[str]
|
||||||
|
start_time: Optional[timedelta]
|
||||||
|
end_time: Optional[timedelta]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -192,9 +196,12 @@ class AirSupportConflictGenerator:
|
|||||||
|
|
||||||
self.air_support.awacs.append(
|
self.air_support.awacs.append(
|
||||||
AwacsInfo(
|
AwacsInfo(
|
||||||
str(awacs_flight.name),
|
dcsGroupName=str(awacs_flight.name),
|
||||||
callsign_for_support_unit(awacs_flight),
|
callsign=callsign_for_support_unit(awacs_flight),
|
||||||
freq,
|
freq=freq,
|
||||||
|
depature_location=None,
|
||||||
|
start_time=None,
|
||||||
|
end_time=None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from .ground_forces.combat_stance import CombatStance
|
|||||||
from .radios import RadioFrequency
|
from .radios import RadioFrequency
|
||||||
from .runways import RunwayData
|
from .runways import RunwayData
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
|
|||||||
@ -88,6 +88,7 @@ from dcs.planes import (
|
|||||||
Tu_22M3,
|
Tu_22M3,
|
||||||
Tu_95MS,
|
Tu_95MS,
|
||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
|
I_16,
|
||||||
)
|
)
|
||||||
from dcs.unittype import FlyingType
|
from dcs.unittype import FlyingType
|
||||||
|
|
||||||
|
|||||||
@ -713,6 +713,10 @@ class AwacsFlightPlan(LoiterFlightPlan):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mission_start_time(self) -> Optional[timedelta]:
|
||||||
|
return self.takeoff_time()
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
if waypoint == self.hold:
|
if waypoint == self.hold:
|
||||||
return self.package.time_over_target
|
return self.package.time_over_target
|
||||||
@ -796,7 +800,17 @@ class FlightPlanBuilder:
|
|||||||
raise RuntimeError("Flight must be a part of the package")
|
raise RuntimeError("Flight must be a part of the package")
|
||||||
if self.package.waypoints is None:
|
if self.package.waypoints is None:
|
||||||
self.regenerate_package_waypoints()
|
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(
|
def generate_flight_plan(
|
||||||
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||||
@ -1013,7 +1027,8 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
targets: List[StrikeTarget] = []
|
targets: List[StrikeTarget] = []
|
||||||
for group in location.groups:
|
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(
|
return self.strike_flightplan(
|
||||||
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from typing import (
|
|||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
from dcs.unitgroup import VehicleGroup
|
from dcs.unitgroup import Group, VehicleGroup
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -32,7 +32,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class StrikeTarget:
|
class StrikeTarget:
|
||||||
name: str
|
name: str
|
||||||
target: Union[VehicleGroup, TheaterGroundObject, Unit]
|
target: Union[VehicleGroup, TheaterGroundObject, Unit, Group]
|
||||||
|
|
||||||
|
|
||||||
class WaypointBuilder:
|
class WaypointBuilder:
|
||||||
|
|||||||
@ -41,6 +41,7 @@ from .flights.flight import FlightWaypoint, FlightWaypointType
|
|||||||
from .radios import RadioFrequency
|
from .radios import RadioFrequency
|
||||||
from .runways import RunwayData
|
from .runways import RunwayData
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
@ -48,8 +49,16 @@ if TYPE_CHECKING:
|
|||||||
class KneeboardPageWriter:
|
class KneeboardPageWriter:
|
||||||
"""Creates kneeboard images."""
|
"""Creates kneeboard images."""
|
||||||
|
|
||||||
def __init__(self, page_margin: int = 24, line_spacing: int = 12) -> None:
|
def __init__(
|
||||||
self.image = Image.new("RGB", (768, 1024), (0xFF, 0xFF, 0xFF))
|
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
|
# These font sizes create a relatively full page for current sorties. If
|
||||||
# we start generating more complicated flight plans, or start including
|
# we start generating more complicated flight plans, or start including
|
||||||
# more information in the comm ladder (the latter of which we should
|
# more information in the comm ladder (the latter of which we should
|
||||||
@ -79,10 +88,10 @@ class KneeboardPageWriter:
|
|||||||
self.y += height + self.line_spacing
|
self.y += height + self.line_spacing
|
||||||
|
|
||||||
def title(self, title: str) -> None:
|
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:
|
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(
|
def table(
|
||||||
self, cells: List[List[str]], headers: Optional[List[str]] = None
|
self, cells: List[List[str]], headers: Optional[List[str]] = None
|
||||||
@ -90,7 +99,7 @@ class KneeboardPageWriter:
|
|||||||
if headers is None:
|
if headers is None:
|
||||||
headers = []
|
headers = []
|
||||||
table = tabulate(cells, headers=headers, numalign="right")
|
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:
|
def write(self, path: Path) -> None:
|
||||||
self.image.save(path)
|
self.image.save(path)
|
||||||
@ -237,6 +246,7 @@ class BriefingPage(KneeboardPage):
|
|||||||
tankers: List[TankerInfo],
|
tankers: List[TankerInfo],
|
||||||
jtacs: List[JtacInfo],
|
jtacs: List[JtacInfo],
|
||||||
start_time: datetime.datetime,
|
start_time: datetime.datetime,
|
||||||
|
dark_kneeboard: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.comms = list(comms)
|
self.comms = list(comms)
|
||||||
@ -244,10 +254,11 @@ class BriefingPage(KneeboardPage):
|
|||||||
self.tankers = tankers
|
self.tankers = tankers
|
||||||
self.jtacs = jtacs
|
self.jtacs = jtacs
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
|
self.dark_kneeboard = dark_kneeboard
|
||||||
self.comms.append(CommInfo("Flight", self.flight.intra_flight_channel))
|
self.comms.append(CommInfo("Flight", self.flight.intra_flight_channel))
|
||||||
|
|
||||||
def write(self, path: Path) -> None:
|
def write(self, path: Path) -> None:
|
||||||
writer = KneeboardPageWriter()
|
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
|
||||||
if self.flight.custom_name is not None:
|
if self.flight.custom_name is not None:
|
||||||
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
|
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
|
||||||
else:
|
else:
|
||||||
@ -285,6 +296,34 @@ class BriefingPage(KneeboardPage):
|
|||||||
["Bingo", "Joker"],
|
["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
|
# Package Section
|
||||||
writer.heading("Comm ladder")
|
writer.heading("Comm ladder")
|
||||||
comm_ladder = []
|
comm_ladder = []
|
||||||
@ -293,10 +332,6 @@ class BriefingPage(KneeboardPage):
|
|||||||
[comm.name, "", "", "", self.format_frequency(comm.freq)]
|
[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:
|
for tanker in self.tankers:
|
||||||
comm_ladder.append(
|
comm_ladder.append(
|
||||||
[
|
[
|
||||||
@ -365,12 +400,21 @@ class BriefingPage(KneeboardPage):
|
|||||||
channel_name = namer.channel_name(channel.radio_id, channel.channel)
|
channel_name = namer.channel_name(channel.radio_id, channel.channel)
|
||||||
return f"{channel_name} {frequency}"
|
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):
|
class KneeboardGenerator(MissionInfoGenerator):
|
||||||
"""Creates kneeboard pages for each client flight in the mission."""
|
"""Creates kneeboard pages for each client flight in the mission."""
|
||||||
|
|
||||||
def __init__(self, mission: Mission, game: "Game") -> None:
|
def __init__(self, mission: Mission, game: "Game") -> None:
|
||||||
super().__init__(mission, game)
|
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:
|
def generate(self) -> None:
|
||||||
"""Generates a kneeboard per client flight."""
|
"""Generates a kneeboard per client flight."""
|
||||||
@ -414,5 +458,6 @@ class KneeboardGenerator(MissionInfoGenerator):
|
|||||||
self.tankers,
|
self.tankers,
|
||||||
self.jtacs,
|
self.jtacs,
|
||||||
self.mission.start_time,
|
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)
|
package = Package(target)
|
||||||
flight = Flight(
|
flight = Flight(
|
||||||
package,
|
package,
|
||||||
|
self.game.player_country if player else self.game.enemy_country,
|
||||||
F_16C_50,
|
F_16C_50,
|
||||||
2,
|
2,
|
||||||
task,
|
task,
|
||||||
@ -914,35 +915,48 @@ class QLiberationMap(QGraphicsView):
|
|||||||
SMALL_LINE = 2
|
SMALL_LINE = 2
|
||||||
|
|
||||||
dist = self.distance_to_pixels(nautical_miles(scale_distance_nm))
|
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(
|
l = self.scene().addLine(
|
||||||
POS_X + PADDING,
|
POS_X + PADDING,
|
||||||
POS_Y + BIG_LINE * 2,
|
POS_Y + BIG_LINE * 2,
|
||||||
POS_X + PADDING + dist,
|
POS_X + PADDING + dist,
|
||||||
POS_Y + BIG_LINE * 2,
|
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(
|
text = self.scene().addText(
|
||||||
"0nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False)
|
"0nm", font=QFont("Trebuchet MS", 6, weight=5, italic=False)
|
||||||
)
|
)
|
||||||
text.setPos(POS_X, POS_Y + BIG_LINE * 2)
|
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(
|
text2 = self.scene().addText(
|
||||||
str(scale_distance_nm) + "nm",
|
str(scale_distance_nm) + "nm",
|
||||||
font=QFont("Trebuchet MS", 6, weight=5, italic=False),
|
font=QFont("Trebuchet MS", 6, weight=5, italic=False),
|
||||||
)
|
)
|
||||||
text2.setPos(POS_X + dist, POS_Y + BIG_LINE * 2)
|
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):
|
for i in range(number_of_points + 1):
|
||||||
d = float(i) / float(number_of_points)
|
d = float(i) / float(number_of_points)
|
||||||
if i == 0 or i == number_of_points:
|
if i == 0 or i == number_of_points:
|
||||||
@ -956,7 +970,15 @@ class QLiberationMap(QGraphicsView):
|
|||||||
POS_X + PADDING + d * dist,
|
POS_X + PADDING + d * dist,
|
||||||
POS_Y + BIG_LINE - h,
|
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):
|
def wheelEvent(self, event: QWheelEvent):
|
||||||
if event.angleDelta().y() > 0:
|
if event.angleDelta().y() > 0:
|
||||||
|
|||||||
@ -48,6 +48,9 @@ class QUnitInfoWindow(QDialog):
|
|||||||
|
|
||||||
header = QLabel(self)
|
header = QLabel(self)
|
||||||
header.setGeometry(0, 0, 720, 360)
|
header.setGeometry(0, 0, 720, 360)
|
||||||
|
|
||||||
|
pixmap = None
|
||||||
|
|
||||||
if (
|
if (
|
||||||
dcs.planes.plane_map.get(self.unit_type.id) is not None
|
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
|
or dcs.helicopters.helicopter_map.get(self.unit_type.id) is not None
|
||||||
|
|||||||
@ -81,6 +81,10 @@ class QPylonEditor(QComboBox):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.setCurrentText(
|
weapon = weapons_data.weapon_ids.get(pylon_default_weapon)
|
||||||
weapons_data.weapon_ids.get(pylon_default_weapon).get("name")
|
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.setChecked(self.game.settings.generate_marks)
|
||||||
self.generate_marks.toggled.connect(self.applySettings)
|
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 = QCheckBox()
|
||||||
self.never_delay_players.setChecked(
|
self.never_delay_players.setChecked(
|
||||||
self.game.settings.never_delay_player_flights
|
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(QLabel("Put Objective Markers on Map"), 1, 0)
|
||||||
self.gameplayLayout.addWidget(self.generate_marks, 1, 1, Qt.AlignRight)
|
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 = (
|
spawn_players_immediately_tooltip = (
|
||||||
"Always spawns player aircraft immediately, even if their start time is "
|
"Always spawns player aircraft immediately, even if their start time is "
|
||||||
"more than 10 minutes after the start of the mission. <strong>This does "
|
"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>"
|
"Should not be used if players have runway or in-air starts.</strong>"
|
||||||
)
|
)
|
||||||
spawn_immediately_label.setToolTip(spawn_players_immediately_tooltip)
|
spawn_immediately_label.setToolTip(spawn_players_immediately_tooltip)
|
||||||
self.gameplayLayout.addWidget(spawn_immediately_label, 2, 0)
|
self.gameplayLayout.addWidget(spawn_immediately_label, 3, 0)
|
||||||
self.gameplayLayout.addWidget(self.never_delay_players, 2, 1, Qt.AlignRight)
|
self.gameplayLayout.addWidget(self.never_delay_players, 3, 1, Qt.AlignRight)
|
||||||
|
|
||||||
start_type_label = QLabel(
|
start_type_label = QLabel(
|
||||||
"Default start type for AI aircraft<br /><strong>Warning: "
|
"Default start type for AI aircraft<br /><strong>Warning: "
|
||||||
@ -460,8 +474,8 @@ class QSettingsWindow(QDialog):
|
|||||||
start_type = StartTypeComboBox(self.game.settings)
|
start_type = StartTypeComboBox(self.game.settings)
|
||||||
start_type.setCurrentText(self.game.settings.default_start_type)
|
start_type.setCurrentText(self.game.settings.default_start_type)
|
||||||
|
|
||||||
self.gameplayLayout.addWidget(start_type_label, 3, 0)
|
self.gameplayLayout.addWidget(start_type_label, 4, 0)
|
||||||
self.gameplayLayout.addWidget(start_type, 3, 1)
|
self.gameplayLayout.addWidget(start_type, 4, 1)
|
||||||
|
|
||||||
self.performance = QGroupBox("Performance")
|
self.performance = QGroupBox("Performance")
|
||||||
self.performanceLayout = QGridLayout()
|
self.performanceLayout = QGridLayout()
|
||||||
@ -629,6 +643,10 @@ class QSettingsWindow(QDialog):
|
|||||||
|
|
||||||
self.game.settings.supercarrier = self.supercarrier.isChecked()
|
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_red_alert_state = self.red_alert.isChecked()
|
||||||
self.game.settings.perf_smoke_gen = self.smoke.isChecked()
|
self.game.settings.perf_smoke_gen = self.smoke.isChecked()
|
||||||
self.game.settings.perf_artillery = self.arti.isChecked()
|
self.game.settings.perf_artillery = self.arti.isChecked()
|
||||||
|
|||||||
@ -14,7 +14,7 @@ mypy-extensions==0.4.3
|
|||||||
nodeenv==1.5.0
|
nodeenv==1.5.0
|
||||||
pathspec==0.8.1
|
pathspec==0.8.1
|
||||||
pefile==2019.4.18
|
pefile==2019.4.18
|
||||||
Pillow==7.2.0
|
Pillow==8.1.1
|
||||||
pre-commit==2.10.1
|
pre-commit==2.10.1
|
||||||
PyInstaller==3.6
|
PyInstaller==3.6
|
||||||
PySide2==5.15.2
|
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