Finish implementation of add/remove squadrons from AirWingConfigurationDialog

Users can now add and remove squadrons with specific buttons. this also allows new aircraft types to be added as well.

- rebased existing PR to develop
- reverted observable and changed to signals
- changed the general concept so that changes only affect the ui data model and not the game directly. Game will only be updated on apply
- removed unused code
- adopt to review comments
- allow user to choose a predefined squadron preset (also alow none value to use the random generator)
- Reuse the squadron defs from the default assigner in the AirWing class
- allow user to re-roll the squadron nickname (also added new ui icons for the button)
This commit is contained in:
RndName
2021-11-16 23:16:19 +01:00
parent e4ba9a8b72
commit d2f7785f9f
14 changed files with 342 additions and 382 deletions

View File

@@ -5,10 +5,8 @@ from typing import Optional, TYPE_CHECKING
from game.squadrons import Squadron
from game.squadrons.squadrondef import SquadronDef
from game.squadrons.squadrondefloader import SquadronDefLoader
from ..ato.flighttype import FlightType
from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig
from .squadrondefgenerator import SquadronDefGenerator
from ..dcs.aircrafttype import AircraftType
from ..theater import ControlPoint
@@ -25,14 +23,6 @@ class DefaultSquadronAssigner:
self.game = game
self.coalition = coalition
self.air_wing = coalition.air_wing
self.squadron_defs = SquadronDefLoader(game, coalition).load()
self.squadron_def_generator = SquadronDefGenerator(self.coalition)
def claim_squadron_def(self, squadron: SquadronDef) -> None:
try:
self.squadron_defs[squadron.aircraft].remove(squadron)
except ValueError:
pass
def assign(self) -> None:
for control_point in self.game.theater.control_points_for(
@@ -47,7 +37,6 @@ class DefaultSquadronAssigner:
)
continue
self.claim_squadron_def(squadron_def)
squadron = Squadron.create_from(
squadron_def, control_point, self.coalition, self.game
)
@@ -74,7 +63,7 @@ class DefaultSquadronAssigner:
# If we can't find any squadron matching the requirement, we should
# create one.
return self.squadron_def_generator.generate_for_task(
return self.air_wing.squadron_def_generator.generate_for_task(
config.primary, control_point
)
@@ -105,7 +94,7 @@ class DefaultSquadronAssigner:
# No premade squadron available for this aircraft that meets the requirements,
# so generate one if possible.
return self.squadron_def_generator.generate_for_aircraft(aircraft)
return self.air_wing.squadron_def_generator.generate_for_aircraft(aircraft)
@staticmethod
def squadron_compatible_with(
@@ -121,18 +110,24 @@ class DefaultSquadronAssigner:
def find_squadron_for_airframe(
self, aircraft: AircraftType, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadron in self.squadron_defs[aircraft]:
if self.squadron_compatible_with(squadron, task, control_point):
for squadron in self.air_wing.squadron_defs[aircraft]:
if not squadron.claimed and self.squadron_compatible_with(
squadron, task, control_point
):
return squadron
return None
def find_squadron_by_name(
self, name: str, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadrons in self.squadron_defs.values():
for squadrons in self.air_wing.squadron_defs.values():
for squadron in squadrons:
if squadron.name == name and self.squadron_compatible_with(
squadron, task, control_point, ignore_base_preference=True
if (
not squadron.claimed
and squadron.name == name
and self.squadron_compatible_with(
squadron, task, control_point, ignore_base_preference=True
)
):
return squadron
return None
@@ -140,8 +135,10 @@ class DefaultSquadronAssigner:
def find_squadron_for_task(
self, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
for squadrons in self.squadron_defs.values():
for squadrons in self.air_wing.squadron_defs.values():
for squadron in squadrons:
if self.squadron_compatible_with(squadron, task, control_point):
if not squadron.claimed and self.squadron_compatible_with(
squadron, task, control_point
):
return squadron
return None

View File

@@ -12,12 +12,12 @@ from gen.flights.ai_flight_planner_db import aircraft_for_task, tasks_for_aircra
from game.ato.flighttype import FlightType
if TYPE_CHECKING:
from game.coalition import Coalition
from game.factions.faction import Faction
class SquadronDefGenerator:
def __init__(self, coalition: Coalition) -> None:
self.coalition = coalition
def __init__(self, faction: Faction) -> None:
self.faction = faction
self.count = itertools.count(1)
self.used_nicknames: set[str] = set()
@@ -26,7 +26,7 @@ class SquadronDefGenerator:
) -> Optional[SquadronDef]:
aircraft_choice: Optional[AircraftType] = None
for aircraft in aircraft_for_task(task):
if aircraft not in self.coalition.faction.aircrafts:
if aircraft not in self.faction.aircrafts:
continue
if not control_point.can_operate(aircraft):
continue
@@ -44,7 +44,7 @@ class SquadronDefGenerator:
return SquadronDef(
name=f"Squadron {next(self.count):03}",
nickname=self.random_nickname(),
country=self.coalition.country_name,
country=self.faction.country,
role="Flying Squadron",
aircraft=aircraft,
livery=None,

View File

@@ -40,7 +40,7 @@ class Coalition:
self.procurement_requests: OrderedSet[AircraftProcurementRequest] = OrderedSet()
self.bullseye = Bullseye(Point(0, 0))
self.faker = Faker(self.faction.locales)
self.air_wing = AirWing(player)
self.air_wing = AirWing(player, game, self.faction)
self.transfers = PendingTransfers(game, player)
# Late initialized because the two coalitions in the game are mutually

View File

@@ -1,21 +0,0 @@
class Event(object):
pass
class Observable(object):
def __init__(self) -> None:
self.callbacks = []
def subscribe(self, callback) -> None:
self.callbacks.append(callback)
def unsubscribe(self, callback) -> None:
self.callbacks.remove(callback)
def fire(self, **attrs) -> None:
e = Event()
e.source = self
for k, v in attrs.items():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)

View File

@@ -7,41 +7,34 @@ from typing import Sequence, Iterator, TYPE_CHECKING, Optional
from game.dcs.aircrafttype import AircraftType
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache
from .squadrondef import SquadronDef
from .squadrondefloader import SquadronDefLoader
from ..campaignloader.squadrondefgenerator import SquadronDefGenerator
from ..factions.faction import Faction
from ..theater import ControlPoint, MissionTarget
from ..observer import Observable
if TYPE_CHECKING:
from game.game import Game
from ..ato.flighttype import FlightType
from .squadron import Squadron
class AirWing(Observable):
def __init__(self, player: bool) -> None:
super().__init__()
class AirWing:
def __init__(self, player: bool, 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)
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:
new_aircraft_type = squadron.aircraft not in self.squadrons
self.squadrons[squadron.aircraft].append(squadron)
if new_aircraft_type:
self.fire(type="add_aircraft_type", obj=squadron.aircraft)
self.fire(type="add_squadron", obj=squadron)
def remove_squadron(self, toRemove: Squadron) -> None:
if toRemove.aircraft in self.squadrons:
self.squadrons[toRemove.aircraft].remove(toRemove)
self.fire(type="remove_squadron", obj=toRemove)
if len(self.squadrons[toRemove.aircraft]) == 0:
self.remove_aircraft_type(toRemove.aircraft)
def remove_aircraft_type(self, toRemove: AircraftType) -> None:
self.squadrons.pop(toRemove)
self.fire(type="remove_aircraft_type", obj=toRemove)
def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]:
return self.squadrons[aircraft]

View File

@@ -418,6 +418,7 @@ class Squadron:
coalition: Coalition,
game: Game,
) -> Squadron:
squadron_def.claimed = True
return Squadron(
squadron_def.name,
squadron_def.nickname,

View File

@@ -28,6 +28,7 @@ class SquadronDef:
mission_types: tuple[FlightType, ...]
operating_bases: OperatingBases
pilot_pool: list[Pilot]
claimed: bool = False
auto_assignable_mission_types: set[FlightType] = field(
init=False, hash=False, compare=False

View File

@@ -10,13 +10,13 @@ from .squadrondef import SquadronDef
if TYPE_CHECKING:
from game import Game
from game.coalition import Coalition
from ..factions.faction import Faction
class SquadronDefLoader:
def __init__(self, game: Game, coalition: Coalition) -> None:
def __init__(self, game: Game, faction: Faction) -> None:
self.game = game
self.coalition = coalition
self.faction = faction
@staticmethod
def squadron_directories() -> Iterator[Path]:
@@ -27,8 +27,8 @@ class SquadronDefLoader:
def load(self) -> dict[AircraftType, list[SquadronDef]]:
squadrons: dict[AircraftType, list[SquadronDef]] = defaultdict(list)
country = self.coalition.country_name
faction = self.coalition.faction
country = self.faction.country
faction = self.faction
any_country = country.startswith("Combined Joint Task Forces ")
for directory in self.squadron_directories():
for path, squadron_def in self.load_squadrons_from(directory):