mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Support reparing damaged runways.
Repairing a damaged runway costs $100M and takes 4 turns (one day). The AI will always repair runways if they can afford it. if a runway is damaged again during the repair the process must begin again. Runways are still operational despite what the UI says. Preventing the player and AI from using damaged runways (except for with helicopters and harriers) is next.
This commit is contained in:
parent
ee768b9147
commit
80bc9d6b23
@ -225,6 +225,11 @@ from this example `Identifier` should be used (which may or may not include cate
|
||||
For example, player accessible Hornet is called `FA_18C_hornet`, and MANPAD Igla is called `AirDefence.SAM_SA_18_Igla_S_MANPADS`
|
||||
"""
|
||||
|
||||
# This should probably be much higher, but the AI doesn't rollover their budget
|
||||
# and isn't smart enough to save to repair a critical runway anyway, so it has
|
||||
# to be cheap enough to repair with a single turn's income.
|
||||
RUNWAY_REPAIR_COST = 100
|
||||
|
||||
"""
|
||||
Prices for the aircraft.
|
||||
This defines both price for the player (although only aircraft listed in CAP/CAS/Transport/Armor/AirDefense roles will be purchasable)
|
||||
|
||||
@ -118,7 +118,7 @@ class Event:
|
||||
logging.info("Commiting mission results")
|
||||
|
||||
for damaged_runway in debriefing.damaged_runways:
|
||||
damaged_runway.damaged = True
|
||||
damaged_runway.damage_runway()
|
||||
|
||||
# ------------------------------
|
||||
# Destroyed aircrafts
|
||||
|
||||
22
game/game.py
22
game/game.py
@ -26,7 +26,7 @@ from .event.frontlineattack import FrontlineAttackEvent
|
||||
from .factions.faction import Faction
|
||||
from .infos.information import Information
|
||||
from .settings import Settings
|
||||
from .theater import ConflictTheater, ControlPoint, OffMapSpawn
|
||||
from .theater import Airfield, ConflictTheater, ControlPoint, OffMapSpawn
|
||||
from .unitmap import UnitMap
|
||||
from .weather import Conditions, TimeOfDay
|
||||
|
||||
@ -212,6 +212,9 @@ class Game:
|
||||
else:
|
||||
event.skip()
|
||||
|
||||
for control_point in self.theater.controlpoints:
|
||||
control_point.process_turn()
|
||||
|
||||
self._enemy_reinforcement()
|
||||
self._budget_player()
|
||||
|
||||
@ -269,9 +272,20 @@ class Game:
|
||||
if g.category in REWARDS.keys() and not g.is_dead:
|
||||
production = production + REWARDS[g.category]
|
||||
|
||||
production = production * 0.75
|
||||
budget_for_armored_units = production / 2
|
||||
budget_for_aircraft = production / 2
|
||||
# TODO: Why doesn't the enemy get the full budget?
|
||||
budget = production * 0.75
|
||||
|
||||
for control_point in self.theater.enemy_points():
|
||||
if budget < db.RUNWAY_REPAIR_COST:
|
||||
break
|
||||
if control_point.runway_can_be_repaired:
|
||||
control_point.begin_runway_repair()
|
||||
budget -= db.RUNWAY_REPAIR_COST
|
||||
self.informations.append(Information(
|
||||
f"OPFOR has begun repairing the runway at {control_point}"))
|
||||
|
||||
budget_for_armored_units = budget / 2
|
||||
budget_for_aircraft = budget / 2
|
||||
|
||||
potential_cp_armor = []
|
||||
for cp in self.theater.enemy_points():
|
||||
|
||||
@ -148,6 +148,45 @@ class PendingOccupancy:
|
||||
return self.present + self.ordered + self.transferring
|
||||
|
||||
|
||||
@dataclass
|
||||
class RunwayStatus:
|
||||
damaged: bool = False
|
||||
repair_turns_remaining: Optional[int] = None
|
||||
|
||||
def damage(self) -> None:
|
||||
self.damaged = True
|
||||
# If the runway is already under repair and is damaged again, progress
|
||||
# is reset.
|
||||
self.repair_turns_remaining = None
|
||||
|
||||
def begin_repair(self) -> None:
|
||||
if self.repair_turns_remaining is not None:
|
||||
logging.error("Runway already under repair. Restarting.")
|
||||
self.repair_turns_remaining = 4
|
||||
|
||||
def process_turn(self) -> None:
|
||||
if self.repair_turns_remaining is not None:
|
||||
if self.repair_turns_remaining == 1:
|
||||
self.repair_turns_remaining = None
|
||||
self.damaged = False
|
||||
else:
|
||||
self.repair_turns_remaining -= 1
|
||||
|
||||
@property
|
||||
def needs_repair(self) -> bool:
|
||||
return self.damaged and self.repair_turns_remaining is None
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.damaged:
|
||||
return "Runway operational"
|
||||
|
||||
turns_remaining = self.repair_turns_remaining
|
||||
if turns_remaining is None:
|
||||
return "Runway damaged"
|
||||
|
||||
return f"Runway repairing, {turns_remaining} turns remaining"
|
||||
|
||||
|
||||
class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
position = None # type: Point
|
||||
@ -367,6 +406,26 @@ class ControlPoint(MissionTarget, ABC):
|
||||
def parking_slots(self) -> Iterator[ParkingSlot]:
|
||||
yield from []
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
...
|
||||
|
||||
@property
|
||||
def runway_can_be_repaired(self) -> bool:
|
||||
return self.runway_status.needs_repair
|
||||
|
||||
def begin_runway_repair(self) -> None:
|
||||
if not self.runway_can_be_repaired:
|
||||
logging.error(f"Cannot repair runway at {self}")
|
||||
return
|
||||
self.runway_status.begin_repair()
|
||||
|
||||
def process_turn(self) -> None:
|
||||
runway_status = self.runway_status
|
||||
if runway_status is not None:
|
||||
runway_status.process_turn()
|
||||
|
||||
|
||||
class Airfield(ControlPoint):
|
||||
|
||||
@ -376,7 +435,7 @@ class Airfield(ControlPoint):
|
||||
size, importance, has_frontline,
|
||||
cptype=ControlPointType.AIRBASE)
|
||||
self.airport = airport
|
||||
self.damaged = False
|
||||
self._runway_status = RunwayStatus()
|
||||
|
||||
def can_land(self, aircraft: FlyingType) -> bool:
|
||||
return True
|
||||
@ -404,7 +463,14 @@ class Airfield(ControlPoint):
|
||||
return self.airport.runways[0].heading
|
||||
|
||||
def has_runway(self) -> bool:
|
||||
return not self.damaged
|
||||
return not self.runway_status.damaged
|
||||
|
||||
@property
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return self._runway_status
|
||||
|
||||
def damage_runway(self) -> None:
|
||||
self.runway_status.damage()
|
||||
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
@ -457,6 +523,14 @@ class NavalControlPoint(ControlPoint, ABC):
|
||||
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||
return dynamic_runways.get(self.name, fallback)
|
||||
|
||||
@property
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return RunwayStatus(damaged=not self.has_runway())
|
||||
|
||||
@property
|
||||
def runway_can_be_repaired(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class Carrier(NavalControlPoint):
|
||||
|
||||
@ -537,3 +611,7 @@ class OffMapSpawn(ControlPoint):
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
logging.warning("TODO: Off map spawns have no runways.")
|
||||
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||
|
||||
@property
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return RunwayStatus()
|
||||
|
||||
@ -4,11 +4,13 @@ from PySide2.QtWidgets import (
|
||||
QDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game import db
|
||||
from game.theater import ControlPoint, ControlPointType
|
||||
from gen.flights.flight import FlightType
|
||||
from qt_ui.dialogs import Dialog
|
||||
@ -59,17 +61,17 @@ class QBaseMenu2(QDialog):
|
||||
title = QLabel("<b>" + self.cp.name + "</b>")
|
||||
title.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
title.setProperty("style", "base-title")
|
||||
aircraft = self.cp.base.total_aircraft
|
||||
armor = self.cp.base.total_armor
|
||||
runway_status = "operational" if self.cp.has_runway() else "damaged"
|
||||
intel_summary = QLabel("\n".join([
|
||||
f"{aircraft} aircraft",
|
||||
f"{armor} ground units",
|
||||
f"Runway {runway_status}"
|
||||
]))
|
||||
self.intel_summary = QLabel()
|
||||
self.update_intel_summary()
|
||||
top_layout.addWidget(title)
|
||||
top_layout.addWidget(intel_summary)
|
||||
top_layout.addWidget(self.intel_summary)
|
||||
top_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.repair_button = QPushButton()
|
||||
self.repair_button.clicked.connect(self.begin_runway_repair)
|
||||
self.update_repair_button()
|
||||
top_layout.addWidget(self.repair_button)
|
||||
|
||||
base_menu_header.setProperty("style", "baseMenuHeader")
|
||||
base_menu_header.setLayout(top_layout)
|
||||
|
||||
@ -96,7 +98,66 @@ class QBaseMenu2(QDialog):
|
||||
bottom_row.addWidget(budget_display)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def closeEvent(self, closeEvent:QCloseEvent):
|
||||
@property
|
||||
def can_repair_runway(self) -> bool:
|
||||
return self.cp.captured and self.cp.runway_can_be_repaired
|
||||
|
||||
@property
|
||||
def can_afford_runway_repair(self) -> bool:
|
||||
return self.game_model.game.budget >= db.RUNWAY_REPAIR_COST
|
||||
|
||||
def begin_runway_repair(self) -> None:
|
||||
if not self.can_afford_runway_repair:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Cannot repair runway",
|
||||
f"Runway repair costs ${db.RUNWAY_REPAIR_COST}M but you have "
|
||||
f"only ${self.game_model.game.budget}M available.",
|
||||
QMessageBox.Ok)
|
||||
return
|
||||
if not self.can_repair_runway:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Cannot repair runway",
|
||||
f"Cannot repair this runway.", QMessageBox.Ok)
|
||||
return
|
||||
|
||||
self.cp.begin_runway_repair()
|
||||
self.game_model.game.budget -= db.RUNWAY_REPAIR_COST
|
||||
self.update_repair_button()
|
||||
self.update_intel_summary()
|
||||
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
||||
|
||||
def update_repair_button(self) -> None:
|
||||
self.repair_button.setVisible(True)
|
||||
turns_remaining = self.cp.runway_status.repair_turns_remaining
|
||||
if self.cp.captured and turns_remaining is not None:
|
||||
self.repair_button.setText("Repairing...")
|
||||
self.repair_button.setDisabled(True)
|
||||
return
|
||||
|
||||
if self.can_repair_runway:
|
||||
if self.can_afford_runway_repair:
|
||||
self.repair_button.setText(f"Repair ${db.RUNWAY_REPAIR_COST}M")
|
||||
self.repair_button.setDisabled(False)
|
||||
return
|
||||
else:
|
||||
self.repair_button.setText(
|
||||
f"Cannot afford repair ${db.RUNWAY_REPAIR_COST}M")
|
||||
self.repair_button.setDisabled(True)
|
||||
return
|
||||
|
||||
self.repair_button.setVisible(False)
|
||||
self.repair_button.setDisabled(True)
|
||||
|
||||
def update_intel_summary(self) -> None:
|
||||
self.intel_summary.setText("\n".join([
|
||||
f"{self.cp.base.total_aircraft} aircraft",
|
||||
f"{self.cp.base.total_armor} ground units",
|
||||
str(self.cp.runway_status)
|
||||
]))
|
||||
|
||||
def closeEvent(self, close_event: QCloseEvent):
|
||||
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
||||
|
||||
def get_base_image(self):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user