mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add date-based loadout restriction.
Follow up work: * Data entry. I plan to do the air-to-air missiles in the near term. I covered some variants of the AIM-120, AIM-7, and AIM-9 here, but there are variants of those weapons for each mounting rack that need to be done still, as well as all the non-US weapons. * Arbitrary start dates. https://github.com/Khopa/dcs_liberation/issues/490
This commit is contained in:
parent
507b217065
commit
34945e7eba
@ -18,6 +18,7 @@ Saves from 2.3 are not compatible with 2.4.
|
||||
* **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases).
|
||||
* **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases).
|
||||
* **[UI]** Multi-SAM objectives now show threat and detection rings per group.
|
||||
* **[Factions]** Initial implementation of date-based loadout restriction. Only a small number of weapons are currently handled.
|
||||
|
||||
## Fixes
|
||||
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import inspect
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Iterator, Set, Tuple, Type, Union, cast
|
||||
from typing import Dict, Iterator, Optional, Set, Tuple, Type, Union, cast
|
||||
|
||||
from dcs.unitgroup import FlyingGroup
|
||||
from dcs.unittype import FlyingType
|
||||
from dcs.weapons_data import Weapons, weapon_ids
|
||||
|
||||
|
||||
PydcsWeapon = Dict[str, Union[int, str]]
|
||||
@ -20,6 +24,14 @@ class Weapon:
|
||||
name: str
|
||||
weight: int
|
||||
|
||||
def available_on(self, date: datetime.date) -> bool:
|
||||
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)
|
||||
if introduction_year is None:
|
||||
logging.warning(
|
||||
f"No introduction year for {self}, assuming always available")
|
||||
return True
|
||||
return date >= datetime.date(introduction_year, 1, 1)
|
||||
|
||||
@property
|
||||
def as_pydcs(self) -> PydcsWeapon:
|
||||
return {
|
||||
@ -28,6 +40,13 @@ class Weapon:
|
||||
"weight": self.weight,
|
||||
}
|
||||
|
||||
@property
|
||||
def fallbacks(self) -> Iterator[Weapon]:
|
||||
yield self
|
||||
fallback = WEAPON_FALLBACK_MAP[self]
|
||||
if fallback is not None:
|
||||
yield from fallback.fallbacks
|
||||
|
||||
@classmethod
|
||||
def from_pydcs(cls, weapon_data: PydcsWeapon) -> Weapon:
|
||||
return cls(
|
||||
@ -36,6 +55,13 @@ class Weapon:
|
||||
cast(int, weapon_data["weight"])
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_clsid(cls, clsid: str) -> Optional[Weapon]:
|
||||
data = weapon_ids.get(clsid)
|
||||
if data is None:
|
||||
return None
|
||||
return cls.from_pydcs(data)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pylon:
|
||||
@ -53,6 +79,11 @@ class Pylon:
|
||||
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
|
||||
return self.number, weapon.as_pydcs
|
||||
|
||||
def available_on(self, date: datetime.date) -> Iterator[Weapon]:
|
||||
for weapon in self.allowed:
|
||||
if weapon.available_on(date):
|
||||
yield weapon
|
||||
|
||||
@classmethod
|
||||
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
||||
# In pydcs these are all arbitrary inner classes of the aircraft type.
|
||||
@ -78,3 +109,37 @@ class Pylon:
|
||||
def iter_pylons(cls, aircraft: Type[FlyingType]) -> Iterator[Pylon]:
|
||||
for pylon in sorted(list(aircraft.pylons)):
|
||||
yield cls.for_aircraft(aircraft, pylon)
|
||||
|
||||
|
||||
_WEAPON_FALLBACKS = [
|
||||
(Weapons.AIM_120C, Weapons.AIM_120B),
|
||||
(Weapons.AIM_120B, Weapons.AIM_7MH),
|
||||
(Weapons.AIM_7MH, Weapons.AIM_7M),
|
||||
(Weapons.AIM_7M, Weapons.AIM_7F),
|
||||
(Weapons.AIM_7F, Weapons.AIM_7E),
|
||||
(Weapons.AIM_7M, Weapons.AIM_9X_Sidewinder_IR_AAM),
|
||||
(Weapons.AIM_9X_Sidewinder_IR_AAM, Weapons.AIM_9P5_Sidewinder_IR_AAM),
|
||||
(Weapons.AIM_9P5_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
|
||||
(Weapons.AIM_9P_Sidewinder_IR_AAM, Weapons.AIM_9M_Sidewinder_IR_AAM),
|
||||
(Weapons.AIM_9M_Sidewinder_IR_AAM, Weapons.AIM_9L_Sidewinder_IR_AAM),
|
||||
]
|
||||
|
||||
WEAPON_FALLBACK_MAP: Dict[Weapon, Optional[Weapon]] = defaultdict(
|
||||
lambda: cast(Optional[Weapon], None),
|
||||
((Weapon.from_pydcs(a), Weapon.from_pydcs(b))
|
||||
for a, b in _WEAPON_FALLBACKS))
|
||||
|
||||
|
||||
WEAPON_INTRODUCTION_YEARS = {
|
||||
Weapon.from_pydcs(Weapons.AIM_120C): 1996,
|
||||
Weapon.from_pydcs(Weapons.AIM_120B): 1994,
|
||||
Weapon.from_pydcs(Weapons.AIM_7MH): 1987,
|
||||
Weapon.from_pydcs(Weapons.AIM_7M): 1982,
|
||||
Weapon.from_pydcs(Weapons.AIM_7F): 1976,
|
||||
Weapon.from_pydcs(Weapons.AIM_7E): 1963,
|
||||
Weapon.from_pydcs(Weapons.AIM_9X_Sidewinder_IR_AAM): 2003,
|
||||
Weapon.from_pydcs(Weapons.AIM_9P5_Sidewinder_IR_AAM): 1963,
|
||||
Weapon.from_pydcs(Weapons.AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||
Weapon.from_pydcs(Weapons.AIM_9M_Sidewinder_IR_AAM): 1983,
|
||||
Weapon.from_pydcs(Weapons.AIM_9L_Sidewinder_IR_AAM): 1977,
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ class Settings:
|
||||
automate_runway_repair: bool = False
|
||||
automate_front_line_reinforcements: bool = False
|
||||
automate_aircraft_reinforcements: bool = False
|
||||
restrict_weapons_by_date: bool = False
|
||||
|
||||
# Performance oriented
|
||||
perf_red_alert_state: bool = True
|
||||
|
||||
@ -74,7 +74,7 @@ from dcs.unittype import FlyingType, UnitType
|
||||
|
||||
from game import db
|
||||
from game.data.cap_capabilities_db import GUNFIGHTERS
|
||||
from game.data.weapons import Pylon
|
||||
from game.data.weapons import Pylon, Weapon
|
||||
from game.factions.faction import Faction
|
||||
from game.settings import Settings
|
||||
from game.theater.controlpoint import (
|
||||
@ -918,6 +918,24 @@ class AircraftConflictGenerator:
|
||||
pylon = Pylon.for_aircraft(flight.unit_type, pylon_number)
|
||||
pylon.equip(group, weapon)
|
||||
|
||||
def _degrade_payload_to_era(self, flight: Flight,
|
||||
group: FlyingGroup) -> None:
|
||||
loadout = dict(group.units[0].pylons)
|
||||
for pylon_number, clsid in loadout.items():
|
||||
weapon = Weapon.from_clsid(clsid["CLSID"])
|
||||
if weapon is None:
|
||||
logging.error(f"Could not find weapon for clsid {clsid}")
|
||||
continue
|
||||
|
||||
if not weapon.available_on(self.game.date):
|
||||
pylon = Pylon.for_aircraft(flight.unit_type, pylon_number)
|
||||
for fallback in weapon.fallbacks:
|
||||
if not pylon.can_equip(fallback):
|
||||
continue
|
||||
if not fallback.available_on(self.game.date):
|
||||
continue
|
||||
pylon.equip(group, fallback)
|
||||
|
||||
def clear_parking_slots(self) -> None:
|
||||
for cp in self.game.theater.controlpoints:
|
||||
for parking_slot in cp.parking_slots:
|
||||
@ -1314,6 +1332,8 @@ class AircraftConflictGenerator:
|
||||
# have their TOTs set.
|
||||
self.flights[-1].waypoints = [takeoff_point] + flight.points
|
||||
self._setup_custom_payload(flight, group)
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
self._degrade_payload_to_era(flight, group)
|
||||
|
||||
def should_delay_flight(self, flight: Flight,
|
||||
start_time: timedelta) -> bool:
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import inspect
|
||||
|
||||
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QSpinBox, QGridLayout, QVBoxLayout, QSizePolicy
|
||||
from PySide2.QtWidgets import (
|
||||
QGridLayout,
|
||||
QGroupBox,
|
||||
QLabel,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from game import Game
|
||||
from game.data.weapons import Pylon
|
||||
from gen.flights.flight import Flight
|
||||
from qt_ui.windows.mission.flight.payload.QPylonEditor import QPylonEditor
|
||||
|
||||
|
||||
class QLoadoutEditor(QGroupBox):
|
||||
|
||||
def __init__(self, flight, game):
|
||||
super(QLoadoutEditor, self).__init__("Use custom loadout")
|
||||
def __init__(self, flight: Flight, game: Game) -> None:
|
||||
super().__init__("Use custom loadout")
|
||||
self.flight = flight
|
||||
self.game = game
|
||||
self.setCheckable(True)
|
||||
@ -25,7 +31,7 @@ class QLoadoutEditor(QGroupBox):
|
||||
label.setSizePolicy(
|
||||
QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
||||
layout.addWidget(label, i, 0)
|
||||
layout.addWidget(QPylonEditor(flight, pylon), i, 1)
|
||||
layout.addWidget(QPylonEditor(game, flight, pylon), i, 1)
|
||||
|
||||
hboxLayout.addLayout(layout)
|
||||
hboxLayout.addStretch()
|
||||
|
||||
@ -4,13 +4,14 @@ from typing import Optional
|
||||
|
||||
from PySide2.QtWidgets import QComboBox
|
||||
|
||||
from game import Game
|
||||
from game.data.weapons import Pylon, Weapon
|
||||
from gen.flights.flight import Flight
|
||||
|
||||
|
||||
class QPylonEditor(QComboBox):
|
||||
|
||||
def __init__(self, flight: Flight, pylon: Pylon) -> None:
|
||||
def __init__(self, game: Game, flight: Flight, pylon: Pylon) -> None:
|
||||
super().__init__()
|
||||
self.flight = flight
|
||||
self.pylon = pylon
|
||||
@ -18,7 +19,11 @@ class QPylonEditor(QComboBox):
|
||||
current = self.flight.loadout.get(self.pylon.number)
|
||||
|
||||
self.addItem("None", None)
|
||||
allowed = sorted(pylon.allowed, key=operator.attrgetter("name"))
|
||||
if game.settings.restrict_weapons_by_date:
|
||||
weapons = pylon.available_on(game.date)
|
||||
else:
|
||||
weapons = pylon.allowed
|
||||
allowed = sorted(weapons, key=operator.attrgetter("name"))
|
||||
for i, weapon in enumerate(allowed):
|
||||
self.addItem(weapon.name, weapon)
|
||||
if current == weapon:
|
||||
|
||||
@ -248,6 +248,30 @@ class QSettingsWindow(QDialog):
|
||||
campaign_layout.setAlignment(Qt.AlignTop)
|
||||
self.campaign_management_page.setLayout(campaign_layout)
|
||||
|
||||
general = QGroupBox("General")
|
||||
campaign_layout.addWidget(general)
|
||||
|
||||
general_layout = QGridLayout()
|
||||
general.setLayout(general_layout)
|
||||
|
||||
def set_restict_weapons_by_date(value: bool) -> None:
|
||||
self.game.settings.restrict_weapons_by_date = value
|
||||
|
||||
restrict_weapons = QCheckBox()
|
||||
restrict_weapons.setChecked(self.game.settings.restrict_weapons_by_date)
|
||||
restrict_weapons.toggled.connect(set_restict_weapons_by_date)
|
||||
|
||||
tooltip_text = (
|
||||
"Restricts weapon availability based on the campaign date. Data is "
|
||||
"extremely incomplete so does not affect all weapons."
|
||||
)
|
||||
restrict_weapons.setToolTip(tooltip_text)
|
||||
restrict_weapons_label = QLabel("Restrict weapons by date (WIP)")
|
||||
restrict_weapons_label.setToolTip(tooltip_text)
|
||||
|
||||
general_layout.addWidget(restrict_weapons_label, 0, 0)
|
||||
general_layout.addWidget(restrict_weapons, 0, 1, Qt.AlignRight)
|
||||
|
||||
automation = QGroupBox("HQ Automation")
|
||||
campaign_layout.addWidget(automation)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user