mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Loadout implementation cleanup.
Loadout selection no longer has two (disagreeing) implementations. What the UI shows is now what the miz will have. We now store the chosen layout in the Flight *always*, not just for custom loadouts. This means that we do loadout lookups at the start of each turn, but the data is cached in pydcs. Era-specific loadout degradation is still done at generation (and presentation) time. This is so that players can toggle that option and have it affect the *current* turn, rather than the next one.
This commit is contained in:
parent
6ca175345f
commit
0e3bc1ce43
143
game/db.py
143
game/db.py
@ -1,8 +1,8 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Tuple, Type, Union
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
from dcs.countries import country_dict
|
||||
from dcs.helicopters import (
|
||||
@ -62,7 +62,6 @@ from dcs.planes import (
|
||||
F_4E,
|
||||
F_5E_3,
|
||||
F_86F_Sabre,
|
||||
F_A_18C,
|
||||
IL_76MD,
|
||||
IL_78M,
|
||||
JF_17,
|
||||
@ -72,7 +71,6 @@ from dcs.planes import (
|
||||
KC_135,
|
||||
KC135MPRS,
|
||||
KJ_2000,
|
||||
L_39C,
|
||||
L_39ZA,
|
||||
MQ_9_Reaper,
|
||||
M_2000C,
|
||||
@ -92,7 +90,6 @@ from dcs.planes import (
|
||||
P_47D_40,
|
||||
P_51D,
|
||||
P_51D_30_NA,
|
||||
PlaneType,
|
||||
RQ_1A_Predator,
|
||||
S_3B,
|
||||
S_3B_Tanker,
|
||||
@ -103,7 +100,6 @@ from dcs.planes import (
|
||||
Su_24MR,
|
||||
Su_25,
|
||||
Su_25T,
|
||||
Su_25TM,
|
||||
Su_27,
|
||||
Su_30,
|
||||
Su_33,
|
||||
@ -1178,141 +1174,6 @@ COMMON_OVERRIDE = {
|
||||
AWACS: "AEW&C",
|
||||
}
|
||||
|
||||
"""
|
||||
This is a list of mappings from the FlightType of a Flight to the type of payload defined in the
|
||||
resources/payloads/UNIT_TYPE.lua file. A Flight has no concept of a PyDCS task, so COMMON_OVERRIDE cannot be
|
||||
used here. This is used in the payload editor, for setting the default loadout of an object.
|
||||
The left element is the FlightType name, and the right element is a tuple containing what is used in the lua file.
|
||||
Some aircraft differ from the standard loadout names, so those have been included here too.
|
||||
The priority goes from first to last - the first element in the tuple will be tried first, then the second, etc.
|
||||
"""
|
||||
|
||||
EXPANDED_TASK_PAYLOAD_OVERRIDE = {
|
||||
"TARCAP": ("CAP HEAVY", "CAP"),
|
||||
"BARCAP": ("CAP HEAVY", "CAP"),
|
||||
"CAS": ("CAS MAVERICK F", "CAS"),
|
||||
"INTERCEPTION": ("CAP HEAVY", "CAP"),
|
||||
"STRIKE": ("STRIKE",),
|
||||
"ANTISHIP": ("ANTISHIP",),
|
||||
"SEAD": ("SEAD",),
|
||||
"DEAD": ("SEAD",),
|
||||
"ESCORT": ("CAP HEAVY", "CAP"),
|
||||
"BAI": ("BAI", "CAS MAVERICK F", "CAS"),
|
||||
"SWEEP": ("CAP HEAVY", "CAP"),
|
||||
"OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
||||
"OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"),
|
||||
"TRANSPORT": (),
|
||||
}
|
||||
|
||||
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||
B_1B: COMMON_OVERRIDE,
|
||||
B_52H: COMMON_OVERRIDE,
|
||||
F_117A: COMMON_OVERRIDE,
|
||||
F_15E: COMMON_OVERRIDE,
|
||||
FA_18C_hornet: {
|
||||
CAP: "CAP HEAVY",
|
||||
Intercept: "CAP HEAVY",
|
||||
CAS: "CAS MAVERICK F",
|
||||
PinpointStrike: "STRIKE",
|
||||
SEAD: "SEAD",
|
||||
AntishipStrike: "ANTISHIP",
|
||||
GroundAttack: "STRIKE",
|
||||
Escort: "CAP HEAVY",
|
||||
FighterSweep: "CAP HEAVY",
|
||||
},
|
||||
F_A_18C: {
|
||||
CAP: "CAP HEAVY",
|
||||
Intercept: "CAP HEAVY",
|
||||
CAS: "CAS MAVERICK F",
|
||||
PinpointStrike: "STRIKE",
|
||||
SEAD: "SEAD",
|
||||
AntishipStrike: "ANTISHIP",
|
||||
GroundAttack: "STRIKE",
|
||||
Escort: "CAP HEAVY",
|
||||
FighterSweep: "CAP HEAVY",
|
||||
},
|
||||
Tu_160: {
|
||||
PinpointStrike: "Kh-65*12",
|
||||
},
|
||||
Tu_22M3: COMMON_OVERRIDE,
|
||||
Tu_95MS: COMMON_OVERRIDE,
|
||||
A_10A: COMMON_OVERRIDE,
|
||||
A_10C: COMMON_OVERRIDE,
|
||||
A_10C_2: COMMON_OVERRIDE,
|
||||
AV8BNA: COMMON_OVERRIDE,
|
||||
C_101CC: COMMON_OVERRIDE,
|
||||
F_5E_3: COMMON_OVERRIDE,
|
||||
F_14A_135_GR: COMMON_OVERRIDE,
|
||||
F_14B: COMMON_OVERRIDE,
|
||||
F_15C: COMMON_OVERRIDE,
|
||||
F_22A: COMMON_OVERRIDE,
|
||||
F_16C_50: COMMON_OVERRIDE,
|
||||
JF_17: COMMON_OVERRIDE,
|
||||
M_2000C: COMMON_OVERRIDE,
|
||||
MiG_15bis: COMMON_OVERRIDE,
|
||||
MiG_19P: COMMON_OVERRIDE,
|
||||
MiG_21Bis: COMMON_OVERRIDE,
|
||||
AJS37: COMMON_OVERRIDE,
|
||||
Su_25T: COMMON_OVERRIDE,
|
||||
Su_25: COMMON_OVERRIDE,
|
||||
Su_27: COMMON_OVERRIDE,
|
||||
Su_33: COMMON_OVERRIDE,
|
||||
MiG_29A: COMMON_OVERRIDE,
|
||||
MiG_29G: COMMON_OVERRIDE,
|
||||
MiG_29S: COMMON_OVERRIDE,
|
||||
Su_24M: COMMON_OVERRIDE,
|
||||
Su_30: COMMON_OVERRIDE,
|
||||
Su_34: COMMON_OVERRIDE,
|
||||
Su_57: COMMON_OVERRIDE,
|
||||
MiG_23MLD: COMMON_OVERRIDE,
|
||||
MiG_27K: COMMON_OVERRIDE,
|
||||
Tornado_GR4: COMMON_OVERRIDE,
|
||||
Tornado_IDS: COMMON_OVERRIDE,
|
||||
Mirage_2000_5: COMMON_OVERRIDE,
|
||||
MiG_31: COMMON_OVERRIDE,
|
||||
S_3B: COMMON_OVERRIDE,
|
||||
SA342M: COMMON_OVERRIDE,
|
||||
SA342L: COMMON_OVERRIDE,
|
||||
SA342Mistral: COMMON_OVERRIDE,
|
||||
Mi_8MT: COMMON_OVERRIDE,
|
||||
Mi_24V: COMMON_OVERRIDE,
|
||||
Mi_28N: COMMON_OVERRIDE,
|
||||
Ka_50: COMMON_OVERRIDE,
|
||||
L_39ZA: COMMON_OVERRIDE,
|
||||
L_39C: COMMON_OVERRIDE,
|
||||
Su_17M4: COMMON_OVERRIDE,
|
||||
F_4E: COMMON_OVERRIDE,
|
||||
P_47D_30: COMMON_OVERRIDE,
|
||||
P_47D_30bl1: COMMON_OVERRIDE,
|
||||
P_47D_40: COMMON_OVERRIDE,
|
||||
B_17G: COMMON_OVERRIDE,
|
||||
P_51D: COMMON_OVERRIDE,
|
||||
P_51D_30_NA: COMMON_OVERRIDE,
|
||||
FW_190D9: COMMON_OVERRIDE,
|
||||
FW_190A8: COMMON_OVERRIDE,
|
||||
Bf_109K_4: COMMON_OVERRIDE,
|
||||
I_16: COMMON_OVERRIDE,
|
||||
SpitfireLFMkIXCW: COMMON_OVERRIDE,
|
||||
SpitfireLFMkIX: COMMON_OVERRIDE,
|
||||
A_20G: COMMON_OVERRIDE,
|
||||
A_4E_C: COMMON_OVERRIDE,
|
||||
MB_339PAN: COMMON_OVERRIDE,
|
||||
OH_58D: COMMON_OVERRIDE,
|
||||
F_16A: COMMON_OVERRIDE,
|
||||
MQ_9_Reaper: COMMON_OVERRIDE,
|
||||
RQ_1A_Predator: COMMON_OVERRIDE,
|
||||
WingLoong_I: COMMON_OVERRIDE,
|
||||
AH_1W: COMMON_OVERRIDE,
|
||||
AH_64D: COMMON_OVERRIDE,
|
||||
AH_64A: COMMON_OVERRIDE,
|
||||
SH_60B: COMMON_OVERRIDE,
|
||||
Hercules: COMMON_OVERRIDE,
|
||||
F_86F_Sabre: COMMON_OVERRIDE,
|
||||
Su_25TM: {
|
||||
SEAD: "Kh-31P*2_Kh-25ML*4_R-73*2_L-081_MPS410",
|
||||
},
|
||||
}
|
||||
|
||||
"""
|
||||
Aircraft livery overrides. Syntax as follows:
|
||||
|
||||
|
||||
@ -61,12 +61,10 @@ from dcs.task import (
|
||||
OptReactOnThreat,
|
||||
OptRestrictJettison,
|
||||
OrbitAction,
|
||||
PinpointStrike,
|
||||
RunwayAttack,
|
||||
SEAD,
|
||||
StartCommand,
|
||||
Targets,
|
||||
Task,
|
||||
Transport,
|
||||
WeaponType,
|
||||
)
|
||||
@ -77,7 +75,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, Weapon
|
||||
from game.data.weapons import Pylon
|
||||
from game.factions.faction import Faction
|
||||
from game.settings import Settings
|
||||
from game.theater.controlpoint import (
|
||||
@ -728,43 +726,13 @@ class AircraftConflictGenerator:
|
||||
def _setup_group(
|
||||
self,
|
||||
group: FlyingGroup,
|
||||
loadout_for_task: Type[Task],
|
||||
package: Package,
|
||||
flight: Flight,
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
did_load_loadout = False
|
||||
unit_type = group.units[0].unit_type
|
||||
|
||||
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
|
||||
# Clear pylons
|
||||
for p in group.units:
|
||||
p.pylons.clear()
|
||||
|
||||
# Now load loadout
|
||||
if loadout_for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][loadout_for_task]
|
||||
group.load_loadout(payload_name)
|
||||
if not group.units[0].pylons and loadout_for_task == RunwayAttack:
|
||||
if PinpointStrike in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||
logging.warning(
|
||||
'No loadout for "Runway Attack" for the {}, defaulting to Strike loadout'.format(
|
||||
str(unit_type)
|
||||
)
|
||||
)
|
||||
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][
|
||||
PinpointStrike
|
||||
]
|
||||
group.load_loadout(payload_name)
|
||||
did_load_loadout = True
|
||||
logging.info(
|
||||
"Loaded overridden payload for {} - {} for task {}".format(
|
||||
unit_type, payload_name, loadout_for_task
|
||||
)
|
||||
)
|
||||
|
||||
if not did_load_loadout:
|
||||
group.load_task_default_loadout(loadout_for_task)
|
||||
self._setup_payload(flight, group)
|
||||
|
||||
if unit_type in db.PLANE_LIVERY_OVERRIDES:
|
||||
for unit_instance in group.units:
|
||||
@ -976,39 +944,20 @@ class AircraftConflictGenerator:
|
||||
else:
|
||||
assert False
|
||||
|
||||
@staticmethod
|
||||
def _setup_custom_payload(flight: Flight, group: FlyingGroup) -> None:
|
||||
if not flight.use_custom_loadout:
|
||||
return
|
||||
|
||||
logging.info("Custom loadout for flight : " + flight.__repr__())
|
||||
def _setup_payload(self, flight: Flight, group: FlyingGroup) -> None:
|
||||
for p in group.units:
|
||||
p.pylons.clear()
|
||||
|
||||
for pylon_number, weapon in flight.loadout.items():
|
||||
loadout = flight.loadout
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
loadout = loadout.degrade_for_date(flight.unit_type, self.game.date)
|
||||
|
||||
for pylon_number, weapon in loadout.pylons.items():
|
||||
if weapon is None:
|
||||
continue
|
||||
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)
|
||||
break
|
||||
|
||||
def clear_parking_slots(self) -> None:
|
||||
for cp in self.game.theater.controlpoints:
|
||||
for parking_slot in cp.parking_slots:
|
||||
@ -1237,7 +1186,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = CAP.name
|
||||
self._setup_group(group, CAP, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
|
||||
if flight.unit_type not in GUNFIGHTERS:
|
||||
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
|
||||
@ -1254,7 +1203,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = FighterSweep.name
|
||||
self._setup_group(group, FighterSweep, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
|
||||
if flight.unit_type not in GUNFIGHTERS:
|
||||
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
|
||||
@ -1271,7 +1220,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = CAS.name
|
||||
self._setup_group(group, CAS, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1295,7 +1244,7 @@ class AircraftConflictGenerator:
|
||||
# waypoint actions the group may perform.
|
||||
group.task = CAS.name
|
||||
# But we still use the SEAD *loadout*.
|
||||
self._setup_group(group, SEAD, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1312,7 +1261,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = SEAD.name
|
||||
self._setup_group(group, SEAD, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1329,7 +1278,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = GroundAttack.name
|
||||
self._setup_group(group, GroundAttack, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1345,7 +1294,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = AntishipStrike.name
|
||||
self._setup_group(group, AntishipStrike, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1361,7 +1310,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = RunwayAttack.name
|
||||
self._setup_group(group, RunwayAttack, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1377,7 +1326,7 @@ class AircraftConflictGenerator:
|
||||
dynamic_runways: Dict[str, RunwayData],
|
||||
) -> None:
|
||||
group.task = CAS.name
|
||||
self._setup_group(group, CAS, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1400,7 +1349,7 @@ class AircraftConflictGenerator:
|
||||
)
|
||||
return
|
||||
|
||||
self._setup_group(group, AWACS, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
|
||||
# Awacs task action
|
||||
self.configure_behavior(
|
||||
@ -1423,7 +1372,7 @@ class AircraftConflictGenerator:
|
||||
# Search Then Engage task, which we have to use instead of the Escort
|
||||
# task for the reasons explained in JoinPointBuilder.
|
||||
group.task = CAP.name
|
||||
self._setup_group(group, CAP, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group, roe=OptROE.Values.OpenFire, restrict_jettison=True
|
||||
)
|
||||
@ -1439,7 +1388,7 @@ class AircraftConflictGenerator:
|
||||
# Search Then Engage task, which we have to use instead of the Escort
|
||||
# task for the reasons explained in JoinPointBuilder.
|
||||
group.task = Transport.name
|
||||
self._setup_group(group, Transport, package, flight, dynamic_runways)
|
||||
self._setup_group(group, package, flight, dynamic_runways)
|
||||
self.configure_behavior(
|
||||
group,
|
||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||
@ -1540,9 +1489,6 @@ class AircraftConflictGenerator:
|
||||
# Set here rather than when the FlightData is created so they waypoints
|
||||
# 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:
|
||||
if start_time.total_seconds() <= 0:
|
||||
|
||||
@ -2,19 +2,19 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type
|
||||
from typing import List, Optional, TYPE_CHECKING, Type
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.point import MovingPoint, PointAction
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game import db
|
||||
from game.data.weapons import Weapon
|
||||
from game.theater.controlpoint import ControlPoint, MissionTarget
|
||||
from game.utils import Distance, meters
|
||||
from gen.flights.loadouts import Loadout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.transfers import Airlift, TransferOrder
|
||||
from game.transfers import TransferOrder
|
||||
from gen.ato import Package
|
||||
from gen.flights.flightplan import FlightPlan
|
||||
|
||||
@ -179,7 +179,7 @@ class Flight:
|
||||
self.flight_type = flight_type
|
||||
# TODO: Replace with FlightPlan.
|
||||
self.targets: List[MissionTarget] = []
|
||||
self.loadout: Dict[int, Optional[Weapon]] = {}
|
||||
self.loadout = Loadout.default_for(self)
|
||||
self.start_type = start_type
|
||||
self.use_custom_loadout = False
|
||||
self.client_count = 0
|
||||
|
||||
121
gen/flights/loadouts.py
Normal file
121
gen/flights/loadouts.py
Normal file
@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import Optional, List, Iterator, Type, TYPE_CHECKING, Mapping
|
||||
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game.data.weapons import Weapon, Pylon
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gen.flights.flight import Flight
|
||||
|
||||
|
||||
class Loadout:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
pylons: Mapping[int, Optional[Weapon]],
|
||||
date: Optional[datetime.date],
|
||||
is_custom: bool = False,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.pylons = {k: v for k, v in pylons.items() if v is not None}
|
||||
self.date = date
|
||||
self.is_custom = is_custom
|
||||
|
||||
def derive_custom(self, name: str) -> Loadout:
|
||||
return Loadout(name, self.pylons, self.date, is_custom=True)
|
||||
|
||||
def degrade_for_date(
|
||||
self, unit_type: Type[FlyingType], date: datetime.date
|
||||
) -> Loadout:
|
||||
if self.date is not None and self.date <= date:
|
||||
return Loadout(self.name, self.pylons, self.date)
|
||||
|
||||
new_pylons = dict(self.pylons)
|
||||
for pylon_number, weapon in self.pylons.items():
|
||||
if not weapon.available_on(date):
|
||||
pylon = Pylon.for_aircraft(unit_type, pylon_number)
|
||||
for fallback in weapon.fallbacks:
|
||||
if not pylon.can_equip(fallback):
|
||||
continue
|
||||
if not fallback.available_on(date):
|
||||
continue
|
||||
new_pylons[pylon_number] = fallback
|
||||
break
|
||||
return Loadout(f"{self.name} ({date.year})", new_pylons, date)
|
||||
|
||||
@classmethod
|
||||
def iter_for(cls, flight: Flight) -> Iterator[Loadout]:
|
||||
# Dict of payload ID (numeric) to:
|
||||
#
|
||||
# {
|
||||
# "name": The name the user set in the ME
|
||||
# "pylons": List (as a dict) of dicts of:
|
||||
# {"CLSID": class ID, "num": pylon number}
|
||||
# "tasks": List (as a dict) of task IDs the payload is used by.
|
||||
# }
|
||||
payloads = flight.unit_type.load_payloads()
|
||||
for payload in payloads["payloads"].values():
|
||||
name = payload["name"]
|
||||
pylons = payload["pylons"]
|
||||
yield Loadout(
|
||||
name,
|
||||
{p["num"]: Weapon.from_clsid(p["CLSID"]) for p in pylons.values()},
|
||||
date=None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def all_for(cls, flight: Flight) -> List[Loadout]:
|
||||
return list(cls.iter_for(flight))
|
||||
|
||||
@classmethod
|
||||
def default_loadout_names_for(cls, flight: Flight) -> Iterator[str]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
# This is a list of mappings from the FlightType of a Flight to the type of
|
||||
# payload defined in the resources/payloads/UNIT_TYPE.lua file. A Flight has no
|
||||
# concept of a PyDCS task, so COMMON_OVERRIDE cannot be used here. This is used
|
||||
# in the payload editor, for setting the default loadout of an object. The left
|
||||
# element is the FlightType name, and the right element is a tuple containing
|
||||
# what is used in the lua file. Some aircraft differ from the standard loadout
|
||||
# names, so those have been included here too. The priority goes from first to
|
||||
# last - the first element in the tuple will be tried first, then the second,
|
||||
# etc.
|
||||
yield from {
|
||||
FlightType.TARCAP: ("CAP HEAVY", "CAP"),
|
||||
FlightType.BARCAP: ("CAP HEAVY", "CAP"),
|
||||
FlightType.CAS: ("CAS MAVERICK F", "CAS"),
|
||||
FlightType.INTERCEPTION: ("CAP HEAVY", "CAP"),
|
||||
FlightType.STRIKE: ("STRIKE",),
|
||||
FlightType.ANTISHIP: ("ANTISHIP",),
|
||||
FlightType.SEAD: ("SEAD",),
|
||||
FlightType.DEAD: ("SEAD",),
|
||||
FlightType.ESCORT: ("CAP HEAVY", "CAP"),
|
||||
FlightType.BAI: ("BAI", "CAS MAVERICK F", "CAS"),
|
||||
FlightType.SWEEP: ("CAP HEAVY", "CAP"),
|
||||
FlightType.OCA_RUNWAY: ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
||||
FlightType.OCA_AIRCRAFT: ("OCA", "CAS MAVERICK F", "CAS"),
|
||||
FlightType.TRANSPORT: (),
|
||||
FlightType.AEWC: (),
|
||||
}.get(flight.flight_type, [])
|
||||
|
||||
@classmethod
|
||||
def default_for(cls, flight: Flight) -> Loadout:
|
||||
# Iterate through each possible payload type for a given aircraft.
|
||||
# Some aircraft have custom loadouts that in aren't the standard set.
|
||||
for name in cls.default_loadout_names_for(flight):
|
||||
# This operation is cached, but must be called before load_by_name will
|
||||
# work.
|
||||
flight.unit_type.load_payloads()
|
||||
payload = flight.unit_type.loadout_by_name(name)
|
||||
if payload is not None:
|
||||
return Loadout(
|
||||
name,
|
||||
{i: Weapon.from_clsid(d["clsid"]) for i, d in payload},
|
||||
date=None,
|
||||
)
|
||||
|
||||
# TODO: Try group.load_task_default_loadout(loadout_for_task)
|
||||
return Loadout("Empty", {}, date=None)
|
||||
@ -1,8 +1,9 @@
|
||||
from PySide2.QtWidgets import QFrame, QGridLayout, QLabel
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtWidgets import QFrame, QLabel, QVBoxLayout
|
||||
|
||||
from game import Game
|
||||
from gen.flights.flight import Flight
|
||||
from gen.flights.loadouts import Loadout
|
||||
from qt_ui.windows.mission.flight.payload.QLoadoutEditor import QLoadoutEditor
|
||||
|
||||
|
||||
@ -11,10 +12,8 @@ class QFlightPayloadTab(QFrame):
|
||||
super(QFlightPayloadTab, self).__init__()
|
||||
self.flight = flight
|
||||
self.payload_editor = QLoadoutEditor(flight, game)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QGridLayout()
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Docs Link
|
||||
docsText = QLabel(
|
||||
@ -23,7 +22,15 @@ class QFlightPayloadTab(QFrame):
|
||||
docsText.setAlignment(Qt.AlignCenter)
|
||||
docsText.setOpenExternalLinks(True)
|
||||
|
||||
self.payload_editor.toggled.connect(self.on_custom_toggled)
|
||||
layout.addWidget(self.payload_editor)
|
||||
layout.addWidget(docsText)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def on_custom_toggled(self, use_custom: bool) -> None:
|
||||
if use_custom:
|
||||
self.flight.loadout = self.flight.loadout.derive_custom("Custom")
|
||||
else:
|
||||
self.flight.loadout = Loadout.default_for(self.flight)
|
||||
self.payload_editor.reset_pylons()
|
||||
|
||||
@ -18,11 +18,9 @@ class QLoadoutEditor(QGroupBox):
|
||||
self.flight = flight
|
||||
self.game = game
|
||||
self.setCheckable(True)
|
||||
self.setChecked(flight.use_custom_loadout)
|
||||
self.setChecked(flight.loadout.is_custom)
|
||||
|
||||
self.toggled.connect(self.on_toggle)
|
||||
|
||||
hboxLayout = QVBoxLayout(self)
|
||||
vbox = QVBoxLayout(self)
|
||||
layout = QGridLayout(self)
|
||||
|
||||
for i, pylon in enumerate(Pylon.iter_pylons(self.flight.unit_type)):
|
||||
@ -31,16 +29,15 @@ class QLoadoutEditor(QGroupBox):
|
||||
layout.addWidget(label, i, 0)
|
||||
layout.addWidget(QPylonEditor(game, flight, pylon), i, 1)
|
||||
|
||||
hboxLayout.addLayout(layout)
|
||||
hboxLayout.addStretch()
|
||||
self.setLayout(hboxLayout)
|
||||
vbox.addLayout(layout)
|
||||
vbox.addStretch()
|
||||
self.setLayout(vbox)
|
||||
|
||||
if not self.isChecked():
|
||||
for i in self.findChildren(QPylonEditor):
|
||||
i.default_loadout()
|
||||
for i in self.findChildren(QPylonEditor):
|
||||
i.set_from(self.flight.loadout)
|
||||
|
||||
def on_toggle(self):
|
||||
def reset_pylons(self) -> None:
|
||||
self.flight.use_custom_loadout = self.isChecked()
|
||||
if not self.isChecked():
|
||||
for i in self.findChildren(QPylonEditor):
|
||||
i.default_loadout()
|
||||
i.set_from(self.flight.loadout)
|
||||
|
||||
@ -4,10 +4,10 @@ from typing import Optional
|
||||
|
||||
from PySide2.QtWidgets import QComboBox
|
||||
|
||||
from game import Game, db
|
||||
from game import Game
|
||||
from game.data.weapons import Pylon, Weapon
|
||||
from gen.flights.flight import Flight
|
||||
from dcs import weapons_data
|
||||
from gen.flights.loadouts import Loadout
|
||||
|
||||
|
||||
class QPylonEditor(QComboBox):
|
||||
@ -17,7 +17,7 @@ class QPylonEditor(QComboBox):
|
||||
self.pylon = pylon
|
||||
self.game = game
|
||||
|
||||
current = self.flight.loadout.get(self.pylon.number)
|
||||
current = self.flight.loadout.pylons.get(self.pylon.number)
|
||||
|
||||
self.addItem("None", None)
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
@ -34,57 +34,29 @@ class QPylonEditor(QComboBox):
|
||||
|
||||
def on_pylon_change(self):
|
||||
selected: Optional[Weapon] = self.currentData()
|
||||
self.flight.loadout[self.pylon.number] = selected
|
||||
self.flight.loadout.pylons[self.pylon.number] = selected
|
||||
|
||||
if selected is None:
|
||||
logging.debug(f"Pylon {self.pylon.number} emptied")
|
||||
else:
|
||||
logging.debug(f"Pylon {self.pylon.number} changed to {selected.name}")
|
||||
|
||||
def default_loadout(self) -> None:
|
||||
self.flight.unit_type.load_payloads()
|
||||
self.setCurrentText("None")
|
||||
pylon_default_weapon = None
|
||||
historical_weapon = None
|
||||
loadout = None
|
||||
# Iterate through each possible payload type for a given aircraft.
|
||||
# Some aircraft have custom loadouts that in aren't the standard set.
|
||||
for payload_override in db.EXPANDED_TASK_PAYLOAD_OVERRIDE.get(
|
||||
self.flight.flight_type.name
|
||||
):
|
||||
if loadout is None:
|
||||
loadout = self.flight.unit_type.loadout_by_name(payload_override)
|
||||
if loadout is not None:
|
||||
for i in loadout:
|
||||
if i[0] == self.pylon.number:
|
||||
pylon_default_weapon = i[1]["clsid"]
|
||||
# TODO: Handle removed pylons better.
|
||||
if pylon_default_weapon == "<CLEAN>":
|
||||
pylon_default_weapon = None
|
||||
if pylon_default_weapon is not None:
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
orig_weapon = Weapon.from_clsid(pylon_default_weapon)
|
||||
if not orig_weapon.available_on(self.game.date):
|
||||
for fallback in orig_weapon.fallbacks:
|
||||
if historical_weapon is None:
|
||||
if not self.pylon.can_equip(fallback):
|
||||
continue
|
||||
if not fallback.available_on(self.game.date):
|
||||
continue
|
||||
historical_weapon = fallback
|
||||
else:
|
||||
historical_weapon = orig_weapon
|
||||
if historical_weapon is not None:
|
||||
self.setCurrentText(
|
||||
weapons_data.weapon_ids.get(historical_weapon.cls_id).get(
|
||||
"name"
|
||||
)
|
||||
)
|
||||
else:
|
||||
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)
|
||||
def weapon_from_loadout(self, loadout: Loadout) -> Optional[Weapon]:
|
||||
weapon = loadout.pylons.get(self.pylon.number)
|
||||
if weapon is None:
|
||||
return None
|
||||
# TODO: Handle removed pylons better.
|
||||
if weapon.cls_id == "<CLEAN>":
|
||||
return None
|
||||
return weapon
|
||||
|
||||
def matching_weapon_name(self, loadout: Loadout) -> str:
|
||||
if self.game.settings.restrict_weapons_by_date:
|
||||
loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date)
|
||||
weapon = self.weapon_from_loadout(loadout)
|
||||
if weapon is None:
|
||||
return ""
|
||||
return weapon.name
|
||||
|
||||
def set_from(self, loadout: Loadout) -> None:
|
||||
self.setCurrentText(self.matching_weapon_name(loadout))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user