Eclipse/Druss99 31c80dfd02 refactor of previous commits
refactor to enum

typing and many other fixes

fix tests

attempt to fix some typescript

more typescript fixes

more typescript test fixes

revert all API changes

update to pydcs

mypy fixes

Use properties to check if player is blue/red/neutral

update requirements.txt

black -_-

bump pydcs and fix mypy

add opponent property

bump pydcs
2025-10-19 19:34:38 +02:00

170 lines
6.4 KiB
Python

from __future__ import annotations
import itertools
from collections import defaultdict
from typing import Iterator, Optional, Sequence, TYPE_CHECKING
from game.ato.closestairfields import ObjectiveDistanceCache
from game.dcs.aircrafttype import AircraftType
from .squadrondefloader import SquadronDefLoader
from ..campaignloader.squadrondefgenerator import SquadronDefGenerator
from ..factions.faction import Faction
from ..theater import ControlPoint, MissionTarget
from ..utils import Distance
if TYPE_CHECKING:
from game.game import Game
from game.theater.player import Player
from ..ato.flighttype import FlightType
from .squadron import Squadron
class AirWing:
def __init__(self, player: Player, game: Game, faction: Faction) -> None:
self.player = player
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
self.squadron_defs = SquadronDefLoader(game, faction).load()
self.squadron_def_generator = SquadronDefGenerator(faction)
self.settings = game.settings
def unclaim_squadron_def(self, squadron: Squadron) -> None:
if squadron.aircraft in self.squadron_defs:
for squadron_def in self.squadron_defs[squadron.aircraft]:
if squadron_def.claimed and squadron_def.name == squadron.name:
squadron_def.claimed = False
def add_squadron(self, squadron: Squadron) -> None:
self.squadrons[squadron.aircraft].append(squadron)
def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]:
return self.squadrons[aircraft]
def can_auto_plan(self, task: FlightType) -> bool:
try:
next(self.auto_assignable_for_task(task))
return True
except StopIteration:
return False
def best_squadrons_for(
self,
location: MissionTarget,
task: FlightType,
size: int,
heli: bool,
this_turn: bool,
preferred_type: Optional[AircraftType] = None,
ignore_range: bool = False,
) -> list[Squadron]:
airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location)
best_aircraft = AircraftType.priority_list_for_task(task)
ordered: list[Squadron] = []
for control_point in airfield_cache.operational_airfields:
if control_point.captured != self.player:
continue
capable_at_base = []
squadrons = [
s
for s in control_point.squadrons
if not preferred_type
or s.aircraft.variant_id == preferred_type.variant_id
]
for squadron in squadrons:
if squadron.can_auto_assign_mission(
location, task, size, heli, this_turn, ignore_range
):
capable_at_base.append(squadron)
if squadron.aircraft not in best_aircraft:
# If it is not already in the list it should be the last one
best_aircraft.append(squadron.aircraft)
ordered.extend(
sorted(
capable_at_base,
key=lambda s: best_aircraft.index(s.aircraft),
)
)
return sorted(
ordered,
key=lambda s: (
# This looks like the opposite of what we want because False sorts
# before True. Distance is also added,
# i.e. 75NM with primary task match is similar to non-primary with 0NM to target
int(s.primary_task != task)
+ Distance.from_meters(s.location.distance_to(location)).nautical_miles
/ self.settings.primary_task_distance_factor
+ best_aircraft.index(s.aircraft) / len(best_aircraft),
),
)
def best_squadron_for(
self,
location: MissionTarget,
task: FlightType,
size: int,
heli: bool,
this_turn: bool,
preferred_type: Optional[AircraftType] = None,
ignore_range: bool = False,
) -> Optional[Squadron]:
for squadron in self.best_squadrons_for(
location, task, size, heli, this_turn, preferred_type, ignore_range
):
return squadron
return None
def best_available_aircrafts_for(self, task: FlightType) -> list[AircraftType]:
"""Returns an ordered list of available aircrafts for the given task"""
aircrafts = []
best_aircraft_for_task = AircraftType.priority_list_for_task(task)
for aircraft, squadrons in self.squadrons.items():
for squadron in squadrons:
if squadron.untasked_aircraft and squadron.capable_of(task):
aircrafts.append(aircraft)
if aircraft not in best_aircraft_for_task:
best_aircraft_for_task.append(aircraft)
break
# Sort the list ordered by the best capability
return sorted(
aircrafts,
key=lambda ac: best_aircraft_for_task.index(ac),
)
def auto_assignable_for_task(self, task: FlightType) -> Iterator[Squadron]:
for squadron in self.iter_squadrons():
if squadron.can_auto_assign(task):
yield squadron
def auto_assignable_for_task_at(
self, task: FlightType, base: ControlPoint
) -> Iterator[Squadron]:
for squadron in self.iter_squadrons():
if squadron.can_auto_assign(task) and squadron.location == base:
yield squadron
def squadron_for(self, aircraft: AircraftType) -> Squadron:
return self.squadrons_for(aircraft)[0]
def iter_squadrons(self) -> Iterator[Squadron]:
return itertools.chain.from_iterable(self.squadrons.values())
def squadron_at_index(self, index: int) -> Squadron:
return list(self.iter_squadrons())[index]
def populate_for_turn_0(self, squadrons_start_full: bool) -> None:
for squadron in self.iter_squadrons():
squadron.populate_for_turn_0(squadrons_start_full)
def end_turn(self) -> None:
for squadron in self.iter_squadrons():
squadron.end_turn()
def reset(self) -> None:
for squadron in self.iter_squadrons():
squadron.return_all_pilots_and_aircraft()
@property
def size(self) -> int:
return sum(len(s) for s in self.squadrons.values())