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]** FOBs generate only $10M per turn (previously $20M like airbases).
|
||||||
* **[Economy]** Carriers and off-map spawns generate no income (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.
|
* **[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
|
## Fixes
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
import inspect
|
import inspect
|
||||||
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
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.unitgroup import FlyingGroup
|
||||||
from dcs.unittype import FlyingType
|
from dcs.unittype import FlyingType
|
||||||
|
from dcs.weapons_data import Weapons, weapon_ids
|
||||||
|
|
||||||
|
|
||||||
PydcsWeapon = Dict[str, Union[int, str]]
|
PydcsWeapon = Dict[str, Union[int, str]]
|
||||||
@ -20,6 +24,14 @@ class Weapon:
|
|||||||
name: str
|
name: str
|
||||||
weight: int
|
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
|
@property
|
||||||
def as_pydcs(self) -> PydcsWeapon:
|
def as_pydcs(self) -> PydcsWeapon:
|
||||||
return {
|
return {
|
||||||
@ -28,6 +40,13 @@ class Weapon:
|
|||||||
"weight": self.weight,
|
"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
|
@classmethod
|
||||||
def from_pydcs(cls, weapon_data: PydcsWeapon) -> Weapon:
|
def from_pydcs(cls, weapon_data: PydcsWeapon) -> Weapon:
|
||||||
return cls(
|
return cls(
|
||||||
@ -36,6 +55,13 @@ class Weapon:
|
|||||||
cast(int, weapon_data["weight"])
|
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)
|
@dataclass(frozen=True)
|
||||||
class Pylon:
|
class Pylon:
|
||||||
@ -53,6 +79,11 @@ class Pylon:
|
|||||||
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
|
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
|
||||||
return self.number, weapon.as_pydcs
|
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
|
@classmethod
|
||||||
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
||||||
# In pydcs these are all arbitrary inner classes of the aircraft type.
|
# 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]:
|
def iter_pylons(cls, aircraft: Type[FlyingType]) -> Iterator[Pylon]:
|
||||||
for pylon in sorted(list(aircraft.pylons)):
|
for pylon in sorted(list(aircraft.pylons)):
|
||||||
yield cls.for_aircraft(aircraft, pylon)
|
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_runway_repair: bool = False
|
||||||
automate_front_line_reinforcements: bool = False
|
automate_front_line_reinforcements: bool = False
|
||||||
automate_aircraft_reinforcements: bool = False
|
automate_aircraft_reinforcements: bool = False
|
||||||
|
restrict_weapons_by_date: bool = False
|
||||||
|
|
||||||
# Performance oriented
|
# Performance oriented
|
||||||
perf_red_alert_state: bool = True
|
perf_red_alert_state: bool = True
|
||||||
|
|||||||
@ -74,7 +74,7 @@ from dcs.unittype import FlyingType, UnitType
|
|||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from game.data.cap_capabilities_db import GUNFIGHTERS
|
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.factions.faction import Faction
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.theater.controlpoint import (
|
from game.theater.controlpoint import (
|
||||||
@ -918,6 +918,24 @@ class AircraftConflictGenerator:
|
|||||||
pylon = Pylon.for_aircraft(flight.unit_type, pylon_number)
|
pylon = Pylon.for_aircraft(flight.unit_type, pylon_number)
|
||||||
pylon.equip(group, weapon)
|
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:
|
def clear_parking_slots(self) -> None:
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
for parking_slot in cp.parking_slots:
|
for parking_slot in cp.parking_slots:
|
||||||
@ -1314,6 +1332,8 @@ class AircraftConflictGenerator:
|
|||||||
# have their TOTs set.
|
# have their TOTs set.
|
||||||
self.flights[-1].waypoints = [takeoff_point] + flight.points
|
self.flights[-1].waypoints = [takeoff_point] + flight.points
|
||||||
self._setup_custom_payload(flight, group)
|
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,
|
def should_delay_flight(self, flight: Flight,
|
||||||
start_time: timedelta) -> bool:
|
start_time: timedelta) -> bool:
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import inspect
|
from PySide2.QtWidgets import (
|
||||||
|
QGridLayout,
|
||||||
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QSpinBox, QGridLayout, QVBoxLayout, QSizePolicy
|
QGroupBox,
|
||||||
|
QLabel,
|
||||||
|
QSizePolicy,
|
||||||
|
QVBoxLayout,
|
||||||
|
)
|
||||||
|
|
||||||
|
from game import Game
|
||||||
from game.data.weapons import Pylon
|
from game.data.weapons import Pylon
|
||||||
|
from gen.flights.flight import Flight
|
||||||
from qt_ui.windows.mission.flight.payload.QPylonEditor import QPylonEditor
|
from qt_ui.windows.mission.flight.payload.QPylonEditor import QPylonEditor
|
||||||
|
|
||||||
|
|
||||||
class QLoadoutEditor(QGroupBox):
|
class QLoadoutEditor(QGroupBox):
|
||||||
|
|
||||||
def __init__(self, flight, game):
|
def __init__(self, flight: Flight, game: Game) -> None:
|
||||||
super(QLoadoutEditor, self).__init__("Use custom loadout")
|
super().__init__("Use custom loadout")
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.game = game
|
self.game = game
|
||||||
self.setCheckable(True)
|
self.setCheckable(True)
|
||||||
@ -25,7 +31,7 @@ class QLoadoutEditor(QGroupBox):
|
|||||||
label.setSizePolicy(
|
label.setSizePolicy(
|
||||||
QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
||||||
layout.addWidget(label, i, 0)
|
layout.addWidget(label, i, 0)
|
||||||
layout.addWidget(QPylonEditor(flight, pylon), i, 1)
|
layout.addWidget(QPylonEditor(game, flight, pylon), i, 1)
|
||||||
|
|
||||||
hboxLayout.addLayout(layout)
|
hboxLayout.addLayout(layout)
|
||||||
hboxLayout.addStretch()
|
hboxLayout.addStretch()
|
||||||
|
|||||||
@ -4,13 +4,14 @@ from typing import Optional
|
|||||||
|
|
||||||
from PySide2.QtWidgets import QComboBox
|
from PySide2.QtWidgets import QComboBox
|
||||||
|
|
||||||
|
from game import Game
|
||||||
from game.data.weapons import Pylon, Weapon
|
from game.data.weapons import Pylon, Weapon
|
||||||
from gen.flights.flight import Flight
|
from gen.flights.flight import Flight
|
||||||
|
|
||||||
|
|
||||||
class QPylonEditor(QComboBox):
|
class QPylonEditor(QComboBox):
|
||||||
|
|
||||||
def __init__(self, flight: Flight, pylon: Pylon) -> None:
|
def __init__(self, game: Game, flight: Flight, pylon: Pylon) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.pylon = pylon
|
self.pylon = pylon
|
||||||
@ -18,7 +19,11 @@ class QPylonEditor(QComboBox):
|
|||||||
current = self.flight.loadout.get(self.pylon.number)
|
current = self.flight.loadout.get(self.pylon.number)
|
||||||
|
|
||||||
self.addItem("None", None)
|
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):
|
for i, weapon in enumerate(allowed):
|
||||||
self.addItem(weapon.name, weapon)
|
self.addItem(weapon.name, weapon)
|
||||||
if current == weapon:
|
if current == weapon:
|
||||||
|
|||||||
@ -248,6 +248,30 @@ class QSettingsWindow(QDialog):
|
|||||||
campaign_layout.setAlignment(Qt.AlignTop)
|
campaign_layout.setAlignment(Qt.AlignTop)
|
||||||
self.campaign_management_page.setLayout(campaign_layout)
|
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")
|
automation = QGroupBox("HQ Automation")
|
||||||
campaign_layout.addWidget(automation)
|
campaign_layout.addWidget(automation)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user