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,
|
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)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
299
game/game.py
299
game/game.py
@ -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(
|
||||||
|
|||||||
@ -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,10 +84,11 @@ 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():
|
||||||
inventory.clear()
|
if inventory.control_point.captured == for_player:
|
||||||
|
inventory.clear()
|
||||||
|
|
||||||
def set_from_control_point(self, control_point: ControlPoint) -> None:
|
def set_from_control_point(self, control_point: ControlPoint) -> None:
|
||||||
"""Set the control point's aircraft inventory.
|
"""Set the control point's aircraft inventory.
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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():
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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 = []
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 = (
|
||||||
|
|||||||
@ -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:
|
||||||
self.generate_cargo_ship(ship)
|
for ship in coalition.transfers.cargo_ships:
|
||||||
|
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(
|
||||||
|
|||||||
@ -27,8 +27,9 @@ 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:
|
||||||
self.generate_convoy(convoy)
|
for convoy in coalition.transfers.convoys:
|
||||||
|
self.generate_convoy(convoy)
|
||||||
|
|
||||||
def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
|
def generate_convoy(self, convoy: Convoy) -> VehicleGroup:
|
||||||
group = self._create_mixed_unit_group(
|
group = self._create_mixed_unit_group(
|
||||||
@ -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]
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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("")
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user