Merge branch 'develop' into add-e2c-to-more-factions

This commit is contained in:
SnappyComebacks 2021-03-27 11:04:13 -06:00
commit ca7a86b6d7
22 changed files with 241 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -88,6 +88,7 @@ from dcs.planes import (
Tu_22M3,
Tu_95MS,
WingLoong_I,
I_16,
)
from dcs.unittype import FlyingType

View File

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

View File

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

View File

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

@ -1 +1 @@
Subproject commit 5ffae3c76b99610ab5065c7317a8a5c72c7e4afb
Subproject commit 42de2ec352903d592ca123950b4b12a15ffa6544

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB