Don't allow operating on broken runways.

Doesn't allow helos or harriers to do it either even though they should
be able to because we don't currently support ground spawns, which would
be needed to prevent those aircraft from using the runway. Even then, I
don't know if they can be forced to *land* vertically.

Fixes https://github.com/Khopa/dcs_liberation/issues/432
This commit is contained in:
Dan Albert 2020-11-25 18:48:07 -08:00
parent a1b64bc72d
commit 2bd673a531
11 changed files with 54 additions and 37 deletions

View File

@ -1223,7 +1223,7 @@ def unit_task(unit: UnitType) -> Optional[Task]:
return None return None
def find_unittype(for_task: Task, country_name: str) -> List[UnitType]: def find_unittype(for_task: Task, country_name: str) -> List[Type[UnitType]]:
return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units] return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units]

View File

@ -59,14 +59,14 @@ class ControlPointAircraftInventory:
return 0 return 0
@property @property
def types_available(self) -> Iterator[FlyingType]: def types_available(self) -> Iterator[Type[FlyingType]]:
"""Iterates over all available aircraft types.""" """Iterates over all available aircraft types."""
for aircraft, count in self.inventory.items(): for aircraft, count in self.inventory.items():
if count > 0: if count > 0:
yield aircraft yield aircraft
@property @property
def all_aircraft(self) -> Iterator[Tuple[FlyingType, int]]: def all_aircraft(self) -> Iterator[Tuple[Type[FlyingType], int]]:
"""Iterates over all available aircraft types, including amounts.""" """Iterates over all available aircraft types, including amounts."""
for aircraft, count in self.inventory.items(): for aircraft, count in self.inventory.items():
if count > 0: if count > 0:
@ -106,12 +106,14 @@ class GlobalAircraftInventory:
return self.inventories[control_point] return self.inventories[control_point]
@property @property
def available_types_for_player(self) -> Iterator[FlyingType]: def available_types_for_player(self) -> Iterator[Type[FlyingType]]:
"""Iterates over all aircraft types available to the player.""" """Iterates over all aircraft types available to the player."""
seen: Set[FlyingType] = set() seen: Set[Type[FlyingType]] = set()
for control_point, inventory in self.inventories.items(): for control_point, inventory in self.inventories.items():
if control_point.captured: if control_point.captured:
for aircraft in inventory.types_available: for aircraft in inventory.types_available:
if not control_point.can_operate(aircraft):
continue
if aircraft not in seen: if aircraft not in seen:
seen.add(aircraft) seen.add(aircraft)
yield aircraft yield aircraft

View File

@ -7,7 +7,7 @@ import re
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.mapping import Point from dcs.mapping import Point
from dcs.ships import ( from dcs.ships import (
@ -283,7 +283,7 @@ class ControlPoint(MissionTarget, ABC):
self.stances[to.id] = CombatStance.DEFENSIVE self.stances[to.id] = CombatStance.DEFENSIVE
@abstractmethod @abstractmethod
def has_runway(self) -> bool: def runway_is_operational(self) -> bool:
""" """
Check whether this control point supports taking offs and landings. Check whether this control point supports taking offs and landings.
:return: :return:
@ -363,7 +363,7 @@ class ControlPoint(MissionTarget, ABC):
BaseDefenseGenerator(game, self).generate() BaseDefenseGenerator(game, self).generate()
@abstractmethod @abstractmethod
def can_land(self, aircraft: FlyingType) -> bool: def can_operate(self, aircraft: Type[FlyingType]) -> bool:
... ...
def aircraft_transferring(self, game: Game) -> int: def aircraft_transferring(self, game: Game) -> int:
@ -437,8 +437,13 @@ class Airfield(ControlPoint):
self.airport = airport self.airport = airport
self._runway_status = RunwayStatus() self._runway_status = RunwayStatus()
def can_land(self, aircraft: FlyingType) -> bool: def can_operate(self, aircraft: FlyingType) -> bool:
return True # TODO: Allow helicopters.
# Need to implement ground spawns so the helos don't use the runway.
# TODO: Allow harrier.
# Needs ground spawns just like helos do, but also need to be able to
# limit takeoff weight to ~20500 lbs or it won't be able to take off.
return self.runway_is_operational()
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
@ -462,7 +467,7 @@ class Airfield(ControlPoint):
def heading(self) -> int: def heading(self) -> int:
return self.airport.runways[0].heading return self.airport.runways[0].heading
def has_runway(self) -> bool: def runway_is_operational(self) -> bool:
return not self.runway_status.damaged return not self.runway_status.damaged
@property @property
@ -503,7 +508,7 @@ class NavalControlPoint(ControlPoint, ABC):
def heading(self) -> int: def heading(self) -> int:
return 0 # TODO compute heading return 0 # TODO compute heading
def has_runway(self) -> bool: def runway_is_operational(self) -> bool:
# Necessary because it's possible for the carrier itself to have sunk # Necessary because it's possible for the carrier itself to have sunk
# while its escorts are still alive. # while its escorts are still alive.
for g in self.ground_objects: for g in self.ground_objects:
@ -525,7 +530,7 @@ class NavalControlPoint(ControlPoint, ABC):
@property @property
def runway_status(self) -> RunwayStatus: def runway_status(self) -> RunwayStatus:
return RunwayStatus(damaged=not self.has_runway()) return RunwayStatus(damaged=not self.runway_is_operational())
@property @property
def runway_can_be_repaired(self) -> bool: def runway_can_be_repaired(self) -> bool:
@ -548,7 +553,7 @@ class Carrier(NavalControlPoint):
def is_carrier(self): def is_carrier(self):
return True return True
def can_land(self, aircraft: FlyingType) -> bool: def can_operate(self, aircraft: FlyingType) -> bool:
return aircraft in db.CARRIER_CAPABLE return aircraft in db.CARRIER_CAPABLE
@property @property
@ -571,7 +576,7 @@ class Lha(NavalControlPoint):
def is_lha(self) -> bool: def is_lha(self) -> bool:
return True return True
def can_land(self, aircraft: FlyingType) -> bool: def can_operate(self, aircraft: FlyingType) -> bool:
return aircraft in db.LHA_CAPABLE return aircraft in db.LHA_CAPABLE
@property @property
@ -581,7 +586,7 @@ class Lha(NavalControlPoint):
class OffMapSpawn(ControlPoint): class OffMapSpawn(ControlPoint):
def has_runway(self) -> bool: def runway_is_operational(self) -> bool:
return True return True
def __init__(self, cp_id: int, name: str, position: Point): def __init__(self, cp_id: int, name: str, position: Point):
@ -600,7 +605,7 @@ class OffMapSpawn(ControlPoint):
def total_aircraft_parking(self) -> int: def total_aircraft_parking(self) -> int:
return 1000 return 1000
def can_land(self, aircraft: FlyingType) -> bool: def can_operate(self, aircraft: FlyingType) -> bool:
return True return True
@property @property

View File

@ -201,7 +201,7 @@ class AircraftAllocator:
def find_aircraft_of_type( def find_aircraft_of_type(
self, flight: ProposedFlight, types: List[Type[FlyingType]], self, flight: ProposedFlight, types: List[Type[FlyingType]],
) -> Optional[Tuple[ControlPoint, FlyingType]]: ) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
airfields_in_range = self.closest_airfields.airfields_within( airfields_in_range = self.closest_airfields.airfields_within(
flight.max_distance flight.max_distance
) )
@ -210,6 +210,8 @@ class AircraftAllocator:
continue continue
inventory = self.global_inventory.for_control_point(airfield) inventory = self.global_inventory.for_control_point(airfield)
for aircraft, available in inventory.all_aircraft: for aircraft, available in inventory.all_aircraft:
if not airfield.can_operate(aircraft):
continue
if aircraft in types and available >= flight.num_aircraft: if aircraft in types and available >= flight.num_aircraft:
inventory.remove_aircraft(aircraft, flight.num_aircraft) inventory.remove_aircraft(aircraft, flight.num_aircraft)
return airfield, aircraft return airfield, aircraft
@ -264,7 +266,7 @@ class PackageBuilder:
continue continue
if airfield == arrival: if airfield == arrival:
continue continue
if not airfield.can_land(aircraft): if not airfield.can_operate(aircraft):
continue continue
if isinstance(airfield, OffMapSpawn): if isinstance(airfield, OffMapSpawn):
continue continue

View File

@ -1,5 +1,5 @@
"""Combo box for selecting aircraft types.""" """Combo box for selecting aircraft types."""
from typing import Iterable from typing import Iterable, Type
from PySide2.QtWidgets import QComboBox from PySide2.QtWidgets import QComboBox
@ -9,7 +9,7 @@ from dcs.unittype import FlyingType
class QAircraftTypeSelector(QComboBox): class QAircraftTypeSelector(QComboBox):
"""Combo box for selecting among the given aircraft types.""" """Combo box for selecting among the given aircraft types."""
def __init__(self, aircraft_types: Iterable[FlyingType]) -> None: def __init__(self, aircraft_types: Iterable[Type[FlyingType]]) -> None:
super().__init__() super().__init__()
for aircraft in aircraft_types: for aircraft in aircraft_types:
self.addItem(f"{aircraft.id}", userData=aircraft) self.addItem(f"{aircraft.id}", userData=aircraft)

View File

@ -1,10 +1,9 @@
"""Combo box for selecting a departure airfield.""" """Combo box for selecting a departure airfield."""
from typing import Iterable from typing import Iterable, Type
from PySide2.QtWidgets import QComboBox from PySide2.QtWidgets import QComboBox
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
from game import db
from game.theater.controlpoint import ControlPoint from game.theater.controlpoint import ControlPoint
@ -16,7 +15,7 @@ class QArrivalAirfieldSelector(QComboBox):
""" """
def __init__(self, destinations: Iterable[ControlPoint], def __init__(self, destinations: Iterable[ControlPoint],
aircraft: FlyingType, optional_text: str) -> None: aircraft: Type[FlyingType], optional_text: str) -> None:
super().__init__() super().__init__()
self.destinations = list(destinations) self.destinations = list(destinations)
self.aircraft = aircraft self.aircraft = aircraft
@ -33,7 +32,7 @@ class QArrivalAirfieldSelector(QComboBox):
def rebuild_selector(self) -> None: def rebuild_selector(self) -> None:
self.clear() self.clear()
for destination in self.destinations: for destination in self.destinations:
if destination.can_land(self.aircraft): if destination.can_operate(self.aircraft):
self.addItem(destination.name, destination) self.addItem(destination.name, destination)
self.model().sort(0) self.model().sort(0)
self.insertItem(0, self.optional_text, None) self.insertItem(0, self.optional_text, None)

View File

@ -1,5 +1,5 @@
"""Combo box for selecting a departure airfield.""" """Combo box for selecting a departure airfield."""
from typing import Iterable from typing import Iterable, Type
from PySide2.QtCore import Signal from PySide2.QtCore import Signal
from PySide2.QtWidgets import QComboBox from PySide2.QtWidgets import QComboBox
@ -20,7 +20,7 @@ class QOriginAirfieldSelector(QComboBox):
def __init__(self, global_inventory: GlobalAircraftInventory, def __init__(self, global_inventory: GlobalAircraftInventory,
origins: Iterable[ControlPoint], origins: Iterable[ControlPoint],
aircraft: FlyingType) -> None: aircraft: Type[FlyingType]) -> None:
super().__init__() super().__init__()
self.global_inventory = global_inventory self.global_inventory = global_inventory
self.origins = list(origins) self.origins = list(origins)
@ -37,12 +37,14 @@ class QOriginAirfieldSelector(QComboBox):
def rebuild_selector(self) -> None: def rebuild_selector(self) -> None:
self.clear() self.clear()
for origin in self.origins: for origin in self.origins:
if not origin.can_operate(self.aircraft):
continue
inventory = self.global_inventory.for_control_point(origin) inventory = self.global_inventory.for_control_point(origin)
available = inventory.available(self.aircraft) available = inventory.available(self.aircraft)
if available: if available:
self.addItem(f"{origin.name} ({available} available)", origin) self.addItem(f"{origin.name} ({available} available)", origin)
self.model().sort(0) self.model().sort(0)
self.update()
@property @property
def available(self) -> int: def available(self) -> int:

View File

@ -33,7 +33,7 @@ class QMapControlPoint(QMapObject):
painter.setBrush(self.brush_color) painter.setBrush(self.brush_color)
painter.setPen(self.pen_color) painter.setPen(self.pen_color)
if not self.control_point.has_runway(): if not self.control_point.runway_is_operational():
painter.setBrush(const.COLORS["black"]) painter.setBrush(const.COLORS["black"])
painter.setPen(self.brush_color) painter.setPen(self.brush_color)

View File

@ -20,9 +20,8 @@ class QBaseMenuTabs(QTabWidget):
self.intel = QIntelInfo(cp, game_model.game) self.intel = QIntelInfo(cp, game_model.game)
self.addTab(self.intel, "Intel") self.addTab(self.intel, "Intel")
else: else:
if cp.has_runway(): self.airfield_command = QAirfieldCommand(cp, game_model)
self.airfield_command = QAirfieldCommand(cp, game_model) self.addTab(self.airfield_command, "Airfield Command")
self.addTab(self.airfield_command, "Airfield Command")
if cp.is_carrier: if cp.is_carrier:
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)

View File

@ -5,6 +5,7 @@ from PySide2.QtWidgets import (
QGroupBox, QGroupBox,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QLayout,
QPushButton, QPushButton,
QSizePolicy, QSizePolicy,
QSpacerItem, QSpacerItem,
@ -45,7 +46,8 @@ class QRecruitBehaviour:
def budget(self, value: int) -> None: def budget(self, value: int) -> None:
self.game_model.game.budget = value self.game_model.game.budget = value
def add_purchase_row(self, unit_type, layout, row): def add_purchase_row(self, unit_type: Type[UnitType], layout: QLayout,
row: int, disabled: bool = False) -> int:
exist = QGroupBox() exist = QGroupBox()
exist.setProperty("style", "buy-box") exist.setProperty("style", "buy-box")
exist.setMaximumHeight(36) exist.setMaximumHeight(36)
@ -80,6 +82,7 @@ class QRecruitBehaviour:
buy = QPushButton("+") buy = QPushButton("+")
buy.setProperty("style", "btn-buy") buy.setProperty("style", "btn-buy")
buy.setDisabled(disabled)
buy.setMinimumSize(16, 16) buy.setMinimumSize(16, 16)
buy.setMaximumSize(16, 16) buy.setMaximumSize(16, 16)
buy.clicked.connect(lambda: self.buy(unit_type)) buy.clicked.connect(lambda: self.buy(unit_type))
@ -87,6 +90,7 @@ class QRecruitBehaviour:
sell = QPushButton("-") sell = QPushButton("-")
sell.setProperty("style", "btn-sell") sell.setProperty("style", "btn-sell")
sell.setDisabled(disabled)
sell.setMinimumSize(16, 16) sell.setMinimumSize(16, 16)
sell.setMaximumSize(16, 16) sell.setMaximumSize(16, 16)
sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))

View File

@ -1,5 +1,5 @@
import logging import logging
from typing import Optional, Set from typing import Optional, Set, Type
from PySide2.QtCore import Qt from PySide2.QtCore import Qt
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
@ -13,7 +13,7 @@ from PySide2.QtWidgets import (
QWidget, QWidget,
) )
from dcs.task import CAP, CAS from dcs.task import CAP, CAS
from dcs.unittype import UnitType from dcs.unittype import FlyingType, UnitType
from game import db from game import db
from game.theater import ControlPoint from game.theater import ControlPoint
@ -51,12 +51,14 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
task_box_layout = QGridLayout() task_box_layout = QGridLayout()
row = 0 row = 0
unit_types: Set[UnitType] = set() unit_types: Set[Type[FlyingType]] = set()
for task in tasks: for task in tasks:
units = db.find_unittype(task, self.game_model.game.player_name) units = db.find_unittype(task, self.game_model.game.player_name)
if not units: if not units:
continue continue
for unit in units: for unit in units:
if not issubclass(unit, FlyingType):
continue
if self.cp.is_carrier and unit not in db.CARRIER_CAPABLE: if self.cp.is_carrier and unit not in db.CARRIER_CAPABLE:
continue continue
if self.cp.is_lha and unit not in db.LHA_CAPABLE: if self.cp.is_lha and unit not in db.LHA_CAPABLE:
@ -65,7 +67,9 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
sorted_units = sorted(unit_types, key=lambda u: db.unit_type_name_2(u)) sorted_units = sorted(unit_types, key=lambda u: db.unit_type_name_2(u))
for unit_type in sorted_units: for unit_type in sorted_units:
row = self.add_purchase_row(unit_type, task_box_layout, row) row = self.add_purchase_row(
unit_type, task_box_layout, row,
disabled=not self.cp.can_operate(unit_type))
stretch = QVBoxLayout() stretch = QVBoxLayout()
stretch.addStretch() stretch.addStretch()
task_box_layout.addLayout(stretch, row, 0) task_box_layout.addLayout(stretch, row, 0)