mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Allow per pilot loadouts and properties.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/3092.
This commit is contained in:
parent
8e670e1a3c
commit
485229b92f
@ -209,6 +209,7 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
|
|||||||
## Features/Improvements
|
## Features/Improvements
|
||||||
|
|
||||||
* **[Flight Planning]** Improved IP selection for targets that are near the center of a threat zone.
|
* **[Flight Planning]** Improved IP selection for targets that are near the center of a threat zone.
|
||||||
|
* **[Flight Planning]** Loadouts and aircraft properties can now be set per-flight member. Warning: AI flights should not use mixed loadouts.
|
||||||
* **[Modding]** Factions can now specify the ship type to be used for cargo shipping. The Handy Wind will be used by default, but WW2 factions can pick something more appropriate.
|
* **[Modding]** Factions can now specify the ship type to be used for cargo shipping. The Handy Wind will be used by default, but WW2 factions can pick something more appropriate.
|
||||||
* **[UI]** An error will be displayed when invalid fast-forward options are selected rather than beginning a never ending simulation.
|
* **[UI]** An error will be displayed when invalid fast-forward options are selected rather than beginning a never ending simulation.
|
||||||
* **[UI]** Added cheats for instantly repairing and destroying runways.
|
* **[UI]** Added cheats for instantly repairing and destroying runways.
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
from collections.abc import Iterator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, List, Optional, TYPE_CHECKING
|
from typing import Any, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
@ -9,10 +10,11 @@ from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
|
|||||||
|
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
from pydcs_extensions.hercules.hercules import Hercules
|
from pydcs_extensions.hercules.hercules import Hercules
|
||||||
|
from .flightmembers import FlightMembers
|
||||||
from .flightroster import FlightRoster
|
from .flightroster import FlightRoster
|
||||||
from .flightstate import FlightState, Navigating, Uninitialized
|
from .flightstate import FlightState, Navigating, Uninitialized
|
||||||
from .flightstate.killed import Killed
|
from .flightstate.killed import Killed
|
||||||
from .loadouts import Loadout, Weapon
|
from .loadouts import Weapon
|
||||||
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
|
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
|
||||||
from ..radio.TacanContainer import TacanContainer
|
from ..radio.TacanContainer import TacanContainer
|
||||||
from ..radio.radios import RadioFrequency
|
from ..radio.radios import RadioFrequency
|
||||||
@ -31,6 +33,8 @@ if TYPE_CHECKING:
|
|||||||
from game.squadrons import Squadron, Pilot
|
from game.squadrons import Squadron, Pilot
|
||||||
from game.theater import ControlPoint
|
from game.theater import ControlPoint
|
||||||
from game.transfers import TransferOrder
|
from game.transfers import TransferOrder
|
||||||
|
from game.data.weapons import WeaponType
|
||||||
|
from .flightmember import FlightMember
|
||||||
from .flightplans.flightplan import FlightPlan
|
from .flightplans.flightplan import FlightPlan
|
||||||
from .flighttype import FlightType
|
from .flighttype import FlightType
|
||||||
from .flightwaypoint import FlightWaypoint
|
from .flightwaypoint import FlightWaypoint
|
||||||
@ -61,21 +65,16 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
self.package = package
|
self.package = package
|
||||||
self.coalition = squadron.coalition
|
self.coalition = squadron.coalition
|
||||||
self.squadron = squadron
|
self.squadron = squadron
|
||||||
|
self.flight_type = flight_type
|
||||||
if claim_inv:
|
if claim_inv:
|
||||||
self.squadron.claim_inventory(count)
|
self.squadron.claim_inventory(count)
|
||||||
if roster is None:
|
if roster is None:
|
||||||
self.roster = FlightRoster(self.squadron, initial_size=count)
|
self.roster = FlightMembers(self, initial_size=count)
|
||||||
else:
|
else:
|
||||||
self.roster = roster
|
self.roster = FlightMembers.from_roster(self, roster)
|
||||||
self.divert = divert
|
self.divert = divert
|
||||||
self.flight_type = flight_type
|
|
||||||
self.loadout = Loadout.default_for(self)
|
|
||||||
if self.squadron.aircraft.name == "F-15I Ra'am":
|
|
||||||
self.loadout.pylons[16] = Weapon.with_clsid(
|
|
||||||
"{IDF_MODS_PROJECT_F-15I_Raam_Dome}"
|
|
||||||
)
|
|
||||||
self.start_type = start_type
|
self.start_type = start_type
|
||||||
self.use_custom_loadout = False
|
|
||||||
self.custom_name = custom_name
|
self.custom_name = custom_name
|
||||||
self.group_id: int = 0
|
self.group_id: int = 0
|
||||||
|
|
||||||
@ -85,6 +84,7 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
self.tcn_name = callsign
|
self.tcn_name = callsign
|
||||||
|
|
||||||
self.initialize_fuel()
|
self.initialize_fuel()
|
||||||
|
self.use_same_loadout_for_all_members = True
|
||||||
|
|
||||||
# Only used by transport missions.
|
# Only used by transport missions.
|
||||||
self.cargo = cargo
|
self.cargo = cargo
|
||||||
@ -138,7 +138,11 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
|
|
||||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
state["state"] = Uninitialized(self, state["squadron"].settings)
|
state["state"] = Uninitialized(self, state["squadron"].settings)
|
||||||
|
if "use_same_loadout_for_all_members" not in state:
|
||||||
|
state["use_same_loadout_for_all_members"] = True
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
|
if isinstance(self.roster, FlightRoster):
|
||||||
|
self.roster = FlightMembers.from_roster(self, self.roster)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def blue(self) -> bool:
|
def blue(self) -> bool:
|
||||||
@ -206,6 +210,9 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
def missing_pilots(self) -> int:
|
def missing_pilots(self) -> int:
|
||||||
return self.roster.missing_pilots
|
return self.roster.missing_pilots
|
||||||
|
|
||||||
|
def iter_members(self) -> Iterator[FlightMember]:
|
||||||
|
yield from self.roster.members
|
||||||
|
|
||||||
def set_flight_type(self, var: FlightType) -> None:
|
def set_flight_type(self, var: FlightType) -> None:
|
||||||
self.flight_type = var
|
self.flight_type = var
|
||||||
|
|
||||||
@ -235,6 +242,11 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
elif self.departure.cptype.name in ["FARP", "FOB"] and not self.is_helo:
|
elif self.departure.cptype.name in ["FARP", "FOB"] and not self.is_helo:
|
||||||
self.fuel = unit_type.fuel_max * 0.75
|
self.fuel = unit_type.fuel_max * 0.75
|
||||||
|
|
||||||
|
def any_member_has_weapon_of_type(self, weapon_type: WeaponType) -> bool:
|
||||||
|
return any(
|
||||||
|
m.loadout.has_weapon_of_type(weapon_type) for m in self.iter_members()
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
@ -294,7 +306,7 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
|
|||||||
Killed(self.state.estimate_position(), self, self.squadron.settings)
|
Killed(self.state.estimate_position(), self, self.squadron.settings)
|
||||||
)
|
)
|
||||||
events.update_flight(self)
|
events.update_flight(self)
|
||||||
for pilot in self.roster.pilots:
|
for pilot in self.roster.iter_pilots():
|
||||||
if pilot is not None:
|
if pilot is not None:
|
||||||
results.kill_pilot(self, pilot)
|
results.kill_pilot(self, pilot)
|
||||||
|
|
||||||
|
|||||||
22
game/ato/flightmember.py
Normal file
22
game/ato/flightmember.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from game.ato.loadouts import Loadout
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.squadrons import Pilot
|
||||||
|
|
||||||
|
|
||||||
|
class FlightMember:
|
||||||
|
def __init__(self, pilot: Pilot | None, loadout: Loadout) -> None:
|
||||||
|
self.pilot = pilot
|
||||||
|
self.loadout = loadout
|
||||||
|
self.use_custom_loadout = False
|
||||||
|
self.properties: dict[str, bool | float | int] = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_player(self) -> bool:
|
||||||
|
if self.pilot is None:
|
||||||
|
return False
|
||||||
|
return self.pilot.player
|
||||||
89
game/ato/flightmembers.py
Normal file
89
game/ato/flightmembers.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from .flightmember import FlightMember
|
||||||
|
from .flightroster import FlightRoster
|
||||||
|
from .iflightroster import IFlightRoster
|
||||||
|
from .loadouts import Loadout
|
||||||
|
from ..data.weapons import Weapon
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.squadrons import Pilot
|
||||||
|
from .flight import Flight
|
||||||
|
|
||||||
|
|
||||||
|
class FlightMembers(IFlightRoster):
|
||||||
|
def __init__(self, flight: Flight, initial_size: int = 0) -> None:
|
||||||
|
self.flight = flight
|
||||||
|
self.members: list[FlightMember] = []
|
||||||
|
self.resize(initial_size)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_roster(flight: Flight, roster: FlightRoster) -> FlightMembers:
|
||||||
|
members = FlightMembers(flight)
|
||||||
|
loadout = Loadout.default_for(flight)
|
||||||
|
if flight.squadron.aircraft.name == "F-15I Ra'am":
|
||||||
|
loadout.pylons[16] = Weapon.with_clsid(
|
||||||
|
"{IDF_MODS_PROJECT_F-15I_Raam_Dome}"
|
||||||
|
)
|
||||||
|
members.members = [FlightMember(p, loadout) for p in roster.pilots]
|
||||||
|
return members
|
||||||
|
|
||||||
|
def iter_pilots(self) -> Iterator[Pilot | None]:
|
||||||
|
yield from (m.pilot for m in self.members)
|
||||||
|
|
||||||
|
def pilot_at(self, idx: int) -> Pilot | None:
|
||||||
|
return self.members[idx].pilot
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_size(self) -> int:
|
||||||
|
return len(self.members)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def player_count(self) -> int:
|
||||||
|
return len([m for m in self.members if m.is_player])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def missing_pilots(self) -> int:
|
||||||
|
return len([m for m in self.members if m.pilot is None])
|
||||||
|
|
||||||
|
def resize(self, new_size: int) -> None:
|
||||||
|
if self.max_size > new_size:
|
||||||
|
self.flight.squadron.return_pilots(
|
||||||
|
[m.pilot for m in self.members[new_size:] if m.pilot is not None]
|
||||||
|
)
|
||||||
|
self.members = self.members[:new_size]
|
||||||
|
return
|
||||||
|
if self.max_size:
|
||||||
|
loadout = self.members[0].loadout.clone()
|
||||||
|
else:
|
||||||
|
loadout = Loadout.default_for(self.flight)
|
||||||
|
if self.flight.squadron.aircraft.name == "F-15I Ra'am":
|
||||||
|
loadout.pylons[16] = Weapon.with_clsid(
|
||||||
|
"{IDF_MODS_PROJECT_F-15I_Raam_Dome}"
|
||||||
|
)
|
||||||
|
for _ in range(new_size - self.max_size):
|
||||||
|
member = FlightMember(self.flight.squadron.claim_available_pilot(), loadout)
|
||||||
|
member.use_custom_loadout = loadout.is_custom
|
||||||
|
self.members.append(member)
|
||||||
|
|
||||||
|
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
|
||||||
|
if pilot is not None:
|
||||||
|
self.flight.squadron.claim_pilot(pilot)
|
||||||
|
if (current_pilot := self.pilot_at(index)) is not None:
|
||||||
|
self.flight.squadron.return_pilot(current_pilot)
|
||||||
|
self.members[index].pilot = pilot
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
self.flight.squadron.return_pilots(
|
||||||
|
[p for p in self.iter_pilots() if p is not None]
|
||||||
|
)
|
||||||
|
|
||||||
|
def use_same_loadout_for_all_members(self) -> None:
|
||||||
|
if not self.members:
|
||||||
|
return
|
||||||
|
loadout = self.members[0].loadout
|
||||||
|
for member in self.members[1:]:
|
||||||
|
member.loadout = loadout.clone()
|
||||||
@ -1,29 +1,30 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from game.ato.iflightroster import IFlightRoster
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.squadrons import Squadron, Pilot
|
from game.squadrons import Squadron, Pilot
|
||||||
|
|
||||||
|
|
||||||
class FlightRoster:
|
class FlightRoster(IFlightRoster):
|
||||||
def __init__(self, squadron: Squadron, initial_size: int = 0) -> None:
|
def __init__(self, squadron: Squadron, initial_size: int = 0) -> None:
|
||||||
self.squadron = squadron
|
self.squadron = squadron
|
||||||
self.pilots: list[Optional[Pilot]] = []
|
self.pilots: list[Optional[Pilot]] = []
|
||||||
self.resize(initial_size)
|
self.resize(initial_size)
|
||||||
|
|
||||||
|
def iter_pilots(self) -> Iterator[Pilot | None]:
|
||||||
|
yield from self.pilots
|
||||||
|
|
||||||
|
def pilot_at(self, idx: int) -> Pilot | None:
|
||||||
|
return self.pilots[idx]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_size(self) -> int:
|
def max_size(self) -> int:
|
||||||
return len(self.pilots)
|
return len(self.pilots)
|
||||||
|
|
||||||
@property
|
|
||||||
def player_count(self) -> int:
|
|
||||||
return len([p for p in self.pilots if p is not None and p.player])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def missing_pilots(self) -> int:
|
|
||||||
return len([p for p in self.pilots if p is None])
|
|
||||||
|
|
||||||
def resize(self, new_size: int) -> None:
|
def resize(self, new_size: int) -> None:
|
||||||
if self.max_size > new_size:
|
if self.max_size > new_size:
|
||||||
self.squadron.return_pilots(
|
self.squadron.return_pilots(
|
||||||
|
|||||||
34
game/ato/iflightroster.py
Normal file
34
game/ato/iflightroster.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional, TYPE_CHECKING, Iterator
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.squadrons import Pilot
|
||||||
|
|
||||||
|
|
||||||
|
class IFlightRoster(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def iter_pilots(self) -> Iterator[Pilot | None]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def pilot_at(self, idx: int) -> Pilot | None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def max_size(self) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def resize(self, new_size: int) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def set_pilot(self, index: int, pilot: Optional[Pilot]) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def clear(self) -> None:
|
||||||
|
...
|
||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
@ -35,6 +36,11 @@ class Loadout:
|
|||||||
def derive_custom(self, name: str) -> Loadout:
|
def derive_custom(self, name: str) -> Loadout:
|
||||||
return Loadout(name, self.pylons, self.date, is_custom=True)
|
return Loadout(name, self.pylons, self.date, is_custom=True)
|
||||||
|
|
||||||
|
def clone(self) -> Loadout:
|
||||||
|
return Loadout(
|
||||||
|
self.name, dict(self.pylons), copy.deepcopy(self.date), self.is_custom
|
||||||
|
)
|
||||||
|
|
||||||
def has_weapon_of_type(self, weapon_type: WeaponType) -> bool:
|
def has_weapon_of_type(self, weapon_type: WeaponType) -> bool:
|
||||||
for weapon in self.pylons.values():
|
for weapon in self.pylons.values():
|
||||||
if weapon is not None and weapon.weapon_group.type is weapon_type:
|
if weapon is not None and weapon.weapon_group.type is weapon_type:
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
from typing import Iterator, Optional, Any, ClassVar
|
from typing import Iterator, Optional, Any, ClassVar
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from dcs.unitgroup import FlyingGroup
|
from dcs.flyingunit import FlyingUnit
|
||||||
from dcs.weapons_data import weapon_ids
|
from dcs.weapons_data import weapon_ids
|
||||||
|
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
@ -278,10 +278,10 @@ class Pylon:
|
|||||||
# configuration.
|
# configuration.
|
||||||
return weapon in self.allowed or weapon.clsid == "<CLEAN>"
|
return weapon in self.allowed or weapon.clsid == "<CLEAN>"
|
||||||
|
|
||||||
def equip(self, group: FlyingGroup[Any], weapon: Weapon) -> None:
|
def equip(self, unit: FlyingUnit, weapon: Weapon) -> None:
|
||||||
if not self.can_equip(weapon):
|
if not self.can_equip(weapon):
|
||||||
logging.error(f"Pylon {self.number} cannot equip {weapon.name}")
|
logging.error(f"Pylon {self.number} cannot equip {weapon.name}")
|
||||||
group.load_pylon(self.make_pydcs_assignment(weapon), self.number)
|
unit.load_pylon(self.make_pydcs_assignment(weapon), self.number)
|
||||||
|
|
||||||
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
|
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
|
||||||
return self.number, weapon.pydcs_data
|
return self.number, weapon.pydcs_data
|
||||||
|
|||||||
@ -27,6 +27,7 @@ from .aircraftbehavior import AircraftBehavior
|
|||||||
from .aircraftpainter import AircraftPainter
|
from .aircraftpainter import AircraftPainter
|
||||||
from .flightdata import FlightData
|
from .flightdata import FlightData
|
||||||
from .waypoints import WaypointGenerator
|
from .waypoints import WaypointGenerator
|
||||||
|
from ...ato.flightmember import FlightMember
|
||||||
from ...ato.flightplans.aewc import AewcFlightPlan
|
from ...ato.flightplans.aewc import AewcFlightPlan
|
||||||
from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan
|
from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan
|
||||||
from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
|
from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
|
||||||
@ -67,13 +68,13 @@ class FlightGroupConfigurator:
|
|||||||
AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group)
|
AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group)
|
||||||
AircraftPainter(self.flight, self.group).apply_livery()
|
AircraftPainter(self.flight, self.group).apply_livery()
|
||||||
self.setup_props()
|
self.setup_props()
|
||||||
self.setup_payload()
|
self.setup_payloads()
|
||||||
self.setup_fuel()
|
self.setup_fuel()
|
||||||
flight_channel = self.setup_radios()
|
flight_channel = self.setup_radios()
|
||||||
|
|
||||||
laser_codes: list[Optional[int]] = []
|
laser_codes: list[Optional[int]] = []
|
||||||
for unit, pilot in zip(self.group.units, self.flight.roster.pilots):
|
for unit, member in zip(self.group.units, self.flight.iter_members()):
|
||||||
self.configure_flight_member(unit, pilot, laser_codes)
|
self.configure_flight_member(unit, member, laser_codes)
|
||||||
|
|
||||||
divert = None
|
divert = None
|
||||||
if self.flight.divert is not None:
|
if self.flight.divert is not None:
|
||||||
@ -143,21 +144,20 @@ class FlightGroupConfigurator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def configure_flight_member(
|
def configure_flight_member(
|
||||||
self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]]
|
self, unit: FlyingUnit, member: FlightMember, laser_codes: list[Optional[int]]
|
||||||
) -> None:
|
) -> None:
|
||||||
player = pilot is not None and pilot.player
|
self.set_skill(unit, member)
|
||||||
self.set_skill(unit, pilot)
|
if member.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and member.is_player:
|
||||||
if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player:
|
|
||||||
laser_codes.append(self.laser_code_registry.get_next_laser_code())
|
laser_codes.append(self.laser_code_registry.get_next_laser_code())
|
||||||
else:
|
else:
|
||||||
laser_codes.append(None)
|
laser_codes.append(None)
|
||||||
settings = self.flight.coalition.game.settings
|
settings = self.flight.coalition.game.settings
|
||||||
if not player or not settings.plugins.get("ewrj"):
|
if not member.is_player or not settings.plugins.get("ewrj"):
|
||||||
return
|
return
|
||||||
jammer_required = settings.plugin_option("ewrj.ecm_required")
|
jammer_required = settings.plugin_option("ewrj.ecm_required")
|
||||||
if jammer_required:
|
if jammer_required:
|
||||||
ecm = WeaponTypeEnum.JAMMER
|
ecm = WeaponTypeEnum.JAMMER
|
||||||
if not self.flight.loadout.has_weapon_of_type(ecm):
|
if not member.loadout.has_weapon_of_type(ecm):
|
||||||
return
|
return
|
||||||
ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}")
|
ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}")
|
||||||
ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")')))
|
ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")')))
|
||||||
@ -221,9 +221,9 @@ class FlightGroupConfigurator:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None:
|
def set_skill(self, unit: FlyingUnit, member: FlightMember) -> None:
|
||||||
if pilot is None or not pilot.player:
|
if not member.is_player:
|
||||||
unit.skill = self.skill_level_for(unit, pilot)
|
unit.skill = self.skill_level_for(unit, member.pilot)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.use_client or "Pilot #1" not in unit.name:
|
if self.use_client or "Pilot #1" not in unit.name:
|
||||||
@ -260,15 +260,18 @@ class FlightGroupConfigurator:
|
|||||||
return levels[new_level]
|
return levels[new_level]
|
||||||
|
|
||||||
def setup_props(self) -> None:
|
def setup_props(self) -> None:
|
||||||
for prop_id, value in self.flight.props.items():
|
for unit, member in zip(self.group.units, self.flight.iter_members()):
|
||||||
for unit in self.group.units:
|
for prop_id, value in member.properties.items():
|
||||||
unit.set_property(prop_id, value)
|
unit.set_property(prop_id, value)
|
||||||
|
|
||||||
def setup_payload(self) -> None:
|
def setup_payloads(self) -> None:
|
||||||
for p in self.group.units:
|
for unit, member in zip(self.group.units, self.flight.iter_members()):
|
||||||
p.pylons.clear()
|
self.setup_payload(unit, member)
|
||||||
|
|
||||||
loadout = self.flight.loadout
|
def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None:
|
||||||
|
unit.pylons.clear()
|
||||||
|
|
||||||
|
loadout = member.loadout
|
||||||
if self.game.settings.restrict_weapons_by_date:
|
if self.game.settings.restrict_weapons_by_date:
|
||||||
loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date)
|
loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date)
|
||||||
|
|
||||||
@ -276,7 +279,7 @@ class FlightGroupConfigurator:
|
|||||||
if weapon is None:
|
if weapon is None:
|
||||||
continue
|
continue
|
||||||
pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number)
|
pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number)
|
||||||
pylon.equip(self.group, weapon)
|
pylon.equip(unit, weapon)
|
||||||
|
|
||||||
def setup_fuel(self) -> None:
|
def setup_fuel(self) -> None:
|
||||||
fuel = self.flight.state.estimate_fuel()
|
fuel = self.flight.state.estimate_fuel()
|
||||||
@ -287,5 +290,8 @@ class FlightGroupConfigurator:
|
|||||||
"starting fuel to 100kg."
|
"starting fuel to 100kg."
|
||||||
)
|
)
|
||||||
fuel = 100
|
fuel = 100
|
||||||
for unit, pilot in zip(self.group.units, self.flight.roster.pilots):
|
for unit, pilot in zip(self.group.units, self.flight.roster.iter_pilots()):
|
||||||
unit.fuel = fuel
|
if pilot is not None and pilot.player:
|
||||||
|
unit.fuel = fuel
|
||||||
|
else:
|
||||||
|
unit.fuel = self.flight.fuel
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class OcaRunwayIngressBuilder(PydcsWaypointBuilder):
|
|||||||
# for more details.
|
# for more details.
|
||||||
# The LGB work around assumes the Airfield position in DCS is on a runway, which seems
|
# The LGB work around assumes the Airfield position in DCS is on a runway, which seems
|
||||||
# to be the case for most if not all airfields.
|
# to be the case for most if not all airfields.
|
||||||
if self.flight.loadout.has_weapon_of_type(WeaponType.LGB):
|
if self.flight.any_member_has_weapon_of_type(WeaponType.LGB):
|
||||||
waypoint.tasks.append(
|
waypoint.tasks.append(
|
||||||
Bombing(
|
Bombing(
|
||||||
position=target.position,
|
position=target.position,
|
||||||
|
|||||||
@ -53,7 +53,7 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
|
|||||||
)
|
)
|
||||||
waypoint.tasks.append(attack_task)
|
waypoint.tasks.append(attack_task)
|
||||||
|
|
||||||
if self.flight.loadout.has_weapon_of_type(WeaponType.ARM):
|
if self.flight.any_member_has_weapon_of_type(WeaponType.ARM):
|
||||||
# Special handling for ARM Weapon types:
|
# Special handling for ARM Weapon types:
|
||||||
# The SEAD flight will Search for the targeted group and then engage it
|
# The SEAD flight will Search for the targeted group and then engage it
|
||||||
# if it is found only. This will prevent AI from having huge problems
|
# if it is found only. This will prevent AI from having huge problems
|
||||||
|
|||||||
@ -60,7 +60,7 @@ class MissionResultsProcessor:
|
|||||||
def _commit_pilot_experience(ato: AirTaskingOrder) -> None:
|
def _commit_pilot_experience(ato: AirTaskingOrder) -> None:
|
||||||
for package in ato.packages:
|
for package in ato.packages:
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
for idx, pilot in enumerate(flight.roster.pilots):
|
for idx, pilot in enumerate(flight.roster.iter_pilots()):
|
||||||
if pilot is None:
|
if pilot is None:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Cannot award experience to pilot #{idx} of {flight} "
|
f"Cannot award experience to pilot #{idx} of {flight} "
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class UnitMap:
|
|||||||
self.airlifts: Dict[str, AirliftUnits] = {}
|
self.airlifts: Dict[str, AirliftUnits] = {}
|
||||||
|
|
||||||
def add_aircraft(self, group: FlyingGroup[Any], flight: Flight) -> None:
|
def add_aircraft(self, group: FlyingGroup[Any], flight: Flight) -> None:
|
||||||
for pilot, unit in zip(flight.roster.pilots, group.units):
|
for pilot, unit in zip(flight.roster.iter_pilots(), group.units):
|
||||||
# The actual name is a String (the pydcs translatable string), which
|
# The actual name is a String (the pydcs translatable string), which
|
||||||
# doesn't define __eq__.
|
# doesn't define __eq__.
|
||||||
name = str(unit.name)
|
name = str(unit.name)
|
||||||
|
|||||||
13
qt_ui/blocksignals.py
Normal file
13
qt_ui/blocksignals.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from collections.abc import Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QWidget
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def block_signals(widget: QWidget) -> Iterator[None]:
|
||||||
|
blocked = widget.blockSignals(True)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
widget.blockSignals(blocked)
|
||||||
@ -121,7 +121,7 @@ class AircraftInventoryData:
|
|||||||
flight_type = flight.flight_type.value
|
flight_type = flight.flight_type.value
|
||||||
target = flight.package.target.name
|
target = flight.package.target.name
|
||||||
for idx in range(0, num_units):
|
for idx in range(0, num_units):
|
||||||
pilot = flight.roster.pilots[idx]
|
pilot = flight.roster.pilot_at(idx)
|
||||||
if pilot is None:
|
if pilot is None:
|
||||||
pilot_name = "Unassigned"
|
pilot_name = "Unassigned"
|
||||||
player = ""
|
player = ""
|
||||||
|
|||||||
@ -89,9 +89,10 @@ class QFlightCreator(QDialog):
|
|||||||
roster = None
|
roster = None
|
||||||
else:
|
else:
|
||||||
roster = FlightRoster(
|
roster = FlightRoster(
|
||||||
squadron, initial_size=self.flight_size_spinner.value()
|
squadron,
|
||||||
|
initial_size=self.flight_size_spinner.value(),
|
||||||
)
|
)
|
||||||
self.roster_editor = FlightRosterEditor(roster)
|
self.roster_editor = FlightRosterEditor(squadron, roster)
|
||||||
self.flight_size_spinner.valueChanged.connect(self.roster_editor.resize)
|
self.flight_size_spinner.valueChanged.connect(self.roster_editor.resize)
|
||||||
self.squadron_selector.currentIndexChanged.connect(self.on_squadron_changed)
|
self.squadron_selector.currentIndexChanged.connect(self.on_squadron_changed)
|
||||||
roster_layout = QHBoxLayout()
|
roster_layout = QHBoxLayout()
|
||||||
@ -232,7 +233,7 @@ class QFlightCreator(QDialog):
|
|||||||
self.roster_editor.replace(None)
|
self.roster_editor.replace(None)
|
||||||
if squadron is not None:
|
if squadron is not None:
|
||||||
self.roster_editor.replace(
|
self.roster_editor.replace(
|
||||||
FlightRoster(squadron, self.flight_size_spinner.value())
|
squadron, FlightRoster(squadron, self.flight_size_spinner.value())
|
||||||
)
|
)
|
||||||
self.on_departure_changed(squadron.location)
|
self.on_departure_changed(squadron.location)
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,11 @@ class QFlightPlanner(QTabWidget):
|
|||||||
self.general_settings_tab = QGeneralFlightSettingsTab(
|
self.general_settings_tab = QGeneralFlightSettingsTab(
|
||||||
gm, package_model, flight, self.waypoint_tab.flight_waypoint_list
|
gm, package_model, flight, self.waypoint_tab.flight_waypoint_list
|
||||||
)
|
)
|
||||||
|
self.payload_tab = QFlightPayloadTab(flight, gm.game)
|
||||||
|
self.general_settings_tab.flight_size_changed.connect(
|
||||||
|
self.payload_tab.resize_for_flight
|
||||||
|
)
|
||||||
|
self.waypoint_tab = QFlightWaypointTab(gm.game, package_model.package, flight)
|
||||||
self.waypoint_tab.loadout_changed.connect(self.payload_tab.reload_from_flight)
|
self.waypoint_tab.loadout_changed.connect(self.payload_tab.reload_from_flight)
|
||||||
self.addTab(self.general_settings_tab, "General Flight settings")
|
self.addTab(self.general_settings_tab, "General Flight settings")
|
||||||
self.addTab(self.payload_tab, "Payload")
|
self.addTab(self.payload_tab, "Payload")
|
||||||
|
|||||||
@ -9,26 +9,41 @@ from PySide2.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
QSpinBox,
|
QSpinBox,
|
||||||
QSlider,
|
QSlider,
|
||||||
|
QCheckBox,
|
||||||
)
|
)
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.ato.flightmember import FlightMember
|
||||||
from game.ato.loadouts import Loadout
|
from game.ato.loadouts import Loadout
|
||||||
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||||
from .QLoadoutEditor import QLoadoutEditor
|
from .QLoadoutEditor import QLoadoutEditor
|
||||||
from .propertyeditor import PropertyEditor
|
from .propertyeditor import PropertyEditor
|
||||||
|
|
||||||
|
|
||||||
class DcsLoadoutSelector(QComboBox):
|
class DcsLoadoutSelector(QComboBox):
|
||||||
def __init__(self, flight: Flight) -> None:
|
def __init__(self, flight: Flight, member: FlightMember) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
for loadout in Loadout.iter_for(flight):
|
for loadout in Loadout.iter_for(flight):
|
||||||
self.addItem(loadout.name, loadout)
|
self.addItem(loadout.name, loadout)
|
||||||
self.model().sort(0)
|
self.model().sort(0)
|
||||||
self.setDisabled(flight.loadout.is_custom)
|
self.setDisabled(member.loadout.is_custom)
|
||||||
if flight.loadout.is_custom:
|
if member.loadout.is_custom:
|
||||||
self.setCurrentText(Loadout.default_for(flight).name)
|
self.setCurrentText(Loadout.default_for(flight).name)
|
||||||
else:
|
else:
|
||||||
self.setCurrentText(flight.loadout.name)
|
self.setCurrentText(member.loadout.name)
|
||||||
|
|
||||||
|
|
||||||
|
class FlightMemberSelector(QSpinBox):
|
||||||
|
def __init__(self, flight: Flight, parent: QWidget | None = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.flight = flight
|
||||||
|
self.setMinimum(0)
|
||||||
|
self.setMaximum(flight.count - 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_member(self) -> FlightMember:
|
||||||
|
return self.flight.roster.members[self.value()]
|
||||||
|
|
||||||
|
|
||||||
class DcsFuelSelector(QHBoxLayout):
|
class DcsFuelSelector(QHBoxLayout):
|
||||||
@ -99,12 +114,41 @@ class QFlightPayloadTab(QFrame):
|
|||||||
def __init__(self, flight: Flight, game: Game):
|
def __init__(self, flight: Flight, game: Game):
|
||||||
super(QFlightPayloadTab, self).__init__()
|
super(QFlightPayloadTab, self).__init__()
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.payload_editor = QLoadoutEditor(flight, game)
|
self.payload_editor = QLoadoutEditor(
|
||||||
|
flight, self.flight.roster.members[0], game
|
||||||
|
)
|
||||||
self.payload_editor.toggled.connect(self.on_custom_toggled)
|
self.payload_editor.toggled.connect(self.on_custom_toggled)
|
||||||
self.payload_editor.saved.connect(self.on_saved_payload)
|
self.payload_editor.saved.connect(self.on_saved_payload)
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.member_selector = FlightMemberSelector(self.flight, self)
|
||||||
|
self.member_selector.valueChanged.connect(self.rebind_to_selected_member)
|
||||||
|
layout.addLayout(QLabeledWidget("Flight member:", self.member_selector))
|
||||||
|
self.same_loadout_for_all_checkbox = QCheckBox(
|
||||||
|
"Use same loadout for all flight members"
|
||||||
|
)
|
||||||
|
self.same_loadout_for_all_checkbox.setChecked(
|
||||||
|
self.flight.use_same_loadout_for_all_members
|
||||||
|
)
|
||||||
|
self.same_loadout_for_all_checkbox.toggled.connect(self.on_same_loadout_toggled)
|
||||||
|
layout.addWidget(self.same_loadout_for_all_checkbox)
|
||||||
|
layout.addWidget(
|
||||||
|
QLabel(
|
||||||
|
"<strong>Warning: AI flights should use the same loadout for all members.</strong>"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
scroll_content = QWidget()
|
||||||
|
scrolling_layout = QVBoxLayout()
|
||||||
|
scroll_content.setLayout(scrolling_layout)
|
||||||
|
|
||||||
|
scroll = QScrollArea()
|
||||||
|
scroll.setWidgetResizable(True)
|
||||||
|
scroll.setWidget(scroll_content)
|
||||||
|
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
layout.addWidget(scroll)
|
||||||
|
|
||||||
# Docs Link
|
# Docs Link
|
||||||
docsText = QLabel(
|
docsText = QLabel(
|
||||||
'<a href="https://github.com/dcs-retribution/dcs-retribution/wiki/Custom-Loadouts"><span style="color:#FFFFFF;">How to create your own default loadout</span></a>'
|
'<a href="https://github.com/dcs-retribution/dcs-retribution/wiki/Custom-Loadouts"><span style="color:#FFFFFF;">How to create your own default loadout</span></a>'
|
||||||
@ -122,6 +166,13 @@ class QFlightPayloadTab(QFrame):
|
|||||||
layout.addLayout(self.fuel_selector)
|
layout.addLayout(self.fuel_selector)
|
||||||
|
|
||||||
self.loadout_selector = DcsLoadoutSelector(flight)
|
self.loadout_selector = DcsLoadoutSelector(flight)
|
||||||
|
self.property_editor = PropertyEditor(
|
||||||
|
self.flight, self.member_selector.selected_member
|
||||||
|
)
|
||||||
|
scrolling_layout.addLayout(self.property_editor)
|
||||||
|
self.loadout_selector = DcsLoadoutSelector(
|
||||||
|
flight, self.member_selector.selected_member
|
||||||
|
)
|
||||||
self.loadout_selector.currentIndexChanged.connect(self.on_new_loadout)
|
self.loadout_selector.currentIndexChanged.connect(self.on_new_loadout)
|
||||||
layout.addWidget(self.loadout_selector)
|
layout.addWidget(self.loadout_selector)
|
||||||
layout.addWidget(self.payload_editor, stretch=1)
|
layout.addWidget(self.payload_editor, stretch=1)
|
||||||
@ -129,8 +180,27 @@ class QFlightPayloadTab(QFrame):
|
|||||||
|
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def resize_for_flight(self) -> None:
|
||||||
|
self.member_selector.setMaximum(self.flight.count - 1)
|
||||||
|
|
||||||
def reload_from_flight(self) -> None:
|
def reload_from_flight(self) -> None:
|
||||||
self.loadout_selector.setCurrentText(self.flight.loadout.name)
|
self.loadout_selector.setCurrentText(
|
||||||
|
self.member_selector.selected_member.loadout.name
|
||||||
|
)
|
||||||
|
|
||||||
|
def rebind_to_selected_member(self) -> None:
|
||||||
|
member = self.member_selector.selected_member
|
||||||
|
self.property_editor.set_flight_member(member)
|
||||||
|
self.loadout_selector.setCurrentText(member.loadout.name)
|
||||||
|
self.loadout_selector.setDisabled(member.loadout.is_custom)
|
||||||
|
self.payload_editor.set_flight_member(member)
|
||||||
|
if self.member_selector.value() != 0:
|
||||||
|
self.loadout_selector.setDisabled(
|
||||||
|
self.flight.use_same_loadout_for_all_members
|
||||||
|
)
|
||||||
|
self.payload_editor.setDisabled(
|
||||||
|
self.flight.use_same_loadout_for_all_members
|
||||||
|
)
|
||||||
|
|
||||||
def loadout_at(self, index: int) -> Loadout:
|
def loadout_at(self, index: int) -> Loadout:
|
||||||
loadout = self.loadout_selector.itemData(index)
|
loadout = self.loadout_selector.itemData(index)
|
||||||
@ -145,18 +215,30 @@ class QFlightPayloadTab(QFrame):
|
|||||||
return loadout
|
return loadout
|
||||||
|
|
||||||
def on_new_loadout(self, index: int) -> None:
|
def on_new_loadout(self, index: int) -> None:
|
||||||
self.flight.loadout = self.loadout_at(index)
|
self.member_selector.selected_member.loadout = self.loadout_at(index)
|
||||||
self.payload_editor.reset_pylons()
|
self.payload_editor.reset_pylons()
|
||||||
|
|
||||||
def on_custom_toggled(self, use_custom: bool) -> None:
|
def on_custom_toggled(self, use_custom: bool) -> None:
|
||||||
self.loadout_selector.setDisabled(use_custom)
|
self.loadout_selector.setDisabled(use_custom)
|
||||||
|
member = self.member_selector.selected_member
|
||||||
|
member.use_custom_loadout = use_custom
|
||||||
if use_custom:
|
if use_custom:
|
||||||
self.flight.loadout = self.flight.loadout.derive_custom("Custom")
|
member.loadout = member.loadout.derive_custom("Custom")
|
||||||
else:
|
else:
|
||||||
self.flight.loadout = self.current_loadout()
|
member.loadout = self.current_loadout()
|
||||||
self.payload_editor.reset_pylons()
|
self.payload_editor.reset_pylons()
|
||||||
|
|
||||||
def on_saved_payload(self, payload_name: str) -> None:
|
def on_saved_payload(self, payload_name: str) -> None:
|
||||||
loadout = self.flight.loadout
|
loadout = self.flight.loadout
|
||||||
self.loadout_selector.addItem(payload_name, loadout)
|
self.loadout_selector.addItem(payload_name, loadout)
|
||||||
self.loadout_selector.setCurrentIndex(self.loadout_selector.count() - 1)
|
self.loadout_selector.setCurrentIndex(self.loadout_selector.count() - 1)
|
||||||
|
|
||||||
|
def on_same_loadout_toggled(self, checked: bool) -> None:
|
||||||
|
self.flight.use_same_loadout_for_all_members = checked
|
||||||
|
if self.member_selector.value():
|
||||||
|
self.loadout_selector.setDisabled(checked)
|
||||||
|
self.payload_editor.setDisabled(checked)
|
||||||
|
if checked:
|
||||||
|
self.flight.roster.use_same_loadout_for_all_members()
|
||||||
|
if self.member_selector.value():
|
||||||
|
self.rebind_to_selected_member()
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
@ -18,20 +19,23 @@ from dcs import lua
|
|||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.ato.flightmember import FlightMember
|
||||||
from game.data.weapons import Pylon
|
from game.data.weapons import Pylon
|
||||||
from game.persistency import payloads_dir
|
from game.persistency import payloads_dir
|
||||||
|
from qt_ui.blocksignals import block_signals
|
||||||
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):
|
||||||
saved = Signal(str)
|
saved = Signal(str)
|
||||||
|
|
||||||
def __init__(self, flight: Flight, game: Game) -> None:
|
def __init__(self, flight: Flight, flight_member: FlightMember, game: Game) -> None:
|
||||||
super().__init__("Use custom loadout")
|
super().__init__("Use custom loadout")
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
|
self.flight_member = flight_member
|
||||||
self.game = game
|
self.game = game
|
||||||
self.setCheckable(True)
|
self.setCheckable(True)
|
||||||
self.setChecked(flight.loadout.is_custom)
|
self.setChecked(flight_member.loadout.is_custom)
|
||||||
|
|
||||||
vbox = QVBoxLayout(self)
|
vbox = QVBoxLayout(self)
|
||||||
layout = QGridLayout(self)
|
layout = QGridLayout(self)
|
||||||
@ -40,7 +44,7 @@ class QLoadoutEditor(QGroupBox):
|
|||||||
label = QLabel(f"<b>{pylon.number}</b>")
|
label = QLabel(f"<b>{pylon.number}</b>")
|
||||||
label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
||||||
layout.addWidget(label, i, 0)
|
layout.addWidget(label, i, 0)
|
||||||
layout.addWidget(QPylonEditor(game, flight, pylon), i, 1)
|
layout.addWidget(QPylonEditor(game, flight, flight_member, pylon), i, 1)
|
||||||
|
|
||||||
vbox.addLayout(layout)
|
vbox.addLayout(layout)
|
||||||
|
|
||||||
@ -60,8 +64,18 @@ class QLoadoutEditor(QGroupBox):
|
|||||||
|
|
||||||
self.setLayout(vbox)
|
self.setLayout(vbox)
|
||||||
|
|
||||||
for i in self.findChildren(QPylonEditor):
|
for pylon_editor in self.iter_pylon_editors():
|
||||||
i.set_from(self.flight.loadout)
|
pylon_editor.set_from(self.flight_member.loadout)
|
||||||
|
|
||||||
|
def iter_pylon_editors(self) -> Iterator[QPylonEditor]:
|
||||||
|
yield from self.findChildren(QPylonEditor)
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
with block_signals(self):
|
||||||
|
self.setChecked(self.flight_member.use_custom_loadout)
|
||||||
|
for pylon_editor in self.iter_pylon_editors():
|
||||||
|
pylon_editor.set_flight_member(flight_member)
|
||||||
|
|
||||||
def _backup_payloads(self) -> None:
|
def _backup_payloads(self) -> None:
|
||||||
ac_id = self.flight.unit_type.dcs_unit_type.id
|
ac_id = self.flight.unit_type.dcs_unit_type.id
|
||||||
@ -146,10 +160,10 @@ class QLoadoutEditor(QGroupBox):
|
|||||||
return payload_name_input
|
return payload_name_input
|
||||||
|
|
||||||
def reset_pylons(self) -> None:
|
def reset_pylons(self) -> None:
|
||||||
self.flight.use_custom_loadout = self.isChecked()
|
self.flight_member.use_custom_loadout = self.isChecked()
|
||||||
if not self.isChecked():
|
if not self.isChecked():
|
||||||
for i in self.findChildren(QPylonEditor):
|
for pylon_editor in self.iter_pylon_editors():
|
||||||
i.set_from(self.flight.loadout)
|
pylon_editor.set_from(self.flight_member.loadout)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -6,19 +6,23 @@ from PySide2.QtWidgets import QComboBox
|
|||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
from game.ato.flightmember import FlightMember
|
||||||
from game.ato.loadouts import Loadout
|
from game.ato.loadouts import Loadout
|
||||||
from game.data.weapons import Pylon, Weapon
|
from game.data.weapons import Pylon, Weapon
|
||||||
|
|
||||||
|
|
||||||
class QPylonEditor(QComboBox):
|
class QPylonEditor(QComboBox):
|
||||||
def __init__(self, game: Game, flight: Flight, pylon: Pylon) -> None:
|
def __init__(
|
||||||
|
self, game: Game, flight: Flight, flight_member: FlightMember, pylon: Pylon
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
|
self.flight_member = flight_member
|
||||||
self.pylon = pylon
|
self.pylon = pylon
|
||||||
self.game = game
|
self.game = game
|
||||||
self.has_added_clean_item = False
|
self.has_added_clean_item = False
|
||||||
|
|
||||||
current = self.flight.loadout.pylons.get(self.pylon.number)
|
current = self.flight_member.loadout.pylons.get(self.pylon.number)
|
||||||
|
|
||||||
self.addItem("None", None)
|
self.addItem("None", None)
|
||||||
if self.game.settings.restrict_weapons_by_date:
|
if self.game.settings.restrict_weapons_by_date:
|
||||||
@ -35,7 +39,7 @@ class QPylonEditor(QComboBox):
|
|||||||
|
|
||||||
def on_pylon_change(self):
|
def on_pylon_change(self):
|
||||||
selected: Optional[Weapon] = self.currentData()
|
selected: Optional[Weapon] = self.currentData()
|
||||||
self.flight.loadout.pylons[self.pylon.number] = selected
|
self.flight_member.loadout.pylons[self.pylon.number] = selected
|
||||||
|
|
||||||
if selected is None:
|
if selected is None:
|
||||||
logging.debug(f"Pylon {self.pylon.number} emptied")
|
logging.debug(f"Pylon {self.pylon.number} emptied")
|
||||||
@ -70,5 +74,9 @@ class QPylonEditor(QComboBox):
|
|||||||
return "None"
|
return "None"
|
||||||
return weapon.name
|
return weapon.name
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
self.set_from(self.flight_member.loadout)
|
||||||
|
|
||||||
def set_from(self, loadout: Loadout) -> None:
|
def set_from(self, loadout: Loadout) -> None:
|
||||||
self.setCurrentText(self.matching_weapon_name(loadout))
|
self.setCurrentText(self.matching_weapon_name(loadout))
|
||||||
|
|||||||
@ -1,21 +1,31 @@
|
|||||||
from PySide6.QtWidgets import QCheckBox
|
from PySide6.QtWidgets import QCheckBox
|
||||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||||
|
|
||||||
from game.ato import Flight
|
from game.ato.flightmember import FlightMember
|
||||||
from .missingpropertydataerror import MissingPropertyDataError
|
from .missingpropertydataerror import MissingPropertyDataError
|
||||||
|
|
||||||
|
|
||||||
class PropertyCheckBox(QCheckBox):
|
class PropertyCheckBox(QCheckBox):
|
||||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
def __init__(
|
||||||
|
self, flight_member: FlightMember, prop: UnitPropertyDescription
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight_member = flight_member
|
||||||
self.prop = prop
|
self.prop = prop
|
||||||
|
|
||||||
if prop.default is None:
|
if prop.default is None:
|
||||||
raise MissingPropertyDataError("default cannot be None")
|
raise MissingPropertyDataError("default cannot be None")
|
||||||
|
|
||||||
self.setChecked(self.flight.props.get(self.prop.identifier, self.prop.default))
|
self.setChecked(
|
||||||
|
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
|
||||||
|
)
|
||||||
self.toggled.connect(self.on_toggle)
|
self.toggled.connect(self.on_toggle)
|
||||||
|
|
||||||
def on_toggle(self, checked: bool) -> None:
|
def on_toggle(self, checked: bool) -> None:
|
||||||
self.flight.props[self.prop.identifier] = checked
|
self.flight_member.properties[self.prop.identifier] = checked
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
self.setChecked(
|
||||||
|
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
|
||||||
|
)
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
from PySide6.QtWidgets import QComboBox
|
from PySide6.QtWidgets import QComboBox
|
||||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||||
|
|
||||||
from game.ato import Flight
|
from game.ato.flightmember import FlightMember
|
||||||
from .missingpropertydataerror import MissingPropertyDataError
|
from .missingpropertydataerror import MissingPropertyDataError
|
||||||
|
|
||||||
|
|
||||||
class PropertyComboBox(QComboBox):
|
class PropertyComboBox(QComboBox):
|
||||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
def __init__(
|
||||||
|
self, flight_member: FlightMember, prop: UnitPropertyDescription
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight_member = flight_member
|
||||||
self.prop = prop
|
self.prop = prop
|
||||||
|
|
||||||
if prop.values is None:
|
if prop.values is None:
|
||||||
@ -16,7 +18,9 @@ class PropertyComboBox(QComboBox):
|
|||||||
if prop.default is None:
|
if prop.default is None:
|
||||||
raise MissingPropertyDataError("default cannot be None")
|
raise MissingPropertyDataError("default cannot be None")
|
||||||
|
|
||||||
current_value = self.flight.props.get(self.prop.identifier, self.prop.default)
|
current_value = self.flight_member.properties.get(
|
||||||
|
self.prop.identifier, self.prop.default
|
||||||
|
)
|
||||||
|
|
||||||
for ident, text in self.prop.values.items():
|
for ident, text in self.prop.values.items():
|
||||||
self.addItem(text, ident)
|
self.addItem(text, ident)
|
||||||
@ -26,4 +30,12 @@ class PropertyComboBox(QComboBox):
|
|||||||
self.currentIndexChanged.connect(self.on_selection_changed)
|
self.currentIndexChanged.connect(self.on_selection_changed)
|
||||||
|
|
||||||
def on_selection_changed(self, _index: int) -> None:
|
def on_selection_changed(self, _index: int) -> None:
|
||||||
self.flight.props[self.prop.identifier] = self.currentData()
|
self.flight_member.properties[self.prop.identifier] = self.currentData()
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
self.setCurrentText(
|
||||||
|
self.flight_member.properties.get(
|
||||||
|
self.prop.identifier, self.prop.values[self.prop.default]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
from PySide2.QtWidgets import QGridLayout, QLabel
|
from PySide2.QtWidgets import QGridLayout, QLabel
|
||||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||||
|
|
||||||
from game.ato import Flight
|
from game.ato import Flight
|
||||||
|
from game.ato.flightmember import FlightMember
|
||||||
from .missingpropertydataerror import MissingPropertyDataError
|
from .missingpropertydataerror import MissingPropertyDataError
|
||||||
from .propertycheckbox import PropertyCheckBox
|
from .propertycheckbox import PropertyCheckBox
|
||||||
from .propertycombobox import PropertyComboBox
|
from .propertycombobox import PropertyComboBox
|
||||||
@ -16,9 +18,10 @@ class UnhandledControlTypeError(RuntimeError):
|
|||||||
|
|
||||||
|
|
||||||
class PropertyEditor(QGridLayout):
|
class PropertyEditor(QGridLayout):
|
||||||
def __init__(self, flight: Flight) -> None:
|
def __init__(self, flight: Flight, flight_member: FlightMember) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight_member = flight_member
|
||||||
|
self.flight_member_update_listeners: list[Callable[[FlightMember], None]] = []
|
||||||
|
|
||||||
for row, prop in enumerate(flight.unit_type.iter_props()):
|
for row, prop in enumerate(flight.unit_type.iter_props()):
|
||||||
if prop.label is None:
|
if prop.label is None:
|
||||||
@ -56,12 +59,23 @@ class PropertyEditor(QGridLayout):
|
|||||||
# "checkbox", "comboList", "groupbox", "label", "slider", "spinbox"
|
# "checkbox", "comboList", "groupbox", "label", "slider", "spinbox"
|
||||||
match prop.control:
|
match prop.control:
|
||||||
case "checkbox":
|
case "checkbox":
|
||||||
return PropertyCheckBox(self.flight, prop)
|
widget = PropertyCheckBox(self.flight_member, prop)
|
||||||
|
self.flight_member_update_listeners.append(widget.set_flight_member)
|
||||||
|
return widget
|
||||||
case "comboList":
|
case "comboList":
|
||||||
return PropertyComboBox(self.flight, prop)
|
widget = PropertyComboBox(self.flight_member, prop)
|
||||||
|
self.flight_member_update_listeners.append(widget.set_flight_member)
|
||||||
|
return widget
|
||||||
case "groupbox" | "label":
|
case "groupbox" | "label":
|
||||||
return None
|
return None
|
||||||
case "slider" | "spinbox":
|
case "slider" | "spinbox":
|
||||||
return PropertySpinBox(self.flight, prop)
|
widget = PropertySpinBox(self.flight_member, prop)
|
||||||
|
self.flight_member_update_listeners.append(widget.set_flight_member)
|
||||||
|
return widget
|
||||||
case _:
|
case _:
|
||||||
raise UnhandledControlTypeError(prop.control)
|
raise UnhandledControlTypeError(prop.control)
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
for listener in self.flight_member_update_listeners:
|
||||||
|
listener(self.flight_member)
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
from PySide6.QtWidgets import QSpinBox
|
from PySide6.QtWidgets import QSpinBox
|
||||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||||
|
|
||||||
from game.ato import Flight
|
from game.ato.flightmember import FlightMember
|
||||||
from .missingpropertydataerror import MissingPropertyDataError
|
from .missingpropertydataerror import MissingPropertyDataError
|
||||||
|
|
||||||
|
|
||||||
class PropertySpinBox(QSpinBox):
|
class PropertySpinBox(QSpinBox):
|
||||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
def __init__(
|
||||||
|
self, flight_member: FlightMember, prop: UnitPropertyDescription
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight_member = flight_member
|
||||||
self.prop = prop
|
self.prop = prop
|
||||||
|
|
||||||
if prop.minimum is None:
|
if prop.minimum is None:
|
||||||
@ -20,9 +22,17 @@ class PropertySpinBox(QSpinBox):
|
|||||||
|
|
||||||
self.setMinimum(prop.minimum)
|
self.setMinimum(prop.minimum)
|
||||||
self.setMaximum(prop.maximum)
|
self.setMaximum(prop.maximum)
|
||||||
self.setValue(self.flight.props.get(self.prop.identifier, self.prop.default))
|
self.setValue(
|
||||||
|
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
|
||||||
|
)
|
||||||
|
|
||||||
self.valueChanged.connect(self.on_value_changed)
|
self.valueChanged.connect(self.on_value_changed)
|
||||||
|
|
||||||
def on_value_changed(self, value: int) -> None:
|
def on_value_changed(self, value: int) -> None:
|
||||||
self.flight.props[self.prop.identifier] = value
|
self.flight_member.properties[self.prop.identifier] = value
|
||||||
|
|
||||||
|
def set_flight_member(self, flight_member: FlightMember) -> None:
|
||||||
|
self.flight_member = flight_member
|
||||||
|
self.setValue(
|
||||||
|
self.flight_member.properties.get(self.prop.identifier, self.prop.default)
|
||||||
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Callable
|
from typing import Optional, Callable, Union
|
||||||
|
|
||||||
from PySide2.QtCore import Signal, QModelIndex
|
from PySide2.QtCore import Signal, QModelIndex
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
@ -16,6 +16,8 @@ from PySide2.QtWidgets import (
|
|||||||
from game import Game
|
from game import Game
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
from game.ato.flightroster import FlightRoster
|
from game.ato.flightroster import FlightRoster
|
||||||
|
from game.ato.iflightroster import IFlightRoster
|
||||||
|
from game.squadrons import Squadron
|
||||||
from game.squadrons.pilot import Pilot
|
from game.squadrons.pilot import Pilot
|
||||||
from qt_ui.models import PackageModel
|
from qt_ui.models import PackageModel
|
||||||
|
|
||||||
@ -23,8 +25,11 @@ from qt_ui.models import PackageModel
|
|||||||
class PilotSelector(QComboBox):
|
class PilotSelector(QComboBox):
|
||||||
available_pilots_changed = Signal()
|
available_pilots_changed = Signal()
|
||||||
|
|
||||||
def __init__(self, roster: Optional[FlightRoster], idx: int) -> None:
|
def __init__(
|
||||||
|
self, squadron: Union[Squadron, None], roster: Optional[IFlightRoster], idx: int
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.squadron = squadron
|
||||||
self.roster = roster
|
self.roster = roster
|
||||||
self.pilot_index = idx
|
self.pilot_index = idx
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
@ -40,10 +45,13 @@ class PilotSelector(QComboBox):
|
|||||||
self.setDisabled(True)
|
self.setDisabled(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.squadron is None:
|
||||||
|
raise RuntimeError("squadron cannot be None if roster is set")
|
||||||
|
|
||||||
self.setEnabled(True)
|
self.setEnabled(True)
|
||||||
self.addItem("Unassigned", None)
|
self.addItem("Unassigned", None)
|
||||||
choices = list(self.roster.squadron.available_pilots)
|
choices = list(self.squadron.available_pilots)
|
||||||
current_pilot = self.roster.pilots[self.pilot_index]
|
current_pilot = self.roster.pilot_at(self.pilot_index)
|
||||||
if current_pilot is not None:
|
if current_pilot is not None:
|
||||||
choices.append(current_pilot)
|
choices.append(current_pilot)
|
||||||
# Put players first, otherwise alphabetically.
|
# Put players first, otherwise alphabetically.
|
||||||
@ -71,23 +79,26 @@ class PilotSelector(QComboBox):
|
|||||||
# The roster resize is handled separately, so we have no pilots to remove.
|
# The roster resize is handled separately, so we have no pilots to remove.
|
||||||
return
|
return
|
||||||
pilot = self.itemData(index)
|
pilot = self.itemData(index)
|
||||||
if pilot == self.roster.pilots[self.pilot_index]:
|
if pilot == self.roster.pilot_at(self.pilot_index):
|
||||||
return
|
return
|
||||||
self.roster.set_pilot(self.pilot_index, pilot)
|
self.roster.set_pilot(self.pilot_index, pilot)
|
||||||
self.available_pilots_changed.emit()
|
self.available_pilots_changed.emit()
|
||||||
|
|
||||||
def replace(self, new_roster: Optional[FlightRoster]) -> None:
|
def replace(self, squadron: Squadron, new_roster: Optional[FlightRoster]) -> None:
|
||||||
|
self.squadron = squadron
|
||||||
self.roster = new_roster
|
self.roster = new_roster
|
||||||
self.rebuild()
|
self.rebuild()
|
||||||
|
|
||||||
|
|
||||||
class PilotControls(QHBoxLayout):
|
class PilotControls(QHBoxLayout):
|
||||||
def __init__(self, roster: Optional[FlightRoster], idx: int) -> None:
|
def __init__(
|
||||||
|
self, squadron: Union[Squadron, None], roster: Optional[FlightRoster], idx: int
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.roster = roster
|
self.roster = roster
|
||||||
self.pilot_index = idx
|
self.pilot_index = idx
|
||||||
|
|
||||||
self.selector = PilotSelector(roster, idx)
|
self.selector = PilotSelector(squadron, roster, idx)
|
||||||
self.selector.currentIndexChanged.connect(self.on_pilot_changed)
|
self.selector.currentIndexChanged.connect(self.on_pilot_changed)
|
||||||
self.addWidget(self.selector)
|
self.addWidget(self.selector)
|
||||||
|
|
||||||
@ -95,8 +106,8 @@ class PilotControls(QHBoxLayout):
|
|||||||
self.player_checkbox.setToolTip("Checked if this pilot is a player.")
|
self.player_checkbox.setToolTip("Checked if this pilot is a player.")
|
||||||
self.on_pilot_changed(self.selector.currentIndex())
|
self.on_pilot_changed(self.selector.currentIndex())
|
||||||
enabled = False
|
enabled = False
|
||||||
if self.roster is not None and self.roster.squadron is not None:
|
if self.roster is not None and squadron is not None:
|
||||||
enabled = self.roster.squadron.aircraft.flyable
|
enabled = squadron.aircraft.flyable
|
||||||
self.player_checkbox.setEnabled(enabled)
|
self.player_checkbox.setEnabled(enabled)
|
||||||
self.addWidget(self.player_checkbox)
|
self.addWidget(self.player_checkbox)
|
||||||
|
|
||||||
@ -106,7 +117,7 @@ class PilotControls(QHBoxLayout):
|
|||||||
def pilot(self) -> Optional[Pilot]:
|
def pilot(self) -> Optional[Pilot]:
|
||||||
if self.roster is None or self.pilot_index >= self.roster.max_size:
|
if self.roster is None or self.pilot_index >= self.roster.max_size:
|
||||||
return None
|
return None
|
||||||
return self.roster.pilots[self.pilot_index]
|
return self.roster.pilot_at(self.pilot_index)
|
||||||
|
|
||||||
def on_player_toggled(self, checked: bool) -> None:
|
def on_player_toggled(self, checked: bool) -> None:
|
||||||
pilot = self.pilot
|
pilot = self.pilot
|
||||||
@ -145,19 +156,19 @@ class PilotControls(QHBoxLayout):
|
|||||||
finally:
|
finally:
|
||||||
self.player_checkbox.blockSignals(False)
|
self.player_checkbox.blockSignals(False)
|
||||||
|
|
||||||
def replace(self, new_roster: Optional[FlightRoster]) -> None:
|
def replace(self, squadron: Squadron, new_roster: Optional[FlightRoster]) -> None:
|
||||||
self.roster = new_roster
|
self.roster = new_roster
|
||||||
if self.roster is None or self.pilot_index >= self.roster.max_size:
|
if self.roster is None or self.pilot_index >= self.roster.max_size:
|
||||||
self.disable_and_clear()
|
self.disable_and_clear()
|
||||||
else:
|
else:
|
||||||
self.enable_and_reset()
|
self.enable_and_reset()
|
||||||
self.selector.replace(new_roster)
|
self.selector.replace(squadron, new_roster)
|
||||||
|
|
||||||
|
|
||||||
class FlightRosterEditor(QVBoxLayout):
|
class FlightRosterEditor(QVBoxLayout):
|
||||||
MAX_PILOTS = 4
|
MAX_PILOTS = 4
|
||||||
|
|
||||||
def __init__(self, roster: Optional[FlightRoster]) -> None:
|
def __init__(self, squadron: Union[Squadron, None], roster: Union[IFlightRoster, None]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.roster = roster
|
self.roster = roster
|
||||||
|
|
||||||
@ -170,7 +181,7 @@ class FlightRosterEditor(QVBoxLayout):
|
|||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
controls = PilotControls(roster, pilot_idx)
|
controls = PilotControls(squadron, roster, pilot_idx)
|
||||||
controls.selector.available_pilots_changed.connect(
|
controls.selector.available_pilots_changed.connect(
|
||||||
make_reset_callback(pilot_idx)
|
make_reset_callback(pilot_idx)
|
||||||
)
|
)
|
||||||
@ -193,15 +204,17 @@ class FlightRosterEditor(QVBoxLayout):
|
|||||||
for controls in self.pilot_controls[new_size:]:
|
for controls in self.pilot_controls[new_size:]:
|
||||||
controls.disable_and_clear()
|
controls.disable_and_clear()
|
||||||
|
|
||||||
def replace(self, new_roster: Optional[FlightRoster]) -> None:
|
def replace(self, squadron: Squadron, new_roster: Optional[FlightRoster]) -> None:
|
||||||
if self.roster is not None:
|
if self.roster is not None:
|
||||||
self.roster.clear()
|
self.roster.clear()
|
||||||
self.roster = new_roster
|
self.roster = new_roster
|
||||||
for controls in self.pilot_controls:
|
for controls in self.pilot_controls:
|
||||||
controls.replace(new_roster)
|
controls.replace(squadron, new_roster)
|
||||||
|
|
||||||
|
|
||||||
class QFlightSlotEditor(QGroupBox):
|
class QFlightSlotEditor(QGroupBox):
|
||||||
|
flight_resized = Signal(int)
|
||||||
|
|
||||||
def __init__(self, package_model: PackageModel, flight: Flight, game: Game):
|
def __init__(self, package_model: PackageModel, flight: Flight, game: Game):
|
||||||
super().__init__("Slots")
|
super().__init__("Slots")
|
||||||
self.package_model = package_model
|
self.package_model = package_model
|
||||||
@ -228,7 +241,7 @@ class QFlightSlotEditor(QGroupBox):
|
|||||||
layout.addWidget(QLabel(str(self.flight.squadron)), 1, 1)
|
layout.addWidget(QLabel(str(self.flight.squadron)), 1, 1)
|
||||||
|
|
||||||
layout.addWidget(QLabel("Assigned pilots:"), 2, 0)
|
layout.addWidget(QLabel("Assigned pilots:"), 2, 0)
|
||||||
self.roster_editor = FlightRosterEditor(flight.roster)
|
self.roster_editor = FlightRosterEditor(flight.squadron, flight.roster)
|
||||||
layout.addLayout(self.roster_editor, 2, 1)
|
layout.addLayout(self.roster_editor, 2, 1)
|
||||||
|
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
@ -251,3 +264,4 @@ class QFlightSlotEditor(QGroupBox):
|
|||||||
self.flight.resize(old_count)
|
self.flight.resize(old_count)
|
||||||
return
|
return
|
||||||
self.roster_editor.resize(new_count)
|
self.roster_editor.resize(new_count)
|
||||||
|
self.flight_resized.emit(new_count)
|
||||||
|
|||||||
@ -19,7 +19,7 @@ from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import (
|
|||||||
|
|
||||||
|
|
||||||
class QGeneralFlightSettingsTab(QFrame):
|
class QGeneralFlightSettingsTab(QFrame):
|
||||||
on_flight_settings_changed = Signal()
|
flight_size_changed = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -30,13 +30,16 @@ class QGeneralFlightSettingsTab(QFrame):
|
|||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.flight_slot_editor = QFlightSlotEditor(package_model, flight, game.game)
|
||||||
|
self.flight_slot_editor.flight_resized.connect(self.flight_size_changed)
|
||||||
|
|
||||||
widgets = [
|
widgets = [
|
||||||
QFlightTypeTaskInfo(flight),
|
QFlightTypeTaskInfo(flight),
|
||||||
QCommsEditor(flight, game),
|
QCommsEditor(flight, game),
|
||||||
FlightPlanPropertiesGroup(
|
FlightPlanPropertiesGroup(
|
||||||
game.game, package_model, flight, flight_wpt_list
|
game.game, package_model, flight, flight_wpt_list
|
||||||
),
|
),
|
||||||
QFlightSlotEditor(package_model, flight, game.game),
|
self.flight_slot_editor,
|
||||||
QFlightStartType(package_model, flight),
|
QFlightStartType(package_model, flight),
|
||||||
QFlightCustomName(flight),
|
QFlightCustomName(flight),
|
||||||
]
|
]
|
||||||
@ -45,6 +48,7 @@ class QGeneralFlightSettingsTab(QFrame):
|
|||||||
for w in widgets:
|
for w in widgets:
|
||||||
layout.addWidget(w, row, 0)
|
layout.addWidget(w, row, 0)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
vstretch = QVBoxLayout()
|
vstretch = QVBoxLayout()
|
||||||
vstretch.addStretch()
|
vstretch.addStretch()
|
||||||
layout.addLayout(vstretch, row, 0)
|
layout.addLayout(vstretch, row, 0)
|
||||||
|
|||||||
@ -226,9 +226,11 @@ class QFlightWaypointTab(QFrame):
|
|||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
self, "Could not recreate flight", str(ex), QMessageBox.Ok
|
self, "Could not recreate flight", str(ex), QMessageBox.Ok
|
||||||
)
|
)
|
||||||
if not self.flight.loadout.is_custom:
|
for member in self.flight.iter_members():
|
||||||
self.flight.loadout = Loadout.default_for(self.flight)
|
if not member.loadout.is_custom:
|
||||||
self.loadout_changed.emit()
|
member.loadout = Loadout.default_for(self.flight)
|
||||||
|
self.loadout_changed.emit()
|
||||||
|
self.flight_waypoint_list.update_list()
|
||||||
self.on_change()
|
self.on_change()
|
||||||
|
|
||||||
def on_change(self):
|
def on_change(self):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user