mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Initial squadrons implementation.
Doesn't actually do anything yet, but squadrons are created for each aircraft type and pilots will be created as needed to fill flights. https://github.com/dcs-liberation/dcs_liberation/issues/276
This commit is contained in:
@@ -1459,6 +1459,13 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
||||
return None
|
||||
|
||||
|
||||
def flying_type_from_name(name: str) -> Optional[Type[FlyingType]]:
|
||||
unit_type = plane_map.get(name)
|
||||
if unit_type is not None:
|
||||
return unit_type
|
||||
return helicopter_map.get(name)
|
||||
|
||||
|
||||
def unit_type_of(unit: Unit) -> UnitType:
|
||||
if isinstance(unit, Vehicle):
|
||||
return vehicle_map[unit.type]
|
||||
|
||||
@@ -27,6 +27,9 @@ from pydcs_extensions.mod_units import MODDED_VEHICLES, MODDED_AIRPLANES
|
||||
|
||||
@dataclass
|
||||
class Faction:
|
||||
#: List of locales to use when generating random names. If not set, Faker will
|
||||
#: choose the default locale.
|
||||
locales: Optional[List[str]]
|
||||
|
||||
# Country used by this faction
|
||||
country: str = field(default="")
|
||||
@@ -132,8 +135,7 @@ class Faction:
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
|
||||
|
||||
faction = Faction()
|
||||
faction = Faction(locales=json.get("locales"))
|
||||
|
||||
faction.country = json.get("country", "/")
|
||||
if faction.country not in [c.name for c in country_dict.values()]:
|
||||
|
||||
24
game/game.py
24
game/game.py
@@ -4,12 +4,13 @@ import random
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any, Dict, List, Iterator
|
||||
|
||||
from dcs.action import Coalition
|
||||
from dcs.mapping import Point
|
||||
from dcs.task import CAP, CAS, PinpointStrike
|
||||
from dcs.vehicles import AirDefence
|
||||
from faker import Faker
|
||||
|
||||
from game import db
|
||||
from game.inventory import GlobalAircraftInventory
|
||||
@@ -33,6 +34,7 @@ from .navmesh import NavMesh
|
||||
from .procurement import AircraftProcurementRequest, ProcurementAi
|
||||
from .profiling import logged_duration
|
||||
from .settings import Settings
|
||||
from .squadrons import Pilot, AirWing
|
||||
from .theater import ConflictTheater
|
||||
from .theater.bullseye import Bullseye
|
||||
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
|
||||
@@ -140,6 +142,12 @@ class Game:
|
||||
|
||||
self.sanitize_sides()
|
||||
|
||||
self.blue_faker = Faker(self.player_faction.locales)
|
||||
self.red_faker = Faker(self.enemy_faction.locales)
|
||||
|
||||
self.blue_air_wing = AirWing(self, player=True)
|
||||
self.red_air_wing = AirWing(self, player=False)
|
||||
|
||||
self.on_load()
|
||||
|
||||
def __getstate__(self) -> Dict[str, Any]:
|
||||
@@ -150,6 +158,8 @@ class Game:
|
||||
del state["red_threat_zone"]
|
||||
del state["blue_navmesh"]
|
||||
del state["red_navmesh"]
|
||||
del state["blue_faker"]
|
||||
del state["red_faker"]
|
||||
return state
|
||||
|
||||
def __setstate__(self, state: Dict[str, Any]) -> None:
|
||||
@@ -205,6 +215,16 @@ class Game:
|
||||
return self.player_faction
|
||||
return self.enemy_faction
|
||||
|
||||
def faker_for(self, player: bool) -> Faker:
|
||||
if player:
|
||||
return self.blue_faker
|
||||
return self.red_faker
|
||||
|
||||
def air_wing_for(self, player: bool) -> AirWing:
|
||||
if player:
|
||||
return self.blue_air_wing
|
||||
return self.red_air_wing
|
||||
|
||||
def country_for(self, player: bool) -> str:
|
||||
if player:
|
||||
return self.player_country
|
||||
@@ -286,6 +306,8 @@ class Game:
|
||||
ObjectiveDistanceCache.set_theater(self.theater)
|
||||
self.compute_conflicts_position()
|
||||
self.compute_threat_zones()
|
||||
self.blue_faker = Faker(self.faction_for(player=True).locales)
|
||||
self.red_faker = Faker(self.faction_for(player=False).locales)
|
||||
|
||||
def reset_ato(self) -> None:
|
||||
self.blue_ato.clear()
|
||||
|
||||
145
game/squadrons.py
Normal file
145
game/squadrons.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Type, Tuple, List, TYPE_CHECKING, Optional, Iterable
|
||||
|
||||
import yaml
|
||||
from dcs.unittype import FlyingType
|
||||
from faker import Faker
|
||||
|
||||
from game.db import flying_type_from_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
|
||||
@dataclass
|
||||
class PilotRecord:
|
||||
missions_flown: int = field(default=0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Pilot:
|
||||
name: str
|
||||
player: bool = field(default=False)
|
||||
alive: bool = field(default=True)
|
||||
record: PilotRecord = field(default_factory=PilotRecord)
|
||||
|
||||
@classmethod
|
||||
def random(cls, faker: Faker) -> Pilot:
|
||||
return Pilot(faker.name())
|
||||
|
||||
|
||||
@dataclass
|
||||
class Squadron:
|
||||
name: str
|
||||
nickname: str
|
||||
role: str
|
||||
aircraft: Type[FlyingType]
|
||||
mission_types: Tuple[FlightType, ...]
|
||||
pilots: List[Pilot]
|
||||
available_pilots: List[Pilot] = field(init=False, hash=False, compare=False)
|
||||
|
||||
# We need a reference to the Game so that we can access the Faker without needing to
|
||||
# persist it to the save game, or having to reconstruct it (it's not cheap) each
|
||||
# time we create or load a squadron.
|
||||
game: Game = field(hash=False, compare=False)
|
||||
player: bool
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.available_pilots = list(self.pilots)
|
||||
|
||||
def claim_available_pilot(self) -> Optional[Pilot]:
|
||||
if not self.available_pilots:
|
||||
self.enlist_new_pilots(1)
|
||||
return self.available_pilots.pop()
|
||||
|
||||
def claim_pilot(self, pilot: Pilot) -> None:
|
||||
if pilot not in self.available_pilots:
|
||||
raise ValueError(
|
||||
f"Cannot assign {pilot} to {self} because they are not available"
|
||||
)
|
||||
self.available_pilots.remove(pilot)
|
||||
|
||||
def return_pilot(self, pilot: Pilot) -> None:
|
||||
self.available_pilots.append(pilot)
|
||||
|
||||
def return_pilots(self, pilots: Iterable[Pilot]) -> None:
|
||||
self.available_pilots.extend(pilots)
|
||||
|
||||
def enlist_new_pilots(self, count: int) -> None:
|
||||
new_pilots = [Pilot(self.faker.name()) for _ in range(count)]
|
||||
self.pilots.extend(new_pilots)
|
||||
self.available_pilots.extend(new_pilots)
|
||||
|
||||
def return_all_pilots(self) -> None:
|
||||
self.available_pilots = self.pilots
|
||||
|
||||
@property
|
||||
def faker(self) -> Faker:
|
||||
return self.game.faker_for(self.player)
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return len(self.pilots)
|
||||
|
||||
def pilot_at_index(self, index: int) -> Pilot:
|
||||
return self.pilots[index]
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, path: Path, game: Game, player: bool) -> Squadron:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
with path.open() as squadron_file:
|
||||
data = yaml.load(squadron_file)
|
||||
|
||||
unit_type = flying_type_from_name(data["aircraft"])
|
||||
if unit_type is None:
|
||||
raise KeyError(f"Could not find any aircraft with the ID {unit_type}")
|
||||
|
||||
return Squadron(
|
||||
name=data["name"],
|
||||
nickname=data["nickname"],
|
||||
role=data["role"],
|
||||
aircraft=unit_type,
|
||||
mission_types=tuple(FlightType.from_name(n) for n in data["mission_types"]),
|
||||
pilots=[Pilot(n) for n in data.get("pilots", [])],
|
||||
game=game,
|
||||
player=player,
|
||||
)
|
||||
|
||||
|
||||
class AirWing:
|
||||
def __init__(self, game: Game, player: bool) -> None:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
self.game = game
|
||||
self.player = player
|
||||
self.squadrons = {
|
||||
aircraft: [
|
||||
Squadron(
|
||||
name=f"Squadron {num + 1:03}",
|
||||
nickname="The Unremarkable",
|
||||
role="Flying Squadron",
|
||||
aircraft=aircraft,
|
||||
mission_types=tuple(FlightType),
|
||||
pilots=[],
|
||||
game=game,
|
||||
player=player,
|
||||
)
|
||||
]
|
||||
for num, aircraft in enumerate(game.faction_for(player).aircrafts)
|
||||
}
|
||||
|
||||
def squadron_for(self, aircraft: Type[FlyingType]) -> Squadron:
|
||||
return self.squadrons[aircraft][0]
|
||||
|
||||
def squadron_at_index(self, index: int) -> Squadron:
|
||||
return list(itertools.chain.from_iterable(self.squadrons.values()))[index]
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return sum(len(s) for s in self.squadrons.values())
|
||||
@@ -251,10 +251,11 @@ class AirliftPlanner:
|
||||
else:
|
||||
transfer = self.transfer
|
||||
|
||||
player = inventory.control_point.captured
|
||||
flight = Flight(
|
||||
self.package,
|
||||
self.game.country_for(inventory.control_point.captured),
|
||||
unit_type,
|
||||
self.game.country_for(player),
|
||||
self.game.air_wing_for(player).squadron_for(unit_type),
|
||||
flight_size,
|
||||
FlightType.TRANSPORT,
|
||||
self.game.settings.default_start_type,
|
||||
|
||||
Reference in New Issue
Block a user