Factor out Coalition from Game.

This commit is contained in:
Dan Albert 2021-07-12 16:10:12 -07:00
parent 4534758c21
commit 17c19d453b
34 changed files with 471 additions and 437 deletions

215
game/coalition.py Normal file
View File

@ -0,0 +1,215 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional
from dcs import Point
from faker import Faker
from game.commander import TheaterCommander
from game.income import Income
from game.navmesh import NavMesh
from game.profiling import logged_duration, MultiEventTracer
from game.threatzones import ThreatZones
from game.transfers import PendingTransfers
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
if TYPE_CHECKING:
from game import Game
from game.data.doctrine import Doctrine
from game.factions.faction import Faction
from game.procurement import AircraftProcurementRequest, ProcurementAi
from game.squadrons import AirWing
from game.theater.bullseye import Bullseye
from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from gen import AirTaskingOrder
class Coalition:
def __init__(
self, game: Game, faction: Faction, budget: float, player: bool
) -> None:
self.game = game
self.player = player
self.faction = faction
self.budget = budget
self.ato = AirTaskingOrder()
self.transit_network = TransitNetwork()
self.procurement_requests: list[AircraftProcurementRequest] = []
self.bullseye = Bullseye(Point(0, 0))
self.faker = Faker(self.faction.locales)
self.air_wing = AirWing(game, self)
self.transfers = PendingTransfers(game, player)
# Late initialized because the two coalitions in the game are mutually
# dependent, so must be both constructed before this property can be set.
self._opponent: Optional[Coalition] = None
# Volatile properties that are not persisted to the save file since they can be
# recomputed on load. Keeping this data out of the save file makes save compat
# breaks less frequent. Each of these properties has a non-underscore-prefixed
# @property that should be used for non-Optional access.
#
# All of these are late-initialized (whether via on_load or called later), but
# will be non-None after the game has finished loading.
self._threat_zone: Optional[ThreatZones] = None
self._navmesh: Optional[NavMesh] = None
self.on_load()
@property
def doctrine(self) -> Doctrine:
return self.faction.doctrine
@property
def coalition_id(self) -> int:
if self.player:
return 2
return 1
@property
def country_name(self) -> str:
return self.faction.country
@property
def opponent(self) -> Coalition:
assert self._opponent is not None
return self._opponent
@property
def threat_zone(self) -> ThreatZones:
assert self._threat_zone is not None
return self._threat_zone
@property
def nav_mesh(self) -> NavMesh:
assert self._navmesh is not None
return self._navmesh
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
del state["_threat_zone"]
del state["_navmesh"]
del state["faker"]
return state
def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state)
# Regenerate any state that was not persisted.
self.on_load()
def on_load(self) -> None:
self.faker = Faker(self.faction.locales)
def set_opponent(self, opponent: Coalition) -> None:
if self._opponent is not None:
raise RuntimeError("Double-initialization of Coalition.opponent")
self._opponent = opponent
def adjust_budget(self, amount: float) -> None:
self.budget += amount
def compute_threat_zones(self) -> None:
self._threat_zone = ThreatZones.for_faction(self.game, self.player)
def compute_nav_meshes(self) -> None:
self._navmesh = NavMesh.from_threat_zones(
self.opponent.threat_zone, self.game.theater
)
def update_transit_network(self) -> None:
self.transit_network = TransitNetworkBuilder(
self.game.theater, self.player
).build()
def set_bullseye(self, bullseye: Bullseye) -> None:
self.bullseye = bullseye
def end_turn(self) -> None:
"""Processes coalition-specific turn finalization.
For more information on turn finalization in general, see the documentation for
`Game.finish_turn`.
"""
self.air_wing.replenish()
self.budget += Income(self.game, self.player).total
# Need to recompute before transfers and deliveries to account for captures.
# This happens in in initialize_turn as well, because cheating doesn't advance a
# turn but can capture bases so we need to recompute there as well.
self.update_transit_network()
# Must happen *before* unit deliveries are handled, or else new units will spawn
# one hop ahead. ControlPoint.process_turn handles unit deliveries. The
# coalition-specific turn-end happens before the theater-wide turn-end, so this
# is handled correctly.
self.transfers.perform_transfers()
def initialize_turn(self) -> None:
"""Processes coalition-specific turn initialization.
For more information on turn initialization in general, see the documentation
for `Game.initialize_turn`.
"""
# Needs to happen *before* planning transfers so we don't cancel them.
self.ato.clear()
self.air_wing.reset()
self.refund_outstanding_orders()
self.procurement_requests.clear()
with logged_duration("Transit network identification"):
self.update_transit_network()
with logged_duration("Procurement of airlift assets"):
self.transfers.order_airlift_assets()
with logged_duration("Transport planning"):
self.transfers.plan_transports()
self.plan_missions()
self.plan_procurement()
def refund_outstanding_orders(self) -> None:
# TODO: Split orders between air and ground units.
# This isn't quite right. If the player has ground purchases automated we should
# be refunding the ground units, and if they have air automated but not ground
# we should be refunding air units.
if self.player and not self.game.settings.automate_aircraft_reinforcements:
return
for cp in self.game.theater.control_points_for(self.player):
cp.pending_unit_deliveries.refund_all(self)
def plan_missions(self) -> None:
color = "Blue" if self.player else "Red"
with MultiEventTracer() as tracer:
mission_planner = CoalitionMissionPlanner(self.game, self.player)
with tracer.trace(f"{color} mission planning"):
with tracer.trace(f"{color} mission identification"):
commander = TheaterCommander(self.game, self.player)
commander.plan_missions(mission_planner, tracer)
with tracer.trace(f"{color} mission fulfillment"):
mission_planner.fulfill_missions()
def plan_procurement(self) -> None:
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it gets much
# more of the budget that turn. Otherwise budget (after repairs) is split evenly
# between air and ground. For the default starting budget of 2000 this gives 600
# to ground forces and 1400 to aircraft. After that the budget will be spent
# proportionally based on how much is already invested.
if self.player:
manage_runways = self.game.settings.automate_runway_repair
manage_front_line = self.game.settings.automate_front_line_reinforcements
manage_aircraft = self.game.settings.automate_aircraft_reinforcements
else:
manage_runways = False
manage_front_line = False
manage_aircraft = False
self.budget = ProcurementAi(
self.game,
self.player,
self.faction,
manage_runways,
manage_front_line,
manage_aircraft,
).spend_budget(self.budget)

View File

@ -14,9 +14,9 @@ from game.theater import (
Airfield, Airfield,
) )
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
NavalGroundObject,
BuildingGroundObject, BuildingGroundObject,
IadsGroundObject, IadsGroundObject,
NavalGroundObject,
) )
from game.transfers import CargoShip, Convoy from game.transfers import CargoShip, Convoy
from game.utils import meters, nautical_miles from game.utils import meters, nautical_miles
@ -163,13 +163,17 @@ class ObjectiveFinder:
def convoys(self) -> Iterator[Convoy]: def convoys(self) -> Iterator[Convoy]:
for front_line in self.front_lines(): for front_line in self.front_lines():
yield from self.game.transfers.convoys.travelling_to( yield from self.game.coalition_for(
self.is_player
).transfers.convoys.travelling_to(
front_line.control_point_hostile_to(self.is_player) front_line.control_point_hostile_to(self.is_player)
) )
def cargo_ships(self) -> Iterator[CargoShip]: def cargo_ships(self) -> Iterator[CargoShip]:
for front_line in self.front_lines(): for front_line in self.front_lines():
yield from self.game.transfers.cargo_ships.travelling_to( yield from self.game.coalition_for(
self.is_player
).transfers.cargo_ships.travelling_to(
front_line.control_point_hostile_to(self.is_player) front_line.control_point_hostile_to(self.is_player)
) )

View File

@ -6,7 +6,7 @@ from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine from game.data.doctrine import Doctrine
from game.theater import NavalGroundObject from game.theater.theatergroundobject import NavalGroundObject
from gen.flights.flight import FlightType from gen.flights.flight import FlightType

View File

@ -18,7 +18,6 @@ from typing import (
Union, Union,
) )
from game import db
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.theater import Airfield, ControlPoint from game.theater import Airfield, ControlPoint
@ -136,10 +135,8 @@ class Debriefing:
self.game = game self.game = game
self.unit_map = unit_map self.unit_map = unit_map
self.player_country = game.player_country self.player_country = game.blue.country_name
self.enemy_country = game.enemy_country self.enemy_country = game.red.country_name
self.player_country_id = db.country_id_from_name(game.player_country)
self.enemy_country_id = db.country_id_from_name(game.enemy_country)
self.air_losses = self.dead_aircraft() self.air_losses = self.dead_aircraft()
self.ground_losses = self.dead_ground_units() self.ground_losses = self.dead_ground_units()

View File

@ -53,7 +53,7 @@ class Event:
@property @property
def is_player_attacking(self) -> bool: def is_player_attacking(self) -> bool:
return self.attacker_name == self.game.player_faction.name return self.attacker_name == self.game.blue.faction.name
@property @property
def tasks(self) -> List[Type[Task]]: def tasks(self) -> List[Type[Task]]:
@ -114,10 +114,10 @@ class Event:
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None: def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
self._transfer_aircraft( self._transfer_aircraft(
self.game.blue_ato, debriefing.air_losses, for_player=True self.game.blue.ato, debriefing.air_losses, for_player=True
) )
self._transfer_aircraft( self._transfer_aircraft(
self.game.red_ato, debriefing.air_losses, for_player=False self.game.red.ato, debriefing.air_losses, for_player=False
) )
def commit_air_losses(self, debriefing: Debriefing) -> None: def commit_air_losses(self, debriefing: Debriefing) -> None:
@ -154,8 +154,8 @@ class Event:
pilot.record.missions_flown += 1 pilot.record.missions_flown += 1
def commit_pilot_experience(self) -> None: def commit_pilot_experience(self) -> None:
self._commit_pilot_experience(self.game.blue_ato) self._commit_pilot_experience(self.game.blue.ato)
self._commit_pilot_experience(self.game.red_ato) self._commit_pilot_experience(self.game.red.ato)
@staticmethod @staticmethod
def commit_front_line_losses(debriefing: Debriefing) -> None: def commit_front_line_losses(debriefing: Debriefing) -> None:

View File

@ -1,13 +1,11 @@
import itertools import itertools
import logging import logging
import math import math
import random from collections import Iterator
import sys
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from enum import Enum from enum import Enum
from typing import Any, List, Type, Union, cast from typing import Any, List, Type, Union, cast
from dcs.action import Coalition
from dcs.mapping import Point from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
@ -19,28 +17,25 @@ from game.plugins import LuaPluginManager
from gen import naming from gen import naming
from gen.ato import AirTaskingOrder from gen.ato import AirTaskingOrder
from gen.conflictgen import Conflict from gen.conflictgen import Conflict
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
from gen.ground_forces.ai_ground_planner import GroundPlanner from gen.ground_forces.ai_ground_planner import GroundPlanner
from . import persistency from . import persistency
from .commander import TheaterCommander from .coalition import Coalition
from .debriefing import Debriefing from .debriefing import Debriefing
from .event.event import Event from .event.event import Event
from .event.frontlineattack import FrontlineAttackEvent from .event.frontlineattack import FrontlineAttackEvent
from .factions.faction import Faction from .factions.faction import Faction
from .income import Income
from .infos.information import Information from .infos.information import Information
from .navmesh import NavMesh from .navmesh import NavMesh
from .procurement import AircraftProcurementRequest, ProcurementAi from .procurement import AircraftProcurementRequest
from .profiling import logged_duration, MultiEventTracer from .profiling import logged_duration
from .settings import Settings, AutoAtoBehavior from .settings import Settings
from .squadrons import AirWing from .squadrons import AirWing
from .theater import ConflictTheater, ControlPoint from .theater import ConflictTheater, ControlPoint
from .theater.bullseye import Bullseye from .theater.bullseye import Bullseye
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .threatzones import ThreatZones from .threatzones import ThreatZones
from .transfers import PendingTransfers
from .unitmap import UnitMap from .unitmap import UnitMap
from .weather import Conditions, TimeOfDay from .weather import Conditions, TimeOfDay
@ -98,10 +93,6 @@ class Game:
self.settings = settings self.settings = settings
self.events: List[Event] = [] self.events: List[Event] = []
self.theater = theater self.theater = theater
self.player_faction = player_faction
self.player_country = player_faction.country
self.enemy_faction = enemy_faction
self.enemy_country = enemy_faction.country
# pass_turn() will be called when initialization is complete which will # pass_turn() will be called when initialization is complete which will
# increment this to turn 0 before it reaches the player. # increment this to turn 0 before it reaches the player.
self.turn = -1 self.turn = -1
@ -124,108 +115,70 @@ class Game:
self.conditions = self.generate_conditions() self.conditions = self.generate_conditions()
self.blue_transit_network = TransitNetwork() self.sanitize_sides(player_faction, enemy_faction)
self.red_transit_network = TransitNetwork() self.blue = Coalition(self, player_faction, player_budget, player=True)
self.red = Coalition(self, enemy_faction, enemy_budget, player=False)
self.blue_procurement_requests: List[AircraftProcurementRequest] = [] self.blue.set_opponent(self.red)
self.red_procurement_requests: List[AircraftProcurementRequest] = [] self.red.set_opponent(self.blue)
self.blue_ato = AirTaskingOrder()
self.red_ato = AirTaskingOrder()
self.blue_bullseye = Bullseye(Point(0, 0))
self.red_bullseye = Bullseye(Point(0, 0))
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
self.transfers = PendingTransfers(self)
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(game_still_initializing=True) self.on_load(game_still_initializing=True)
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
del state["blue_threat_zone"]
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: def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state) self.__dict__.update(state)
# Regenerate any state that was not persisted. # Regenerate any state that was not persisted.
self.on_load() self.on_load()
@property
def coalitions(self) -> Iterator[Coalition]:
yield self.blue
yield self.red
def ato_for(self, player: bool) -> AirTaskingOrder: def ato_for(self, player: bool) -> AirTaskingOrder:
if player: return self.coalition_for(player).ato
return self.blue_ato
return self.red_ato
def procurement_requests_for( def procurement_requests_for(
self, player: bool self, player: bool
) -> List[AircraftProcurementRequest]: ) -> list[AircraftProcurementRequest]:
if player: return self.coalition_for(player).procurement_requests
return self.blue_procurement_requests
return self.red_procurement_requests
def transit_network_for(self, player: bool) -> TransitNetwork: def transit_network_for(self, player: bool) -> TransitNetwork:
if player: return self.coalition_for(player).transit_network
return self.blue_transit_network
return self.red_transit_network
def generate_conditions(self) -> Conditions: def generate_conditions(self) -> Conditions:
return Conditions.generate( return Conditions.generate(
self.theater, self.current_day, self.current_turn_time_of_day, self.settings self.theater, self.current_day, self.current_turn_time_of_day, self.settings
) )
def sanitize_sides(self) -> None: @staticmethod
def sanitize_sides(player_faction: Faction, enemy_faction: Faction) -> None:
""" """
Make sure the opposing factions are using different countries Make sure the opposing factions are using different countries
:return: :return:
""" """
if self.player_country == self.enemy_country: if player_faction.country == enemy_faction.country:
if self.player_country == "USA": if player_faction.country == "USA":
self.enemy_country = "USAF Aggressors" enemy_faction.country = "USAF Aggressors"
elif self.player_country == "Russia": elif player_faction.country == "Russia":
self.enemy_country = "USSR" enemy_faction.country = "USSR"
else: else:
self.enemy_country = "Russia" enemy_faction.country = "Russia"
def faction_for(self, player: bool) -> Faction: def faction_for(self, player: bool) -> Faction:
if player: return self.coalition_for(player).faction
return self.player_faction
return self.enemy_faction
def faker_for(self, player: bool) -> Faker: def faker_for(self, player: bool) -> Faker:
if player: return self.coalition_for(player).faker
return self.blue_faker
return self.red_faker
def air_wing_for(self, player: bool) -> AirWing: def air_wing_for(self, player: bool) -> AirWing:
if player: return self.coalition_for(player).air_wing
return self.blue_air_wing
return self.red_air_wing
def country_for(self, player: bool) -> str: def country_for(self, player: bool) -> str:
if player: return self.coalition_for(player).country_name
return self.player_country
return self.enemy_country
def bullseye_for(self, player: bool) -> Bullseye: def bullseye_for(self, player: bool) -> Bullseye:
if player: return self.coalition_for(player).bullseye
return self.blue_bullseye
return self.red_bullseye
def _generate_player_event( def _generate_player_event(
self, event_class: Type[Event], player_cp: ControlPoint, enemy_cp: ControlPoint self, event_class: Type[Event], player_cp: ControlPoint, enemy_cp: ControlPoint
@ -236,8 +189,8 @@ class Game:
player_cp, player_cp,
enemy_cp, enemy_cp,
enemy_cp.position, enemy_cp.position,
self.player_faction.name, self.blue.faction.name,
self.enemy_faction.name, self.red.faction.name,
) )
) )
@ -249,20 +202,13 @@ class Game:
front_line.red_cp, front_line.red_cp,
) )
def adjust_budget(self, amount: float, player: bool) -> None: def coalition_for(self, player: bool) -> Coalition:
if player: if player:
self.budget += amount return self.blue
else: return self.red
self.enemy_budget += amount
def process_player_income(self) -> None: def adjust_budget(self, amount: float, player: bool) -> None:
self.budget += Income(self, player=True).total self.coalition_for(player).adjust_budget(amount)
def process_enemy_income(self) -> None:
# TODO: Clean up save compat.
if not hasattr(self, "enemy_budget"):
self.enemy_budget = 0
self.enemy_budget += Income(self, player=False).total
@staticmethod @staticmethod
def initiate_event(event: Event) -> UnitMap: def initiate_event(event: Event) -> UnitMap:
@ -293,12 +239,6 @@ class Game:
self.compute_conflicts_position() self.compute_conflicts_position()
if not game_still_initializing: if not game_still_initializing:
self.compute_threat_zones() 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()
self.red_ato.clear()
def finish_turn(self, skipped: bool = False) -> None: def finish_turn(self, skipped: bool = False) -> None:
"""Finalizes the current turn and advances to the next turn. """Finalizes the current turn and advances to the next turn.
@ -333,23 +273,16 @@ class Game:
) )
self.turn += 1 self.turn += 1
# Need to recompute before transfers and deliveries to account for captures. # The coalition-specific turn finalization *must* happen before unit deliveries,
# This happens in in initialize_turn as well, because cheating doesn't advance a # since the coalition-specific finalization handles transit network updates and
# turn but can capture bases so we need to recompute there as well. # transfer processing. If in the other order, units may be delivered to captured
self.compute_transit_networks() # bases, and freshly delivered units will spawn one leg through their journey.
self.blue.end_turn()
self.red.end_turn()
# Must happen *before* unit deliveries are handled, or else new units will spawn
# one hop ahead. ControlPoint.process_turn handles unit deliveries.
self.transfers.perform_transfers()
# Needs to happen *before* planning transfers so we don't cancel them.
self.reset_ato()
for control_point in self.theater.controlpoints: for control_point in self.theater.controlpoints:
control_point.process_turn(self) control_point.process_turn(self)
self.blue_air_wing.replenish()
self.red_air_wing.replenish()
if not skipped: if not skipped:
for cp in self.theater.player_points(): for cp in self.theater.player_points():
cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY) cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY)
@ -360,9 +293,6 @@ class Game:
self.conditions = self.generate_conditions() self.conditions = self.generate_conditions()
self.process_enemy_income()
self.process_player_income()
def begin_turn_0(self) -> None: def begin_turn_0(self) -> None:
"""Initialization for the first turn of the game.""" """Initialization for the first turn of the game."""
self.turn = 0 self.turn = 0
@ -402,8 +332,8 @@ class Game:
def set_bullseye(self) -> None: def set_bullseye(self) -> None:
player_cp, enemy_cp = self.theater.closest_opposing_control_points() player_cp, enemy_cp = self.theater.closest_opposing_control_points()
self.blue_bullseye = Bullseye(enemy_cp.position) self.blue.bullseye = Bullseye(enemy_cp.position)
self.red_bullseye = Bullseye(player_cp.position) self.red.bullseye = Bullseye(player_cp.position)
def initialize_turn(self, for_red: bool = True, for_blue: bool = True) -> None: def initialize_turn(self, for_red: bool = True, for_blue: bool = True) -> None:
"""Performs turn initialization for the specified players. """Performs turn initialization for the specified players.
@ -451,13 +381,20 @@ class Game:
if turn_state in (TurnState.LOSS, TurnState.WIN): if turn_state in (TurnState.LOSS, TurnState.WIN):
return self.process_win_loss(turn_state) return self.process_win_loss(turn_state)
# Plan flights & combat for next turn
with logged_duration("Computing conflict positions"):
self.compute_conflicts_position()
with logged_duration("Threat zone computation"):
self.compute_threat_zones()
# Plan Coalition specific turn # Plan Coalition specific turn
if for_red:
self.initialize_turn_for(player=False)
if for_blue: if for_blue:
self.initialize_turn_for(player=True) self.initialize_turn_for(player=True)
if for_red:
self.initialize_turn_for(player=False)
# Plan GroundWar # Plan GroundWar
self.ground_planners = {}
for cp in self.theater.controlpoints: for cp in self.theater.controlpoints:
if cp.has_frontline: if cp.has_frontline:
gplanner = GroundPlanner(cp, self) gplanner = GroundPlanner(cp, self)
@ -465,83 +402,10 @@ class Game:
self.ground_planners[cp.id] = gplanner self.ground_planners[cp.id] = gplanner
def initialize_turn_for(self, player: bool) -> None: def initialize_turn_for(self, player: bool) -> None:
"""Processes coalition-specific turn initialization. self.aircraft_inventory.reset(player)
for cp in self.theater.control_points_for(player):
For more information on turn initialization in general, see the documentation
for `Game.initialize_turn`.
Args:
player: True if the player coalition is being initialized. False for opfor
initialization.
"""
self.ato_for(player).clear()
self.air_wing_for(player).reset()
self.aircraft_inventory.reset()
for cp in self.theater.controlpoints:
self.aircraft_inventory.set_from_control_point(cp) self.aircraft_inventory.set_from_control_point(cp)
# Refund all pending deliveries for opfor and if player self.coalition_for(player).initialize_turn()
# has automate_aircraft_reinforcements
if (not player and not cp.captured) or (
player
and cp.captured
and self.settings.automate_aircraft_reinforcements
):
cp.pending_unit_deliveries.refund_all(self)
# Plan flights & combat for next turn
with logged_duration("Computing conflict positions"):
self.compute_conflicts_position()
with logged_duration("Threat zone computation"):
self.compute_threat_zones()
with logged_duration("Transit network identification"):
self.compute_transit_networks()
self.ground_planners = {}
self.procurement_requests_for(player).clear()
with logged_duration("Procurement of airlift assets"):
self.transfers.order_airlift_assets()
with logged_duration("Transport planning"):
self.transfers.plan_transports()
color = "Blue" if player else "Red"
with MultiEventTracer() as tracer:
mission_planner = CoalitionMissionPlanner(self, player)
with tracer.trace(f"{color} mission planning"):
with tracer.trace(f"{color} mission identification"):
commander = TheaterCommander(self, player)
commander.plan_missions(mission_planner, tracer)
with tracer.trace(f"{color} mission fulfillment"):
mission_planner.fulfill_missions()
self.plan_procurement_for(player)
def plan_procurement_for(self, for_player: bool) -> None:
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it
# gets much more of the budget that turn. Otherwise budget (after
# repairs) is split evenly between air and ground. For the default
# starting budget of 2000 this gives 600 to ground forces and 1400 to
# aircraft. After that the budget will be spend proportionally based on how much is already invested
if for_player:
self.budget = ProcurementAi(
self,
for_player=True,
faction=self.player_faction,
manage_runways=self.settings.automate_runway_repair,
manage_front_line=self.settings.automate_front_line_reinforcements,
manage_aircraft=self.settings.automate_aircraft_reinforcements,
).spend_budget(self.budget)
else:
self.enemy_budget = ProcurementAi(
self,
for_player=False,
faction=self.enemy_faction,
manage_runways=True,
manage_front_line=True,
manage_aircraft=True,
).spend_budget(self.enemy_budget)
def message(self, text: str) -> None: def message(self, text: str) -> None:
self.informations.append(Information(text, turn=self.turn)) self.informations.append(Information(text, turn=self.turn))
@ -568,32 +432,20 @@ class Game:
self.current_group_id += 1 self.current_group_id += 1
return self.current_group_id return self.current_group_id
def compute_transit_networks(self) -> None:
self.blue_transit_network = self.compute_transit_network_for(player=True)
self.red_transit_network = self.compute_transit_network_for(player=False)
def compute_transit_network_for(self, player: bool) -> TransitNetwork: def compute_transit_network_for(self, player: bool) -> TransitNetwork:
return TransitNetworkBuilder(self.theater, player).build() return TransitNetworkBuilder(self.theater, player).build()
def compute_threat_zones(self) -> None: def compute_threat_zones(self) -> None:
self.blue_threat_zone = ThreatZones.for_faction(self, player=True) self.blue.compute_threat_zones()
self.red_threat_zone = ThreatZones.for_faction(self, player=False) self.red.compute_threat_zones()
self.blue_navmesh = NavMesh.from_threat_zones( self.blue.compute_nav_meshes()
self.red_threat_zone, self.theater self.red.compute_nav_meshes()
)
self.red_navmesh = NavMesh.from_threat_zones(
self.blue_threat_zone, self.theater
)
def threat_zone_for(self, player: bool) -> ThreatZones: def threat_zone_for(self, player: bool) -> ThreatZones:
if player: return self.coalition_for(player).threat_zone
return self.blue_threat_zone
return self.red_threat_zone
def navmesh_for(self, player: bool) -> NavMesh: def navmesh_for(self, player: bool) -> NavMesh:
if player: return self.coalition_for(player).nav_mesh
return self.blue_navmesh
return self.red_navmesh
def compute_conflicts_position(self) -> None: def compute_conflicts_position(self) -> None:
""" """
@ -636,7 +488,7 @@ class Game:
if cpoint is not None: if cpoint is not None:
zones.append(cpoint) zones.append(cpoint)
packages = itertools.chain(self.blue_ato.packages, self.red_ato.packages) packages = itertools.chain(self.blue.ato.packages, self.red.ato.packages)
for package in packages: for package in packages:
if package.primary_task is FlightType.BARCAP: if package.primary_task is FlightType.BARCAP:
# BARCAPs will be planned at most locations on smaller theaters, # BARCAPs will be planned at most locations on smaller theaters,
@ -682,25 +534,6 @@ class Game:
""" """
return self.__culling_zones return self.__culling_zones
# 1 = red, 2 = blue
def get_player_coalition_id(self) -> int:
return 2
def get_enemy_coalition_id(self) -> int:
return 1
def get_player_coalition(self) -> Coalition:
return Coalition.Blue
def get_enemy_coalition(self) -> Coalition:
return Coalition.Red
def get_player_color(self) -> str:
return "blue"
def get_enemy_color(self) -> str:
return "red"
def process_win_loss(self, turn_state: TurnState) -> None: def process_win_loss(self, turn_state: TurnState) -> None:
if turn_state is TurnState.WIN: if turn_state is TurnState.WIN:
self.message( self.message(

View File

@ -2,9 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING, Type from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING
from dcs.unittype import FlyingType
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from gen.flights.flight import Flight from gen.flights.flight import Flight
@ -86,9 +84,10 @@ class GlobalAircraftInventory:
cp: ControlPointAircraftInventory(cp) for cp in control_points cp: ControlPointAircraftInventory(cp) for cp in control_points
} }
def reset(self) -> None: def reset(self, for_player: bool) -> None:
"""Clears all control points and their inventories.""" """Clears the inventory of every control point owned by the given coalition."""
for inventory in self.inventories.values(): for inventory in self.inventories.values():
if inventory.control_point.captured == for_player:
inventory.clear() inventory.clear()
def set_from_control_point(self, control_point: ControlPoint) -> None: def set_from_control_point(self, control_point: ControlPoint) -> None:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from typing import Iterable, List, Set, TYPE_CHECKING, cast from typing import List, Set, TYPE_CHECKING, cast
from dcs import Mission from dcs import Mission
from dcs.action import DoScript, DoScriptFile from dcs.action import DoScript, DoScriptFile
@ -81,10 +81,10 @@ class Operation:
return Conflict( return Conflict(
cls.game.theater, cls.game.theater,
FrontLine(player_cp, enemy_cp), FrontLine(player_cp, enemy_cp),
cls.game.player_faction.name, cls.game.blue.faction.name,
cls.game.enemy_faction.name, cls.game.red.faction.name,
cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.blue.country_name),
cls.current_mission.country(cls.game.enemy_country), cls.current_mission.country(cls.game.red.country_name),
mid_point, mid_point,
) )
@ -95,14 +95,14 @@ class Operation:
@classmethod @classmethod
def _setup_mission_coalitions(cls) -> None: def _setup_mission_coalitions(cls) -> None:
cls.current_mission.coalition["blue"] = Coalition( cls.current_mission.coalition["blue"] = Coalition(
"blue", bullseye=cls.game.blue_bullseye.to_pydcs() "blue", bullseye=cls.game.blue.bullseye.to_pydcs()
) )
cls.current_mission.coalition["red"] = Coalition( cls.current_mission.coalition["red"] = Coalition(
"red", bullseye=cls.game.red_bullseye.to_pydcs() "red", bullseye=cls.game.red.bullseye.to_pydcs()
) )
p_country = cls.game.player_country p_country = cls.game.blue.country_name
e_country = cls.game.enemy_country e_country = cls.game.red.country_name
cls.current_mission.coalition["blue"].add_country( cls.current_mission.coalition["blue"].add_country(
country_dict[db.country_id_from_name(p_country)]() country_dict[db.country_id_from_name(p_country)]()
) )
@ -268,7 +268,7 @@ class Operation:
and cls.game.settings.perf_destroyed_units and cls.game.settings.perf_destroyed_units
): ):
cls.current_mission.static_group( cls.current_mission.static_group(
country=cls.current_mission.country(cls.game.player_country), country=cls.current_mission.country(cls.game.blue.country_name),
name="", name="",
_type=utype, _type=utype,
hidden=True, hidden=True,
@ -358,18 +358,18 @@ class Operation:
cls.airgen.clear_parking_slots() cls.airgen.clear_parking_slots()
cls.airgen.generate_flights( cls.airgen.generate_flights(
cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.blue.country_name),
cls.game.blue_ato, cls.game.blue.ato,
cls.groundobjectgen.runways, cls.groundobjectgen.runways,
) )
cls.airgen.generate_flights( cls.airgen.generate_flights(
cls.current_mission.country(cls.game.enemy_country), cls.current_mission.country(cls.game.red.country_name),
cls.game.red_ato, cls.game.red.ato,
cls.groundobjectgen.runways, cls.groundobjectgen.runways,
) )
cls.airgen.spawn_unused_aircraft( cls.airgen.spawn_unused_aircraft(
cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.blue.country_name),
cls.current_mission.country(cls.game.enemy_country), cls.current_mission.country(cls.game.red.country_name),
) )
@classmethod @classmethod
@ -380,10 +380,10 @@ class Operation:
player_cp = front_line.blue_cp player_cp = front_line.blue_cp
enemy_cp = front_line.red_cp enemy_cp = front_line.red_cp
conflict = Conflict.frontline_cas_conflict( conflict = Conflict.frontline_cas_conflict(
cls.game.player_faction.name, cls.game.blue.faction.name,
cls.game.enemy_faction.name, cls.game.red.faction.name,
cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.blue.country_name),
cls.current_mission.country(cls.game.enemy_country), cls.current_mission.country(cls.game.red.country_name),
front_line, front_line,
cls.game.theater, cls.game.theater,
) )

View File

@ -72,7 +72,9 @@ class ProcurementAi:
return 1 return 1
for cp in self.owned_points: for cp in self.owned_points:
cp_ground_units = cp.allocated_ground_units(self.game.transfers) cp_ground_units = cp.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)
armor_investment += cp_ground_units.total_value armor_investment += cp_ground_units.total_value
cp_aircraft = cp.allocated_aircraft(self.game) cp_aircraft = cp.allocated_aircraft(self.game)
aircraft_investment += cp_aircraft.total_value aircraft_investment += cp_aircraft.total_value
@ -316,7 +318,9 @@ class ProcurementAi:
continue continue
purchase_target = cp.frontline_unit_count_limit * FRONTLINE_RESERVES_FACTOR purchase_target = cp.frontline_unit_count_limit * FRONTLINE_RESERVES_FACTOR
allocated = cp.allocated_ground_units(self.game.transfers) allocated = cp.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)
if allocated.total >= purchase_target: if allocated.total >= purchase_target:
# Control point is already sufficiently defended. # Control point is already sufficiently defended.
continue continue
@ -343,7 +347,9 @@ class ProcurementAi:
if not cp.can_recruit_ground_units(self.game): if not cp.can_recruit_ground_units(self.game):
continue continue
allocated = cp.allocated_ground_units(self.game.transfers) allocated = cp.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)
if allocated.total >= self.game.settings.reserves_procurement_target: if allocated.total >= self.game.settings.reserves_procurement_target:
continue continue
@ -356,7 +362,9 @@ class ProcurementAi:
def cost_ratio_of_ground_unit( def cost_ratio_of_ground_unit(
self, control_point: ControlPoint, unit_class: GroundUnitClass self, control_point: ControlPoint, unit_class: GroundUnitClass
) -> float: ) -> float:
allocations = control_point.allocated_ground_units(self.game.transfers) allocations = control_point.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)
class_cost = 0 class_cost = 0
total_cost = 0 total_cost = 0
for unit_type, count in allocations.all.items(): for unit_type, count in allocations.all.items():

View File

@ -20,10 +20,11 @@ import yaml
from faker import Faker from faker import Faker
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.settings import AutoAtoBehavior from game.settings import AutoAtoBehavior, Settings
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
from game.coalition import Coalition
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
@ -96,16 +97,13 @@ class Squadron:
init=False, hash=False, compare=False init=False, hash=False, compare=False
) )
# We need a reference to the Game so that we can access the Faker without needing to coalition: Coalition = field(hash=False, compare=False)
# persist it to the save game, or having to reconstruct it (it's not cheap) each settings: Settings = field(hash=False, compare=False)
# time we create or load a squadron.
game: Game = field(hash=False, compare=False)
player: bool
def __post_init__(self) -> None: def __post_init__(self) -> None:
if any(p.status is not PilotStatus.Active for p in self.pilot_pool): if any(p.status is not PilotStatus.Active for p in self.pilot_pool):
raise ValueError("Squadrons can only be created with active pilots.") raise ValueError("Squadrons can only be created with active pilots.")
self._recruit_pilots(self.game.settings.squadron_pilot_limit) self._recruit_pilots(self.settings.squadron_pilot_limit)
self.auto_assignable_mission_types = set(self.mission_types) self.auto_assignable_mission_types = set(self.mission_types)
def __str__(self) -> str: def __str__(self) -> str:
@ -113,9 +111,13 @@ class Squadron:
return self.name return self.name
return f'{self.name} "{self.nickname}"' return f'{self.name} "{self.nickname}"'
@property
def player(self) -> bool:
return self.coalition.player
@property @property
def pilot_limits_enabled(self) -> bool: def pilot_limits_enabled(self) -> bool:
return self.game.settings.enable_squadron_pilot_limits return self.settings.enable_squadron_pilot_limits
def claim_new_pilot_if_allowed(self) -> Optional[Pilot]: def claim_new_pilot_if_allowed(self) -> Optional[Pilot]:
if self.pilot_limits_enabled: if self.pilot_limits_enabled:
@ -131,7 +133,7 @@ class Squadron:
if not self.player: if not self.player:
return self.available_pilots.pop() return self.available_pilots.pop()
preference = self.game.settings.auto_ato_behavior preference = self.settings.auto_ato_behavior
# No preference, so the first pilot is fine. # No preference, so the first pilot is fine.
if preference is AutoAtoBehavior.Default: if preference is AutoAtoBehavior.Default:
@ -184,7 +186,7 @@ class Squadron:
return return
replenish_count = min( replenish_count = min(
self.game.settings.squadron_replenishment_rate, self.settings.squadron_replenishment_rate,
self._number_of_unfilled_pilot_slots, self._number_of_unfilled_pilot_slots,
) )
if replenish_count > 0: if replenish_count > 0:
@ -206,7 +208,7 @@ class Squadron:
@property @property
def faker(self) -> Faker: def faker(self) -> Faker:
return self.game.faker_for(self.player) return self.coalition.faker
def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]: def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]:
return [p for p in self.current_roster if p.status == status] return [p for p in self.current_roster if p.status == status]
@ -228,7 +230,7 @@ class Squadron:
@property @property
def _number_of_unfilled_pilot_slots(self) -> int: def _number_of_unfilled_pilot_slots(self) -> int:
return self.game.settings.squadron_pilot_limit - len(self.active_pilots) return self.settings.squadron_pilot_limit - len(self.active_pilots)
@property @property
def number_of_available_pilots(self) -> int: def number_of_available_pilots(self) -> int:
@ -252,7 +254,7 @@ class Squadron:
return self.current_roster[index] return self.current_roster[index]
@classmethod @classmethod
def from_yaml(cls, path: Path, game: Game, player: bool) -> Squadron: def from_yaml(cls, path: Path, game: Game, coalition: Coalition) -> Squadron:
from gen.flights.ai_flight_planner_db import tasks_for_aircraft from gen.flights.ai_flight_planner_db import tasks_for_aircraft
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
@ -287,8 +289,8 @@ class Squadron:
livery=data.get("livery"), livery=data.get("livery"),
mission_types=tuple(mission_types), mission_types=tuple(mission_types),
pilot_pool=pilots, pilot_pool=pilots,
game=game, coalition=coalition,
player=player, settings=game.settings,
) )
def __setstate__(self, state: dict[str, Any]) -> None: def __setstate__(self, state: dict[str, Any]) -> None:
@ -299,9 +301,9 @@ class Squadron:
class SquadronLoader: class SquadronLoader:
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, coalition: Coalition) -> None:
self.game = game self.game = game
self.player = player self.coalition = coalition
@staticmethod @staticmethod
def squadron_directories() -> Iterator[Path]: def squadron_directories() -> Iterator[Path]:
@ -312,8 +314,8 @@ class SquadronLoader:
def load(self) -> dict[AircraftType, list[Squadron]]: def load(self) -> dict[AircraftType, list[Squadron]]:
squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list) squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
country = self.game.country_for(self.player) country = self.coalition.country_name
faction = self.game.faction_for(self.player) faction = self.coalition.faction
any_country = country.startswith("Combined Joint Task Forces ") any_country = country.startswith("Combined Joint Task Forces ")
for directory in self.squadron_directories(): for directory in self.squadron_directories():
for path, squadron in self.load_squadrons_from(directory): for path, squadron in self.load_squadrons_from(directory):
@ -347,7 +349,7 @@ class SquadronLoader:
for squadron_path in directory.glob("*/*.yaml"): for squadron_path in directory.glob("*/*.yaml"):
try: try:
yield squadron_path, Squadron.from_yaml( yield squadron_path, Squadron.from_yaml(
squadron_path, self.game, self.player squadron_path, self.game, self.coalition
) )
except Exception as ex: except Exception as ex:
raise RuntimeError( raise RuntimeError(
@ -356,29 +358,28 @@ class SquadronLoader:
class AirWing: class AirWing:
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, coalition: Coalition) -> None:
from gen.flights.ai_flight_planner_db import tasks_for_aircraft from gen.flights.ai_flight_planner_db import tasks_for_aircraft
self.game = game self.game = game
self.player = player self.squadrons = SquadronLoader(game, coalition).load()
self.squadrons = SquadronLoader(game, player).load()
count = itertools.count(1) count = itertools.count(1)
for aircraft in game.faction_for(player).aircrafts: for aircraft in coalition.faction.aircrafts:
if aircraft in self.squadrons: if aircraft in self.squadrons:
continue continue
self.squadrons[aircraft] = [ self.squadrons[aircraft] = [
Squadron( Squadron(
name=f"Squadron {next(count):03}", name=f"Squadron {next(count):03}",
nickname=self.random_nickname(), nickname=self.random_nickname(),
country=game.country_for(player), country=coalition.country_name,
role="Flying Squadron", role="Flying Squadron",
aircraft=aircraft, aircraft=aircraft,
livery=None, livery=None,
mission_types=tuple(tasks_for_aircraft(aircraft)), mission_types=tuple(tasks_for_aircraft(aircraft)),
pilot_pool=[], pilot_pool=[],
game=game, coalition=coalition,
player=player, settings=game.settings,
) )
] ]

View File

@ -40,11 +40,7 @@ from gen.ground_forces.combat_stance import CombatStance
from gen.runways import RunwayAssigner, RunwayData from gen.runways import RunwayAssigner, RunwayData
from .base import Base from .base import Base
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
from .theatergroundobject import ( from .theatergroundobject import GenericCarrierGroundObject, TheaterGroundObject
GenericCarrierGroundObject,
TheaterGroundObject,
NavalGroundObject,
)
from ..dcs.aircrafttype import AircraftType from ..dcs.aircrafttype import AircraftType
from ..dcs.groundunittype import GroundUnitType from ..dcs.groundunittype import GroundUnitType
from ..utils import nautical_miles from ..utils import nautical_miles
@ -606,7 +602,7 @@ class ControlPoint(MissionTarget, ABC):
# TODO: Should be Airbase specific. # TODO: Should be Airbase specific.
def capture(self, game: Game, for_player: bool) -> None: def capture(self, game: Game, for_player: bool) -> None:
self.pending_unit_deliveries.refund_all(game) self.pending_unit_deliveries.refund_all(game.coalition_for(for_player))
self.retreat_ground_units(game) self.retreat_ground_units(game)
self.retreat_air_units(game) self.retreat_air_units(game)
self.depopulate_uncapturable_tgos() self.depopulate_uncapturable_tgos()
@ -623,11 +619,7 @@ class ControlPoint(MissionTarget, ABC):
... ...
def aircraft_transferring(self, game: Game) -> dict[AircraftType, int]: def aircraft_transferring(self, game: Game) -> dict[AircraftType, int]:
if self.captured: ato = game.coalition_for(self.captured).ato
ato = game.blue_ato
else:
ato = game.red_ato
transferring: defaultdict[AircraftType, int] = defaultdict(int) transferring: defaultdict[AircraftType, int] = defaultdict(int)
for package in ato.packages: for package in ato.packages:
for flight in package.flights: for flight in package.flights:

View File

@ -11,7 +11,7 @@ from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence from dcs.vehicles import AirDefence
from game import Game, db from game import Game
from game.factions.faction import Faction from game.factions.faction import Faction
from game.scenery_group import SceneryGroup from game.scenery_group import SceneryGroup
from game.theater import Carrier, Lha, PointWithHeading from game.theater import Carrier, Lha, PointWithHeading
@ -171,14 +171,11 @@ class ControlPointGroundObjectGenerator:
@property @property
def faction_name(self) -> str: def faction_name(self) -> str:
if self.control_point.captured: return self.faction.name
return self.game.player_faction.name
else:
return self.game.enemy_faction.name
@property @property
def faction(self) -> Faction: def faction(self) -> Faction:
return db.FACTIONS[self.faction_name] return self.game.coalition_for(self.control_point.captured).faction
def generate(self) -> bool: def generate(self) -> bool:
self.control_point.connected_objectives = [] self.control_point.connected_objectives = []

View File

@ -316,7 +316,9 @@ class AirliftPlanner:
capacity = flight_size * capacity_each capacity = flight_size * capacity_each
if capacity < self.transfer.size: if capacity < self.transfer.size:
transfer = self.game.transfers.split_transfer(self.transfer, capacity) transfer = self.game.coalition_for(
self.for_player
).transfers.split_transfer(self.transfer, capacity)
else: else:
transfer = self.transfer transfer = self.transfer
@ -534,8 +536,9 @@ class CargoShipMap(TransportMap[CargoShip]):
class PendingTransfers: class PendingTransfers:
def __init__(self, game: Game) -> None: def __init__(self, game: Game, player: bool) -> None:
self.game = game self.game = game
self.player = player
self.convoys = ConvoyMap() self.convoys = ConvoyMap()
self.cargo_ships = CargoShipMap() self.cargo_ships = CargoShipMap()
self.pending_transfers: List[TransferOrder] = [] self.pending_transfers: List[TransferOrder] = []
@ -609,7 +612,7 @@ class PendingTransfers:
flight = transport.flight flight = transport.flight
flight.package.remove_flight(flight) flight.package.remove_flight(flight)
if not flight.package.flights: if not flight.package.flights:
self.game.ato_for(transport.player_owned).remove_package(flight.package) self.game.ato_for(self.player).remove_package(flight.package)
self.game.aircraft_inventory.return_from_flight(flight) self.game.aircraft_inventory.return_from_flight(flight)
flight.clear_roster() flight.clear_roster()
@ -647,7 +650,7 @@ class PendingTransfers:
self.arrange_transport(transfer) self.arrange_transport(transfer)
def order_airlift_assets(self) -> None: def order_airlift_assets(self) -> None:
for control_point in self.game.theater.controlpoints: for control_point in self.game.theater.control_points_for(self.player):
if self.game.air_wing_for(control_point.captured).can_auto_plan( if self.game.air_wing_for(control_point.captured).can_auto_plan(
FlightType.TRANSPORT FlightType.TRANSPORT
): ):
@ -682,7 +685,7 @@ class PendingTransfers:
# aesthetic. # aesthetic.
gap += 1 gap += 1
self.game.procurement_requests_for(player=control_point.captured).append( self.game.procurement_requests_for(self.player).append(
AircraftProcurementRequest( AircraftProcurementRequest(
control_point, nautical_miles(200), FlightType.TRANSPORT, gap control_point, nautical_miles(200), FlightType.TRANSPORT, gap
) )

View File

@ -6,6 +6,7 @@ from dataclasses import dataclass
from typing import Optional, TYPE_CHECKING, Any from typing import Optional, TYPE_CHECKING, Any
from game.theater import ControlPoint from game.theater import ControlPoint
from .coalition import Coalition
from .dcs.groundunittype import GroundUnitType from .dcs.groundunittype import GroundUnitType
from .dcs.unittype import UnitType from .dcs.unittype import UnitType
from .theater.transitnetwork import ( from .theater.transitnetwork import (
@ -41,24 +42,22 @@ class PendingUnitDeliveries:
for k, v in units.items(): for k, v in units.items():
self.units[k] -= v self.units[k] -= v
def refund_all(self, game: Game) -> None: def refund_all(self, coalition: Coalition) -> None:
self.refund(game, self.units) self.refund(coalition, self.units)
self.units = defaultdict(int) self.units = defaultdict(int)
def refund_ground_units(self, game: Game) -> None: def refund_ground_units(self, coalition: Coalition) -> None:
ground_units: dict[UnitType[Any], int] = { ground_units: dict[UnitType[Any], int] = {
u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType) u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType)
} }
self.refund(game, ground_units) self.refund(coalition, ground_units)
for gu in ground_units.keys(): for gu in ground_units.keys():
del self.units[gu] del self.units[gu]
def refund(self, game: Game, units: dict[UnitType[Any], int]) -> None: def refund(self, coalition: Coalition, units: dict[UnitType[Any], int]) -> None:
for unit_type, count in units.items(): for unit_type, count in units.items():
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}") logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
game.adjust_budget( coalition.adjust_budget(unit_type.price * count)
unit_type.price * count, player=self.destination.captured
)
def pending_orders(self, unit_type: UnitType[Any]) -> int: def pending_orders(self, unit_type: UnitType[Any]) -> int:
pending_units = self.units.get(unit_type) pending_units = self.units.get(unit_type)
@ -71,19 +70,20 @@ class PendingUnitDeliveries:
return self.pending_orders(unit_type) + current_units return self.pending_orders(unit_type) + current_units
def process(self, game: Game) -> None: def process(self, game: Game) -> None:
coalition = game.coalition_for(self.destination.captured)
ground_unit_source = self.find_ground_unit_source(game) ground_unit_source = self.find_ground_unit_source(game)
if ground_unit_source is None: if ground_unit_source is None:
game.message( game.message(
f"{self.destination.name} lost its source for ground unit " f"{self.destination.name} lost its source for ground unit "
"reinforcements. Refunding purchase price." "reinforcements. Refunding purchase price."
) )
self.refund_ground_units(game) self.refund_ground_units(coalition)
bought_units: dict[UnitType[Any], int] = {} bought_units: dict[UnitType[Any], int] = {}
units_needing_transfer: dict[GroundUnitType, int] = {} units_needing_transfer: dict[GroundUnitType, int] = {}
sold_units: dict[UnitType[Any], int] = {} sold_units: dict[UnitType[Any], int] = {}
for unit_type, count in self.units.items(): for unit_type, count in self.units.items():
coalition = "Ally" if self.destination.captured else "Enemy" allegiance = "Ally" if self.destination.captured else "Enemy"
d: dict[Any, int] d: dict[Any, int]
if ( if (
isinstance(unit_type, GroundUnitType) isinstance(unit_type, GroundUnitType)
@ -98,11 +98,11 @@ class PendingUnitDeliveries:
if count >= 0: if count >= 0:
d[unit_type] = count d[unit_type] = count
game.message( game.message(
f"{coalition} reinforcements: {unit_type} x {count} at {source}" f"{allegiance} reinforcements: {unit_type} x {count} at {source}"
) )
else: else:
sold_units[unit_type] = -count sold_units[unit_type] = -count
game.message(f"{coalition} sold: {unit_type} x {-count} at {source}") game.message(f"{allegiance} sold: {unit_type} x {-count} at {source}")
self.units = defaultdict(int) self.units = defaultdict(int)
self.destination.base.commission_units(bought_units) self.destination.base.commission_units(bought_units)
@ -111,16 +111,19 @@ class PendingUnitDeliveries:
if units_needing_transfer: if units_needing_transfer:
if ground_unit_source is None: if ground_unit_source is None:
raise RuntimeError( raise RuntimeError(
f"ground unit source could not be found for {self.destination} but still tried to " f"Ground unit source could not be found for {self.destination} but "
f"transfer units to there" "still tried to transfer units to there"
) )
ground_unit_source.base.commission_units(units_needing_transfer) ground_unit_source.base.commission_units(units_needing_transfer)
self.create_transfer(game, ground_unit_source, units_needing_transfer) self.create_transfer(coalition, ground_unit_source, units_needing_transfer)
def create_transfer( def create_transfer(
self, game: Game, source: ControlPoint, units: dict[GroundUnitType, int] self,
coalition: Coalition,
source: ControlPoint,
units: dict[GroundUnitType, int],
) -> None: ) -> None:
game.transfers.new_transfer(TransferOrder(source, self.destination, units)) coalition.transfers.new_transfer(TransferOrder(source, self.destination, units))
def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]:
# This is running *after* the turn counter has been incremented, so this is the # This is running *after* the turn counter has been incremented, so this is the

View File

@ -22,7 +22,6 @@ from dcs.planes import (
C_101EB, C_101EB,
F_14B, F_14B,
JF_17, JF_17,
PlaneType,
Su_33, Su_33,
Tu_22M3, Tu_22M3,
) )
@ -262,8 +261,8 @@ class AircraftConflictGenerator:
@cached_property @cached_property
def use_client(self) -> bool: def use_client(self) -> bool:
"""True if Client should be used instead of Player.""" """True if Client should be used instead of Player."""
blue_clients = self.client_slots_in_ato(self.game.blue_ato) blue_clients = self.client_slots_in_ato(self.game.blue.ato)
red_clients = self.client_slots_in_ato(self.game.red_ato) red_clients = self.client_slots_in_ato(self.game.red.ato)
return blue_clients + red_clients > 1 return blue_clients + red_clients > 1
@staticmethod @staticmethod
@ -601,12 +600,11 @@ class AircraftConflictGenerator:
if not isinstance(control_point, Airfield): if not isinstance(control_point, Airfield):
continue continue
faction = self.game.coalition_for(control_point.captured).faction
if control_point.captured: if control_point.captured:
country = player_country country = player_country
faction = self.game.player_faction
else: else:
country = enemy_country country = enemy_country
faction = self.game.enemy_faction
for aircraft, available in inventory.all_aircraft: for aircraft, available in inventory.all_aircraft:
try: try:
@ -699,11 +697,7 @@ class AircraftConflictGenerator:
if flight.from_cp.cptype != ControlPointType.AIRBASE: if flight.from_cp.cptype != ControlPointType.AIRBASE:
return return
if flight.from_cp.captured: coalition = self.game.coalition_for(flight.departure.captured).coalition_id
coalition = self.game.get_player_coalition_id()
else:
coalition = self.game.get_enemy_coalition_id()
trigger.add_condition(CoalitionHasAirdrome(coalition, flight.from_cp.id)) trigger.add_condition(CoalitionHasAirdrome(coalition, flight.from_cp.id))
def generate_planned_flight( def generate_planned_flight(

View File

@ -105,6 +105,8 @@ class AirSupportConflictGenerator:
else self.conflict.red_cp else self.conflict.red_cp
) )
country = self.mission.country(self.game.blue.country_name)
if not self.game.settings.disable_legacy_tanker: if not self.game.settings.disable_legacy_tanker:
fallback_tanker_number = 0 fallback_tanker_number = 0
@ -130,10 +132,8 @@ class AirSupportConflictGenerator:
tanker_heading, TANKER_DISTANCE tanker_heading, TANKER_DISTANCE
) )
tanker_group = self.mission.refuel_flight( tanker_group = self.mission.refuel_flight(
country=self.mission.country(self.game.player_country), country=country,
name=namegen.next_tanker_name( name=namegen.next_tanker_name(country, tanker_unit_type),
self.mission.country(self.game.player_country), tanker_unit_type
),
airport=None, airport=None,
plane_type=unit_type, plane_type=unit_type,
position=tanker_position, position=tanker_position,
@ -211,10 +211,8 @@ class AirSupportConflictGenerator:
return return
awacs_flight = self.mission.awacs_flight( awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country), country=country,
name=namegen.next_awacs_name( name=namegen.next_awacs_name(country),
self.mission.country(self.game.player_country)
),
plane_type=unit_type, plane_type=unit_type,
altitude=AWACS_ALT, altitude=AWACS_ALT,
airport=None, airport=None,

View File

@ -144,16 +144,16 @@ class GroundConflictGenerator:
) )
# Add JTAC # Add JTAC
if self.game.player_faction.has_jtac: if self.game.blue.faction.has_jtac:
n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id) n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id)
code = 1688 - len(self.jtacs) code = 1688 - len(self.jtacs)
utype = self.game.player_faction.jtac_unit utype = self.game.blue.faction.jtac_unit
if utype is None: if utype is None:
utype = AircraftType.named("MQ-9 Reaper") utype = AircraftType.named("MQ-9 Reaper")
jtac = self.mission.flight_group( jtac = self.mission.flight_group(
country=self.mission.country(self.game.player_country), country=self.mission.country(self.game.blue.country_name),
name=n, name=n,
aircraft_type=utype.dcs_unit_type, aircraft_type=utype.dcs_unit_type,
position=position[0], position=position[0],
@ -715,7 +715,7 @@ class GroundConflictGenerator:
if is_player if is_player
else int(heading_sum(heading, 90)) else int(heading_sum(heading, 90))
) )
country = self.game.player_country if is_player else self.game.enemy_country country = self.game.coalition_for(is_player).country_name
for group in groups: for group in groups:
if group.role == CombatGroupRole.ARTILLERY: if group.role == CombatGroupRole.ARTILLERY:
distance_from_frontline = ( distance_from_frontline = (

View File

@ -24,12 +24,13 @@ class CargoShipGenerator:
def generate(self) -> None: def generate(self) -> None:
# Reset the count to make generation deterministic. # Reset the count to make generation deterministic.
for ship in self.game.transfers.cargo_ships: for coalition in self.game.coalitions:
for ship in coalition.transfers.cargo_ships:
self.generate_cargo_ship(ship) self.generate_cargo_ship(ship)
def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup: def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup:
country = self.mission.country( country = self.mission.country(
self.game.player_country if ship.player_owned else self.game.enemy_country self.game.coalition_for(ship.player_owned).country_name
) )
waypoints = ship.route waypoints = ship.route
group = self.mission.ship_group( group = self.mission.ship_group(

View File

@ -27,7 +27,8 @@ class ConvoyGenerator:
def generate(self) -> None: def generate(self) -> None:
# Reset the count to make generation deterministic. # Reset the count to make generation deterministic.
for convoy in self.game.transfers.convoys: for coalition in self.game.coalitions:
for convoy in coalition.transfers.convoys:
self.generate_convoy(convoy) self.generate_convoy(convoy)
def generate_convoy(self, convoy: Convoy) -> VehicleGroup: def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
@ -53,9 +54,7 @@ class ConvoyGenerator:
units: dict[GroundUnitType, int], units: dict[GroundUnitType, int],
for_player: bool, for_player: bool,
) -> VehicleGroup: ) -> VehicleGroup:
country = self.mission.country( country = self.mission.country(self.game.coalition_for(for_player).country_name)
self.game.player_country if for_player else self.game.enemy_country
)
unit_types = list(units.items()) unit_types = list(units.items())
main_unit_type, main_unit_count = unit_types[0] main_unit_type, main_unit_count = unit_types[0]

View File

@ -228,7 +228,7 @@ class CoalitionMissionPlanner:
self.game = game self.game = game
self.is_player = is_player self.is_player = is_player
self.objective_finder = ObjectiveFinder(self.game, self.is_player) self.objective_finder = ObjectiveFinder(self.game, self.is_player)
self.ato = self.game.blue_ato if is_player else self.game.red_ato self.ato = self.game.coalition_for(is_player).ato
self.threat_zones = self.game.threat_zone_for(not self.is_player) self.threat_zones = self.game.threat_zone_for(not self.is_player)
self.procurement_requests = self.game.procurement_requests_for(self.is_player) self.procurement_requests = self.game.procurement_requests_for(self.is_player)
self.faction: Faction = self.game.faction_for(self.is_player) self.faction: Faction = self.game.faction_for(self.is_player)

View File

@ -38,8 +38,8 @@ class ForcedOptionsGenerator:
self.mission.forced_options.labels = ForcedOptions.Labels.None_ self.mission.forced_options.labels = ForcedOptions.Labels.None_
def _set_unrestricted_satnav(self) -> None: def _set_unrestricted_satnav(self) -> None:
blue = self.game.player_faction blue = self.game.blue.faction
red = self.game.enemy_faction red = self.game.red.faction
if blue.unrestricted_satnav or red.unrestricted_satnav: if blue.unrestricted_satnav or red.unrestricted_satnav:
self.mission.forced_options.unrestricted_satnav = True self.mission.forced_options.unrestricted_satnav = True

View File

@ -25,7 +25,7 @@ from typing import (
from dcs import Mission, Point, unitgroup from dcs import Mission, Point, unitgroup
from dcs.action import SceneryDestructionZone from dcs.action import SceneryDestructionZone
from dcs.country import Country from dcs.country import Country
from dcs.point import StaticPoint, MovingPoint from dcs.point import StaticPoint
from dcs.statics import Fortification, fortification_map, warehouse_map from dcs.statics import Fortification, fortification_map, warehouse_map
from dcs.task import ( from dcs.task import (
ActivateBeaconCommand, ActivateBeaconCommand,
@ -36,8 +36,8 @@ from dcs.task import (
) )
from dcs.triggers import TriggerStart, TriggerZone from dcs.triggers import TriggerStart, TriggerZone
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup from dcs.unitgroup import ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import StaticType, UnitType, ShipType, VehicleType from dcs.unittype import StaticType, ShipType, VehicleType
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
from game import db from game import db
@ -587,13 +587,7 @@ class HelipadGenerator:
self.tacan_registry = tacan_registry self.tacan_registry = tacan_registry
def generate(self) -> None: def generate(self) -> None:
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
if self.cp.captured:
country_name = self.game.player_country
else:
country_name = self.game.enemy_country
country = self.m.country(country_name)
for i, helipad in enumerate(self.cp.helipads): for i, helipad in enumerate(self.cp.helipads):
name = self.cp.name + "_helipad_" + str(i) name = self.cp.name + "_helipad_" + str(i)
logging.info("Generating helipad : " + name) logging.info("Generating helipad : " + name)
@ -636,12 +630,7 @@ class GroundObjectsGenerator:
def generate(self) -> None: def generate(self) -> None:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if cp.captured: country = self.m.country(self.game.coalition_for(cp.captured).country_name)
country_name = self.game.player_country
else:
country_name = self.game.enemy_country
country = self.m.country(country_name)
HelipadGenerator( HelipadGenerator(
self.m, cp, self.game, self.radio_registry, self.tacan_registry self.m, cp, self.game, self.radio_registry, self.tacan_registry
).generate() ).generate()

View File

@ -97,7 +97,7 @@ class VisualGenerator:
break break
self.mission.static_group( self.mission.static_group(
self.mission.country(self.game.enemy_country), self.mission.country(self.game.red.country_name),
"", "",
_type=v, _type=v,
position=pos, position=pos,

View File

@ -12,11 +12,10 @@ from PySide2.QtCore import (
) )
from PySide2.QtGui import QIcon from PySide2.QtGui import QIcon
from game import db
from game.game import Game from game.game import Game
from game.squadrons import Squadron, Pilot from game.squadrons import Squadron, Pilot
from game.theater.missiontarget import MissionTarget from game.theater.missiontarget import MissionTarget
from game.transfers import TransferOrder from game.transfers import TransferOrder, PendingTransfers
from gen.ato import AirTaskingOrder, Package from gen.ato import AirTaskingOrder, Package
from gen.flights.flight import Flight, FlightType from gen.flights.flight import Flight, FlightType
from gen.flights.traveltime import TotEstimator from gen.flights.traveltime import TotEstimator
@ -281,9 +280,9 @@ class AtoModel(QAbstractListModel):
self.package_models.clear() self.package_models.clear()
if self.game is not None: if self.game is not None:
if player: if player:
self.ato = self.game.blue_ato self.ato = self.game.blue.ato
else: else:
self.ato = self.game.red_ato self.ato = self.game.red.ato
else: else:
self.ato = AirTaskingOrder() self.ato = AirTaskingOrder()
self.endResetModel() self.endResetModel()
@ -316,8 +315,12 @@ class TransferModel(QAbstractListModel):
super().__init__() super().__init__()
self.game_model = game_model self.game_model = game_model
@property
def transfers(self) -> PendingTransfers:
return self.game_model.game.coalition_for(player=True).transfers
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return self.game_model.game.transfers.pending_transfer_count return self.transfers.pending_transfer_count
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any: def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
if not index.isValid(): if not index.isValid():
@ -345,7 +348,7 @@ class TransferModel(QAbstractListModel):
"""Updates the game with the new unit transfer.""" """Updates the game with the new unit transfer."""
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
# TODO: Needs to regenerate base inventory tab. # TODO: Needs to regenerate base inventory tab.
self.game_model.game.transfers.new_transfer(transfer) self.transfers.new_transfer(transfer)
self.endInsertRows() self.endInsertRows()
def cancel_transfer_at_index(self, index: QModelIndex) -> None: def cancel_transfer_at_index(self, index: QModelIndex) -> None:
@ -354,15 +357,15 @@ class TransferModel(QAbstractListModel):
def cancel_transfer(self, transfer: TransferOrder) -> None: def cancel_transfer(self, transfer: TransferOrder) -> None:
"""Cancels the planned unit transfer at the given index.""" """Cancels the planned unit transfer at the given index."""
index = self.game_model.game.transfers.index_of_transfer(transfer) index = self.transfers.index_of_transfer(transfer)
self.beginRemoveRows(QModelIndex(), index, index) self.beginRemoveRows(QModelIndex(), index, index)
# TODO: Needs to regenerate base inventory tab. # TODO: Needs to regenerate base inventory tab.
self.game_model.game.transfers.cancel_transfer(transfer) self.transfers.cancel_transfer(transfer)
self.endRemoveRows() self.endRemoveRows()
def transfer_at_index(self, index: QModelIndex) -> TransferOrder: def transfer_at_index(self, index: QModelIndex) -> TransferOrder:
"""Returns the transfer located at the given index.""" """Returns the transfer located at the given index."""
return self.game_model.game.transfers.transfer_at_index(index.row()) return self.transfers.transfer_at_index(index.row())
class AirWingModel(QAbstractListModel): class AirWingModel(QAbstractListModel):
@ -488,8 +491,8 @@ class GameModel:
self.ato_model = AtoModel(self, AirTaskingOrder()) self.ato_model = AtoModel(self, AirTaskingOrder())
self.red_ato_model = AtoModel(self, AirTaskingOrder()) self.red_ato_model = AtoModel(self, AirTaskingOrder())
else: else:
self.ato_model = AtoModel(self, self.game.blue_ato) self.ato_model = AtoModel(self, self.game.blue.ato)
self.red_ato_model = AtoModel(self, self.game.red_ato) self.red_ato_model = AtoModel(self, self.game.red.ato)
def ato_model_for(self, player: bool) -> AtoModel: def ato_model_for(self, player: bool) -> AtoModel:
if player: if player:

View File

@ -24,8 +24,8 @@ class QFactionsInfos(QGroupBox):
def setGame(self, game: Game): def setGame(self, game: Game):
if game is not None: if game is not None:
self.player_name.setText(game.player_faction.name) self.player_name.setText(game.blue.faction.name)
self.enemy_name.setText(game.enemy_faction.name) self.enemy_name.setText(game.red.faction.name)
else: else:
self.player_name.setText("") self.player_name.setText("")
self.enemy_name.setText("") self.enemy_name.setText("")

View File

@ -168,7 +168,7 @@ class QTopPanel(QFrame):
package.time_over_target = estimator.earliest_tot() package.time_over_target = estimator.earliest_tot()
def ato_has_clients(self) -> bool: def ato_has_clients(self) -> bool:
for package in self.game.blue_ato.packages: for package in self.game.blue.ato.packages:
for flight in package.flights: for flight in package.flights:
if flight.client_count > 0: if flight.client_count > 0:
return True return True
@ -236,7 +236,7 @@ class QTopPanel(QFrame):
def check_no_missing_pilots(self) -> bool: def check_no_missing_pilots(self) -> bool:
missing_pilots = [] missing_pilots = []
for package in self.game.blue_ato.packages: for package in self.game.blue.ato.packages:
for flight in package.flights: for flight in package.flights:
if flight.missing_pilots > 0: if flight.missing_pilots > 0:
missing_pilots.append((package, flight)) missing_pilots.append((package, flight))
@ -282,8 +282,8 @@ class QTopPanel(QFrame):
closest_cps[0], closest_cps[0],
closest_cps[1], closest_cps[1],
self.game.theater.controlpoints[0].position, self.game.theater.controlpoints[0].position,
self.game.player_faction.name, self.game.blue.faction.name,
self.game.enemy_faction.name, self.game.red.faction.name,
) )
unit_map = self.game.initiate_event(game_event) unit_map = self.game.initiate_event(game_event)

View File

@ -4,7 +4,6 @@ from typing import Iterable, Type
from PySide2.QtWidgets import QComboBox from PySide2.QtWidgets import QComboBox
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
from game import db
from gen.flights.ai_flight_planner_db import aircraft_for_task from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
@ -13,16 +12,12 @@ class QAircraftTypeSelector(QComboBox):
"""Combo box for selecting among the given aircraft types.""" """Combo box for selecting among the given aircraft types."""
def __init__( def __init__(
self, self, aircraft_types: Iterable[Type[FlyingType]], mission_type: FlightType
aircraft_types: Iterable[Type[FlyingType]],
country: str,
mission_type: FlightType,
) -> None: ) -> None:
super().__init__() super().__init__()
self.model().sort(0) self.model().sort(0)
self.setSizeAdjustPolicy(self.AdjustToContents) self.setSizeAdjustPolicy(self.AdjustToContents)
self.country = country
self.update_items(mission_type, aircraft_types) self.update_items(mission_type, aircraft_types)
def update_items(self, mission_type: FlightType, aircraft_types): def update_items(self, mission_type: FlightType, aircraft_types):

View File

@ -336,8 +336,12 @@ class SupplyRouteJs(QObject):
def find_transports(self) -> List[MultiGroupTransport]: def find_transports(self) -> List[MultiGroupTransport]:
if self.sea_route: if self.sea_route:
return self.find_in_transport_map(self.game.transfers.cargo_ships) return self.find_in_transport_map(
return self.find_in_transport_map(self.game.transfers.convoys) self.game.blue.transfers.cargo_ships
) + self.find_in_transport_map(self.game.red.transfers.cargo_ships)
return self.find_in_transport_map(
self.game.blue.transfers.convoys
) + self.find_in_transport_map(self.game.red.transfers.convoys)
@Property(list, notify=activeTransportsChanged) @Property(list, notify=activeTransportsChanged)
def activeTransports(self) -> List[str]: def activeTransports(self) -> List[str]:
@ -672,8 +676,8 @@ class NavMeshJs(QObject):
@classmethod @classmethod
def from_game(cls, game: Game) -> NavMeshJs: def from_game(cls, game: Game) -> NavMeshJs:
return NavMeshJs( return NavMeshJs(
cls.to_polys(game.blue_navmesh, game.theater), cls.to_polys(game.blue.nav_mesh, game.theater),
cls.to_polys(game.red_navmesh, game.theater), cls.to_polys(game.red.nav_mesh, game.theater),
) )
@ -870,8 +874,8 @@ class MapModel(QObject):
def reset_atos(self) -> None: def reset_atos(self) -> None:
self._flights = self._flights_in_ato( self._flights = self._flights_in_ato(
self.game.blue_ato, blue=True self.game.blue.ato, blue=True
) + self._flights_in_ato(self.game.red_ato, blue=False) ) + self._flights_in_ato(self.game.red.ato, blue=False)
self.flightsChanged.emit() self.flightsChanged.emit()
@Property(list, notify=flightsChanged) @Property(list, notify=flightsChanged)

View File

@ -3,12 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, Iterator from typing import Optional, Iterator
from PySide2.QtCore import ( from PySide2.QtCore import QItemSelectionModel, QModelIndex, QSize
QItemSelectionModel,
QModelIndex,
Qt,
QSize,
)
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QAbstractItemView, QAbstractItemView,
QCheckBox, QCheckBox,
@ -183,7 +178,7 @@ class AirInventoryView(QWidget):
self.table.setSortingEnabled(True) self.table.setSortingEnabled(True)
def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]: def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]:
for package in self.game_model.game.blue_ato.packages: for package in self.game_model.game.blue.ato.packages:
for flight in package.flights: for flight in package.flights:
yield from AircraftInventoryData.from_flight(flight) yield from AircraftInventoryData.from_flight(flight)

View File

@ -73,11 +73,15 @@ class DepartingConvoysList(QFrame):
task_box_layout = QGridLayout() task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)
for convoy in game_model.game.transfers.convoys.departing_from(cp): for convoy in game_model.game.coalition_for(
cp.captured
).transfers.convoys.departing_from(cp):
group_info = DepartingConvoyInfo(convoy) group_info = DepartingConvoyInfo(convoy)
task_box_layout.addWidget(group_info) task_box_layout.addWidget(group_info)
for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp): for cargo_ship in game_model.game.coalition_for(
cp.captured
).transfers.cargo_ships.departing_from(cp):
group_info = DepartingConvoyInfo(cargo_ship) group_info = DepartingConvoyInfo(cargo_ship)
task_box_layout.addWidget(group_info) task_box_layout.addWidget(group_info)

View File

@ -195,7 +195,9 @@ class QBaseMenu2(QDialog):
ground_unit_limit = self.cp.frontline_unit_count_limit ground_unit_limit = self.cp.frontline_unit_count_limit
deployable_unit_info = "" deployable_unit_info = ""
allocated = self.cp.allocated_ground_units(self.game_model.game.transfers) allocated = self.cp.allocated_ground_units(
self.game_model.game.coalition_for(self.cp.captured).transfers
)
unit_overage = max( unit_overage = max(
allocated.total_present - self.cp.frontline_unit_count_limit, 0 allocated.total_present - self.cp.frontline_unit_count_limit, 0
) )

View File

@ -45,7 +45,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
row = 0 row = 0
unit_types: Set[AircraftType] = set() unit_types: Set[AircraftType] = set()
for unit_type in self.game_model.game.player_faction.aircrafts: for unit_type in self.game_model.game.blue.faction.aircrafts:
if self.cp.is_carrier and not unit_type.carrier_capable: if self.cp.is_carrier and not unit_type.carrier_capable:
continue continue
if self.cp.is_lha and not unit_type.lha_capable: if self.cp.is_lha and not unit_type.lha_capable:

View File

@ -1,7 +1,6 @@
import logging import logging
from typing import List, Optional from typing import List, Optional
from PySide2 import QtCore
from PySide2.QtGui import Qt from PySide2.QtGui import Qt
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QComboBox, QComboBox,
@ -307,7 +306,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
self.buySamBox = QGroupBox("Buy SAM site :") self.buySamBox = QGroupBox("Buy SAM site :")
self.buyArmorBox = QGroupBox("Buy defensive position :") self.buyArmorBox = QGroupBox("Buy defensive position :")
faction = self.game.player_faction faction = self.game.blue.faction
# Sams # Sams

View File

@ -38,7 +38,7 @@ class QFlightCreator(QDialog):
self.game = game self.game = game
self.package = package self.package = package
self.custom_name_text = None self.custom_name_text = None
self.country = self.game.player_country self.country = self.game.blue.country_name
self.setWindowTitle("Create flight") self.setWindowTitle("Create flight")
self.setWindowIcon(EVENT_ICONS["strike"]) self.setWindowIcon(EVENT_ICONS["strike"])
@ -52,7 +52,6 @@ class QFlightCreator(QDialog):
self.aircraft_selector = QAircraftTypeSelector( self.aircraft_selector = QAircraftTypeSelector(
self.game.aircraft_inventory.available_types_for_player, self.game.aircraft_inventory.available_types_for_player,
self.game.player_country,
self.task_selector.currentData(), self.task_selector.currentData(),
) )
self.aircraft_selector.setCurrentIndex(0) self.aircraft_selector.setCurrentIndex(0)