mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Factor out Coalition from Game.
This commit is contained in:
parent
4534758c21
commit
17c19d453b
215
game/coalition.py
Normal file
215
game/coalition.py
Normal 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)
|
||||
@ -14,9 +14,9 @@ from game.theater import (
|
||||
Airfield,
|
||||
)
|
||||
from game.theater.theatergroundobject import (
|
||||
NavalGroundObject,
|
||||
BuildingGroundObject,
|
||||
IadsGroundObject,
|
||||
NavalGroundObject,
|
||||
)
|
||||
from game.transfers import CargoShip, Convoy
|
||||
from game.utils import meters, nautical_miles
|
||||
@ -163,13 +163,17 @@ class ObjectiveFinder:
|
||||
|
||||
def convoys(self) -> Iterator[Convoy]:
|
||||
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)
|
||||
)
|
||||
|
||||
def cargo_ships(self) -> Iterator[CargoShip]:
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ from game.commander.missionproposals import EscortType
|
||||
from game.commander.tasks.packageplanningtask import PackagePlanningTask
|
||||
from game.commander.theaterstate import TheaterState
|
||||
from game.data.doctrine import Doctrine
|
||||
from game.theater import NavalGroundObject
|
||||
from game.theater.theatergroundobject import NavalGroundObject
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
|
||||
|
||||
@ -18,7 +18,6 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from game import db
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import Airfield, ControlPoint
|
||||
@ -136,10 +135,8 @@ class Debriefing:
|
||||
self.game = game
|
||||
self.unit_map = unit_map
|
||||
|
||||
self.player_country = game.player_country
|
||||
self.enemy_country = game.enemy_country
|
||||
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.player_country = game.blue.country_name
|
||||
self.enemy_country = game.red.country_name
|
||||
|
||||
self.air_losses = self.dead_aircraft()
|
||||
self.ground_losses = self.dead_ground_units()
|
||||
|
||||
@ -53,7 +53,7 @@ class Event:
|
||||
|
||||
@property
|
||||
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
|
||||
def tasks(self) -> List[Type[Task]]:
|
||||
@ -114,10 +114,10 @@ class Event:
|
||||
|
||||
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
|
||||
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.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:
|
||||
@ -154,8 +154,8 @@ class Event:
|
||||
pilot.record.missions_flown += 1
|
||||
|
||||
def commit_pilot_experience(self) -> None:
|
||||
self._commit_pilot_experience(self.game.blue_ato)
|
||||
self._commit_pilot_experience(self.game.red_ato)
|
||||
self._commit_pilot_experience(self.game.blue.ato)
|
||||
self._commit_pilot_experience(self.game.red.ato)
|
||||
|
||||
@staticmethod
|
||||
def commit_front_line_losses(debriefing: Debriefing) -> None:
|
||||
|
||||
299
game/game.py
299
game/game.py
@ -1,13 +1,11 @@
|
||||
import itertools
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import sys
|
||||
from collections import Iterator
|
||||
from datetime import date, datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, List, Type, Union, cast
|
||||
|
||||
from dcs.action import Coalition
|
||||
from dcs.mapping import Point
|
||||
from dcs.task import CAP, CAS, PinpointStrike
|
||||
from dcs.vehicles import AirDefence
|
||||
@ -19,28 +17,25 @@ from game.plugins import LuaPluginManager
|
||||
from gen import naming
|
||||
from gen.ato import AirTaskingOrder
|
||||
from gen.conflictgen import Conflict
|
||||
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from gen.flights.flight import FlightType
|
||||
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
||||
from . import persistency
|
||||
from .commander import TheaterCommander
|
||||
from .coalition import Coalition
|
||||
from .debriefing import Debriefing
|
||||
from .event.event import Event
|
||||
from .event.frontlineattack import FrontlineAttackEvent
|
||||
from .factions.faction import Faction
|
||||
from .income import Income
|
||||
from .infos.information import Information
|
||||
from .navmesh import NavMesh
|
||||
from .procurement import AircraftProcurementRequest, ProcurementAi
|
||||
from .profiling import logged_duration, MultiEventTracer
|
||||
from .settings import Settings, AutoAtoBehavior
|
||||
from .procurement import AircraftProcurementRequest
|
||||
from .profiling import logged_duration
|
||||
from .settings import Settings
|
||||
from .squadrons import AirWing
|
||||
from .theater import ConflictTheater, ControlPoint
|
||||
from .theater.bullseye import Bullseye
|
||||
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
|
||||
from .threatzones import ThreatZones
|
||||
from .transfers import PendingTransfers
|
||||
from .unitmap import UnitMap
|
||||
from .weather import Conditions, TimeOfDay
|
||||
|
||||
@ -98,10 +93,6 @@ class Game:
|
||||
self.settings = settings
|
||||
self.events: List[Event] = []
|
||||
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
|
||||
# increment this to turn 0 before it reaches the player.
|
||||
self.turn = -1
|
||||
@ -124,108 +115,70 @@ class Game:
|
||||
|
||||
self.conditions = self.generate_conditions()
|
||||
|
||||
self.blue_transit_network = TransitNetwork()
|
||||
self.red_transit_network = TransitNetwork()
|
||||
|
||||
self.blue_procurement_requests: List[AircraftProcurementRequest] = []
|
||||
self.red_procurement_requests: List[AircraftProcurementRequest] = []
|
||||
|
||||
self.blue_ato = AirTaskingOrder()
|
||||
self.red_ato = AirTaskingOrder()
|
||||
|
||||
self.blue_bullseye = Bullseye(Point(0, 0))
|
||||
self.red_bullseye = Bullseye(Point(0, 0))
|
||||
self.sanitize_sides(player_faction, enemy_faction)
|
||||
self.blue = Coalition(self, player_faction, player_budget, player=True)
|
||||
self.red = Coalition(self, enemy_faction, enemy_budget, player=False)
|
||||
self.blue.set_opponent(self.red)
|
||||
self.red.set_opponent(self.blue)
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
self.__dict__.update(state)
|
||||
# Regenerate any state that was not persisted.
|
||||
self.on_load()
|
||||
|
||||
@property
|
||||
def coalitions(self) -> Iterator[Coalition]:
|
||||
yield self.blue
|
||||
yield self.red
|
||||
|
||||
def ato_for(self, player: bool) -> AirTaskingOrder:
|
||||
if player:
|
||||
return self.blue_ato
|
||||
return self.red_ato
|
||||
return self.coalition_for(player).ato
|
||||
|
||||
def procurement_requests_for(
|
||||
self, player: bool
|
||||
) -> List[AircraftProcurementRequest]:
|
||||
if player:
|
||||
return self.blue_procurement_requests
|
||||
return self.red_procurement_requests
|
||||
) -> list[AircraftProcurementRequest]:
|
||||
return self.coalition_for(player).procurement_requests
|
||||
|
||||
def transit_network_for(self, player: bool) -> TransitNetwork:
|
||||
if player:
|
||||
return self.blue_transit_network
|
||||
return self.red_transit_network
|
||||
return self.coalition_for(player).transit_network
|
||||
|
||||
def generate_conditions(self) -> Conditions:
|
||||
return Conditions.generate(
|
||||
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
|
||||
:return:
|
||||
"""
|
||||
if self.player_country == self.enemy_country:
|
||||
if self.player_country == "USA":
|
||||
self.enemy_country = "USAF Aggressors"
|
||||
elif self.player_country == "Russia":
|
||||
self.enemy_country = "USSR"
|
||||
if player_faction.country == enemy_faction.country:
|
||||
if player_faction.country == "USA":
|
||||
enemy_faction.country = "USAF Aggressors"
|
||||
elif player_faction.country == "Russia":
|
||||
enemy_faction.country = "USSR"
|
||||
else:
|
||||
self.enemy_country = "Russia"
|
||||
enemy_faction.country = "Russia"
|
||||
|
||||
def faction_for(self, player: bool) -> Faction:
|
||||
if player:
|
||||
return self.player_faction
|
||||
return self.enemy_faction
|
||||
return self.coalition_for(player).faction
|
||||
|
||||
def faker_for(self, player: bool) -> Faker:
|
||||
if player:
|
||||
return self.blue_faker
|
||||
return self.red_faker
|
||||
return self.coalition_for(player).faker
|
||||
|
||||
def air_wing_for(self, player: bool) -> AirWing:
|
||||
if player:
|
||||
return self.blue_air_wing
|
||||
return self.red_air_wing
|
||||
return self.coalition_for(player).air_wing
|
||||
|
||||
def country_for(self, player: bool) -> str:
|
||||
if player:
|
||||
return self.player_country
|
||||
return self.enemy_country
|
||||
return self.coalition_for(player).country_name
|
||||
|
||||
def bullseye_for(self, player: bool) -> Bullseye:
|
||||
if player:
|
||||
return self.blue_bullseye
|
||||
return self.red_bullseye
|
||||
return self.coalition_for(player).bullseye
|
||||
|
||||
def _generate_player_event(
|
||||
self, event_class: Type[Event], player_cp: ControlPoint, enemy_cp: ControlPoint
|
||||
@ -236,8 +189,8 @@ class Game:
|
||||
player_cp,
|
||||
enemy_cp,
|
||||
enemy_cp.position,
|
||||
self.player_faction.name,
|
||||
self.enemy_faction.name,
|
||||
self.blue.faction.name,
|
||||
self.red.faction.name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -249,20 +202,13 @@ class Game:
|
||||
front_line.red_cp,
|
||||
)
|
||||
|
||||
def adjust_budget(self, amount: float, player: bool) -> None:
|
||||
def coalition_for(self, player: bool) -> Coalition:
|
||||
if player:
|
||||
self.budget += amount
|
||||
else:
|
||||
self.enemy_budget += amount
|
||||
return self.blue
|
||||
return self.red
|
||||
|
||||
def process_player_income(self) -> None:
|
||||
self.budget += Income(self, player=True).total
|
||||
|
||||
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
|
||||
def adjust_budget(self, amount: float, player: bool) -> None:
|
||||
self.coalition_for(player).adjust_budget(amount)
|
||||
|
||||
@staticmethod
|
||||
def initiate_event(event: Event) -> UnitMap:
|
||||
@ -293,12 +239,6 @@ class Game:
|
||||
self.compute_conflicts_position()
|
||||
if not game_still_initializing:
|
||||
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:
|
||||
"""Finalizes the current turn and advances to the next turn.
|
||||
@ -333,23 +273,16 @@ class Game:
|
||||
)
|
||||
self.turn += 1
|
||||
|
||||
# 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.compute_transit_networks()
|
||||
# The coalition-specific turn finalization *must* happen before unit deliveries,
|
||||
# since the coalition-specific finalization handles transit network updates and
|
||||
# transfer processing. If in the other order, units may be delivered to captured
|
||||
# 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:
|
||||
control_point.process_turn(self)
|
||||
|
||||
self.blue_air_wing.replenish()
|
||||
self.red_air_wing.replenish()
|
||||
|
||||
if not skipped:
|
||||
for cp in self.theater.player_points():
|
||||
cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY)
|
||||
@ -360,9 +293,6 @@ class Game:
|
||||
|
||||
self.conditions = self.generate_conditions()
|
||||
|
||||
self.process_enemy_income()
|
||||
self.process_player_income()
|
||||
|
||||
def begin_turn_0(self) -> None:
|
||||
"""Initialization for the first turn of the game."""
|
||||
self.turn = 0
|
||||
@ -402,8 +332,8 @@ class Game:
|
||||
|
||||
def set_bullseye(self) -> None:
|
||||
player_cp, enemy_cp = self.theater.closest_opposing_control_points()
|
||||
self.blue_bullseye = Bullseye(enemy_cp.position)
|
||||
self.red_bullseye = Bullseye(player_cp.position)
|
||||
self.blue.bullseye = Bullseye(enemy_cp.position)
|
||||
self.red.bullseye = Bullseye(player_cp.position)
|
||||
|
||||
def initialize_turn(self, for_red: bool = True, for_blue: bool = True) -> None:
|
||||
"""Performs turn initialization for the specified players.
|
||||
@ -451,13 +381,20 @@ class Game:
|
||||
if turn_state in (TurnState.LOSS, TurnState.WIN):
|
||||
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
|
||||
if for_red:
|
||||
self.initialize_turn_for(player=False)
|
||||
if for_blue:
|
||||
self.initialize_turn_for(player=True)
|
||||
if for_red:
|
||||
self.initialize_turn_for(player=False)
|
||||
|
||||
# Plan GroundWar
|
||||
self.ground_planners = {}
|
||||
for cp in self.theater.controlpoints:
|
||||
if cp.has_frontline:
|
||||
gplanner = GroundPlanner(cp, self)
|
||||
@ -465,83 +402,10 @@ class Game:
|
||||
self.ground_planners[cp.id] = gplanner
|
||||
|
||||
def initialize_turn_for(self, player: bool) -> None:
|
||||
"""Processes coalition-specific turn initialization.
|
||||
|
||||
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.reset(player)
|
||||
for cp in self.theater.control_points_for(player):
|
||||
self.aircraft_inventory.set_from_control_point(cp)
|
||||
# Refund all pending deliveries for opfor and if player
|
||||
# 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)
|
||||
self.coalition_for(player).initialize_turn()
|
||||
|
||||
def message(self, text: str) -> None:
|
||||
self.informations.append(Information(text, turn=self.turn))
|
||||
@ -568,32 +432,20 @@ class Game:
|
||||
self.current_group_id += 1
|
||||
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:
|
||||
return TransitNetworkBuilder(self.theater, player).build()
|
||||
|
||||
def compute_threat_zones(self) -> None:
|
||||
self.blue_threat_zone = ThreatZones.for_faction(self, player=True)
|
||||
self.red_threat_zone = ThreatZones.for_faction(self, player=False)
|
||||
self.blue_navmesh = NavMesh.from_threat_zones(
|
||||
self.red_threat_zone, self.theater
|
||||
)
|
||||
self.red_navmesh = NavMesh.from_threat_zones(
|
||||
self.blue_threat_zone, self.theater
|
||||
)
|
||||
self.blue.compute_threat_zones()
|
||||
self.red.compute_threat_zones()
|
||||
self.blue.compute_nav_meshes()
|
||||
self.red.compute_nav_meshes()
|
||||
|
||||
def threat_zone_for(self, player: bool) -> ThreatZones:
|
||||
if player:
|
||||
return self.blue_threat_zone
|
||||
return self.red_threat_zone
|
||||
return self.coalition_for(player).threat_zone
|
||||
|
||||
def navmesh_for(self, player: bool) -> NavMesh:
|
||||
if player:
|
||||
return self.blue_navmesh
|
||||
return self.red_navmesh
|
||||
return self.coalition_for(player).nav_mesh
|
||||
|
||||
def compute_conflicts_position(self) -> None:
|
||||
"""
|
||||
@ -636,7 +488,7 @@ class Game:
|
||||
if cpoint is not None:
|
||||
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:
|
||||
if package.primary_task is FlightType.BARCAP:
|
||||
# BARCAPs will be planned at most locations on smaller theaters,
|
||||
@ -682,25 +534,6 @@ class Game:
|
||||
"""
|
||||
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:
|
||||
if turn_state is TurnState.WIN:
|
||||
self.message(
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING, Type
|
||||
|
||||
from dcs.unittype import FlyingType
|
||||
from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from gen.flights.flight import Flight
|
||||
@ -86,10 +84,11 @@ class GlobalAircraftInventory:
|
||||
cp: ControlPointAircraftInventory(cp) for cp in control_points
|
||||
}
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Clears all control points and their inventories."""
|
||||
def reset(self, for_player: bool) -> None:
|
||||
"""Clears the inventory of every control point owned by the given coalition."""
|
||||
for inventory in self.inventories.values():
|
||||
inventory.clear()
|
||||
if inventory.control_point.captured == for_player:
|
||||
inventory.clear()
|
||||
|
||||
def set_from_control_point(self, control_point: ControlPoint) -> None:
|
||||
"""Set the control point's aircraft inventory.
|
||||
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
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.action import DoScript, DoScriptFile
|
||||
@ -81,10 +81,10 @@ class Operation:
|
||||
return Conflict(
|
||||
cls.game.theater,
|
||||
FrontLine(player_cp, enemy_cp),
|
||||
cls.game.player_faction.name,
|
||||
cls.game.enemy_faction.name,
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
cls.game.blue.faction.name,
|
||||
cls.game.red.faction.name,
|
||||
cls.current_mission.country(cls.game.blue.country_name),
|
||||
cls.current_mission.country(cls.game.red.country_name),
|
||||
mid_point,
|
||||
)
|
||||
|
||||
@ -95,14 +95,14 @@ class Operation:
|
||||
@classmethod
|
||||
def _setup_mission_coalitions(cls) -> None:
|
||||
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(
|
||||
"red", bullseye=cls.game.red_bullseye.to_pydcs()
|
||||
"red", bullseye=cls.game.red.bullseye.to_pydcs()
|
||||
)
|
||||
|
||||
p_country = cls.game.player_country
|
||||
e_country = cls.game.enemy_country
|
||||
p_country = cls.game.blue.country_name
|
||||
e_country = cls.game.red.country_name
|
||||
cls.current_mission.coalition["blue"].add_country(
|
||||
country_dict[db.country_id_from_name(p_country)]()
|
||||
)
|
||||
@ -268,7 +268,7 @@ class Operation:
|
||||
and cls.game.settings.perf_destroyed_units
|
||||
):
|
||||
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="",
|
||||
_type=utype,
|
||||
hidden=True,
|
||||
@ -358,18 +358,18 @@ class Operation:
|
||||
cls.airgen.clear_parking_slots()
|
||||
|
||||
cls.airgen.generate_flights(
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
cls.game.blue_ato,
|
||||
cls.current_mission.country(cls.game.blue.country_name),
|
||||
cls.game.blue.ato,
|
||||
cls.groundobjectgen.runways,
|
||||
)
|
||||
cls.airgen.generate_flights(
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
cls.game.red_ato,
|
||||
cls.current_mission.country(cls.game.red.country_name),
|
||||
cls.game.red.ato,
|
||||
cls.groundobjectgen.runways,
|
||||
)
|
||||
cls.airgen.spawn_unused_aircraft(
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
cls.current_mission.country(cls.game.blue.country_name),
|
||||
cls.current_mission.country(cls.game.red.country_name),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -380,10 +380,10 @@ class Operation:
|
||||
player_cp = front_line.blue_cp
|
||||
enemy_cp = front_line.red_cp
|
||||
conflict = Conflict.frontline_cas_conflict(
|
||||
cls.game.player_faction.name,
|
||||
cls.game.enemy_faction.name,
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
cls.game.blue.faction.name,
|
||||
cls.game.red.faction.name,
|
||||
cls.current_mission.country(cls.game.blue.country_name),
|
||||
cls.current_mission.country(cls.game.red.country_name),
|
||||
front_line,
|
||||
cls.game.theater,
|
||||
)
|
||||
|
||||
@ -72,7 +72,9 @@ class ProcurementAi:
|
||||
return 1
|
||||
|
||||
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
|
||||
cp_aircraft = cp.allocated_aircraft(self.game)
|
||||
aircraft_investment += cp_aircraft.total_value
|
||||
@ -316,7 +318,9 @@ class ProcurementAi:
|
||||
continue
|
||||
|
||||
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:
|
||||
# Control point is already sufficiently defended.
|
||||
continue
|
||||
@ -343,7 +347,9 @@ class ProcurementAi:
|
||||
if not cp.can_recruit_ground_units(self.game):
|
||||
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:
|
||||
continue
|
||||
|
||||
@ -356,7 +362,9 @@ class ProcurementAi:
|
||||
def cost_ratio_of_ground_unit(
|
||||
self, control_point: ControlPoint, unit_class: GroundUnitClass
|
||||
) -> 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
|
||||
total_cost = 0
|
||||
for unit_type, count in allocations.all.items():
|
||||
|
||||
@ -20,10 +20,11 @@ import yaml
|
||||
from faker import Faker
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.settings import AutoAtoBehavior
|
||||
from game.settings import AutoAtoBehavior, Settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.coalition import Coalition
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
|
||||
@ -96,16 +97,13 @@ class Squadron:
|
||||
init=False, hash=False, compare=False
|
||||
)
|
||||
|
||||
# We need a reference to the Game so that we can access the Faker without needing to
|
||||
# persist it to the save game, or having to reconstruct it (it's not cheap) each
|
||||
# time we create or load a squadron.
|
||||
game: Game = field(hash=False, compare=False)
|
||||
player: bool
|
||||
coalition: Coalition = field(hash=False, compare=False)
|
||||
settings: Settings = field(hash=False, compare=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if any(p.status is not PilotStatus.Active for p in self.pilot_pool):
|
||||
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)
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -113,9 +111,13 @@ class Squadron:
|
||||
return self.name
|
||||
return f'{self.name} "{self.nickname}"'
|
||||
|
||||
@property
|
||||
def player(self) -> bool:
|
||||
return self.coalition.player
|
||||
|
||||
@property
|
||||
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]:
|
||||
if self.pilot_limits_enabled:
|
||||
@ -131,7 +133,7 @@ class Squadron:
|
||||
if not self.player:
|
||||
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.
|
||||
if preference is AutoAtoBehavior.Default:
|
||||
@ -184,7 +186,7 @@ class Squadron:
|
||||
return
|
||||
|
||||
replenish_count = min(
|
||||
self.game.settings.squadron_replenishment_rate,
|
||||
self.settings.squadron_replenishment_rate,
|
||||
self._number_of_unfilled_pilot_slots,
|
||||
)
|
||||
if replenish_count > 0:
|
||||
@ -206,7 +208,7 @@ class Squadron:
|
||||
|
||||
@property
|
||||
def faker(self) -> Faker:
|
||||
return self.game.faker_for(self.player)
|
||||
return self.coalition.faker
|
||||
|
||||
def _pilots_with_status(self, status: PilotStatus) -> list[Pilot]:
|
||||
return [p for p in self.current_roster if p.status == status]
|
||||
@ -228,7 +230,7 @@ class Squadron:
|
||||
|
||||
@property
|
||||
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
|
||||
def number_of_available_pilots(self) -> int:
|
||||
@ -252,7 +254,7 @@ class Squadron:
|
||||
return self.current_roster[index]
|
||||
|
||||
@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.flight import FlightType
|
||||
|
||||
@ -287,8 +289,8 @@ class Squadron:
|
||||
livery=data.get("livery"),
|
||||
mission_types=tuple(mission_types),
|
||||
pilot_pool=pilots,
|
||||
game=game,
|
||||
player=player,
|
||||
coalition=coalition,
|
||||
settings=game.settings,
|
||||
)
|
||||
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
@ -299,9 +301,9 @@ class Squadron:
|
||||
|
||||
|
||||
class SquadronLoader:
|
||||
def __init__(self, game: Game, player: bool) -> None:
|
||||
def __init__(self, game: Game, coalition: Coalition) -> None:
|
||||
self.game = game
|
||||
self.player = player
|
||||
self.coalition = coalition
|
||||
|
||||
@staticmethod
|
||||
def squadron_directories() -> Iterator[Path]:
|
||||
@ -312,8 +314,8 @@ class SquadronLoader:
|
||||
|
||||
def load(self) -> dict[AircraftType, list[Squadron]]:
|
||||
squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
|
||||
country = self.game.country_for(self.player)
|
||||
faction = self.game.faction_for(self.player)
|
||||
country = self.coalition.country_name
|
||||
faction = self.coalition.faction
|
||||
any_country = country.startswith("Combined Joint Task Forces ")
|
||||
for directory in self.squadron_directories():
|
||||
for path, squadron in self.load_squadrons_from(directory):
|
||||
@ -347,7 +349,7 @@ class SquadronLoader:
|
||||
for squadron_path in directory.glob("*/*.yaml"):
|
||||
try:
|
||||
yield squadron_path, Squadron.from_yaml(
|
||||
squadron_path, self.game, self.player
|
||||
squadron_path, self.game, self.coalition
|
||||
)
|
||||
except Exception as ex:
|
||||
raise RuntimeError(
|
||||
@ -356,29 +358,28 @@ class SquadronLoader:
|
||||
|
||||
|
||||
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
|
||||
|
||||
self.game = game
|
||||
self.player = player
|
||||
self.squadrons = SquadronLoader(game, player).load()
|
||||
self.squadrons = SquadronLoader(game, coalition).load()
|
||||
|
||||
count = itertools.count(1)
|
||||
for aircraft in game.faction_for(player).aircrafts:
|
||||
for aircraft in coalition.faction.aircrafts:
|
||||
if aircraft in self.squadrons:
|
||||
continue
|
||||
self.squadrons[aircraft] = [
|
||||
Squadron(
|
||||
name=f"Squadron {next(count):03}",
|
||||
nickname=self.random_nickname(),
|
||||
country=game.country_for(player),
|
||||
country=coalition.country_name,
|
||||
role="Flying Squadron",
|
||||
aircraft=aircraft,
|
||||
livery=None,
|
||||
mission_types=tuple(tasks_for_aircraft(aircraft)),
|
||||
pilot_pool=[],
|
||||
game=game,
|
||||
player=player,
|
||||
coalition=coalition,
|
||||
settings=game.settings,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@ -40,11 +40,7 @@ from gen.ground_forces.combat_stance import CombatStance
|
||||
from gen.runways import RunwayAssigner, RunwayData
|
||||
from .base import Base
|
||||
from .missiontarget import MissionTarget
|
||||
from .theatergroundobject import (
|
||||
GenericCarrierGroundObject,
|
||||
TheaterGroundObject,
|
||||
NavalGroundObject,
|
||||
)
|
||||
from .theatergroundobject import GenericCarrierGroundObject, TheaterGroundObject
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..utils import nautical_miles
|
||||
@ -606,7 +602,7 @@ class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
# TODO: Should be Airbase specific.
|
||||
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_air_units(game)
|
||||
self.depopulate_uncapturable_tgos()
|
||||
@ -623,11 +619,7 @@ class ControlPoint(MissionTarget, ABC):
|
||||
...
|
||||
|
||||
def aircraft_transferring(self, game: Game) -> dict[AircraftType, int]:
|
||||
if self.captured:
|
||||
ato = game.blue_ato
|
||||
else:
|
||||
ato = game.red_ato
|
||||
|
||||
ato = game.coalition_for(self.captured).ato
|
||||
transferring: defaultdict[AircraftType, int] = defaultdict(int)
|
||||
for package in ato.packages:
|
||||
for flight in package.flights:
|
||||
|
||||
@ -11,7 +11,7 @@ from dcs.mapping import Point
|
||||
from dcs.task import CAP, CAS, PinpointStrike
|
||||
from dcs.vehicles import AirDefence
|
||||
|
||||
from game import Game, db
|
||||
from game import Game
|
||||
from game.factions.faction import Faction
|
||||
from game.scenery_group import SceneryGroup
|
||||
from game.theater import Carrier, Lha, PointWithHeading
|
||||
@ -171,14 +171,11 @@ class ControlPointGroundObjectGenerator:
|
||||
|
||||
@property
|
||||
def faction_name(self) -> str:
|
||||
if self.control_point.captured:
|
||||
return self.game.player_faction.name
|
||||
else:
|
||||
return self.game.enemy_faction.name
|
||||
return self.faction.name
|
||||
|
||||
@property
|
||||
def faction(self) -> Faction:
|
||||
return db.FACTIONS[self.faction_name]
|
||||
return self.game.coalition_for(self.control_point.captured).faction
|
||||
|
||||
def generate(self) -> bool:
|
||||
self.control_point.connected_objectives = []
|
||||
|
||||
@ -316,7 +316,9 @@ class AirliftPlanner:
|
||||
capacity = flight_size * capacity_each
|
||||
|
||||
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:
|
||||
transfer = self.transfer
|
||||
|
||||
@ -534,8 +536,9 @@ class CargoShipMap(TransportMap[CargoShip]):
|
||||
|
||||
|
||||
class PendingTransfers:
|
||||
def __init__(self, game: Game) -> None:
|
||||
def __init__(self, game: Game, player: bool) -> None:
|
||||
self.game = game
|
||||
self.player = player
|
||||
self.convoys = ConvoyMap()
|
||||
self.cargo_ships = CargoShipMap()
|
||||
self.pending_transfers: List[TransferOrder] = []
|
||||
@ -609,7 +612,7 @@ class PendingTransfers:
|
||||
flight = transport.flight
|
||||
flight.package.remove_flight(flight)
|
||||
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)
|
||||
flight.clear_roster()
|
||||
|
||||
@ -647,7 +650,7 @@ class PendingTransfers:
|
||||
self.arrange_transport(transfer)
|
||||
|
||||
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(
|
||||
FlightType.TRANSPORT
|
||||
):
|
||||
@ -682,7 +685,7 @@ class PendingTransfers:
|
||||
# aesthetic.
|
||||
gap += 1
|
||||
|
||||
self.game.procurement_requests_for(player=control_point.captured).append(
|
||||
self.game.procurement_requests_for(self.player).append(
|
||||
AircraftProcurementRequest(
|
||||
control_point, nautical_miles(200), FlightType.TRANSPORT, gap
|
||||
)
|
||||
|
||||
@ -6,6 +6,7 @@ from dataclasses import dataclass
|
||||
from typing import Optional, TYPE_CHECKING, Any
|
||||
|
||||
from game.theater import ControlPoint
|
||||
from .coalition import Coalition
|
||||
from .dcs.groundunittype import GroundUnitType
|
||||
from .dcs.unittype import UnitType
|
||||
from .theater.transitnetwork import (
|
||||
@ -41,24 +42,22 @@ class PendingUnitDeliveries:
|
||||
for k, v in units.items():
|
||||
self.units[k] -= v
|
||||
|
||||
def refund_all(self, game: Game) -> None:
|
||||
self.refund(game, self.units)
|
||||
def refund_all(self, coalition: Coalition) -> None:
|
||||
self.refund(coalition, self.units)
|
||||
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] = {
|
||||
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():
|
||||
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():
|
||||
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
|
||||
game.adjust_budget(
|
||||
unit_type.price * count, player=self.destination.captured
|
||||
)
|
||||
coalition.adjust_budget(unit_type.price * count)
|
||||
|
||||
def pending_orders(self, unit_type: UnitType[Any]) -> int:
|
||||
pending_units = self.units.get(unit_type)
|
||||
@ -71,19 +70,20 @@ class PendingUnitDeliveries:
|
||||
return self.pending_orders(unit_type) + current_units
|
||||
|
||||
def process(self, game: Game) -> None:
|
||||
coalition = game.coalition_for(self.destination.captured)
|
||||
ground_unit_source = self.find_ground_unit_source(game)
|
||||
if ground_unit_source is None:
|
||||
game.message(
|
||||
f"{self.destination.name} lost its source for ground unit "
|
||||
"reinforcements. Refunding purchase price."
|
||||
)
|
||||
self.refund_ground_units(game)
|
||||
self.refund_ground_units(coalition)
|
||||
|
||||
bought_units: dict[UnitType[Any], int] = {}
|
||||
units_needing_transfer: dict[GroundUnitType, int] = {}
|
||||
sold_units: dict[UnitType[Any], int] = {}
|
||||
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]
|
||||
if (
|
||||
isinstance(unit_type, GroundUnitType)
|
||||
@ -98,11 +98,11 @@ class PendingUnitDeliveries:
|
||||
if count >= 0:
|
||||
d[unit_type] = count
|
||||
game.message(
|
||||
f"{coalition} reinforcements: {unit_type} x {count} at {source}"
|
||||
f"{allegiance} reinforcements: {unit_type} x {count} at {source}"
|
||||
)
|
||||
else:
|
||||
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.destination.base.commission_units(bought_units)
|
||||
@ -111,16 +111,19 @@ class PendingUnitDeliveries:
|
||||
if units_needing_transfer:
|
||||
if ground_unit_source is None:
|
||||
raise RuntimeError(
|
||||
f"ground unit source could not be found for {self.destination} but still tried to "
|
||||
f"transfer units to there"
|
||||
f"Ground unit source could not be found for {self.destination} but "
|
||||
"still tried to transfer units to there"
|
||||
)
|
||||
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(
|
||||
self, game: Game, source: ControlPoint, units: dict[GroundUnitType, int]
|
||||
self,
|
||||
coalition: Coalition,
|
||||
source: ControlPoint,
|
||||
units: dict[GroundUnitType, int],
|
||||
) -> 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]:
|
||||
# This is running *after* the turn counter has been incremented, so this is the
|
||||
|
||||
@ -22,7 +22,6 @@ from dcs.planes import (
|
||||
C_101EB,
|
||||
F_14B,
|
||||
JF_17,
|
||||
PlaneType,
|
||||
Su_33,
|
||||
Tu_22M3,
|
||||
)
|
||||
@ -262,8 +261,8 @@ class AircraftConflictGenerator:
|
||||
@cached_property
|
||||
def use_client(self) -> bool:
|
||||
"""True if Client should be used instead of Player."""
|
||||
blue_clients = self.client_slots_in_ato(self.game.blue_ato)
|
||||
red_clients = self.client_slots_in_ato(self.game.red_ato)
|
||||
blue_clients = self.client_slots_in_ato(self.game.blue.ato)
|
||||
red_clients = self.client_slots_in_ato(self.game.red.ato)
|
||||
return blue_clients + red_clients > 1
|
||||
|
||||
@staticmethod
|
||||
@ -601,12 +600,11 @@ class AircraftConflictGenerator:
|
||||
if not isinstance(control_point, Airfield):
|
||||
continue
|
||||
|
||||
faction = self.game.coalition_for(control_point.captured).faction
|
||||
if control_point.captured:
|
||||
country = player_country
|
||||
faction = self.game.player_faction
|
||||
else:
|
||||
country = enemy_country
|
||||
faction = self.game.enemy_faction
|
||||
|
||||
for aircraft, available in inventory.all_aircraft:
|
||||
try:
|
||||
@ -699,11 +697,7 @@ class AircraftConflictGenerator:
|
||||
if flight.from_cp.cptype != ControlPointType.AIRBASE:
|
||||
return
|
||||
|
||||
if flight.from_cp.captured:
|
||||
coalition = self.game.get_player_coalition_id()
|
||||
else:
|
||||
coalition = self.game.get_enemy_coalition_id()
|
||||
|
||||
coalition = self.game.coalition_for(flight.departure.captured).coalition_id
|
||||
trigger.add_condition(CoalitionHasAirdrome(coalition, flight.from_cp.id))
|
||||
|
||||
def generate_planned_flight(
|
||||
|
||||
@ -105,6 +105,8 @@ class AirSupportConflictGenerator:
|
||||
else self.conflict.red_cp
|
||||
)
|
||||
|
||||
country = self.mission.country(self.game.blue.country_name)
|
||||
|
||||
if not self.game.settings.disable_legacy_tanker:
|
||||
fallback_tanker_number = 0
|
||||
|
||||
@ -130,10 +132,8 @@ class AirSupportConflictGenerator:
|
||||
tanker_heading, TANKER_DISTANCE
|
||||
)
|
||||
tanker_group = self.mission.refuel_flight(
|
||||
country=self.mission.country(self.game.player_country),
|
||||
name=namegen.next_tanker_name(
|
||||
self.mission.country(self.game.player_country), tanker_unit_type
|
||||
),
|
||||
country=country,
|
||||
name=namegen.next_tanker_name(country, tanker_unit_type),
|
||||
airport=None,
|
||||
plane_type=unit_type,
|
||||
position=tanker_position,
|
||||
@ -211,10 +211,8 @@ class AirSupportConflictGenerator:
|
||||
return
|
||||
|
||||
awacs_flight = self.mission.awacs_flight(
|
||||
country=self.mission.country(self.game.player_country),
|
||||
name=namegen.next_awacs_name(
|
||||
self.mission.country(self.game.player_country)
|
||||
),
|
||||
country=country,
|
||||
name=namegen.next_awacs_name(country),
|
||||
plane_type=unit_type,
|
||||
altitude=AWACS_ALT,
|
||||
airport=None,
|
||||
|
||||
@ -144,16 +144,16 @@ class GroundConflictGenerator:
|
||||
)
|
||||
|
||||
# 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)
|
||||
code = 1688 - len(self.jtacs)
|
||||
|
||||
utype = self.game.player_faction.jtac_unit
|
||||
utype = self.game.blue.faction.jtac_unit
|
||||
if utype is None:
|
||||
utype = AircraftType.named("MQ-9 Reaper")
|
||||
|
||||
jtac = self.mission.flight_group(
|
||||
country=self.mission.country(self.game.player_country),
|
||||
country=self.mission.country(self.game.blue.country_name),
|
||||
name=n,
|
||||
aircraft_type=utype.dcs_unit_type,
|
||||
position=position[0],
|
||||
@ -715,7 +715,7 @@ class GroundConflictGenerator:
|
||||
if is_player
|
||||
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:
|
||||
if group.role == CombatGroupRole.ARTILLERY:
|
||||
distance_from_frontline = (
|
||||
|
||||
@ -24,12 +24,13 @@ class CargoShipGenerator:
|
||||
|
||||
def generate(self) -> None:
|
||||
# Reset the count to make generation deterministic.
|
||||
for ship in self.game.transfers.cargo_ships:
|
||||
self.generate_cargo_ship(ship)
|
||||
for coalition in self.game.coalitions:
|
||||
for ship in coalition.transfers.cargo_ships:
|
||||
self.generate_cargo_ship(ship)
|
||||
|
||||
def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup:
|
||||
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
|
||||
group = self.mission.ship_group(
|
||||
|
||||
@ -27,8 +27,9 @@ class ConvoyGenerator:
|
||||
|
||||
def generate(self) -> None:
|
||||
# Reset the count to make generation deterministic.
|
||||
for convoy in self.game.transfers.convoys:
|
||||
self.generate_convoy(convoy)
|
||||
for coalition in self.game.coalitions:
|
||||
for convoy in coalition.transfers.convoys:
|
||||
self.generate_convoy(convoy)
|
||||
|
||||
def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
|
||||
group = self._create_mixed_unit_group(
|
||||
@ -53,9 +54,7 @@ class ConvoyGenerator:
|
||||
units: dict[GroundUnitType, int],
|
||||
for_player: bool,
|
||||
) -> VehicleGroup:
|
||||
country = self.mission.country(
|
||||
self.game.player_country if for_player else self.game.enemy_country
|
||||
)
|
||||
country = self.mission.country(self.game.coalition_for(for_player).country_name)
|
||||
|
||||
unit_types = list(units.items())
|
||||
main_unit_type, main_unit_count = unit_types[0]
|
||||
|
||||
@ -228,7 +228,7 @@ class CoalitionMissionPlanner:
|
||||
self.game = game
|
||||
self.is_player = 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.procurement_requests = self.game.procurement_requests_for(self.is_player)
|
||||
self.faction: Faction = self.game.faction_for(self.is_player)
|
||||
|
||||
@ -38,8 +38,8 @@ class ForcedOptionsGenerator:
|
||||
self.mission.forced_options.labels = ForcedOptions.Labels.None_
|
||||
|
||||
def _set_unrestricted_satnav(self) -> None:
|
||||
blue = self.game.player_faction
|
||||
red = self.game.enemy_faction
|
||||
blue = self.game.blue.faction
|
||||
red = self.game.red.faction
|
||||
if blue.unrestricted_satnav or red.unrestricted_satnav:
|
||||
self.mission.forced_options.unrestricted_satnav = True
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ from typing import (
|
||||
from dcs import Mission, Point, unitgroup
|
||||
from dcs.action import SceneryDestructionZone
|
||||
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.task import (
|
||||
ActivateBeaconCommand,
|
||||
@ -36,8 +36,8 @@ from dcs.task import (
|
||||
)
|
||||
from dcs.triggers import TriggerStart, TriggerZone
|
||||
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
|
||||
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
|
||||
from dcs.unittype import StaticType, UnitType, ShipType, VehicleType
|
||||
from dcs.unitgroup import ShipGroup, StaticGroup, VehicleGroup
|
||||
from dcs.unittype import StaticType, ShipType, VehicleType
|
||||
from dcs.vehicles import vehicle_map
|
||||
|
||||
from game import db
|
||||
@ -587,13 +587,7 @@ class HelipadGenerator:
|
||||
self.tacan_registry = tacan_registry
|
||||
|
||||
def generate(self) -> None:
|
||||
|
||||
if self.cp.captured:
|
||||
country_name = self.game.player_country
|
||||
else:
|
||||
country_name = self.game.enemy_country
|
||||
country = self.m.country(country_name)
|
||||
|
||||
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
|
||||
for i, helipad in enumerate(self.cp.helipads):
|
||||
name = self.cp.name + "_helipad_" + str(i)
|
||||
logging.info("Generating helipad : " + name)
|
||||
@ -636,12 +630,7 @@ class GroundObjectsGenerator:
|
||||
|
||||
def generate(self) -> None:
|
||||
for cp in self.game.theater.controlpoints:
|
||||
if cp.captured:
|
||||
country_name = self.game.player_country
|
||||
else:
|
||||
country_name = self.game.enemy_country
|
||||
country = self.m.country(country_name)
|
||||
|
||||
country = self.m.country(self.game.coalition_for(cp.captured).country_name)
|
||||
HelipadGenerator(
|
||||
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
||||
).generate()
|
||||
|
||||
@ -97,7 +97,7 @@ class VisualGenerator:
|
||||
break
|
||||
|
||||
self.mission.static_group(
|
||||
self.mission.country(self.game.enemy_country),
|
||||
self.mission.country(self.game.red.country_name),
|
||||
"",
|
||||
_type=v,
|
||||
position=pos,
|
||||
|
||||
@ -12,11 +12,10 @@ from PySide2.QtCore import (
|
||||
)
|
||||
from PySide2.QtGui import QIcon
|
||||
|
||||
from game import db
|
||||
from game.game import Game
|
||||
from game.squadrons import Squadron, Pilot
|
||||
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.flights.flight import Flight, FlightType
|
||||
from gen.flights.traveltime import TotEstimator
|
||||
@ -281,9 +280,9 @@ class AtoModel(QAbstractListModel):
|
||||
self.package_models.clear()
|
||||
if self.game is not None:
|
||||
if player:
|
||||
self.ato = self.game.blue_ato
|
||||
self.ato = self.game.blue.ato
|
||||
else:
|
||||
self.ato = self.game.red_ato
|
||||
self.ato = self.game.red.ato
|
||||
else:
|
||||
self.ato = AirTaskingOrder()
|
||||
self.endResetModel()
|
||||
@ -316,8 +315,12 @@ class TransferModel(QAbstractListModel):
|
||||
super().__init__()
|
||||
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:
|
||||
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:
|
||||
if not index.isValid():
|
||||
@ -345,7 +348,7 @@ class TransferModel(QAbstractListModel):
|
||||
"""Updates the game with the new unit transfer."""
|
||||
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||
# TODO: Needs to regenerate base inventory tab.
|
||||
self.game_model.game.transfers.new_transfer(transfer)
|
||||
self.transfers.new_transfer(transfer)
|
||||
self.endInsertRows()
|
||||
|
||||
def cancel_transfer_at_index(self, index: QModelIndex) -> None:
|
||||
@ -354,15 +357,15 @@ class TransferModel(QAbstractListModel):
|
||||
|
||||
def cancel_transfer(self, transfer: TransferOrder) -> None:
|
||||
"""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)
|
||||
# TODO: Needs to regenerate base inventory tab.
|
||||
self.game_model.game.transfers.cancel_transfer(transfer)
|
||||
self.transfers.cancel_transfer(transfer)
|
||||
self.endRemoveRows()
|
||||
|
||||
def transfer_at_index(self, index: QModelIndex) -> TransferOrder:
|
||||
"""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):
|
||||
@ -488,8 +491,8 @@ class GameModel:
|
||||
self.ato_model = AtoModel(self, AirTaskingOrder())
|
||||
self.red_ato_model = AtoModel(self, AirTaskingOrder())
|
||||
else:
|
||||
self.ato_model = AtoModel(self, self.game.blue_ato)
|
||||
self.red_ato_model = AtoModel(self, self.game.red_ato)
|
||||
self.ato_model = AtoModel(self, self.game.blue.ato)
|
||||
self.red_ato_model = AtoModel(self, self.game.red.ato)
|
||||
|
||||
def ato_model_for(self, player: bool) -> AtoModel:
|
||||
if player:
|
||||
|
||||
@ -24,8 +24,8 @@ class QFactionsInfos(QGroupBox):
|
||||
|
||||
def setGame(self, game: Game):
|
||||
if game is not None:
|
||||
self.player_name.setText(game.player_faction.name)
|
||||
self.enemy_name.setText(game.enemy_faction.name)
|
||||
self.player_name.setText(game.blue.faction.name)
|
||||
self.enemy_name.setText(game.red.faction.name)
|
||||
else:
|
||||
self.player_name.setText("")
|
||||
self.enemy_name.setText("")
|
||||
|
||||
@ -168,7 +168,7 @@ class QTopPanel(QFrame):
|
||||
package.time_over_target = estimator.earliest_tot()
|
||||
|
||||
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:
|
||||
if flight.client_count > 0:
|
||||
return True
|
||||
@ -236,7 +236,7 @@ class QTopPanel(QFrame):
|
||||
|
||||
def check_no_missing_pilots(self) -> bool:
|
||||
missing_pilots = []
|
||||
for package in self.game.blue_ato.packages:
|
||||
for package in self.game.blue.ato.packages:
|
||||
for flight in package.flights:
|
||||
if flight.missing_pilots > 0:
|
||||
missing_pilots.append((package, flight))
|
||||
@ -282,8 +282,8 @@ class QTopPanel(QFrame):
|
||||
closest_cps[0],
|
||||
closest_cps[1],
|
||||
self.game.theater.controlpoints[0].position,
|
||||
self.game.player_faction.name,
|
||||
self.game.enemy_faction.name,
|
||||
self.game.blue.faction.name,
|
||||
self.game.red.faction.name,
|
||||
)
|
||||
|
||||
unit_map = self.game.initiate_event(game_event)
|
||||
|
||||
@ -4,7 +4,6 @@ from typing import Iterable, Type
|
||||
from PySide2.QtWidgets import QComboBox
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game import db
|
||||
from gen.flights.ai_flight_planner_db import aircraft_for_task
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
@ -13,16 +12,12 @@ class QAircraftTypeSelector(QComboBox):
|
||||
"""Combo box for selecting among the given aircraft types."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
aircraft_types: Iterable[Type[FlyingType]],
|
||||
country: str,
|
||||
mission_type: FlightType,
|
||||
self, aircraft_types: Iterable[Type[FlyingType]], mission_type: FlightType
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.model().sort(0)
|
||||
self.setSizeAdjustPolicy(self.AdjustToContents)
|
||||
self.country = country
|
||||
self.update_items(mission_type, aircraft_types)
|
||||
|
||||
def update_items(self, mission_type: FlightType, aircraft_types):
|
||||
|
||||
@ -336,8 +336,12 @@ class SupplyRouteJs(QObject):
|
||||
|
||||
def find_transports(self) -> List[MultiGroupTransport]:
|
||||
if self.sea_route:
|
||||
return self.find_in_transport_map(self.game.transfers.cargo_ships)
|
||||
return self.find_in_transport_map(self.game.transfers.convoys)
|
||||
return self.find_in_transport_map(
|
||||
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)
|
||||
def activeTransports(self) -> List[str]:
|
||||
@ -672,8 +676,8 @@ class NavMeshJs(QObject):
|
||||
@classmethod
|
||||
def from_game(cls, game: Game) -> NavMeshJs:
|
||||
return NavMeshJs(
|
||||
cls.to_polys(game.blue_navmesh, game.theater),
|
||||
cls.to_polys(game.red_navmesh, game.theater),
|
||||
cls.to_polys(game.blue.nav_mesh, game.theater),
|
||||
cls.to_polys(game.red.nav_mesh, game.theater),
|
||||
)
|
||||
|
||||
|
||||
@ -870,8 +874,8 @@ class MapModel(QObject):
|
||||
|
||||
def reset_atos(self) -> None:
|
||||
self._flights = self._flights_in_ato(
|
||||
self.game.blue_ato, blue=True
|
||||
) + self._flights_in_ato(self.game.red_ato, blue=False)
|
||||
self.game.blue.ato, blue=True
|
||||
) + self._flights_in_ato(self.game.red.ato, blue=False)
|
||||
self.flightsChanged.emit()
|
||||
|
||||
@Property(list, notify=flightsChanged)
|
||||
|
||||
@ -3,12 +3,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Iterator
|
||||
|
||||
from PySide2.QtCore import (
|
||||
QItemSelectionModel,
|
||||
QModelIndex,
|
||||
Qt,
|
||||
QSize,
|
||||
)
|
||||
from PySide2.QtCore import QItemSelectionModel, QModelIndex, QSize
|
||||
from PySide2.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QCheckBox,
|
||||
@ -183,7 +178,7 @@ class AirInventoryView(QWidget):
|
||||
self.table.setSortingEnabled(True)
|
||||
|
||||
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:
|
||||
yield from AircraftInventoryData.from_flight(flight)
|
||||
|
||||
|
||||
@ -73,11 +73,15 @@ class DepartingConvoysList(QFrame):
|
||||
task_box_layout = QGridLayout()
|
||||
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)
|
||||
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)
|
||||
task_box_layout.addWidget(group_info)
|
||||
|
||||
|
||||
@ -195,7 +195,9 @@ class QBaseMenu2(QDialog):
|
||||
ground_unit_limit = self.cp.frontline_unit_count_limit
|
||||
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(
|
||||
allocated.total_present - self.cp.frontline_unit_count_limit, 0
|
||||
)
|
||||
|
||||
@ -45,7 +45,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
||||
row = 0
|
||||
|
||||
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:
|
||||
continue
|
||||
if self.cp.is_lha and not unit_type.lha_capable:
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from PySide2 import QtCore
|
||||
from PySide2.QtGui import Qt
|
||||
from PySide2.QtWidgets import (
|
||||
QComboBox,
|
||||
@ -307,7 +306,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
|
||||
self.buySamBox = QGroupBox("Buy SAM site :")
|
||||
self.buyArmorBox = QGroupBox("Buy defensive position :")
|
||||
|
||||
faction = self.game.player_faction
|
||||
faction = self.game.blue.faction
|
||||
|
||||
# Sams
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ class QFlightCreator(QDialog):
|
||||
self.game = game
|
||||
self.package = package
|
||||
self.custom_name_text = None
|
||||
self.country = self.game.player_country
|
||||
self.country = self.game.blue.country_name
|
||||
|
||||
self.setWindowTitle("Create flight")
|
||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||
@ -52,7 +52,6 @@ class QFlightCreator(QDialog):
|
||||
|
||||
self.aircraft_selector = QAircraftTypeSelector(
|
||||
self.game.aircraft_inventory.available_types_for_player,
|
||||
self.game.player_country,
|
||||
self.task_selector.currentData(),
|
||||
)
|
||||
self.aircraft_selector.setCurrentIndex(0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user