mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Disallow squadrons from disabling mission types.
After this change, players will always have the final say in what missions a squadron can be assigned to. Squadrons are not able to influence the default auto-assignable missions either because that property is always overridden by the campaign's air wing configuration (the primary and secondary task properties). The `mission-types` field of the squadron definition has been removed since it is no longer capable of influencing anything. I haven't bothered cleaning up the now useless data in all the existing squadrons though. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2785.
This commit is contained in:
parent
1ac36d03da
commit
94b8aa7213
@ -14,6 +14,8 @@ Saves from 6.x are not compatible with 7.0.
|
||||
* **[Modding]** Add support for VSN F-4B and F-4C mod.
|
||||
* **[Modding]** Custom factions can now be defined in YAML as well as JSON. JSON support may be removed in the future if having both formats causes confusion.
|
||||
* **[Modding]** Campaigns which require custom factions can now define those factions directly in the campaign YAML. See Operation Aliied Sword for an example.
|
||||
* **[Modding]** The `mission_types` field in squadron files has been removed. Squadron task capability is now determined by airframe, and the auto-assignable list has always been overridden by the campaign settings.
|
||||
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
|
||||
|
||||
## Fixes
|
||||
|
||||
|
||||
@ -115,7 +115,7 @@ class DefaultSquadronAssigner:
|
||||
) -> bool:
|
||||
if ignore_base_preference:
|
||||
return control_point.can_operate(squadron.aircraft)
|
||||
return squadron.operates_from(control_point) and task in squadron.mission_types
|
||||
return squadron.operates_from(control_point) and squadron.capable_of(task)
|
||||
|
||||
def find_squadron_for_airframe(
|
||||
self, aircraft: AircraftType, task: FlightType, control_point: ControlPoint
|
||||
|
||||
@ -4,12 +4,12 @@ import itertools
|
||||
import random
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.squadrons.operatingbases import OperatingBases
|
||||
from game.squadrons.squadrondef import SquadronDef
|
||||
from game.theater import ControlPoint
|
||||
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.factions.faction import Faction
|
||||
@ -48,7 +48,7 @@ class SquadronDefGenerator:
|
||||
role="Flying Squadron",
|
||||
aircraft=aircraft,
|
||||
livery=None,
|
||||
mission_types=tuple(tasks_for_aircraft(aircraft)),
|
||||
auto_assignable_mission_types=set(tasks_for_aircraft(aircraft)),
|
||||
operating_bases=OperatingBases.default_for_aircraft(aircraft),
|
||||
female_pilot_percentage=6,
|
||||
pilot_pool=[],
|
||||
|
||||
@ -2,11 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from collections import defaultdict
|
||||
from typing import Sequence, Iterator, TYPE_CHECKING, Optional
|
||||
from typing import Iterator, Optional, Sequence, TYPE_CHECKING
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.ato.ai_flight_planner_db import aircraft_for_task
|
||||
from game.ato.closestairfields import ObjectiveDistanceCache
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from .squadrondefloader import SquadronDefLoader
|
||||
from ..campaignloader.squadrondefgenerator import SquadronDefGenerator
|
||||
from ..factions.faction import Faction
|
||||
@ -82,7 +82,7 @@ class AirWing:
|
||||
best_aircraft_for_task = aircraft_for_task(task)
|
||||
for aircraft, squadrons in self.squadrons.items():
|
||||
for squadron in squadrons:
|
||||
if squadron.untasked_aircraft and task in squadron.mission_types:
|
||||
if squadron.untasked_aircraft and squadron.capable_of(task):
|
||||
aircrafts.append(aircraft)
|
||||
if aircraft not in best_aircraft_for_task:
|
||||
best_aircraft_for_task.append(aircraft)
|
||||
|
||||
@ -12,6 +12,7 @@ from faker import Faker
|
||||
from game.ato import Flight, FlightType, Package
|
||||
from game.settings import AutoAtoBehavior, Settings
|
||||
from .pilot import Pilot, PilotStatus
|
||||
from ..ato.ai_flight_planner_db import aircraft_for_task
|
||||
from ..db.database import Database
|
||||
from ..utils import meters
|
||||
|
||||
@ -32,7 +33,7 @@ class Squadron:
|
||||
role: str
|
||||
aircraft: AircraftType
|
||||
livery: Optional[str]
|
||||
mission_types: tuple[FlightType, ...]
|
||||
auto_assignable_mission_types: set[FlightType]
|
||||
operating_bases: OperatingBases
|
||||
female_pilot_percentage: int
|
||||
|
||||
@ -46,10 +47,6 @@ class Squadron:
|
||||
default_factory=list, init=False, hash=False, compare=False
|
||||
)
|
||||
|
||||
auto_assignable_mission_types: set[FlightType] = field(
|
||||
init=False, hash=False, compare=False
|
||||
)
|
||||
|
||||
coalition: Coalition = field(hash=False, compare=False)
|
||||
flight_db: Database[Flight] = field(hash=False, compare=False)
|
||||
settings: Settings = field(hash=False, compare=False)
|
||||
@ -63,9 +60,6 @@ class Squadron:
|
||||
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
||||
pending_deliveries: int = field(init=False, hash=False, compare=False, default=0)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.auto_assignable_mission_types = set(self.mission_types)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.nickname is None:
|
||||
return self.name
|
||||
@ -94,16 +88,12 @@ class Squadron:
|
||||
def pilot_limits_enabled(self) -> bool:
|
||||
return self.settings.enable_squadron_pilot_limits
|
||||
|
||||
def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None:
|
||||
self.mission_types = tuple(mission_types)
|
||||
self.auto_assignable_mission_types.intersection_update(self.mission_types)
|
||||
|
||||
def set_auto_assignable_mission_types(
|
||||
self, mission_types: Iterable[FlightType]
|
||||
) -> None:
|
||||
self.auto_assignable_mission_types = set(self.mission_types).intersection(
|
||||
mission_types
|
||||
)
|
||||
self.auto_assignable_mission_types = {
|
||||
t for t in mission_types if self.capable_of(t)
|
||||
}
|
||||
|
||||
def claim_new_pilot_if_allowed(self) -> Optional[Pilot]:
|
||||
if self.pilot_limits_enabled:
|
||||
@ -257,7 +247,20 @@ class Squadron:
|
||||
def has_unfilled_pilot_slots(self) -> bool:
|
||||
return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0
|
||||
|
||||
def capable_of(self, task: FlightType) -> bool:
|
||||
"""Returns True if the squadron is capable of performing the given task.
|
||||
|
||||
A squadron may be capable of performing a task even if it will not be
|
||||
automatically assigned to it.
|
||||
"""
|
||||
return self.aircraft in aircraft_for_task(task)
|
||||
|
||||
def can_auto_assign(self, task: FlightType) -> bool:
|
||||
"""Returns True if the squadron may be automatically assigned the given task.
|
||||
|
||||
A squadron may be capable of performing a task even if it will not be
|
||||
automatically assigned to it.
|
||||
"""
|
||||
return task in self.auto_assignable_mission_types
|
||||
|
||||
def can_auto_assign_mission(
|
||||
@ -432,7 +435,7 @@ class Squadron:
|
||||
squadron_def.role,
|
||||
squadron_def.aircraft,
|
||||
squadron_def.livery,
|
||||
squadron_def.mission_types,
|
||||
squadron_def.auto_assignable_mission_types,
|
||||
squadron_def.operating_bases,
|
||||
squadron_def.female_pilot_percentage,
|
||||
squadron_def.pilot_pool,
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
import yaml
|
||||
|
||||
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.squadrons.operatingbases import OperatingBases
|
||||
from game.squadrons.pilot import Pilot
|
||||
@ -25,30 +24,24 @@ class SquadronDef:
|
||||
role: str
|
||||
aircraft: AircraftType
|
||||
livery: Optional[str]
|
||||
mission_types: tuple[FlightType, ...]
|
||||
auto_assignable_mission_types: set[FlightType]
|
||||
operating_bases: OperatingBases
|
||||
female_pilot_percentage: int
|
||||
pilot_pool: list[Pilot]
|
||||
claimed: bool = False
|
||||
|
||||
auto_assignable_mission_types: set[FlightType] = field(
|
||||
init=False, hash=False, compare=False
|
||||
)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.auto_assignable_mission_types = set(self.mission_types)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.nickname is None:
|
||||
return self.name
|
||||
return f'{self.name} "{self.nickname}"'
|
||||
|
||||
def set_allowed_mission_types(self, mission_types: Iterable[FlightType]) -> None:
|
||||
self.mission_types = tuple(mission_types)
|
||||
self.auto_assignable_mission_types.intersection_update(self.mission_types)
|
||||
def capable_of(self, task: FlightType) -> bool:
|
||||
"""Returns True if the squadron is capable of performing the given task.
|
||||
|
||||
def can_auto_assign(self, task: FlightType) -> bool:
|
||||
return task in self.auto_assignable_mission_types
|
||||
A squadron may be capable of performing a task even if it will not be
|
||||
automatically assigned to it.
|
||||
"""
|
||||
return self.aircraft in aircraft_for_task(task)
|
||||
|
||||
def operates_from(self, control_point: ControlPoint) -> bool:
|
||||
if not control_point.can_operate(self.aircraft):
|
||||
@ -62,8 +55,6 @@ class SquadronDef:
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, path: Path) -> SquadronDef:
|
||||
from game.ato.ai_flight_planner_db import tasks_for_aircraft
|
||||
from game.ato import FlightType
|
||||
|
||||
with path.open(encoding="utf8") as squadron_file:
|
||||
data = yaml.safe_load(squadron_file)
|
||||
@ -78,16 +69,6 @@ class SquadronDef:
|
||||
pilots.extend([Pilot(n, player=True) for n in data.get("players", [])])
|
||||
female_pilot_percentage = data.get("female_pilot_percentage", 6)
|
||||
|
||||
mission_types = [FlightType.from_name(n) for n in data["mission_types"]]
|
||||
tasks = tasks_for_aircraft(unit_type)
|
||||
for mission_type in list(mission_types):
|
||||
if mission_type not in tasks:
|
||||
logging.error(
|
||||
f"Squadron has mission type {mission_type} but {unit_type} is not "
|
||||
f"capable of that task: {path}"
|
||||
)
|
||||
mission_types.remove(mission_type)
|
||||
|
||||
return SquadronDef(
|
||||
name=data["name"],
|
||||
nickname=data.get("nickname"),
|
||||
@ -95,7 +76,7 @@ class SquadronDef:
|
||||
role=data["role"],
|
||||
aircraft=unit_type,
|
||||
livery=data.get("livery"),
|
||||
mission_types=tuple(mission_types),
|
||||
auto_assignable_mission_types=set(tasks_for_aircraft(unit_type)),
|
||||
operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})),
|
||||
female_pilot_percentage=female_pilot_percentage,
|
||||
pilot_pool=pilots,
|
||||
|
||||
@ -44,9 +44,6 @@ class QMissionType:
|
||||
self, mission_type: FlightType, allowed: bool, auto_assignable: bool
|
||||
) -> None:
|
||||
self.flight_type = mission_type
|
||||
self.allowed_checkbox = QCheckBox()
|
||||
self.allowed_checkbox.setChecked(allowed)
|
||||
self.allowed_checkbox.toggled.connect(self.update_auto_assignable)
|
||||
self.auto_assignable_checkbox = QCheckBox()
|
||||
self.auto_assignable_checkbox.setEnabled(allowed)
|
||||
self.auto_assignable_checkbox.setChecked(auto_assignable)
|
||||
@ -56,10 +53,6 @@ class QMissionType:
|
||||
if not checked:
|
||||
self.auto_assignable_checkbox.setChecked(False)
|
||||
|
||||
@property
|
||||
def allowed(self) -> bool:
|
||||
return self.allowed_checkbox.isChecked()
|
||||
|
||||
@property
|
||||
def auto_assignable(self) -> bool:
|
||||
return self.auto_assignable_checkbox.isChecked()
|
||||
@ -72,27 +65,20 @@ class MissionTypeControls(QGridLayout):
|
||||
self.mission_types: list[QMissionType] = []
|
||||
|
||||
self.addWidget(QLabel("Mission Type"), 0, 0)
|
||||
self.addWidget(QLabel("Allow"), 0, 1)
|
||||
self.addWidget(QLabel("Auto-Assign"), 0, 2)
|
||||
self.addWidget(QLabel("Auto-Assign"), 0, 1)
|
||||
|
||||
for i, task in enumerate(FlightType):
|
||||
if task is FlightType.FERRY:
|
||||
# Not plannable so just skip it.
|
||||
continue
|
||||
allowed = task in squadron.mission_types
|
||||
auto_assignable = task in squadron.auto_assignable_mission_types
|
||||
mission_type = QMissionType(task, allowed, auto_assignable)
|
||||
mission_type = QMissionType(
|
||||
task, squadron.capable_of(task), auto_assignable
|
||||
)
|
||||
self.mission_types.append(mission_type)
|
||||
|
||||
self.addWidget(QLabel(task.value), i + 1, 0)
|
||||
self.addWidget(mission_type.allowed_checkbox, i + 1, 1)
|
||||
self.addWidget(mission_type.auto_assignable_checkbox, i + 1, 2)
|
||||
|
||||
@property
|
||||
def allowed_mission_types(self) -> Iterator[FlightType]:
|
||||
for mission_type in self.mission_types:
|
||||
if mission_type.allowed:
|
||||
yield mission_type.flight_type
|
||||
self.addWidget(mission_type.auto_assignable_checkbox, i + 1, 1)
|
||||
|
||||
@property
|
||||
def auto_assignable_mission_types(self) -> Iterator[FlightType]:
|
||||
@ -233,10 +219,6 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
self.squadron.pilot_pool = [
|
||||
Pilot(n, player=True) for n in player_names
|
||||
] + self.squadron.pilot_pool
|
||||
# Set the allowed mission types
|
||||
self.squadron.set_allowed_mission_types(
|
||||
set(self.mission_types.allowed_mission_types)
|
||||
)
|
||||
# Also update the auto assignable mission types
|
||||
self.squadron.set_auto_assignable_mission_types(
|
||||
set(self.mission_types.auto_assignable_mission_types)
|
||||
|
||||
@ -77,11 +77,12 @@ class AutoAssignedTaskControls(QVBoxLayout):
|
||||
|
||||
return callback
|
||||
|
||||
for task in squadron_model.squadron.mission_types:
|
||||
checkbox = QCheckBox(text=task.value)
|
||||
checkbox.setChecked(squadron_model.is_auto_assignable(task))
|
||||
checkbox.toggled.connect(make_callback(task))
|
||||
self.addWidget(checkbox)
|
||||
for task in FlightType:
|
||||
if self.squadron_model.squadron.capable_of(task):
|
||||
checkbox = QCheckBox(text=task.value)
|
||||
checkbox.setChecked(squadron_model.is_auto_assignable(task))
|
||||
checkbox.toggled.connect(make_callback(task))
|
||||
self.addWidget(checkbox)
|
||||
|
||||
self.addStretch()
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ class SquadronSelector(QComboBox):
|
||||
return
|
||||
|
||||
for squadron in self.air_wing.squadrons_for(aircraft):
|
||||
if task in squadron.mission_types and squadron.untasked_aircraft:
|
||||
if squadron.capable_of(task) and squadron.untasked_aircraft:
|
||||
self.addItem(f"{squadron.location}: {squadron}", squadron)
|
||||
|
||||
if self.count() == 0:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user