mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Clean up convoy code.
This commit is contained in:
parent
50d8e08a34
commit
6cffc47f3c
@ -206,7 +206,7 @@ class Debriefing:
|
||||
|
||||
convoy_unit = self.unit_map.convoy_unit(unit_name)
|
||||
if convoy_unit is not None:
|
||||
if convoy_unit.transfer.player:
|
||||
if convoy_unit.convoy.player_owned:
|
||||
losses.player_convoy.append(convoy_unit)
|
||||
else:
|
||||
losses.enemy_convoy.append(convoy_unit)
|
||||
|
||||
@ -159,9 +159,9 @@ class Event:
|
||||
def commit_convoy_losses(debriefing: Debriefing) -> None:
|
||||
for loss in debriefing.convoy_losses:
|
||||
unit_type = loss.unit_type
|
||||
transfer = loss.transfer
|
||||
available = loss.transfer.units.get(unit_type, 0)
|
||||
convoy_name = f"convoy from {transfer.position} to {transfer.destination}"
|
||||
convoy = loss.convoy
|
||||
available = loss.convoy.units.get(unit_type, 0)
|
||||
convoy_name = f"convoy from {convoy.origin} to {convoy.destination}"
|
||||
if available <= 0:
|
||||
logging.error(
|
||||
f"Found killed {unit_type} in {convoy_name} but that convoy has "
|
||||
@ -170,7 +170,7 @@ class Event:
|
||||
continue
|
||||
|
||||
logging.info(f"{unit_type} destroyed in {convoy_name}")
|
||||
transfer.units[unit_type] -= 1
|
||||
convoy.kill_unit(unit_type)
|
||||
|
||||
@staticmethod
|
||||
def commit_ground_object_losses(debriefing: Debriefing) -> None:
|
||||
|
||||
12
game/game.py
12
game/game.py
@ -34,7 +34,7 @@ from .settings import Settings
|
||||
from .theater import ConflictTheater, ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import MissileSiteGroundObject
|
||||
from .threatzones import ThreatZones
|
||||
from .transfers import PendingTransfers, RoadTransferOrder
|
||||
from .transfers import Convoy, ConvoyMap, PendingTransfers, RoadTransferOrder
|
||||
from .unitmap import UnitMap
|
||||
from .weather import Conditions, TimeOfDay
|
||||
|
||||
@ -122,7 +122,7 @@ class Game:
|
||||
|
||||
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
|
||||
|
||||
self._transfers = PendingTransfers()
|
||||
self.transfers = PendingTransfers()
|
||||
|
||||
self.sanitize_sides()
|
||||
|
||||
@ -154,14 +154,6 @@ class Game:
|
||||
# Regenerate any state that was not persisted.
|
||||
self.on_load()
|
||||
|
||||
@property
|
||||
def transfers(self) -> PendingTransfers:
|
||||
try:
|
||||
return self._transfers
|
||||
except AttributeError:
|
||||
self._transfers = PendingTransfers()
|
||||
return self._transfers
|
||||
|
||||
def generate_conditions(self) -> Conditions:
|
||||
return Conditions.generate(
|
||||
self.theater, self.current_day, self.current_turn_time_of_day, self.settings
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Iterator, List, Type
|
||||
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type
|
||||
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
from game.theater.supplyroutes import SupplyRoute
|
||||
from gen.naming import namegen
|
||||
@ -37,8 +43,6 @@ class RoadTransferOrder(TransferOrder):
|
||||
#: point a turn through the supply line.
|
||||
position: ControlPoint = field(init=False)
|
||||
|
||||
name: str = field(init=False, default_factory=namegen.next_convoy_name)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.position = self.origin
|
||||
|
||||
@ -51,14 +55,11 @@ class RoadTransferOrder(TransferOrder):
|
||||
|
||||
|
||||
class Convoy(MissionTarget):
|
||||
def __init__(self, transfer: RoadTransferOrder) -> None:
|
||||
self.transfer = transfer
|
||||
count = sum(c for c in transfer.units.values())
|
||||
super().__init__(
|
||||
f"{transfer.name} of {count} units moving from {transfer.position} to "
|
||||
f"{transfer.destination}",
|
||||
transfer.position.position,
|
||||
)
|
||||
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
|
||||
super().__init__(namegen.next_convoy_name(), origin.position)
|
||||
self.origin = origin
|
||||
self.destination = destination
|
||||
self.transfers: List[RoadTransferOrder] = []
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
if self.is_friendly(for_player):
|
||||
@ -68,11 +69,93 @@ class Convoy(MissionTarget):
|
||||
yield from super().mission_types(for_player)
|
||||
|
||||
def is_friendly(self, to_player: bool) -> bool:
|
||||
return self.transfer.position.captured
|
||||
return self.origin.captured
|
||||
|
||||
def add_units(self, transfer: RoadTransferOrder) -> None:
|
||||
self.transfers.append(transfer)
|
||||
|
||||
def remove_units(self, transfer: RoadTransferOrder) -> None:
|
||||
self.transfers.remove(transfer)
|
||||
|
||||
def kill_unit(self, unit_type: Type[VehicleType]) -> None:
|
||||
for transfer in self.transfers:
|
||||
if unit_type in transfer.units:
|
||||
transfer.units[unit_type] -= 1
|
||||
return
|
||||
raise KeyError
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return sum(sum(t.units.values()) for t in self.transfers)
|
||||
|
||||
@property
|
||||
def units(self) -> Dict[Type[VehicleType], int]:
|
||||
units: Dict[Type[VehicleType], int] = defaultdict(int)
|
||||
for transfer in self.transfers:
|
||||
for unit_type, count in transfer.units.items():
|
||||
units[unit_type] += count
|
||||
return units
|
||||
|
||||
@property
|
||||
def player_owned(self) -> bool:
|
||||
return self.origin.captured
|
||||
|
||||
|
||||
class ConvoyMap:
|
||||
def __init__(self) -> None:
|
||||
# Dict of origin -> destination -> convoy.
|
||||
self.convoys: Dict[ControlPoint, Dict[ControlPoint, Convoy]] = defaultdict(dict)
|
||||
|
||||
def convoy_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool:
|
||||
return destination in self.convoys[origin]
|
||||
|
||||
def find_convoy(
|
||||
self, origin: ControlPoint, destination: ControlPoint
|
||||
) -> Optional[Convoy]:
|
||||
return self.convoys[origin].get(destination)
|
||||
|
||||
def find_or_create_convoy(
|
||||
self, origin: ControlPoint, destination: ControlPoint
|
||||
) -> Convoy:
|
||||
convoy = self.find_convoy(origin, destination)
|
||||
if convoy is None:
|
||||
convoy = Convoy(origin, destination)
|
||||
self.convoys[origin][destination] = convoy
|
||||
return convoy
|
||||
|
||||
def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]:
|
||||
yield from self.convoys[origin].values()
|
||||
|
||||
def disband_convoy(self, convoy: Convoy) -> None:
|
||||
del self.convoys[convoy.origin][convoy.destination]
|
||||
|
||||
def add(self, transfer: RoadTransferOrder) -> None:
|
||||
next_stop = transfer.next_stop()
|
||||
self.find_or_create_convoy(transfer.position, next_stop).add_units(transfer)
|
||||
|
||||
def remove(self, transfer: RoadTransferOrder) -> None:
|
||||
next_stop = transfer.next_stop()
|
||||
convoy = self.find_convoy(transfer.position, next_stop)
|
||||
if convoy is None:
|
||||
logging.error(
|
||||
f"Attempting to remove {transfer} from convoy but it is in no convoy."
|
||||
)
|
||||
return
|
||||
convoy.remove_units(transfer)
|
||||
if not convoy.transfers:
|
||||
self.disband_convoy(convoy)
|
||||
|
||||
def disband_all(self) -> None:
|
||||
self.convoys = defaultdict(dict)
|
||||
|
||||
def __iter__(self) -> Iterator[Convoy]:
|
||||
for destination_dict in self.convoys.values():
|
||||
yield from destination_dict.values()
|
||||
|
||||
|
||||
class PendingTransfers:
|
||||
def __init__(self) -> None:
|
||||
self.convoys = ConvoyMap()
|
||||
self.pending_transfers: List[RoadTransferOrder] = []
|
||||
|
||||
def __iter__(self) -> Iterator[RoadTransferOrder]:
|
||||
@ -88,8 +171,10 @@ class PendingTransfers:
|
||||
def new_transfer(self, transfer: RoadTransferOrder) -> None:
|
||||
transfer.origin.base.commit_losses(transfer.units)
|
||||
self.pending_transfers.append(transfer)
|
||||
self.convoys.add(transfer)
|
||||
|
||||
def cancel_transfer(self, transfer: RoadTransferOrder) -> None:
|
||||
self.convoys.remove(transfer)
|
||||
self.pending_transfers.remove(transfer)
|
||||
transfer.origin.base.commision_units(transfer.units)
|
||||
|
||||
@ -99,8 +184,16 @@ class PendingTransfers:
|
||||
if not self.perform_transfer(transfer):
|
||||
incomplete.append(transfer)
|
||||
self.pending_transfers = incomplete
|
||||
self.rebuild_convoys()
|
||||
|
||||
def rebuild_convoys(self) -> None:
|
||||
self.convoys.disband_all()
|
||||
for transfer in self.pending_transfers:
|
||||
self.convoys.add(transfer)
|
||||
|
||||
def perform_transfer(self, transfer: RoadTransferOrder) -> bool:
|
||||
# TODO: Can be improved to use the convoy map.
|
||||
# The convoy map already has a lot of the data that we're recomputing here.
|
||||
if transfer.player != transfer.destination.captured:
|
||||
logging.info(
|
||||
f"Transfer destination {transfer.destination.name} was captured."
|
||||
|
||||
@ -9,7 +9,7 @@ from dcs.unittype import VehicleType
|
||||
from game import db
|
||||
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import BuildingGroundObject
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import Convoy, RoadTransferOrder
|
||||
from gen.flights.flight import Flight
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ class GroundObjectUnit:
|
||||
@dataclass(frozen=True)
|
||||
class ConvoyUnit:
|
||||
unit_type: Type[VehicleType]
|
||||
transfer: RoadTransferOrder
|
||||
convoy: Convoy
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -121,7 +121,7 @@ class UnitMap:
|
||||
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
|
||||
return self.ground_object_units.get(name, None)
|
||||
|
||||
def add_convoy_units(self, group: Group, transfer: RoadTransferOrder) -> None:
|
||||
def add_convoy_units(self, group: Group, convoy: Convoy) -> None:
|
||||
for unit in group.units:
|
||||
# The actual name is a String (the pydcs translatable string), which
|
||||
# doesn't define __eq__.
|
||||
@ -135,7 +135,7 @@ class UnitMap:
|
||||
raise RuntimeError(
|
||||
f"{name} is a {unit_type.__name__}, expected a VehicleType"
|
||||
)
|
||||
self.convoys[name] = ConvoyUnit(unit_type, transfer)
|
||||
self.convoys[name] = ConvoyUnit(unit_type, convoy)
|
||||
|
||||
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
|
||||
return self.convoys.get(name, None)
|
||||
|
||||
@ -1697,7 +1697,7 @@ class BaiIngressBuilder(PydcsWaypointBuilder):
|
||||
if isinstance(target_group, TheaterGroundObject):
|
||||
group_name = target_group.group_name
|
||||
elif isinstance(target_group, Convoy):
|
||||
group_name = target_group.transfer.name
|
||||
group_name = target_group.name
|
||||
else:
|
||||
logging.error(
|
||||
"Unexpected target type for BAI mission: %s",
|
||||
|
||||
@ -10,7 +10,7 @@ from dcs.unit import Vehicle
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import Convoy, RoadTransferOrder
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import kph
|
||||
|
||||
@ -27,25 +27,23 @@ class ConvoyGenerator:
|
||||
|
||||
def generate(self) -> None:
|
||||
# Reset the count to make generation deterministic.
|
||||
for transfer in self.game.transfers:
|
||||
self.generate_convoy_for(transfer)
|
||||
|
||||
def generate_convoy_for(self, transfer: RoadTransferOrder) -> VehicleGroup:
|
||||
next_hop = transfer.path()[0]
|
||||
origin = transfer.position.convoy_spawns[next_hop]
|
||||
destination = next_hop.convoy_spawns[transfer.position]
|
||||
for convoy in self.game.transfers.convoys:
|
||||
self.generate_convoy(convoy)
|
||||
|
||||
def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
|
||||
group = self._create_mixed_unit_group(
|
||||
transfer.name,
|
||||
origin,
|
||||
transfer.units,
|
||||
transfer.player,
|
||||
convoy.name,
|
||||
convoy.origin.position,
|
||||
convoy.units,
|
||||
convoy.player_owned,
|
||||
)
|
||||
group.add_waypoint(
|
||||
destination, speed=kph(40).kph, move_formation=PointAction.OnRoad
|
||||
convoy.destination.position,
|
||||
speed=kph(40).kph,
|
||||
move_formation=PointAction.OnRoad,
|
||||
)
|
||||
self.make_drivable(group)
|
||||
self.unit_map.add_convoy_units(group, transfer)
|
||||
self.unit_map.add_convoy_units(group, convoy)
|
||||
return group
|
||||
|
||||
def _create_mixed_unit_group(
|
||||
|
||||
@ -333,7 +333,7 @@ class NameGenerator:
|
||||
@classmethod
|
||||
def next_convoy_name(cls) -> str:
|
||||
cls.convoy_number += 1
|
||||
return f"Convoy {cls.convoy_number:04}"
|
||||
return f"Convoy {cls.convoy_number:03}"
|
||||
|
||||
@classmethod
|
||||
def random_objective_name(cls):
|
||||
|
||||
@ -44,7 +44,7 @@ from game.theater.conflicttheater import FrontLine, ReferencePoint
|
||||
from game.theater.theatergroundobject import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import Convoy, RoadTransferOrder
|
||||
from game.utils import Distance, meters, nautical_miles
|
||||
from game.weather import TimeOfDay
|
||||
from gen import Conflict, Package
|
||||
@ -827,7 +827,7 @@ class QLiberationMap(QGraphicsView):
|
||||
self,
|
||||
scene: QGraphicsScene,
|
||||
frontline: FrontLine,
|
||||
convoys: List[RoadTransferOrder],
|
||||
convoys: List[Convoy],
|
||||
) -> None:
|
||||
"""
|
||||
Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from.
|
||||
@ -895,7 +895,15 @@ class QLiberationMap(QGraphicsView):
|
||||
def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None:
|
||||
scene = self.scene()
|
||||
|
||||
convoys = self._transfers_between(a, b)
|
||||
convoy_map = self.game.transfers.convoys
|
||||
convoys = []
|
||||
convoy = convoy_map.find_convoy(a, b)
|
||||
if convoy is not None:
|
||||
convoys.append(convoy)
|
||||
convoy = convoy_map.find_convoy(b, a)
|
||||
if convoy is not None:
|
||||
convoys.append(convoy)
|
||||
|
||||
frontline = FrontLine(a, b, self.game.theater)
|
||||
if a.front_is_active(b):
|
||||
if DisplayOptions.actual_frontline_pos:
|
||||
@ -909,7 +917,7 @@ class QLiberationMap(QGraphicsView):
|
||||
self,
|
||||
scene: QGraphicsScene,
|
||||
frontline: FrontLine,
|
||||
convoys: List[RoadTransferOrder],
|
||||
convoys: List[Convoy],
|
||||
) -> None:
|
||||
posx = frontline.position
|
||||
h = frontline.attack_heading
|
||||
@ -925,7 +933,7 @@ class QLiberationMap(QGraphicsView):
|
||||
self,
|
||||
scene: QGraphicsScene,
|
||||
frontline: FrontLine,
|
||||
convoys: List[RoadTransferOrder],
|
||||
convoys: List[Convoy],
|
||||
) -> None:
|
||||
self.draw_bezier_frontline(scene, frontline, convoys)
|
||||
vector = Conflict.frontline_vector(
|
||||
|
||||
@ -9,7 +9,7 @@ from PySide2.QtWidgets import (
|
||||
)
|
||||
|
||||
from game.theater import ControlPoint
|
||||
from game.transfers import RoadTransferOrder
|
||||
from game.transfers import Convoy
|
||||
from qt_ui.uiconstants import COLORS
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ class SupplyRouteSegment(QGraphicsLineItem):
|
||||
y1: float,
|
||||
control_point_a: ControlPoint,
|
||||
control_point_b: ControlPoint,
|
||||
convoys: List[RoadTransferOrder],
|
||||
convoys: List[Convoy],
|
||||
parent: Optional[QGraphicsItem] = None,
|
||||
) -> None:
|
||||
super().__init__(x0, y0, x1, y1, parent)
|
||||
@ -37,19 +37,18 @@ class SupplyRouteSegment(QGraphicsLineItem):
|
||||
def has_convoys(self) -> bool:
|
||||
return bool(self.convoys)
|
||||
|
||||
@cached_property
|
||||
def convoy_size(self) -> int:
|
||||
return sum(sum(c.units.values()) for c in self.convoys)
|
||||
|
||||
def make_tooltip(self) -> str:
|
||||
if not self.has_convoys:
|
||||
return "No convoys present on this supply route."
|
||||
units = "units" if self.convoy_size > 1 else "unit"
|
||||
|
||||
return (
|
||||
f"{self.convoy_size} {units} transferring between {self.control_point_a} "
|
||||
f"and {self.control_point_b}."
|
||||
)
|
||||
convoys = []
|
||||
for convoy in self.convoys:
|
||||
units = "units" if convoy.size > 1 else "unit"
|
||||
convoys.append(
|
||||
f"{convoy.size} {units} transferring from {convoy.origin} to "
|
||||
f"{convoy.destination}"
|
||||
)
|
||||
return "\n".join(convoys)
|
||||
|
||||
@property
|
||||
def line_color(self) -> QColor:
|
||||
|
||||
@ -12,15 +12,15 @@ from PySide2.QtWidgets import (
|
||||
|
||||
from game import db
|
||||
from game.theater import ControlPoint
|
||||
from game.transfers import Convoy, RoadTransferOrder
|
||||
from game.transfers import Convoy
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.uiconstants import VEHICLES_ICONS
|
||||
|
||||
|
||||
class DepartingConvoyInfo(QGroupBox):
|
||||
def __init__(self, convoy: RoadTransferOrder, game_model: GameModel) -> None:
|
||||
super().__init__(f"To {convoy.destination}")
|
||||
def __init__(self, convoy: Convoy, game_model: GameModel) -> None:
|
||||
super().__init__(f"{convoy.name} to {convoy.destination}")
|
||||
self.convoy = convoy
|
||||
|
||||
main_layout = QVBoxLayout()
|
||||
@ -61,7 +61,7 @@ class DepartingConvoyInfo(QGroupBox):
|
||||
# complicated. We could instead generate this at the start of the turn (and
|
||||
# update whenever transfers are created or canceled) and also use that time to
|
||||
# precalculate things like the next stop and group names.
|
||||
Dialog.open_new_package_dialog(Convoy(self.convoy), parent=self.window())
|
||||
Dialog.open_new_package_dialog(self.convoy, parent=self.window())
|
||||
|
||||
|
||||
class DepartingConvoysList(QFrame):
|
||||
@ -78,9 +78,8 @@ class DepartingConvoysList(QFrame):
|
||||
task_box_layout = QGridLayout()
|
||||
scroll_content.setLayout(task_box_layout)
|
||||
|
||||
for convoy in game_model.game.transfers:
|
||||
if convoy.position != cp:
|
||||
continue
|
||||
convoy_map = game_model.game.transfers.convoys
|
||||
for convoy in convoy_map.departing_from(cp):
|
||||
group_info = DepartingConvoyInfo(convoy, game_model)
|
||||
task_box_layout.addWidget(group_info)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user