refactor of previous commits

refactor to enum

typing and many other fixes

fix tests

attempt to fix some typescript

more typescript fixes

more typescript test fixes

revert all API changes

update to pydcs

mypy fixes

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

update requirements.txt

black -_-

bump pydcs and fix mypy

add opponent property

bump pydcs
This commit is contained in:
Eclipse/Druss99 2025-01-17 17:02:07 -05:00 committed by Raffson
parent 362ce66f80
commit 31c80dfd02
78 changed files with 739 additions and 350 deletions

View File

@ -264,6 +264,11 @@ class ForceGroup:
units = unit_group.generate_units( units = unit_group.generate_units(
ground_object, unit_type, unit_count, fixed_pos, fixed_hdg ground_object, unit_type, unit_count, fixed_pos, fixed_hdg
) )
# If the control point is neutral, the units are dead
if ground_object.control_point.captured.is_neutral:
for unit in units:
if not unit.is_static:
unit.alive = False
# Get or create the TheaterGroup # Get or create the TheaterGroup
ground_group = ground_object.group_by_name(group_name) ground_group = ground_object.group_by_name(group_name)
if ground_group is not None: if ground_group is not None:

View File

@ -35,6 +35,7 @@ if TYPE_CHECKING:
from game.sim.gameupdateevents import GameUpdateEvents from game.sim.gameupdateevents import GameUpdateEvents
from game.sim.simulationresults import SimulationResults from game.sim.simulationresults import SimulationResults
from game.squadrons import Squadron, Pilot from game.squadrons import Squadron, Pilot
from game.theater.player import Player
from game.transfers import TransferOrder from game.transfers import TransferOrder
from game.data.weapons import WeaponType from game.data.weapons import WeaponType
from .flightmember import FlightMember from .flightmember import FlightMember
@ -174,7 +175,7 @@ class Flight(
self.roster = FlightMembers.from_roster(self, self.roster) self.roster = FlightMembers.from_roster(self, self.roster)
@property @property
def blue(self) -> bool: def blue(self) -> Player:
return self.squadron.player return self.squadron.player
@property @property

View File

@ -11,7 +11,7 @@ from ..packagewaypoints import PackageWaypoints
if TYPE_CHECKING: if TYPE_CHECKING:
from game.coalition import Coalition from game.coalition import Coalition
from game.data.doctrine import Doctrine from game.data.doctrine import Doctrine
from game.theater import ConflictTheater from game.theater import ConflictTheater, Player
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
from ..flight import Flight from ..flight import Flight
from ..package import Package from ..package import Package
@ -71,7 +71,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
return self.flight.coalition return self.flight.coalition
@property @property
def is_player(self) -> bool: def is_player(self) -> Player:
return self.coalition.player return self.coalition.player
@property @property

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional, TYPE_CHECKING, Union from typing import Any, Optional, TYPE_CHECKING, Union

View File

@ -26,6 +26,7 @@ from game.theater.controlpoint import (
Carrier, Carrier,
ControlPoint, ControlPoint,
ControlPointType, ControlPointType,
Player,
Fob, Fob,
Lha, Lha,
OffMapSpawn, OffMapSpawn,
@ -123,9 +124,13 @@ class MizCampaignLoader:
def control_point_from_airport( def control_point_from_airport(
self, airport: Airport, ctld_zones: List[Tuple[Point, float]] self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
) -> ControlPoint: ) -> ControlPoint:
cp = Airfield( if airport.dynamic_spawn:
airport, self.theater, starts_blue=airport.is_blue(), ctld_zones=ctld_zones starting_coalition = Player.NEUTRAL
) elif airport.is_blue():
starting_coalition = Player.BLUE
else:
starting_coalition = Player.RED
cp = Airfield(airport, self.theater, starting_coalition, ctld_zones=ctld_zones)
# Use the unlimited aircraft option to determine if an airfield should # Use the unlimited aircraft option to determine if an airfield should
# be owned by the player when the campaign is "inverted". # be owned by the player when the campaign is "inverted".
@ -133,43 +138,44 @@ class MizCampaignLoader:
return cp return cp
def country(self, blue: bool) -> Country: def country(self, blue: Player) -> Country:
country = self.mission.country( if blue.is_blue:
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name country = self.mission.country(self.BLUE_COUNTRY.name)
) else:
country = self.mission.country(self.RED_COUNTRY.name)
# Should be guaranteed because we initialized them. # Should be guaranteed because we initialized them.
assert country assert country
return country return country
@property @property
def blue(self) -> Country: def blue(self) -> Country:
return self.country(blue=True) return self.country(blue=Player.BLUE)
@property @property
def red(self) -> Country: def red(self) -> Country:
return self.country(blue=False) return self.country(blue=Player.RED)
def off_map_spawns(self, blue: bool) -> Iterator[PlaneGroup]: def off_map_spawns(self, blue: Player) -> Iterator[PlaneGroup]:
for group in self.country(blue).plane_group: for group in self.country(blue).plane_group:
if group.units[0].type == self.OFF_MAP_UNIT_TYPE: if group.units[0].type == self.OFF_MAP_UNIT_TYPE:
yield group yield group
def carriers(self, blue: bool) -> Iterator[ShipGroup]: def carriers(self, blue: Player) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group: for group in self.country(blue).ship_group:
if group.units[0].type == self.CV_UNIT_TYPE: if group.units[0].type == self.CV_UNIT_TYPE:
yield group yield group
def lhas(self, blue: bool) -> Iterator[ShipGroup]: def lhas(self, blue: Player) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group: for group in self.country(blue).ship_group:
if group.units[0].type == self.LHA_UNIT_TYPE: if group.units[0].type == self.LHA_UNIT_TYPE:
yield group yield group
def fobs(self, blue: bool) -> Iterator[VehicleGroup]: def fobs(self, blue: Player) -> Iterator[VehicleGroup]:
for group in self.country(blue).vehicle_group: for group in self.country(blue).vehicle_group:
if group.units[0].type == self.FOB_UNIT_TYPE: if group.units[0].type == self.FOB_UNIT_TYPE:
yield group yield group
def invisible_fobs(self, blue: bool) -> Iterator[VehicleGroup]: def invisible_fobs(self, blue: Player) -> Iterator[VehicleGroup]:
for group in self.country(blue).vehicle_group: for group in self.country(blue).vehicle_group:
if group.units[0].type == self.INVISIBLE_FOB_UNIT_TYPE: if group.units[0].type == self.INVISIBLE_FOB_UNIT_TYPE:
yield group yield group
@ -290,12 +296,12 @@ class MizCampaignLoader:
def control_points(self) -> dict[UUID, ControlPoint]: def control_points(self) -> dict[UUID, ControlPoint]:
control_points = {} control_points = {}
for airport in self.mission.terrain.airport_list(): for airport in self.mission.terrain.airport_list():
if airport.is_blue() or airport.is_red(): if airport.is_blue() or airport.is_red() or airport.is_neutral():
ctld_zones = self.get_ctld_zones(airport.name) ctld_zones = self.get_ctld_zones(airport.name)
control_point = self.control_point_from_airport(airport, ctld_zones) control_point = self.control_point_from_airport(airport, ctld_zones)
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for blue in (False, True): for blue in (Player.RED, Player.BLUE):
for group in self.off_map_spawns(blue): for group in self.off_map_spawns(blue):
control_point = OffMapSpawn( control_point = OffMapSpawn(
str(group.name), group.position, self.theater, starts_blue=blue str(group.name), group.position, self.theater, starts_blue=blue
@ -348,13 +354,13 @@ class MizCampaignLoader:
@property @property
def front_line_path_groups(self) -> Iterator[VehicleGroup]: def front_line_path_groups(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=True).vehicle_group: for group in self.country(blue=Player.BLUE).vehicle_group:
if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: if group.units[0].type == self.FRONT_LINE_UNIT_TYPE:
yield group yield group
@property @property
def shipping_lane_groups(self) -> Iterator[ShipGroup]: def shipping_lane_groups(self) -> Iterator[ShipGroup]:
for group in self.country(blue=True).ship_group: for group in self.country(blue=Player.BLUE).ship_group:
if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE:
yield group yield group
@ -378,7 +384,7 @@ class MizCampaignLoader:
@property @property
def cp_convoy_spawns(self) -> Iterator[VehicleGroup]: def cp_convoy_spawns(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=True).vehicle_group: for group in self.country(blue=Player.BLUE).vehicle_group:
if group.units[0].type == self.CP_CONVOY_SPAWN_TYPE: if group.units[0].type == self.CP_CONVOY_SPAWN_TYPE:
yield group yield group

View File

@ -17,6 +17,7 @@ from game.procurement import AircraftProcurementRequest, ProcurementAi
from game.profiling import MultiEventTracer, logged_duration from game.profiling import MultiEventTracer, logged_duration
from game.squadrons import AirWing from game.squadrons import AirWing
from game.theater.bullseye import Bullseye from game.theater.bullseye import Bullseye
from game.theater.player import Player
from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
from game.transfers import PendingTransfers from game.transfers import PendingTransfers
@ -32,7 +33,7 @@ if TYPE_CHECKING:
class Coalition: class Coalition:
def __init__( def __init__(
self, game: Game, faction: Faction, budget: float, player: bool self, game: Game, faction: Faction, budget: float, player: Player
) -> None: ) -> None:
self.game = game self.game = game
self.player = player self.player = player
@ -68,9 +69,11 @@ class Coalition:
@property @property
def coalition_id(self) -> int: def coalition_id(self) -> int:
if self.player: if self.player.is_blue:
return 2 return 2
return 1 elif self.player.is_red:
return 1
return 0
@property @property
def opponent(self) -> Coalition: def opponent(self) -> Coalition:
@ -95,8 +98,9 @@ class Coalition:
state = self.__dict__.copy() state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically # Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility. # recomputed on load for the sake of save compatibility.
del state["_threat_zone"] if state["player"] != Player.NEUTRAL:
del state["_navmesh"] del state["_threat_zone"]
del state["_navmesh"]
del state["faker"] del state["faker"]
return state return state
@ -203,7 +207,7 @@ class Coalition:
squadron.refund_orders() squadron.refund_orders()
def plan_missions(self, now: datetime) -> None: def plan_missions(self, now: datetime) -> None:
color = "Blue" if self.player else "Red" color = "Blue" if self.player.is_blue else "Red"
with MultiEventTracer() as tracer: with MultiEventTracer() as tracer:
with tracer.trace(f"{color} mission planning"): with tracer.trace(f"{color} mission planning"):
with tracer.trace(f"{color} mission identification"): with tracer.trace(f"{color} mission identification"):
@ -220,7 +224,7 @@ class Coalition:
# to ground forces and 1400 to aircraft. After that the budget will be spent # to ground forces and 1400 to aircraft. After that the budget will be spent
# proportionally based on how much is already invested. # proportionally based on how much is already invested.
if self.player: if self.player.is_blue:
manage_runways = self.game.settings.automate_runway_repair manage_runways = self.game.settings.automate_runway_repair
manage_front_line = self.game.settings.automate_front_line_reinforcements manage_front_line = self.game.settings.automate_front_line_reinforcements
manage_aircraft = self.game.settings.automate_aircraft_reinforcements manage_aircraft = self.game.settings.automate_aircraft_reinforcements

View File

@ -16,6 +16,7 @@ from game.theater import (
OffMapSpawn, OffMapSpawn,
ParkingType, ParkingType,
NavalControlPoint, NavalControlPoint,
Player,
) )
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
@ -35,7 +36,7 @@ MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder: class ObjectiveFinder:
"""Identifies potential objectives for the mission planner.""" """Identifies potential objectives for the mission planner."""
def __init__(self, game: Game, is_player: bool) -> None: def __init__(self, game: Game, is_player: Player) -> None:
self.game = game self.game = game
self.is_player = is_player self.is_player = is_player
@ -154,7 +155,7 @@ class ObjectiveFinder:
airfields_in_proximity = self.closest_airfields_to(cp) airfields_in_proximity = self.closest_airfields_to(cp)
airbase_threat_range = self.game.settings.airbase_threat_range airbase_threat_range = self.game.settings.airbase_threat_range
if ( if (
not self.is_player self.is_player.is_red
and randint(1, 100) and randint(1, 100)
> self.game.settings.opfor_autoplanner_aggressiveness > self.game.settings.opfor_autoplanner_aggressiveness
): ):
@ -216,7 +217,7 @@ class ObjectiveFinder:
def farthest_friendly_control_point(self) -> ControlPoint: def farthest_friendly_control_point(self) -> ControlPoint:
"""Finds the friendly control point that is farthest from any threats.""" """Finds the friendly control point that is farthest from any threats."""
threat_zones = self.game.threat_zone_for(not self.is_player) threat_zones = self.game.threat_zone_for(self.is_player.opponent)
farthest = None farthest = None
max_distance = meters(0) max_distance = meters(0)
@ -234,7 +235,7 @@ class ObjectiveFinder:
def closest_friendly_control_point(self) -> ControlPoint: def closest_friendly_control_point(self) -> ControlPoint:
"""Finds the friendly control point that is closest to any threats.""" """Finds the friendly control point that is closest to any threats."""
threat_zones = self.game.threat_zone_for(not self.is_player) threat_zones = self.game.threat_zone_for(self.is_player.opponent)
closest = None closest = None
min_distance = meters(math.inf) min_distance = meters(math.inf)
@ -258,14 +259,14 @@ class ObjectiveFinder:
return ( return (
c c
for c in self.game.theater.controlpoints for c in self.game.theater.controlpoints
if not c.is_friendly(self.is_player) if not c.is_friendly(self.is_player) and c.captured != Player.NEUTRAL
) )
def prioritized_points(self) -> list[ControlPoint]: def prioritized_points(self) -> list[ControlPoint]:
prioritized = [] prioritized = []
capturable_later = [] capturable_later = []
isolated = [] isolated = []
for cp in self.game.theater.control_points_for(not self.is_player): for cp in self.game.theater.control_points_for(self.is_player.opponent):
if cp.is_isolated: if cp.is_isolated:
isolated.append(cp) isolated.append(cp)
continue continue

View File

@ -43,7 +43,9 @@ class PackageFulfiller:
@property @property
def is_player(self) -> bool: def is_player(self) -> bool:
return self.coalition.player if self.coalition.player.is_blue:
return True
return False
@property @property
def ato(self) -> AirTaskingOrder: def ato(self) -> AirTaskingOrder:

View File

@ -10,7 +10,7 @@ from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
from game.commander.theaterstate import TheaterState from game.commander.theaterstate import TheaterState
from game.htn import CompoundTask, Method from game.htn import CompoundTask, Method
from game.theater import ControlPoint, FrontLine from game.theater import ControlPoint, FrontLine, Player
@dataclass(frozen=True) @dataclass(frozen=True)
@ -26,12 +26,12 @@ class CaptureBase(CompoundTask[TheaterState]):
def enemy_cp(self, state: TheaterState) -> ControlPoint: def enemy_cp(self, state: TheaterState) -> ControlPoint:
return self.front_line.control_point_hostile_to(state.context.coalition.player) return self.front_line.control_point_hostile_to(state.context.coalition.player)
def units_deployable(self, state: TheaterState, player: bool) -> int: def units_deployable(self, state: TheaterState, player: Player) -> int:
cp = self.front_line.control_point_friendly_to(player) cp = self.front_line.control_point_friendly_to(player)
ammo_depots = list(state.ammo_dumps_at(cp)) ammo_depots = list(state.ammo_dumps_at(cp))
return cp.deployable_front_line_units_with(len(ammo_depots)) return cp.deployable_front_line_units_with(len(ammo_depots))
def unit_cap(self, state: TheaterState, player: bool) -> int: def unit_cap(self, state: TheaterState, player: Player) -> int:
cp = self.front_line.control_point_friendly_to(player) cp = self.front_line.control_point_friendly_to(player)
ammo_depots = list(state.ammo_dumps_at(cp)) ammo_depots = list(state.ammo_dumps_at(cp))
return cp.front_line_capacity_with(len(ammo_depots)) return cp.front_line_capacity_with(len(ammo_depots))

View File

@ -11,10 +11,11 @@ from game.theater import FrontLine
if TYPE_CHECKING: if TYPE_CHECKING:
from game.coalition import Coalition from game.coalition import Coalition
from game.theater.player import Player
class FrontLineStanceTask(TheaterCommanderTask, ABC): class FrontLineStanceTask(TheaterCommanderTask, ABC):
def __init__(self, front_line: FrontLine, player: bool) -> None: def __init__(self, front_line: FrontLine, player: Player) -> None:
self.front_line = front_line self.front_line = front_line
self.friendly_cp = self.front_line.control_point_friendly_to(player) self.friendly_cp = self.front_line.control_point_friendly_to(player)
self.enemy_cp = self.front_line.control_point_hostile_to(player) self.enemy_cp = self.front_line.control_point_hostile_to(player)

View File

@ -6,7 +6,7 @@ from game.ato.flighttype import FlightType
from game.commander.missionproposals import EscortType 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.theater import FrontLine from game.theater import FrontLine, Player
@dataclass @dataclass
@ -19,9 +19,8 @@ class PlanCas(PackagePlanningTask[FrontLine]):
# An exception is made for turn zero since that's not being truly planned, but # An exception is made for turn zero since that's not being truly planned, but
# just to determine what missions should be planned on turn 1 (when there *will* # just to determine what missions should be planned on turn 1 (when there *will*
# be ground units) and what aircraft should be ordered. # be ground units) and what aircraft should be ordered.
enemy_cp = self.target.control_point_friendly_to( player = state.context.coalition.player.opponent
player=not state.context.coalition.player enemy_cp = self.target.control_point_friendly_to(player)
)
if enemy_cp.deployable_front_line_units == 0 and state.context.turn > 0: if enemy_cp.deployable_front_line_units == 0 and state.context.turn > 0:
return False return False
return super().preconditions_met(state) return super().preconditions_met(state)

View File

@ -67,10 +67,11 @@ from game.profiling import MultiEventTracer
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
from game.theater.player import Player
class TheaterCommander(Planner[TheaterState, TheaterCommanderTask]): class TheaterCommander(Planner[TheaterState, TheaterCommanderTask]):
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, player: Player) -> None:
super().__init__( super().__init__(
PlanNextAction( PlanNextAction(
aircraft_cold_start=game.settings.default_start_type is StartType.COLD aircraft_cold_start=game.settings.default_start_type is StartType.COLD

View File

@ -20,6 +20,7 @@ from game.theater import (
ControlPoint, ControlPoint,
FrontLine, FrontLine,
MissionTarget, MissionTarget,
Player,
) )
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
@ -152,7 +153,7 @@ class TheaterState(WorldState["TheaterState"]):
@classmethod @classmethod
def from_game( def from_game(
cls, game: Game, player: bool, now: datetime, tracer: MultiEventTracer cls, game: Game, player: Player, now: datetime, tracer: MultiEventTracer
) -> TheaterState: ) -> TheaterState:
coalition = game.coalition_for(player) coalition = game.coalition_for(player)
finder = ObjectiveFinder(game, player) finder = ObjectiveFinder(game, player)
@ -213,8 +214,8 @@ class TheaterState(WorldState["TheaterState"]):
) )
), ),
strike_targets=list(finder.strike_targets()), strike_targets=list(finder.strike_targets()),
enemy_barcaps=list(game.theater.control_points_for(not player)), enemy_barcaps=list(game.theater.control_points_for(player.opponent)),
threat_zones=game.threat_zone_for(not player), threat_zones=game.threat_zone_for(player.opponent),
vulnerable_control_points=vulnerable_control_points, vulnerable_control_points=vulnerable_control_points,
control_point_priority_queue=ordered_capturable_points, control_point_priority_queue=ordered_capturable_points,
priority_cp=( priority_cp=(

View File

@ -16,7 +16,7 @@ from uuid import UUID
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, Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -45,9 +45,9 @@ class AirLosses:
def losses(self) -> Iterator[FlyingUnit]: def losses(self) -> Iterator[FlyingUnit]:
return itertools.chain(self.player, self.enemy) return itertools.chain(self.player, self.enemy)
def by_type(self, player: bool) -> Dict[AircraftType, int]: def by_type(self, player: Player) -> Dict[AircraftType, int]:
losses_by_type: Dict[AircraftType, int] = defaultdict(int) losses_by_type: Dict[AircraftType, int] = defaultdict(int)
losses = self.player if player else self.enemy losses = self.player if player.is_blue else self.enemy
for loss in losses: for loss in losses:
losses_by_type[loss.flight.unit_type] += 1 losses_by_type[loss.flight.unit_type] += 1
return losses_by_type return losses_by_type
@ -87,7 +87,7 @@ class GroundLosses:
@dataclass(frozen=True) @dataclass(frozen=True)
class BaseCaptureEvent: class BaseCaptureEvent:
control_point: ControlPoint control_point: ControlPoint
captured_by_player: bool captured_by_player: Player
@dataclass(frozen=True) @dataclass(frozen=True)
@ -166,7 +166,7 @@ class Debriefing:
def merge_simulation_results(self, results: SimulationResults) -> None: def merge_simulation_results(self, results: SimulationResults) -> None:
for air_loss in results.air_losses: for air_loss in results.air_losses:
if air_loss.flight.squadron.player: if air_loss.flight.squadron.player.is_blue:
self.air_losses.player.append(air_loss) self.air_losses.player.append(air_loss)
else: else:
self.air_losses.enemy.append(air_loss) self.air_losses.enemy.append(air_loss)
@ -209,9 +209,9 @@ class Debriefing:
def casualty_count(self, control_point: ControlPoint) -> int: def casualty_count(self, control_point: ControlPoint) -> int:
return len([x for x in self.front_line_losses if x.origin == control_point]) return len([x for x in self.front_line_losses if x.origin == control_point])
def front_line_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]: def front_line_losses_by_type(self, player: Player) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int) losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player: if player.is_blue:
losses = self.ground_losses.player_front_line losses = self.ground_losses.player_front_line
else: else:
losses = self.ground_losses.enemy_front_line losses = self.ground_losses.enemy_front_line
@ -219,9 +219,9 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1 losses_by_type[loss.unit_type] += 1
return losses_by_type return losses_by_type
def convoy_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]: def convoy_losses_by_type(self, player: Player) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int) losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player: if player.is_blue:
losses = self.ground_losses.player_convoy losses = self.ground_losses.player_convoy
else: else:
losses = self.ground_losses.enemy_convoy losses = self.ground_losses.enemy_convoy
@ -229,9 +229,9 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1 losses_by_type[loss.unit_type] += 1
return losses_by_type return losses_by_type
def cargo_ship_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]: def cargo_ship_losses_by_type(self, player: Player) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int) losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player: if player.is_blue:
ships = self.ground_losses.player_cargo_ships ships = self.ground_losses.player_cargo_ships
else: else:
ships = self.ground_losses.enemy_cargo_ships ships = self.ground_losses.enemy_cargo_ships
@ -240,9 +240,9 @@ class Debriefing:
losses_by_type[unit_type] += count losses_by_type[unit_type] += count
return losses_by_type return losses_by_type
def airlift_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]: def airlift_losses_by_type(self, player: Player) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int) losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player: if player.is_blue:
losses = self.ground_losses.player_airlifts losses = self.ground_losses.player_airlifts
else: else:
losses = self.ground_losses.enemy_airlifts losses = self.ground_losses.enemy_airlifts
@ -251,9 +251,9 @@ class Debriefing:
losses_by_type[unit_type] += 1 losses_by_type[unit_type] += 1
return losses_by_type return losses_by_type
def ground_object_losses_by_type(self, player: bool) -> Dict[str, int]: def ground_object_losses_by_type(self, player: Player) -> Dict[str, int]:
losses_by_type: Dict[str, int] = defaultdict(int) losses_by_type: Dict[str, int] = defaultdict(int)
if player: if player.is_blue:
losses = self.ground_losses.player_ground_objects losses = self.ground_losses.player_ground_objects
else: else:
losses = self.ground_losses.enemy_ground_objects losses = self.ground_losses.enemy_ground_objects
@ -261,9 +261,9 @@ class Debriefing:
losses_by_type[loss.theater_unit.type.id] += 1 losses_by_type[loss.theater_unit.type.id] += 1
return losses_by_type return losses_by_type
def scenery_losses_by_type(self, player: bool) -> Dict[str, int]: def scenery_losses_by_type(self, player: Player) -> Dict[str, int]:
losses_by_type: Dict[str, int] = defaultdict(int) losses_by_type: Dict[str, int] = defaultdict(int)
if player: if player.is_blue:
losses = self.ground_losses.player_scenery losses = self.ground_losses.player_scenery
else: else:
losses = self.ground_losses.enemy_scenery losses = self.ground_losses.enemy_scenery
@ -279,7 +279,7 @@ class Debriefing:
if aircraft is None: if aircraft is None:
logging.error(f"Could not find Flight matching {unit_name}") logging.error(f"Could not find Flight matching {unit_name}")
continue continue
if aircraft.flight.departure.captured: if aircraft.flight.departure.captured.is_blue:
player_losses.append(aircraft) player_losses.append(aircraft)
else: else:
enemy_losses.append(aircraft) enemy_losses.append(aircraft)
@ -290,7 +290,7 @@ class Debriefing:
for unit_name in self.state_data.killed_ground_units: for unit_name in self.state_data.killed_ground_units:
front_line_unit = self.unit_map.front_line_unit(unit_name) front_line_unit = self.unit_map.front_line_unit(unit_name)
if front_line_unit is not None: if front_line_unit is not None:
if front_line_unit.origin.captured: if front_line_unit.origin.captured.is_blue:
losses.player_front_line.append(front_line_unit) losses.player_front_line.append(front_line_unit)
else: else:
losses.enemy_front_line.append(front_line_unit) losses.enemy_front_line.append(front_line_unit)
@ -298,7 +298,7 @@ class Debriefing:
convoy_unit = self.unit_map.convoy_unit(unit_name) convoy_unit = self.unit_map.convoy_unit(unit_name)
if convoy_unit is not None: if convoy_unit is not None:
if convoy_unit.convoy.player_owned: if convoy_unit.convoy.player_owned.is_blue:
losses.player_convoy.append(convoy_unit) losses.player_convoy.append(convoy_unit)
else: else:
losses.enemy_convoy.append(convoy_unit) losses.enemy_convoy.append(convoy_unit)
@ -306,7 +306,7 @@ class Debriefing:
cargo_ship = self.unit_map.cargo_ship(unit_name) cargo_ship = self.unit_map.cargo_ship(unit_name)
if cargo_ship is not None: if cargo_ship is not None:
if cargo_ship.player_owned: if cargo_ship.player_owned.is_blue:
losses.player_cargo_ships.append(cargo_ship) losses.player_cargo_ships.append(cargo_ship)
else: else:
losses.enemy_cargo_ships.append(cargo_ship) losses.enemy_cargo_ships.append(cargo_ship)
@ -314,7 +314,9 @@ class Debriefing:
ground_object = self.unit_map.theater_units(unit_name) ground_object = self.unit_map.theater_units(unit_name)
if ground_object is not None: if ground_object is not None:
if ground_object.theater_unit.ground_object.is_friendly(to_player=True): if ground_object.theater_unit.ground_object.is_friendly(
to_player=Player.BLUE
):
losses.player_ground_objects.append(ground_object) losses.player_ground_objects.append(ground_object)
else: else:
losses.enemy_ground_objects.append(ground_object) losses.enemy_ground_objects.append(ground_object)
@ -323,7 +325,9 @@ class Debriefing:
scenery_object = self.unit_map.scenery_object(unit_name) scenery_object = self.unit_map.scenery_object(unit_name)
# Try appending object to the name, because we do this for building statics. # Try appending object to the name, because we do this for building statics.
if scenery_object is not None: if scenery_object is not None:
if scenery_object.ground_unit.ground_object.is_friendly(to_player=True): if scenery_object.ground_unit.ground_object.is_friendly(
to_player=Player.BLUE
):
losses.player_scenery.append(scenery_object) losses.player_scenery.append(scenery_object)
else: else:
losses.enemy_scenery.append(scenery_object) losses.enemy_scenery.append(scenery_object)
@ -331,9 +335,9 @@ class Debriefing:
airfield = self.unit_map.airfield(unit_name) airfield = self.unit_map.airfield(unit_name)
if airfield is not None: if airfield is not None:
if airfield.captured: if airfield.captured.is_blue:
losses.player_airfields.append(airfield) losses.player_airfields.append(airfield)
else: elif airfield.captured.is_red:
losses.enemy_airfields.append(airfield) losses.enemy_airfields.append(airfield)
continue continue
@ -349,7 +353,7 @@ class Debriefing:
for unit_name in self.state_data.killed_aircraft: for unit_name in self.state_data.killed_aircraft:
airlift_unit = self.unit_map.airlift_unit(unit_name) airlift_unit = self.unit_map.airlift_unit(unit_name)
if airlift_unit is not None: if airlift_unit is not None:
if airlift_unit.transfer.player: if airlift_unit.transfer.player.is_blue:
losses.player_airlifts.append(airlift_unit) losses.player_airlifts.append(airlift_unit)
else: else:
losses.enemy_airlifts.append(airlift_unit) losses.enemy_airlifts.append(airlift_unit)
@ -377,8 +381,10 @@ class Debriefing:
# Captured base is not a part of the campaign. This happens when neutral # Captured base is not a part of the campaign. This happens when neutral
# bases are near the conflict. Nothing to do. # bases are near the conflict. Nothing to do.
continue continue
if int(new_owner_id_str) == blue_coalition_id:
captured_by_player = int(new_owner_id_str) == blue_coalition_id captured_by_player = Player.BLUE
else:
captured_by_player = Player.RED
if control_point.is_friendly(to_player=captured_by_player): if control_point.is_friendly(to_player=captured_by_player):
# Base is currently friendly to the new owner. Was captured and # Base is currently friendly to the new owner. Was captured and
# recaptured in the same mission. Nothing to do. # recaptured in the same mission. Nothing to do.

View File

@ -6,7 +6,7 @@ import math
from collections.abc import Iterator from collections.abc import Iterator
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from enum import Enum from enum import Enum
from typing import Any, List, TYPE_CHECKING, Type, Union, cast from typing import Any, List, TYPE_CHECKING, Union, cast
from uuid import UUID from uuid import UUID
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
@ -32,7 +32,7 @@ from .infos.information import Information
from .lasercodes.lasercoderegistry import LaserCodeRegistry from .lasercodes.lasercoderegistry import LaserCodeRegistry
from .profiling import logged_duration from .profiling import logged_duration
from .settings import Settings from .settings import Settings
from .theater import ConflictTheater from .theater import ConflictTheater, Player
from .theater.bullseye import Bullseye from .theater.bullseye import Bullseye
from .theater.theatergroundobject import ( from .theater.theatergroundobject import (
EwrGroundObject, EwrGroundObject,
@ -138,8 +138,11 @@ class Game:
self.conditions = self.generate_conditions(forced_time=start_time) self.conditions = self.generate_conditions(forced_time=start_time)
self.sanitize_sides(player_faction, enemy_faction) self.sanitize_sides(player_faction, enemy_faction)
self.blue = Coalition(self, player_faction, player_budget, player=True) self.blue = Coalition(self, player_faction, player_budget, player=Player.BLUE)
self.red = Coalition(self, enemy_faction, enemy_budget, player=False) self.red = Coalition(self, enemy_faction, enemy_budget, player=Player.RED)
neutral_faction = player_faction
neutral_faction.country = self.neutral_country
self.neutral = Coalition(self, neutral_faction, 0, player=Player.NEUTRAL)
self.blue.set_opponent(self.red) self.blue.set_opponent(self.red)
self.red.set_opponent(self.blue) self.red.set_opponent(self.blue)
@ -178,10 +181,10 @@ class Game:
def point_in_world(self, x: float, y: float) -> Point: def point_in_world(self, x: float, y: float) -> Point:
return Point(x, y, self.theater.terrain) return Point(x, y, self.theater.terrain)
def ato_for(self, player: bool) -> AirTaskingOrder: def ato_for(self, player: Player) -> AirTaskingOrder:
return self.coalition_for(player).ato return self.coalition_for(player).ato
def transit_network_for(self, player: bool) -> TransitNetwork: def transit_network_for(self, player: Player) -> TransitNetwork:
return self.coalition_for(player).transit_network return self.coalition_for(player).transit_network
def generate_conditions(self, forced_time: time | None = None) -> Conditions: def generate_conditions(self, forced_time: time | None = None) -> Conditions:
@ -209,32 +212,35 @@ class Game:
else: else:
enemy_faction.country = country_with_name("Russia") enemy_faction.country = country_with_name("Russia")
def faction_for(self, player: bool) -> Faction: def faction_for(self, player: Player) -> Faction:
return self.coalition_for(player).faction return self.coalition_for(player).faction
def faker_for(self, player: bool) -> Faker: def faker_for(self, player: Player) -> Faker:
return self.coalition_for(player).faker return self.coalition_for(player).faker
def air_wing_for(self, player: bool) -> AirWing: def air_wing_for(self, player: Player) -> AirWing:
return self.coalition_for(player).air_wing return self.coalition_for(player).air_wing
@property @property
def neutral_country(self) -> Type[Country]: def neutral_country(self) -> Country:
"""Return the best fitting country that can be used as neutral faction in the generated mission""" """Return the best fitting country that can be used as neutral faction in the generated mission"""
countries_in_use = {self.red.faction.country, self.blue.faction.country} countries_in_use = {self.red.faction.country, self.blue.faction.country}
if UnitedNationsPeacekeepers not in countries_in_use: if UnitedNationsPeacekeepers not in countries_in_use:
return UnitedNationsPeacekeepers return UnitedNationsPeacekeepers()
elif Switzerland.name not in countries_in_use: elif Switzerland.name not in countries_in_use:
return Switzerland return Switzerland()
else: else:
return USAFAggressors return USAFAggressors()
def coalition_for(self, player: bool) -> Coalition: def coalition_for(self, player: Player) -> Coalition:
if player: if player.is_neutral:
return self.neutral
elif player.is_blue:
return self.blue return self.blue
return self.red else:
return self.red
def adjust_budget(self, amount: float, player: bool) -> None: def adjust_budget(self, amount: float, player: Player) -> None:
self.coalition_for(player).adjust_budget(amount) self.coalition_for(player).adjust_budget(amount)
def on_load(self, game_still_initializing: bool = False) -> None: def on_load(self, game_still_initializing: bool = False) -> None:
@ -489,7 +495,7 @@ 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_network_for(self, player: bool) -> TransitNetwork: def compute_transit_network_for(self, player: Player) -> TransitNetwork:
return TransitNetworkBuilder(self.theater, player).build() return TransitNetworkBuilder(self.theater, player).build()
def compute_threat_zones(self, events: GameUpdateEvents) -> None: def compute_threat_zones(self, events: GameUpdateEvents) -> None:
@ -498,10 +504,10 @@ class Game:
self.blue.compute_nav_meshes(events) self.blue.compute_nav_meshes(events)
self.red.compute_nav_meshes(events) self.red.compute_nav_meshes(events)
def threat_zone_for(self, player: bool) -> ThreatZones: def threat_zone_for(self, player: Player) -> ThreatZones:
return self.coalition_for(player).threat_zone return self.coalition_for(player).threat_zone
def navmesh_for(self, player: bool) -> NavMesh: def navmesh_for(self, player: Player) -> NavMesh:
return self.coalition_for(player).nav_mesh return self.coalition_for(player).nav_mesh
def compute_unculled_zones(self, events: GameUpdateEvents) -> None: def compute_unculled_zones(self, events: GameUpdateEvents) -> None:

View File

@ -66,7 +66,7 @@ class GroundUnitOrders:
bought_units: dict[GroundUnitType, int] = {} bought_units: dict[GroundUnitType, int] = {}
units_needing_transfer: dict[GroundUnitType, int] = {} units_needing_transfer: dict[GroundUnitType, int] = {}
for unit_type, count in self.units.items(): for unit_type, count in self.units.items():
allegiance = "Ally" if self.destination.captured else "Enemy" allegiance = "Ally" if self.destination.captured.is_blue else "Enemy"
d: dict[GroundUnitType, int] d: dict[GroundUnitType, int]
if self.destination != ground_unit_source: if self.destination != ground_unit_source:
source = ground_unit_source source = ground_unit_source

View File

@ -4,6 +4,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from game.config import REWARDS from game.config import REWARDS
from game.theater.player import Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -22,8 +23,8 @@ class BuildingIncome:
class Income: class Income:
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, player: Player) -> None:
if player: if player.is_blue:
self.multiplier = game.settings.player_income_multiplier self.multiplier = game.settings.player_income_multiplier
else: else:
self.multiplier = game.settings.enemy_income_multiplier self.multiplier = game.settings.enemy_income_multiplier

View File

@ -220,7 +220,7 @@ class AircraftGenerator:
): ):
continue continue
if control_point.captured: if control_point.captured.is_blue:
country = player_country country = player_country
else: else:
country = enemy_country country = enemy_country
@ -237,12 +237,12 @@ class AircraftGenerator:
squadron.location, Fob squadron.location, Fob
) )
if ( if (
squadron.coalition.player squadron.coalition.player.is_blue
and self.game.settings.perf_disable_untasked_blufor_aircraft and self.game.settings.perf_disable_untasked_blufor_aircraft
): ):
return return
elif ( elif (
not squadron.coalition.player not squadron.coalition.player.is_red
and self.game.settings.perf_disable_untasked_opfor_aircraft and self.game.settings.perf_disable_untasked_opfor_aircraft
): ):
return return
@ -274,7 +274,7 @@ class AircraftGenerator:
).create_idle_aircraft() ).create_idle_aircraft()
if group: if group:
if ( if (
not squadron.coalition.player squadron.coalition.player.is_red
and squadron.aircraft.flyable and squadron.aircraft.flyable
and ( and (
self.game.settings.enable_squadron_pilot_limits self.game.settings.enable_squadron_pilot_limits

View File

@ -14,6 +14,7 @@ if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.radio.radios import RadioFrequency from game.radio.radios import RadioFrequency
from game.runways import RunwayData from game.runways import RunwayData
from game.theater.player import Player
@dataclass(frozen=True) @dataclass(frozen=True)
@ -42,7 +43,7 @@ class FlightData:
size: int size: int
#: True if this flight belongs to the player's coalition. #: True if this flight belongs to the player's coalition.
friendly: bool friendly: Player
#: Number of seconds after mission start the flight is set to depart. #: Number of seconds after mission start the flight is set to depart.
departure_delay: timedelta departure_delay: timedelta

View File

@ -299,7 +299,7 @@ class FlightGroupConfigurator:
unit.set_player() unit.set_player()
def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill:
if self.flight.squadron.player: if self.flight.squadron.player.is_blue:
base_skill = Skill(self.game.settings.player_skill) base_skill = Skill(self.game.settings.player_skill)
else: else:
base_skill = Skill(self.game.settings.enemy_skill) base_skill = Skill(self.game.settings.enemy_skill)

View File

@ -17,8 +17,8 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData from game.runways import RunwayData
from game.theater import ControlPoint, FrontLine from game.theater import ControlPoint, FrontLine
from .aircraft.flightdata import FlightData from .aircraft.flightdata import FlightData
from .missiondata import AwacsInfo, TankerInfo
from .flotgenerator import JtacInfo from .flotgenerator import JtacInfo
from .missiondata import AwacsInfo, TankerInfo
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -182,7 +182,7 @@ class BriefingGenerator(MissionInfoGenerator):
def generate_allied_flights_by_departure(self) -> None: def generate_allied_flights_by_departure(self) -> None:
"""Create iterable to display allied flights grouped by departure airfield.""" """Create iterable to display allied flights grouped by departure airfield."""
for flight in self.flights: for flight in self.flights:
if not flight.client_units and flight.friendly: if not flight.client_units and flight.friendly.is_blue:
name = flight.departure.airfield_name name = flight.departure.airfield_name
if ( if (
name in self.allied_flights_by_departure name in self.allied_flights_by_departure

View File

@ -18,6 +18,7 @@ from game.utils import kph
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
from game.theater.player import Player
class ConvoyGenerator: class ConvoyGenerator:
@ -94,7 +95,7 @@ class ConvoyGenerator:
name: str, name: str,
position: Point, position: Point,
units: dict[GroundUnitType, int], units: dict[GroundUnitType, int],
for_player: bool, for_player: Player,
) -> VehicleGroup: ) -> VehicleGroup:
unit_types = list(units.items()) unit_types = list(units.items())
main_unit_type, main_unit_count = unit_types[0] main_unit_type, main_unit_count = unit_types[0]

View File

@ -15,6 +15,7 @@ FRONTLINE_COLORS = Rgba(255, 0, 0, 255)
WHITE = Rgba(255, 255, 255, 255) WHITE = Rgba(255, 255, 255, 255)
CP_RED = Rgba(255, 0, 0, 80) CP_RED = Rgba(255, 0, 0, 80)
CP_BLUE = Rgba(0, 0, 255, 80) CP_BLUE = Rgba(0, 0, 255, 80)
CP_NEUTRAL = Rgba(128, 128, 128, 80)
BLUE_PATH_COLOR = Rgba(0, 0, 255, 100) BLUE_PATH_COLOR = Rgba(0, 0, 255, 100)
RED_PATH_COLOR = Rgba(255, 0, 0, 100) RED_PATH_COLOR = Rgba(255, 0, 0, 100)
ACTIVE_PATH_COLOR = Rgba(255, 80, 80, 100) ACTIVE_PATH_COLOR = Rgba(255, 80, 80, 100)
@ -35,10 +36,12 @@ class DrawingsGenerator:
Generate cps as circles Generate cps as circles
""" """
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if cp.captured: if cp.captured.is_blue:
color = CP_BLUE color = CP_BLUE
else: elif cp.captured.is_red:
color = CP_RED color = CP_RED
else:
color = CP_NEUTRAL
shape = self.player_layer.add_circle( shape = self.player_layer.add_circle(
cp.position, cp.position,
TRIGGER_RADIUS_CAPTURE, TRIGGER_RADIUS_CAPTURE,
@ -61,9 +64,9 @@ class DrawingsGenerator:
continue continue
else: else:
# Determine path color # Determine path color
if cp.captured and destination.captured: if cp.captured.is_blue and destination.captured.is_blue:
color = BLUE_PATH_COLOR color = BLUE_PATH_COLOR
elif not cp.captured and not destination.captured: elif cp.captured.is_red and destination.captured.is_red:
color = RED_PATH_COLOR color = RED_PATH_COLOR
else: else:
color = ACTIVE_PATH_COLOR color = ACTIVE_PATH_COLOR

View File

@ -40,7 +40,7 @@ from game.ground_forces.ai_ground_planner import (
from game.ground_forces.combat_stance import CombatStance from game.ground_forces.combat_stance import CombatStance
from game.naming import namegen from game.naming import namegen
from game.radio.radios import RadioRegistry from game.radio.radios import RadioRegistry
from game.theater.controlpoint import ControlPoint from game.theater.controlpoint import ControlPoint, Player
from game.unitmap import UnitMap from game.unitmap import UnitMap
from game.utils import Heading from game.utils import Heading
from .frontlineconflictdescription import FrontLineConflictDescription from .frontlineconflictdescription import FrontLineConflictDescription
@ -101,12 +101,12 @@ class FlotGenerator:
# Create player groups at random position # Create player groups at random position
player_groups = self._generate_groups( player_groups = self._generate_groups(
self.player_planned_combat_groups, is_player=True self.player_planned_combat_groups, is_player=Player.BLUE
) )
# Create enemy groups at random position # Create enemy groups at random position
enemy_groups = self._generate_groups( enemy_groups = self._generate_groups(
self.enemy_planned_combat_groups, is_player=False self.enemy_planned_combat_groups, is_player=Player.RED
) )
# TODO: Differentiate AirConflict and GroundConflict classes. # TODO: Differentiate AirConflict and GroundConflict classes.
@ -193,7 +193,7 @@ class FlotGenerator:
callsign=callsign, callsign=callsign,
region=frontline, region=frontline,
code=str(code), code=str(code),
blue=True, blue=Player.BLUE,
freq=freq, freq=freq,
) )
) )
@ -215,7 +215,7 @@ class FlotGenerator:
def gen_infantry_group_for_group( def gen_infantry_group_for_group(
self, self,
group: VehicleGroup, group: VehicleGroup,
is_player: bool, is_player: Player,
side: Country, side: Country,
forward_heading: Heading, forward_heading: Heading,
) -> None: ) -> None:
@ -294,7 +294,7 @@ class FlotGenerator:
GroundForcePainter(faction, vehicle).apply_livery() GroundForcePainter(faction, vehicle).apply_livery()
vg.hidden_on_mfd = True vg.hidden_on_mfd = True
def _earliest_tot_on_flot(self, player: bool) -> timedelta: def _earliest_tot_on_flot(self, player: Player) -> timedelta:
tots = [ tots = [
x.time_over_target x.time_over_target
for x in self.game.ato_for(player).packages for x in self.game.ato_for(player).packages
@ -413,7 +413,7 @@ class FlotGenerator:
""" """
duration = timedelta() duration = timedelta()
if stance in [CombatStance.DEFENSIVE, CombatStance.AGGRESSIVE]: if stance in [CombatStance.DEFENSIVE, CombatStance.AGGRESSIVE]:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player) duration = self._earliest_tot_on_flot(to_cp.coalition.player.opponent)
self._set_reform_waypoint(dcs_group, forward_heading, duration) self._set_reform_waypoint(dcs_group, forward_heading, duration)
if stance == CombatStance.AGGRESSIVE: if stance == CombatStance.AGGRESSIVE:
# Attack nearest enemy if any # Attack nearest enemy if any
@ -503,7 +503,7 @@ class FlotGenerator:
""" """
duration = timedelta() duration = timedelta()
if stance in [CombatStance.DEFENSIVE, CombatStance.AGGRESSIVE]: if stance in [CombatStance.DEFENSIVE, CombatStance.AGGRESSIVE]:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player) duration = self._earliest_tot_on_flot(to_cp.coalition.player.opponent)
self._set_reform_waypoint(dcs_group, forward_heading, duration) self._set_reform_waypoint(dcs_group, forward_heading, duration)
if stance in [ if stance in [
CombatStance.AGGRESSIVE, CombatStance.AGGRESSIVE,
@ -762,7 +762,7 @@ class FlotGenerator:
) )
def _generate_groups( def _generate_groups(
self, groups: list[CombatGroup], is_player: bool self, groups: list[CombatGroup], is_player: Player
) -> List[Tuple[VehicleGroup, CombatGroup]]: ) -> List[Tuple[VehicleGroup, CombatGroup]]:
"""Finds valid positions for planned groups and generates a pydcs group for them""" """Finds valid positions for planned groups and generates a pydcs group for them"""
positioned_groups = [] positioned_groups = []
@ -795,7 +795,7 @@ class FlotGenerator:
final_position, final_position,
heading=spawn_heading.opposite, heading=spawn_heading.opposite,
) )
if is_player: if is_player == Player.BLUE:
g.set_skill(Skill(self.game.settings.player_skill)) g.set_skill(Skill(self.game.settings.player_skill))
else: else:
g.set_skill(Skill(self.game.settings.enemy_vehicle_skill)) g.set_skill(Skill(self.game.settings.enemy_vehicle_skill))
@ -813,7 +813,7 @@ class FlotGenerator:
def _generate_group( def _generate_group(
self, self,
player: bool, player: Player,
side: Country, side: Country,
unit_type: GroundUnitType, unit_type: GroundUnitType,
count: int, count: int,

View File

@ -10,15 +10,14 @@ from dcs import Mission
from dcs.action import DoScript, DoScriptFile from dcs.action import DoScript, DoScriptFile
from dcs.translation import String from dcs.translation import String
from dcs.triggers import TriggerStart from dcs.triggers import TriggerStart
from dcs.unit import Skill
from game.ato import FlightType from game.ato import FlightType
from game.data.units import UnitClass
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.plugins import LuaPluginManager from game.plugins import LuaPluginManager
from game.theater import TheaterGroundObject from game.theater import TheaterGroundObject
from game.theater.iadsnetwork.iadsrole import IadsRole from game.theater.iadsnetwork.iadsrole import IadsRole
from game.utils import escape_string_for_lua from game.utils import escape_string_for_lua
from game.data.units import UnitClass
from .missiondata import MissionData from .missiondata import MissionData
if TYPE_CHECKING: if TYPE_CHECKING:
@ -144,7 +143,7 @@ class LuaGenerator:
target_points = lua_data.add_item("TargetPoints") target_points = lua_data.add_item("TargetPoints")
for flight in self.mission_data.flights: for flight in self.mission_data.flights:
if flight.friendly and flight.flight_type in [ if flight.friendly.is_blue and flight.flight_type in [
FlightType.ANTISHIP, FlightType.ANTISHIP,
FlightType.DEAD, FlightType.DEAD,
FlightType.SEAD, FlightType.SEAD,
@ -178,7 +177,7 @@ class LuaGenerator:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
coalition_object = ( coalition_object = (
lua_data.get_or_create_item("BlueAA") lua_data.get_or_create_item("BlueAA")
if cp.captured if cp.captured.is_blue
else lua_data.get_or_create_item("RedAA") else lua_data.get_or_create_item("RedAA")
) )
for ground_object in cp.ground_objects: for ground_object in cp.ground_objects:

View File

@ -15,6 +15,7 @@ from game.runways import RunwayData
if TYPE_CHECKING: if TYPE_CHECKING:
from game.radio.radios import RadioFrequency from game.radio.radios import RadioFrequency
from game.radio.tacan import TacanChannel from game.radio.tacan import TacanChannel
from game.theater.player import Player
from game.utils import Distance from game.utils import Distance
from uuid import UUID from uuid import UUID
@ -24,7 +25,7 @@ class GroupInfo:
group_name: str group_name: str
callsign: str callsign: str
freq: RadioFrequency freq: RadioFrequency
blue: bool blue: Player
@dataclass @dataclass
@ -85,7 +86,7 @@ class LogisticsInfo:
pilot_names: list[str] pilot_names: list[str]
transport: AircraftType transport: AircraftType
blue: bool blue: Player
logistic_unit: str = field(default_factory=str) logistic_unit: str = field(default_factory=str)
pickup_zone: str = field(default_factory=str) pickup_zone: str = field(default_factory=str)

View File

@ -391,9 +391,12 @@ class MissionGenerator:
tmu.theater_unit.position, tmu.theater_unit.position,
self.mission.terrain, self.mission.terrain,
).dict() ).dict()
warehouse["coalition"] = ( if tmu.theater_unit.ground_object.coalition.player.is_neutral:
"blue" if tmu.theater_unit.ground_object.coalition.player else "red" warehouse["coalition"] = "neutral"
) elif tmu.theater_unit.ground_object.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
warehouse["dynamicCargo"] = settings.dynamic_cargo warehouse["dynamicCargo"] = settings.dynamic_cargo
if tmu.theater_unit.is_ship or tmu.dcs_unit.category == "Heliports": # type: ignore if tmu.theater_unit.is_ship or tmu.dcs_unit.category == "Heliports": # type: ignore
warehouse["dynamicSpawn"] = settings.dynamic_slots warehouse["dynamicSpawn"] = settings.dynamic_slots

View File

@ -13,6 +13,7 @@ from dcs.vehicles import vehicle_map
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.naming import namegen from game.naming import namegen
from game.theater import Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -25,12 +26,12 @@ class RebellionGenerator:
def generate(self) -> None: def generate(self) -> None:
ownfor_country = self.mission.country( ownfor_country = self.mission.country(
self.game.coalition_for(player=True).faction.country.name self.game.coalition_for(player=Player.BLUE).faction.country.name
) )
for rz in self.game.theater.ownfor_rebel_zones: for rz in self.game.theater.ownfor_rebel_zones:
self._generate_rebel_zone(ownfor_country, rz) self._generate_rebel_zone(ownfor_country, rz)
opfor_country = self.mission.country( opfor_country = self.mission.country(
self.game.coalition_for(player=False).faction.country.name self.game.coalition_for(player=Player.RED).faction.country.name
) )
for rz in self.game.theater.opfor_rebel_zones: for rz in self.game.theater.opfor_rebel_zones:
self._generate_rebel_zone(opfor_country, rz) self._generate_rebel_zone(opfor_country, rz)

View File

@ -67,6 +67,7 @@ from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
from game.runways import RunwayData from game.runways import RunwayData
from game.theater import ( from game.theater import (
ControlPoint, ControlPoint,
Player,
TheaterGroundObject, TheaterGroundObject,
TheaterUnit, TheaterUnit,
NavalControlPoint, NavalControlPoint,
@ -408,7 +409,7 @@ class GroundObjectGenerator:
# Align the trigger zones to the faction color on the DCS briefing/F10 map. # Align the trigger zones to the faction color on the DCS briefing/F10 map.
color = ( color = (
{1: 0.2, 2: 0.7, 3: 1, 4: 0.15} {1: 0.2, 2: 0.7, 3: 1, 4: 0.15}
if scenery.ground_object.is_friendly(to_player=True) if scenery.ground_object.is_friendly(to_player=Player.BLUE)
else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15} else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15}
) )
@ -878,7 +879,12 @@ class HelipadGenerator:
pad.position, pad.position,
self.m.terrain, self.m.terrain,
).dict() ).dict()
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo? # configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse self.m.warehouses.warehouses[pad.id] = warehouse
@ -1005,7 +1011,12 @@ class GroundSpawnRoadbaseGenerator:
pad.position, pad.position,
self.m.terrain, self.m.terrain,
).dict() ).dict()
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo? # configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse self.m.warehouses.warehouses[pad.id] = warehouse
@ -1131,7 +1142,12 @@ class GroundSpawnLargeGenerator:
pad.position, pad.position,
self.m.terrain, self.m.terrain,
).dict() ).dict()
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo? # configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse self.m.warehouses.warehouses[pad.id] = warehouse
@ -1275,7 +1291,12 @@ class GroundSpawnGenerator:
pad.position, pad.position,
self.m.terrain, self.m.terrain,
).dict() ).dict()
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo? # configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse self.m.warehouses.warehouses[pad.id] = warehouse

View File

@ -12,7 +12,6 @@ from dcs.action import (
RemoveSceneObjects, RemoveSceneObjects,
RemoveSceneObjectsMask, RemoveSceneObjectsMask,
SceneryDestructionZone, SceneryDestructionZone,
Smoke,
) )
from dcs.condition import ( from dcs.condition import (
AllOfCoalitionOutsideZone, AllOfCoalitionOutsideZone,
@ -99,9 +98,12 @@ class TriggerGenerator:
raise RuntimeError( raise RuntimeError(
f"Could not find {airfield.airport.name} in the mission" f"Could not find {airfield.airport.name} in the mission"
) )
cp_airport.set_coalition( if airfield.captured.is_neutral:
airfield.captured and player_coalition or enemy_coalition cp_airport.set_coalition("neutral")
) elif airfield.captured.is_blue:
cp_airport.set_coalition(player_coalition)
elif airfield.captured.is_red:
cp_airport.set_coalition(enemy_coalition)
def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None:
""" """
@ -138,7 +140,7 @@ class TriggerGenerator:
zone = self.mission.triggers.add_triggerzone( zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK" location, radius=10, hidden=True, name="MARK"
) )
if cp.captured: if cp.captured.is_blue:
name = ground_object.obj_name + " [ALLY]" name = ground_object.obj_name + " [ALLY]"
else: else:
name = ground_object.obj_name + " [ENEMY]" name = ground_object.obj_name + " [ENEMY]"
@ -186,7 +188,7 @@ class TriggerGenerator:
""" """
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier: if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured: if cp.captured.is_blue:
attacking_coalition = enemy_coalition attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition defending_coalition = player_coalition
@ -242,6 +244,51 @@ class TriggerGenerator:
recapture_trigger.add_action(ClearFlag(flag=flag)) recapture_trigger.add_action(ClearFlag(flag=flag))
self.mission.triggerrules.triggers.append(recapture_trigger) self.mission.triggerrules.triggers.append(recapture_trigger)
if cp.captured.is_neutral:
red_capture_trigger = TriggerCondition(
Event.NoEvent, "Capture Trigger"
)
red_capture_trigger.add_condition(
AllOfCoalitionOutsideZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
red_capture_trigger.add_condition(
PartOfCoalitionInZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
red_capture_trigger.add_condition(FlagIsFalse(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"'
)
red_capture_trigger.add_action(DoScript(script_string))
red_capture_trigger.add_action(SetFlag(flag=flag))
self.mission.triggerrules.triggers.append(red_capture_trigger)
inverted_recapture_trigger = TriggerCondition(
Event.NoEvent, "Capture Trigger"
)
inverted_recapture_trigger.add_condition(
AllOfCoalitionOutsideZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
inverted_recapture_trigger.add_condition(
PartOfCoalitionInZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
inverted_recapture_trigger.add_condition(FlagIsTrue(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"'
)
inverted_recapture_trigger.add_action(DoScript(script_string))
inverted_recapture_trigger.add_action(ClearFlag(flag=flag))
self.mission.triggerrules.triggers.append(
inverted_recapture_trigger
)
def generate(self) -> None: def generate(self) -> None:
player_coalition = "blue" player_coalition = "blue"
enemy_coalition = "red" enemy_coalition = "red"

View File

@ -55,7 +55,7 @@ class GameStats:
turn_data = GameTurnMetadata() turn_data = GameTurnMetadata()
for cp in game.theater.controlpoints: for cp in game.theater.controlpoints:
if cp.captured: if cp.captured.is_blue:
for squadron in cp.squadrons: for squadron in cp.squadrons:
turn_data.allied_units.aircraft_count += squadron.owned_aircraft turn_data.allied_units.aircraft_count += squadron.owned_aircraft
turn_data.allied_units.vehicles_count += sum(cp.base.armor.values()) turn_data.allied_units.vehicles_count += sum(cp.base.armor.values())

View File

@ -35,6 +35,7 @@ from game.runways import RunwayData
from game.settings import Settings from game.settings import Settings
from game.squadrons import AirWing from game.squadrons import AirWing
from game.squadrons import Squadron from game.squadrons import Squadron
from game.theater.player import Player
from game.theater.controlpoint import ( from game.theater.controlpoint import (
ControlPoint, ControlPoint,
OffMapSpawn, OffMapSpawn,
@ -833,7 +834,7 @@ class PretenseAircraftGenerator:
""" """
self.initialize_pretense_data_structures(cp) self.initialize_pretense_data_structures(cp)
is_player = True is_player = Player.BLUE
if country == cp.coalition.faction.country: if country == cp.coalition.faction.country:
offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp)
@ -868,7 +869,7 @@ class PretenseAircraftGenerator:
coalition = ( coalition = (
self.game.coalition_for(is_player) self.game.coalition_for(is_player)
if country == self.game.coalition_for(is_player).faction.country if country == self.game.coalition_for(is_player).faction.country
else self.game.coalition_for(False) else self.game.coalition_for(Player.RED)
) )
self.generate_pretense_aircraft_for_other_side(cp, coalition, ato) self.generate_pretense_aircraft_for_other_side(cp, coalition, ato)

View File

@ -25,7 +25,7 @@ from game.missiongenerator.aircraft.flightgroupspawner import (
) )
from game.missiongenerator.missiondata import MissionData from game.missiongenerator.missiondata import MissionData
from game.naming import NameGenerator from game.naming import NameGenerator
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, Player
class PretenseNameGenerator(NameGenerator): class PretenseNameGenerator(NameGenerator):
@ -87,7 +87,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner):
def insert_into_pretense(self, name: str) -> None: def insert_into_pretense(self, name: str) -> None:
cp = self.flight.departure cp = self.flight.departure
is_player = True is_player = Player.BLUE
cp_side = ( cp_side = (
2 2
if self.flight.coalition if self.flight.coalition

View File

@ -1495,13 +1495,13 @@ class PretenseLuaGenerator(LuaGenerator):
cp_name.replace("ä", "a") cp_name.replace("ä", "a")
cp_name.replace("ö", "o") cp_name.replace("ö", "o")
cp_name.replace("ø", "o") cp_name.replace("ø", "o")
cp_side = 2 if cp.captured else 1 cp_side = 2 if cp.captured.is_blue else 1
if isinstance(cp, OffMapSpawn): if isinstance(cp, OffMapSpawn):
continue continue
elif ( elif (
cp.is_fleet cp.is_fleet
and cp.captured and cp.captured.is_blue
and self.game.settings.pretense_controllable_carrier and self.game.settings.pretense_controllable_carrier
): ):
# Friendly carrier, generate carrier parameters # Friendly carrier, generate carrier parameters
@ -1591,7 +1591,7 @@ class PretenseLuaGenerator(LuaGenerator):
# Also connect carrier and LHA control points to adjacent friendly points # Also connect carrier and LHA control points to adjacent friendly points
if cp.is_fleet and ( if cp.is_fleet and (
not self.game.settings.pretense_controllable_carrier not self.game.settings.pretense_controllable_carrier
or not cp.captured or cp.captured.is_red
): ):
num_of_carrier_connections = 0 num_of_carrier_connections = 0
for ( for (
@ -1616,7 +1616,7 @@ class PretenseLuaGenerator(LuaGenerator):
try: try:
if ( if (
cp.is_fleet cp.is_fleet
and cp.captured and cp.captured.is_blue
and self.game.settings.pretense_controllable_carrier and self.game.settings.pretense_controllable_carrier
): ):
break break

View File

@ -28,6 +28,7 @@ from game.missiongenerator.visualsgenerator import VisualsGenerator
from game.naming import namegen from game.naming import namegen
from game.persistency import pre_pretense_backups_dir from game.persistency import pre_pretense_backups_dir
from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator
from game.theater import Player
from game.theater.bullseye import Bullseye from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap from game.unitmap import UnitMap
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@ -233,7 +234,7 @@ class PretenseMissionGenerator(MissionGenerator):
callsign=callsign, callsign=callsign,
region=frontline, region=frontline,
code=str(code), code=str(code),
blue=True, blue=Player.BLUE,
freq=freq, freq=freq,
) )
) )

View File

@ -49,6 +49,7 @@ from game.theater import (
TheaterUnit, TheaterUnit,
NavalControlPoint, NavalControlPoint,
PresetLocation, PresetLocation,
Player,
) )
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
CarrierGroundObject, CarrierGroundObject,
@ -248,7 +249,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
""" """
unit_type = None unit_type = None
faction = self.coalition.faction faction = self.coalition.faction
is_player = True is_player = Player.BLUE
side = ( side = (
2 2
if self.country == self.game.coalition_for(is_player).faction.country if self.country == self.game.coalition_for(is_player).faction.country
@ -458,7 +459,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
control_point.name control_point.name
) )
is_player = True is_player = Player.BLUE
side = ( side = (
2 2
if self.country if self.country
@ -567,7 +568,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
control_point.name control_point.name
) )
is_player = True is_player = Player.BLUE
side = ( side = (
2 2
if self.country == self.game.coalition_for(is_player).faction.country if self.country == self.game.coalition_for(is_player).faction.country

View File

@ -1,9 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging
import math
import random import random
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING
from dcs import Point from dcs import Point
from dcs.action import ( from dcs.action import (
@ -11,10 +9,6 @@ from dcs.action import (
DoScript, DoScript,
MarkToAll, MarkToAll,
SetFlag, SetFlag,
RemoveSceneObjects,
RemoveSceneObjectsMask,
SceneryDestructionZone,
Smoke,
) )
from dcs.condition import ( from dcs.condition import (
AllOfCoalitionOutsideZone, AllOfCoalitionOutsideZone,
@ -22,7 +16,6 @@ from dcs.condition import (
FlagIsTrue, FlagIsTrue,
PartOfCoalitionInZone, PartOfCoalitionInZone,
TimeAfter, TimeAfter,
TimeSinceFlag,
) )
from dcs.mission import Mission from dcs.mission import Mission
from dcs.task import Option from dcs.task import Option
@ -31,12 +24,11 @@ from dcs.terrain.syria.airports import Damascus, Khalkhalah
from dcs.translation import String from dcs.translation import String
from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.triggers import Event, TriggerCondition, TriggerOnce
from dcs.unit import Skill from dcs.unit import Skill
from numpy import cross, einsum, arctan2
from shapely import MultiPolygon, Point as ShapelyPoint from shapely import MultiPolygon, Point as ShapelyPoint
from game.naming import ALPHA_MILITARY from game.naming import ALPHA_MILITARY
from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator
from game.theater import Airfield from game.theater import Airfield, Player
from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn
if TYPE_CHECKING: if TYPE_CHECKING:
@ -157,7 +149,7 @@ class PretenseTriggerGenerator:
zone = self.mission.triggers.add_triggerzone( zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK" location, radius=10, hidden=True, name="MARK"
) )
if cp.captured: if cp.captured.is_blue:
name = ground_object.obj_name + " [ALLY]" name = ground_object.obj_name + " [ALLY]"
else: else:
name = ground_object.obj_name + " [ENEMY]" name = ground_object.obj_name + " [ENEMY]"
@ -174,7 +166,7 @@ class PretenseTriggerGenerator:
""" """
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier: if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured: if cp.captured.is_blue:
attacking_coalition = enemy_coalition attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition defending_coalition = player_coalition
@ -243,7 +235,7 @@ class PretenseTriggerGenerator:
self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh" self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh"
) )
sea_zones_landmap = self.game.coalition_for( sea_zones_landmap = self.game.coalition_for(
player=False player=Player.RED
).nav_mesh.theater.landmap ).nav_mesh.theater.landmap
if ( if (
self.game.settings.pretense_controllable_carrier self.game.settings.pretense_controllable_carrier
@ -251,7 +243,7 @@ class PretenseTriggerGenerator:
): ):
navmesh_number = 0 navmesh_number = 0
for navmesh_poly in self.game.coalition_for( for navmesh_poly in self.game.coalition_for(
player=use_blue_navmesh player=Player.BLUE if use_blue_navmesh else Player.RED
).nav_mesh.polys: ).nav_mesh.polys:
navmesh_number += 1 navmesh_number += 1
if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly): if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly):
@ -325,7 +317,7 @@ class PretenseTriggerGenerator:
if ( if (
cp.is_fleet cp.is_fleet
and self.game.settings.pretense_controllable_carrier and self.game.settings.pretense_controllable_carrier
and cp.captured and cp.captured.is_blue
): ):
# Friendly carrier zones are generated above # Friendly carrier zones are generated above
continue continue

View File

@ -8,7 +8,7 @@ from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple
from game.config import RUNWAY_REPAIR_COST from game.config import RUNWAY_REPAIR_COST
from game.data.units import UnitClass from game.data.units import UnitClass
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint, MissionTarget, ParkingType from game.theater import ControlPoint, MissionTarget, ParkingType, Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -34,20 +34,20 @@ class ProcurementAi:
def __init__( def __init__(
self, self,
game: Game, game: Game,
for_player: bool, owner: Player,
faction: Faction, faction: Faction,
manage_runways: bool, manage_runways: bool,
manage_front_line: bool, manage_front_line: bool,
manage_aircraft: bool, manage_aircraft: bool,
) -> None: ) -> None:
self.game = game self.game = game
self.is_player = for_player self.is_player = owner
self.air_wing = game.air_wing_for(for_player) self.air_wing = game.air_wing_for(owner)
self.faction = faction self.faction = faction
self.manage_runways = manage_runways self.manage_runways = manage_runways
self.manage_front_line = manage_front_line self.manage_front_line = manage_front_line
self.manage_aircraft = manage_aircraft self.manage_aircraft = manage_aircraft
self.threat_zones = self.game.threat_zone_for(not self.is_player) self.threat_zones = self.game.threat_zone_for(self.is_player.opponent)
def calculate_ground_unit_budget_share(self) -> float: def calculate_ground_unit_budget_share(self) -> float:
armor_investment = 0 armor_investment = 0
@ -114,7 +114,7 @@ class ProcurementAi:
if control_point.runway_can_be_repaired: if control_point.runway_can_be_repaired:
control_point.begin_runway_repair() control_point.begin_runway_repair()
budget -= RUNWAY_REPAIR_COST budget -= RUNWAY_REPAIR_COST
if self.is_player: if self.is_player.is_blue:
self.game.message( self.game.message(
"We have begun repairing the runway at " f"{control_point}" "We have begun repairing the runway at " f"{control_point}"
) )
@ -223,7 +223,7 @@ class ProcurementAi:
@property @property
def owned_points(self) -> List[ControlPoint]: def owned_points(self) -> List[ControlPoint]:
if self.is_player: if self.is_player.is_blue:
return self.game.theater.player_points() return self.game.theater.player_points()
else: else:
return self.game.theater.enemy_points() return self.game.theater.enemy_points()

View File

@ -29,12 +29,16 @@ class ControlPointJs(BaseModel):
destination = None destination = None
if control_point.target_position is not None: if control_point.target_position is not None:
destination = control_point.target_position.latlng() destination = control_point.target_position.latlng()
if control_point.captured.is_blue:
blue = True
else:
blue = False
return ControlPointJs( return ControlPointJs(
id=control_point.id, id=control_point.id,
name=control_point.name, name=control_point.name,
blue=control_point.captured, blue=blue,
position=control_point.position.latlng(), position=control_point.position.latlng(),
mobile=control_point.moveable and control_point.captured, mobile=control_point.moveable and control_point.captured.is_blue,
destination=destination, destination=destination,
sidc=str(control_point.sidc()), sidc=str(control_point.sidc()),
) )

View File

@ -6,6 +6,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, status
from starlette.responses import Response from starlette.responses import Response
from game import Game from game import Game
from game.theater.player import Player
from .models import ControlPointJs from .models import ControlPointJs
from ..dependencies import GameContext from ..dependencies import GameContext
from ..leaflet import LeafletPoint from ..leaflet import LeafletPoint
@ -75,7 +76,7 @@ def set_destination(
) )
if not cp.moveable: if not cp.moveable:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile") raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile")
if not cp.captured: if not cp.captured.is_blue:
raise HTTPException( raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player" status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player"
) )
@ -120,7 +121,7 @@ def cancel_travel(cp_id: UUID, game: Game = Depends(GameContext.require)) -> Non
) )
if not cp.moveable: if not cp.moveable:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile") raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile")
if not cp.captured: if not cp.captured.is_blue:
raise HTTPException( raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player" status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player"
) )

View File

@ -15,6 +15,7 @@ from game.server.mapzones.models import ThreatZonesJs, UnculledZoneJs
from game.server.navmesh.models import NavMeshJs from game.server.navmesh.models import NavMeshJs
from game.server.supplyroutes.models import SupplyRouteJs from game.server.supplyroutes.models import SupplyRouteJs
from game.server.tgos.models import TgoJs from game.server.tgos.models import TgoJs
from game.theater import Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -26,9 +27,9 @@ class GameUpdateEventsJs(BaseModel):
new_combats: list[FrozenCombatJs] new_combats: list[FrozenCombatJs]
updated_combats: list[FrozenCombatJs] updated_combats: list[FrozenCombatJs]
ended_combats: list[UUID] ended_combats: list[UUID]
navmesh_updates: dict[bool, NavMeshJs] navmesh_updates: dict[Player, NavMeshJs]
updated_unculled_zones: list[UnculledZoneJs] updated_unculled_zones: list[UnculledZoneJs]
threat_zones_updated: dict[bool, ThreatZonesJs] threat_zones_updated: dict[Player, ThreatZonesJs]
new_flights: list[FlightJs] new_flights: list[FlightJs]
updated_flights: list[FlightJs] updated_flights: list[FlightJs]
deleted_flights: set[UUID] deleted_flights: set[UUID]

View File

@ -41,9 +41,13 @@ class FlightJs(BaseModel):
waypoints = None waypoints = None
if with_waypoints: if with_waypoints:
waypoints = waypoints_for_flight(flight) waypoints = waypoints_for_flight(flight)
if flight.blue.is_blue:
blue = True
else:
blue = False
return FlightJs( return FlightJs(
id=flight.id, id=flight.id,
blue=flight.blue, blue=blue,
position=position, position=position,
sidc=str(flight.sidc()), sidc=str(flight.sidc()),
waypoints=waypoints, waypoints=waypoints,

View File

@ -5,6 +5,7 @@ from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
from game.server.leaflet import LeafletPoint from game.server.leaflet import LeafletPoint
from game.theater.player import Player
from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode, IadsNetwork from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode, IadsNetwork
@ -34,8 +35,16 @@ class IadsConnectionJs(BaseModel):
iads_connections = [] iads_connections = []
tgo = network_node.group.ground_object tgo = network_node.group.ground_object
for id, connection in network_node.connections.items(): for id, connection in network_node.connections.items():
if connection.ground_object.is_friendly(True) != tgo.is_friendly(True): if connection.ground_object.is_friendly(Player.BLUE) != tgo.is_friendly(
Player.BLUE
):
continue # Skip connections which are not from same coalition continue # Skip connections which are not from same coalition
if tgo.is_friendly(Player.BLUE):
blue = True
elif tgo.is_friendly(Player.RED):
blue = False
else:
continue # Skip neutral
iads_connections.append( iads_connections.append(
IadsConnectionJs( IadsConnectionJs(
id=id, id=id,
@ -49,7 +58,7 @@ class IadsConnectionJs(BaseModel):
network_node.group.alive_units > 0 network_node.group.alive_units > 0
and connection.alive_units > 0 and connection.alive_units > 0
), ),
blue=tgo.is_friendly(True), blue=blue,
is_power="power" is_power="power"
in [tgo.category, connection.ground_object.category], in [tgo.category, connection.ground_object.category],
) )

View File

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
from pydantic import BaseModel from pydantic import BaseModel
from game.server.leaflet import LeafletPoint, LeafletPoly, ShapelyUtil from game.server.leaflet import LeafletPoint, LeafletPoly, ShapelyUtil
from game.theater import ConflictTheater from game.theater import ConflictTheater, Player
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
if TYPE_CHECKING: if TYPE_CHECKING:
@ -85,9 +85,9 @@ class ThreatZoneContainerJs(BaseModel):
def for_game(game: Game) -> ThreatZoneContainerJs: def for_game(game: Game) -> ThreatZoneContainerJs:
return ThreatZoneContainerJs( return ThreatZoneContainerJs(
blue=ThreatZonesJs.from_zones( blue=ThreatZonesJs.from_zones(
game.threat_zone_for(player=True), game.theater game.threat_zone_for(player=Player.BLUE), game.theater
), ),
red=ThreatZonesJs.from_zones( red=ThreatZonesJs.from_zones(
game.threat_zone_for(player=False), game.theater game.threat_zone_for(player=Player.RED), game.theater
), ),
) )

View File

@ -2,12 +2,13 @@ from fastapi import APIRouter, Depends
from game import Game from game import Game
from game.server import GameContext from game.server import GameContext
from game.theater.player import Player
from .models import NavMeshJs from .models import NavMeshJs
router: APIRouter = APIRouter(prefix="/navmesh") router: APIRouter = APIRouter(prefix="/navmesh")
@router.get("/", operation_id="get_navmesh", response_model=NavMeshJs) @router.get("/", operation_id="get_navmesh", response_model=NavMeshJs)
def get(for_player: bool, game: Game = Depends(GameContext.require)) -> NavMeshJs: def get(for_player: Player, game: Game = Depends(GameContext.require)) -> NavMeshJs:
mesh = game.coalition_for(for_player).nav_mesh mesh = game.coalition_for(for_player).nav_mesh
return NavMeshJs.from_navmesh(mesh, game) return NavMeshJs.from_navmesh(mesh, game)

View File

@ -76,6 +76,10 @@ class SupplyRouteJs(BaseModel):
def for_link( def for_link(
game: Game, a: ControlPoint, b: ControlPoint, points: list[Point], sea: bool game: Game, a: ControlPoint, b: ControlPoint, points: list[Point], sea: bool
) -> SupplyRouteJs: ) -> SupplyRouteJs:
if a.captured.is_blue:
blue = True
else:
blue = False
return SupplyRouteJs( return SupplyRouteJs(
# Although these are not persistent objects in the backend, the frontend # Although these are not persistent objects in the backend, the frontend
# needs unique IDs for anything that it will use in a list. That means that # needs unique IDs for anything that it will use in a list. That means that
@ -93,7 +97,7 @@ class SupplyRouteJs(BaseModel):
points=[p.latlng() for p in points], points=[p.latlng() for p in points],
front_active=not sea and a.front_is_active(b), front_active=not sea and a.front_is_active(b),
is_sea=sea, is_sea=sea,
blue=a.captured, blue=blue,
active_transports=TransportFinder(game, a, b).describe_active_transports( active_transports=TransportFinder(game, a, b).describe_active_transports(
sea sea
), ),

View File

@ -34,12 +34,16 @@ class TgoJs(BaseModel):
def for_tgo(tgo: TheaterGroundObject) -> TgoJs: def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
threat_ranges = [group.max_threat_range().meters for group in tgo.groups] threat_ranges = [group.max_threat_range().meters for group in tgo.groups]
detection_ranges = [group.max_detection_range().meters for group in tgo.groups] detection_ranges = [group.max_detection_range().meters for group in tgo.groups]
if tgo.control_point.captured.is_blue:
blue = True
else:
blue = False
return TgoJs( return TgoJs(
id=tgo.id, id=tgo.id,
name=tgo.name, name=tgo.name,
control_point_name=tgo.control_point.name, control_point_name=tgo.control_point.name,
category=tgo.category, category=tgo.category,
blue=tgo.control_point.captured, blue=blue,
position=tgo.position.latlng(), position=tgo.position.latlng(),
units=[unit.display_name for unit in tgo.units], units=[unit.display_name for unit in tgo.units],
threat_ranges=threat_ranges, threat_ranges=threat_ranges,

View File

@ -46,7 +46,7 @@ class AirCombat(JoinableCombat):
blue_flights = [] blue_flights = []
red_flights = [] red_flights = []
for flight in self.flights: for flight in self.flights:
if flight.squadron.player: if flight.squadron.player.is_blue:
blue_flights.append(str(flight)) blue_flights.append(str(flight))
else: else:
red_flights.append(str(flight)) red_flights.append(str(flight))
@ -71,7 +71,7 @@ class AirCombat(JoinableCombat):
blue = [] blue = []
red = [] red = []
for flight in self.flights: for flight in self.flights:
if flight.squadron.player: if flight.squadron.player.is_blue:
blue.append(flight) blue.append(flight)
else: else:
red.append(flight) red.append(flight)

View File

@ -6,6 +6,7 @@ from collections.abc import Iterator
from datetime import timedelta from datetime import timedelta
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from game.theater.player import Player
from .aircombat import AirCombat from .aircombat import AirCombat
from .aircraftengagementzones import AircraftEngagementZones from .aircraftengagementzones import AircraftEngagementZones
from .atip import AtIp from .atip import AtIp
@ -31,8 +32,10 @@ class CombatInitiator:
def update_active_combats(self) -> None: def update_active_combats(self) -> None:
blue_a2a = AircraftEngagementZones.from_ato(self.game.blue.ato) blue_a2a = AircraftEngagementZones.from_ato(self.game.blue.ato)
red_a2a = AircraftEngagementZones.from_ato(self.game.red.ato) red_a2a = AircraftEngagementZones.from_ato(self.game.red.ato)
blue_sam = SamEngagementZones.from_theater(self.game.theater, player=True) blue_sam = SamEngagementZones.from_theater(
red_sam = SamEngagementZones.from_theater(self.game.theater, player=False) self.game.theater, player=Player.BLUE
)
red_sam = SamEngagementZones.from_theater(self.game.theater, player=Player.RED)
# Check each vulnerable flight to see if it has initiated combat. If any flight # Check each vulnerable flight to see if it has initiated combat. If any flight
# initiates combat, a single FrozenCombat will be created for all involved # initiates combat, a single FrozenCombat will be created for all involved
@ -46,7 +49,7 @@ class CombatInitiator:
if flight.state.in_combat: if flight.state.in_combat:
return return
if flight.squadron.player: if flight.squadron.player.is_blue:
a2a = red_a2a a2a = red_a2a
own_a2a = blue_a2a own_a2a = blue_a2a
sam = red_sam sam = red_sam

View File

@ -9,7 +9,7 @@ from shapely.ops import unary_union
from game.utils import dcs_to_shapely_point from game.utils import dcs_to_shapely_point
if TYPE_CHECKING: if TYPE_CHECKING:
from game.theater import ConflictTheater, TheaterGroundObject from game.theater import ConflictTheater, TheaterGroundObject, Player
from game.threatzones import ThreatPoly from game.threatzones import ThreatPoly
@ -31,7 +31,9 @@ class SamEngagementZones:
yield tgo yield tgo
@classmethod @classmethod
def from_theater(cls, theater: ConflictTheater, player: bool) -> SamEngagementZones: def from_theater(
cls, theater: ConflictTheater, player: Player
) -> SamEngagementZones:
commit_regions = [] commit_regions = []
individual_zones = [] individual_zones = []
for cp in theater.control_points_for(player): for cp in theater.control_points_for(player):

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING:
from game.ato import Flight, Package from game.ato import Flight, Package
from game.navmesh import NavMesh from game.navmesh import NavMesh
from game.sim.combat import FrozenCombat from game.sim.combat import FrozenCombat
from game.theater import ControlPoint, FrontLine, TheaterGroundObject from game.theater import ControlPoint, FrontLine, TheaterGroundObject, Player
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode
@ -24,9 +24,9 @@ class GameUpdateEvents:
updated_combats: list[FrozenCombat] = field(default_factory=list) updated_combats: list[FrozenCombat] = field(default_factory=list)
ended_combats: list[FrozenCombat] = field(default_factory=list) ended_combats: list[FrozenCombat] = field(default_factory=list)
updated_flight_positions: list[tuple[Flight, Point]] = field(default_factory=list) updated_flight_positions: list[tuple[Flight, Point]] = field(default_factory=list)
navmesh_updates: dict[bool, NavMesh] = field(default_factory=dict) navmesh_updates: dict[Player, NavMesh] = field(default_factory=dict)
unculled_zones_updated: list[Point] = field(default_factory=list) unculled_zones_updated: list[Point] = field(default_factory=list)
threat_zones_updated: dict[bool, ThreatZones] = field(default_factory=dict) threat_zones_updated: dict[Player, ThreatZones] = field(default_factory=dict)
new_flights: set[Flight] = field(default_factory=set) new_flights: set[Flight] = field(default_factory=set)
updated_flights: set[Flight] = field(default_factory=set) updated_flights: set[Flight] = field(default_factory=set)
deleted_flights: set[UUID] = field(default_factory=set) deleted_flights: set[UUID] = field(default_factory=set)
@ -70,7 +70,7 @@ class GameUpdateEvents:
self.updated_flight_positions.append((flight, new_position)) self.updated_flight_positions.append((flight, new_position))
return self return self
def update_navmesh(self, player: bool, navmesh: NavMesh) -> GameUpdateEvents: def update_navmesh(self, player: Player, navmesh: NavMesh) -> GameUpdateEvents:
self.navmesh_updates[player] = navmesh self.navmesh_updates[player] = navmesh
return self return self
@ -78,7 +78,9 @@ class GameUpdateEvents:
self.unculled_zones_updated = zones self.unculled_zones_updated = zones
return self return self
def update_threat_zones(self, player: bool, zones: ThreatZones) -> GameUpdateEvents: def update_threat_zones(
self, player: Player, zones: ThreatZones
) -> GameUpdateEvents:
self.threat_zones_updated[player] = zones self.threat_zones_updated[player] = zones
return self return self

View File

@ -145,7 +145,7 @@ class MissionResultsProcessor:
def commit_captures(self, debriefing: Debriefing, events: GameUpdateEvents) -> None: def commit_captures(self, debriefing: Debriefing, events: GameUpdateEvents) -> None:
for captured in debriefing.base_captures: for captured in debriefing.base_captures:
try: try:
if captured.captured_by_player: if captured.captured_by_player.is_blue:
self.game.message( self.game.message(
f"{captured.control_point} captured!", f"{captured.control_point} captured!",
f"We took control of {captured.control_point}.", f"We took control of {captured.control_point}.",

View File

@ -14,12 +14,13 @@ from ..utils import Distance
if TYPE_CHECKING: if TYPE_CHECKING:
from game.game import Game from game.game import Game
from game.theater.player import Player
from ..ato.flighttype import FlightType from ..ato.flighttype import FlightType
from .squadron import Squadron from .squadron import Squadron
class AirWing: class AirWing:
def __init__(self, player: bool, game: Game, faction: Faction) -> None: def __init__(self, player: Player, game: Game, faction: Faction) -> None:
self.player = player self.player = player
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list) self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
self.squadron_defs = SquadronDefLoader(game, faction).load() self.squadron_defs = SquadronDefLoader(game, faction).load()

View File

@ -24,7 +24,7 @@ if TYPE_CHECKING:
from game import Game from game import Game
from game.coalition import Coalition from game.coalition import Coalition
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.theater import ControlPoint, MissionTarget from game.theater import ControlPoint, MissionTarget, Player
from .operatingbases import OperatingBases from .operatingbases import OperatingBases
from .squadrondef import SquadronDef from .squadrondef import SquadronDef
@ -96,7 +96,7 @@ class Squadron:
self._livery_pool: list[str] = [] self._livery_pool: list[str] = []
@property @property
def player(self) -> bool: def player(self) -> Player:
return self.coalition.player return self.coalition.player
def assign_to_base(self, base: ControlPoint) -> None: def assign_to_base(self, base: ControlPoint) -> None:
@ -134,7 +134,7 @@ class Squadron:
return self.claim_new_pilot_if_allowed() return self.claim_new_pilot_if_allowed()
# For opfor, so player/AI option is irrelevant. # For opfor, so player/AI option is irrelevant.
if not self.player: if self.player != Player.BLUE:
return self.available_pilots.pop() return self.available_pilots.pop()
preference = self.settings.auto_ato_behavior preference = self.settings.auto_ato_behavior

View File

@ -15,6 +15,7 @@ from .daytimemap import DaytimeMap
from .frontline import FrontLine from .frontline import FrontLine
from .iadsnetwork.iadsnetwork import IadsNetwork from .iadsnetwork.iadsnetwork import IadsNetwork
from .landmap import poly_contains, load_landmap from .landmap import poly_contains, load_landmap
from .player import Player
from .seasonalconditions import SeasonalConditions from .seasonalconditions import SeasonalConditions
from ..utils import Heading from ..utils import Heading
@ -170,10 +171,10 @@ class ConflictTheater:
return new_point return new_point
def control_points_for( def control_points_for(
self, player: bool, state_check: bool = False self, player: Player, state_check: bool = False
) -> Iterator[ControlPoint]: ) -> Iterator[ControlPoint]:
for point in self.controlpoints: for point in self.controlpoints:
if point.captured == player: if point.captured is player:
if not state_check: if not state_check:
yield point yield point
elif point.is_carrier and point.runway_is_operational(): elif point.is_carrier and point.runway_is_operational():
@ -182,14 +183,21 @@ class ConflictTheater:
yield point yield point
def player_points(self, state_check: bool = False) -> List[ControlPoint]: def player_points(self, state_check: bool = False) -> List[ControlPoint]:
return list(self.control_points_for(player=True, state_check=state_check)) return list(
self.control_points_for(player=Player.BLUE, state_check=state_check)
)
def conflicts(self) -> Iterator[FrontLine]: def conflicts(self) -> Iterator[FrontLine]:
for cp in self.player_points(): for cp in self.player_points():
yield from cp.front_lines.values() yield from cp.front_lines.values()
def enemy_points(self, state_check: bool = False) -> List[ControlPoint]: def enemy_points(self, state_check: bool = False) -> List[ControlPoint]:
return list(self.control_points_for(player=False, state_check=state_check)) return list(self.control_points_for(player=Player.RED, state_check=state_check))
def neutral_points(self, state_check: bool = False) -> List[ControlPoint]:
return list(
self.control_points_for(player=Player.NEUTRAL, state_check=state_check)
)
def closest_control_point( def closest_control_point(
self, point: Point, allow_naval: bool = False self, point: Point, allow_naval: bool = False
@ -259,10 +267,12 @@ class ConflictTheater:
""" """
closest_cps = list() closest_cps = list()
distances_to_cp = dict() distances_to_cp = dict()
if cp.captured: if cp.captured.is_blue:
control_points = self.player_points() control_points = self.player_points()
else: elif cp.captured.is_red:
control_points = self.enemy_points() control_points = self.enemy_points()
elif cp.captured.is_neutral:
control_points = self.neutral_points()
for other_cp in control_points: for other_cp in control_points:
if cp == other_cp: if cp == other_cp:
continue continue

View File

@ -68,6 +68,7 @@ from .base import Base
from .frontline import FrontLine from .frontline import FrontLine
from .interfaces.CTLD import CTLD from .interfaces.CTLD import CTLD
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
from .player import Player
from .theatergroundobject import ( from .theatergroundobject import (
GenericCarrierGroundObject, GenericCarrierGroundObject,
TheaterGroundObject, TheaterGroundObject,
@ -377,7 +378,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
position: Point, position: Point,
at: StartingPosition, at: StartingPosition,
theater: ConflictTheater, theater: ConflictTheater,
starts_blue: bool, starting_coalition: Player,
cptype: ControlPointType = ControlPointType.AIRBASE, cptype: ControlPointType = ControlPointType.AIRBASE,
is_invisible: bool = False, is_invisible: bool = False,
) -> None: ) -> None:
@ -386,8 +387,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self.full_name = name self.full_name = name
self.at = at self.at = at
self.theater = theater self.theater = theater
self.starts_blue = starts_blue
self.is_invisible = is_invisible self.is_invisible = is_invisible
self.starting_coalition = starting_coalition
self.connected_objectives: List[TheaterGroundObject] = [] self.connected_objectives: List[TheaterGroundObject] = []
self.preset_locations = PresetLocations() self.preset_locations = PresetLocations()
self.helipads: List[PointWithHeading] = [] self.helipads: List[PointWithHeading] = []
@ -435,7 +436,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def finish_init(self, game: Game) -> None: def finish_init(self, game: Game) -> None:
assert self._coalition is None assert self._coalition is None
self._coalition = game.coalition_for(self.starts_blue) self._coalition = game.coalition_for(self.starting_coalition)
assert self._front_line_db is None assert self._front_line_db is None
self._front_line_db = game.db.front_lines self._front_line_db = game.db.front_lines
@ -444,7 +445,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
# the entire game state when it comes up. # the entire game state when it comes up.
from game.sim import GameUpdateEvents from game.sim import GameUpdateEvents
self._create_missing_front_lines(laser_code_registry, GameUpdateEvents()) if self.captured != Player.NEUTRAL:
self._create_missing_front_lines(laser_code_registry, GameUpdateEvents())
@property @property
def front_line_db(self) -> Database[FrontLine]: def front_line_db(self) -> Database[FrontLine]:
@ -455,9 +457,11 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self, laser_code_registry: LaserCodeRegistry, events: GameUpdateEvents self, laser_code_registry: LaserCodeRegistry, events: GameUpdateEvents
) -> None: ) -> None:
for connection in self.convoy_routes.keys(): for connection in self.convoy_routes.keys():
if not connection.front_line_active_with( if (
self not connection.front_line_active_with(self)
) and not connection.is_friendly_to(self): and not connection.is_friendly_to(self)
and connection.captured != Player.NEUTRAL
):
self._create_front_line_with(laser_code_registry, connection, events) self._create_front_line_with(laser_code_registry, connection, events)
def _create_front_line_with( def _create_front_line_with(
@ -491,6 +495,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
@property @property
def has_frontline(self) -> bool: def has_frontline(self) -> bool:
if self.captured.is_neutral:
return False
return bool(self.front_lines) return bool(self.front_lines)
def front_line_active_with(self, other: ControlPoint) -> bool: def front_line_active_with(self, other: ControlPoint) -> bool:
@ -500,14 +506,17 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
return self.front_lines[other] return self.front_lines[other]
@property @property
def captured(self) -> bool: def captured(self) -> Player:
return self.coalition.player return self.coalition.player
@property @property
def standard_identity(self) -> StandardIdentity: def standard_identity(self) -> StandardIdentity:
return ( if self.captured.is_neutral:
StandardIdentity.FRIEND if self.captured else StandardIdentity.HOSTILE_FAKER return StandardIdentity.UNKNOWN
) elif self.captured.is_blue:
return StandardIdentity.FRIEND
else:
return StandardIdentity.HOSTILE_FAKER
@property @property
def sidc_status(self) -> Status: def sidc_status(self) -> Status:
@ -819,7 +828,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
found.append(g) found.append(g)
return found return found
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: Player) -> bool:
return self.captured == to_player return self.captured == to_player
def is_friendly_to(self, control_point: ControlPoint) -> bool: def is_friendly_to(self, control_point: ControlPoint) -> bool:
@ -828,7 +837,9 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def capture_equipment(self, game: Game) -> None: def capture_equipment(self, game: Game) -> None:
total = self.base.total_armor_value total = self.base.total_armor_value
self.base.armor.clear() self.base.armor.clear()
game.adjust_budget(total, player=not self.captured) game.adjust_budget(
total, player=Player.BLUE if self.captured.is_red else Player.RED
)
game.message( game.message(
f"{self.name} is not connected to any friendly points. Ground " f"{self.name} is not connected to any friendly points. Ground "
f"vehicles have been captured and sold for ${total}M." f"vehicles have been captured and sold for ${total}M."
@ -857,7 +868,9 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def capture_aircraft(self, game: Game, airframe: AircraftType, count: int) -> None: def capture_aircraft(self, game: Game, airframe: AircraftType, count: int) -> None:
value = airframe.price * count value = airframe.price * count
game.adjust_budget(value, player=not self.captured) game.adjust_budget(
value, player=Player.BLUE if self.captured.is_red else Player.RED
)
game.message( game.message(
f"No valid retreat destination in range of {self.name} for {airframe} " f"No valid retreat destination in range of {self.name} for {airframe} "
f"{count} aircraft have been captured and sold for ${value}M." f"{count} aircraft have been captured and sold for ${value}M."
@ -960,7 +973,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
pass pass
# TODO: Should be Airbase specific. # TODO: Should be Airbase specific.
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None: def capture(self, game: Game, events: GameUpdateEvents, for_player: Player) -> None:
new_coalition = game.coalition_for(for_player) new_coalition = game.coalition_for(for_player)
self.ground_unit_orders.refund_all(self.coalition) self.ground_unit_orders.refund_all(self.coalition)
self.retreat_ground_units(game) self.retreat_ground_units(game)
@ -1125,6 +1138,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
@property @property
def has_active_frontline(self) -> bool: def has_active_frontline(self) -> bool:
if self.captured.is_neutral:
return False
return any(not c.is_friendly(self.captured) for c in self.connected_points) return any(not c.is_friendly(self.captured) for c in self.connected_points)
def front_is_active(self, other: ControlPoint) -> bool: def front_is_active(self, other: ControlPoint) -> bool:
@ -1211,7 +1226,7 @@ class Airfield(ControlPoint, CTLD):
self, self,
airport: Airport, airport: Airport,
theater: ConflictTheater, theater: ConflictTheater,
starts_blue: bool, starting_coalition: Player,
ctld_zones: Optional[List[Tuple[Point, float]]] = None, ctld_zones: Optional[List[Tuple[Point, float]]] = None,
influence_zone: Optional[List[Tuple[Point, float]]] = None, influence_zone: Optional[List[Tuple[Point, float]]] = None,
) -> None: ) -> None:
@ -1220,7 +1235,7 @@ class Airfield(ControlPoint, CTLD):
airport.position, airport.position,
airport, airport,
theater, theater,
starts_blue, starting_coalition,
cptype=ControlPointType.AIRBASE, cptype=ControlPointType.AIRBASE,
) )
self.airport = airport self.airport = airport
@ -1252,7 +1267,7 @@ class Airfield(ControlPoint, CTLD):
return True return True
return self.runway_is_operational() return self.runway_is_operational()
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -1373,7 +1388,7 @@ class NavalControlPoint(
def is_fleet(self) -> bool: def is_fleet(self) -> bool:
return True return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if self.is_friendly(for_player): if self.is_friendly(for_player):
@ -1482,7 +1497,7 @@ class NavalControlPoint(
class Carrier(NavalControlPoint): class Carrier(NavalControlPoint):
def __init__( def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
): ):
super().__init__( super().__init__(
name, name,
@ -1497,7 +1512,7 @@ class Carrier(NavalControlPoint):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None: def capture(self, game: Game, events: GameUpdateEvents, for_player: Player) -> None:
raise RuntimeError("Carriers cannot be captured") raise RuntimeError("Carriers cannot be captured")
@property @property
@ -1522,7 +1537,7 @@ class EssexCarrier(Carrier):
class Lha(NavalControlPoint): class Lha(NavalControlPoint):
def __init__( def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
): ):
super().__init__( super().__init__(
name, at, at, theater, starts_blue, cptype=ControlPointType.LHA_GROUP name, at, at, theater, starts_blue, cptype=ControlPointType.LHA_GROUP
@ -1532,7 +1547,7 @@ class Lha(NavalControlPoint):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.AMPHIBIOUS_ASSAULT_SHIP_GENERAL return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.AMPHIBIOUS_ASSAULT_SHIP_GENERAL
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None: def capture(self, game: Game, events: GameUpdateEvents, for_player: Player) -> None:
raise RuntimeError("LHAs cannot be captured") raise RuntimeError("LHAs cannot be captured")
@property @property
@ -1555,7 +1570,7 @@ class OffMapSpawn(ControlPoint):
return True return True
def __init__( def __init__(
self, name: str, position: Point, theater: ConflictTheater, starts_blue: bool self, name: str, position: Point, theater: ConflictTheater, starts_blue: Player
): ):
super().__init__( super().__init__(
name, name,
@ -1570,10 +1585,10 @@ class OffMapSpawn(ControlPoint):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None: def capture(self, game: Game, events: GameUpdateEvents, for_player: Player) -> None:
raise RuntimeError("Off map control points cannot be captured") raise RuntimeError("Off map control points cannot be captured")
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
yield from [] yield from []
def total_aircraft_parking(self, parking_type: ParkingType) -> int: def total_aircraft_parking(self, parking_type: ParkingType) -> int:
@ -1630,7 +1645,7 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
name: str, name: str,
at: Point, at: Point,
theater: ConflictTheater, theater: ConflictTheater,
starts_blue: bool, starts_blue: Player,
ctld_zones: Optional[List[Tuple[Point, float]]] = None, ctld_zones: Optional[List[Tuple[Point, float]]] = None,
is_invisible: bool = False, is_invisible: bool = False,
influence_zone: Optional[List[Tuple[Point, float]]] = None, influence_zone: Optional[List[Tuple[Point, float]]] = None,
@ -1667,7 +1682,7 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
def runway_status(self) -> RunwayStatus: def runway_status(self) -> RunwayStatus:
return RunwayStatus() return RunwayStatus()
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):

View File

@ -8,6 +8,7 @@ from typing import Any, Iterator, List, TYPE_CHECKING, Tuple
from dcs.mapping import Point from dcs.mapping import Point
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
from .player import Player
from ..lasercodes.lasercode import LaserCode from ..lasercodes.lasercode import LaserCode
from ..utils import Heading, pairwise from ..utils import Heading, pairwise
@ -89,19 +90,22 @@ class FrontLine(MissionTarget):
def update_position(self) -> None: def update_position(self) -> None:
self.position = self._compute_position() self.position = self._compute_position()
def control_point_friendly_to(self, player: bool) -> ControlPoint: def control_point_friendly_to(self, player: Player) -> ControlPoint:
if player: if player.is_blue:
return self.blue_cp return self.blue_cp
return self.red_cp return self.red_cp
def control_point_hostile_to(self, player: bool) -> ControlPoint: def control_point_hostile_to(self, player: Player) -> ControlPoint:
return self.control_point_friendly_to(not player) if player.is_blue:
return self.red_cp
else:
return self.blue_cp
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: Player) -> bool:
"""Returns True if the objective is in friendly territory.""" """Returns True if the objective is in friendly territory."""
return False return False
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
yield from [ yield from [
@ -210,6 +214,6 @@ class FrontLine(MissionTarget):
raise ValueError( raise ValueError(
"Cannot sort control points that are friendly to each other" "Cannot sort control points that are friendly to each other"
) )
if a.captured: if a.captured.is_blue:
return a, b return a, b
return b, a return b, a

View File

@ -16,6 +16,7 @@ from game.theater.theatergroundobject import (
TheaterGroundObject, TheaterGroundObject,
) )
from game.theater.theatergroup import IadsGroundGroup from game.theater.theatergroup import IadsGroundGroup
from game.theater.player import Player
if TYPE_CHECKING: if TYPE_CHECKING:
from game.game import Game from game.game import Game
@ -31,7 +32,7 @@ class SkynetNode:
"""Dataclass for a SkynetNode used in the LUA Data table by the luagenerator""" """Dataclass for a SkynetNode used in the LUA Data table by the luagenerator"""
dcs_name: str dcs_name: str
player: bool player: Player
iads_role: IadsRole iads_role: IadsRole
properties: dict[str, str] = field(default_factory=dict) properties: dict[str, str] = field(default_factory=dict)
connections: dict[str, list[str]] = field(default_factory=lambda: defaultdict(list)) connections: dict[str, list[str]] = field(default_factory=lambda: defaultdict(list))
@ -62,7 +63,7 @@ class SkynetNode:
def from_group(cls, group: IadsGroundGroup) -> SkynetNode: def from_group(cls, group: IadsGroundGroup) -> SkynetNode:
node = cls( node = cls(
cls.dcs_name_for_group(group), cls.dcs_name_for_group(group),
group.ground_object.is_friendly(True), group.ground_object.coalition.player,
group.iads_role, group.iads_role,
) )
unit_type = group.units[0].unit_type unit_type = group.units[0].unit_type
@ -314,8 +315,8 @@ class IadsNetwork:
self._make_advanced_connections_by_range(node) self._make_advanced_connections_by_range(node)
def _is_friendly(self, node: IadsNetworkNode, tgo: TheaterGroundObject) -> bool: def _is_friendly(self, node: IadsNetworkNode, tgo: TheaterGroundObject) -> bool:
node_friendly = node.group.ground_object.is_friendly(True) node_friendly = node.group.ground_object.is_friendly(Player.BLUE)
tgo_friendly = tgo.is_friendly(True) tgo_friendly = tgo.is_friendly(Player.BLUE)
return node_friendly == tgo_friendly return node_friendly == tgo_friendly
def _update_network( def _update_network(

View File

@ -6,7 +6,7 @@ from dcs.mapping import Point
if TYPE_CHECKING: if TYPE_CHECKING:
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.theater import TheaterUnit, Coalition from game.theater import TheaterUnit, Coalition, Player
class MissionTarget: class MissionTarget:
@ -24,11 +24,11 @@ class MissionTarget:
"""Computes the distance to the given mission target.""" """Computes the distance to the given mission target."""
return self.position.distance_to_point(other.position) return self.position.distance_to_point(other.position)
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: Player) -> bool:
"""Returns True if the objective is in friendly territory.""" """Returns True if the objective is in friendly territory."""
raise NotImplementedError raise NotImplementedError
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if self.is_friendly(for_player): if self.is_friendly(for_player):

30
game/theater/player.py Normal file
View File

@ -0,0 +1,30 @@
from enum import Enum
class Player(Enum):
NEUTRAL = "Neutral"
BLUE = "Blue"
RED = "Red"
@property
def is_red(self) -> bool:
"""Returns True if the player is Red."""
return self == Player.RED
@property
def is_blue(self) -> bool:
"""Returns True if the player is Blue."""
return self == Player.BLUE
@property
def is_neutral(self) -> bool:
"""Returns True if the player is Neutral."""
return self == Player.NEUTRAL
@property
def opponent(self) -> "Player":
"""Returns the opponent player."""
if self.is_blue:
return Player.RED
else:
return Player.BLUE

View File

@ -32,6 +32,7 @@ from . import (
Fob, Fob,
OffMapSpawn, OffMapSpawn,
) )
from .player import Player
from .theatergroup import ( from .theatergroup import (
IadsGroundGroup, IadsGroundGroup,
IadsRole, IadsRole,
@ -158,12 +159,12 @@ class GameGenerator:
game.settings.version = VERSION game.settings.version = VERSION
return game return game
def should_remove_carrier(self, player: bool) -> bool: def should_remove_carrier(self, player: Player) -> bool:
faction = self.player if player else self.enemy faction = self.player if player.is_blue else self.enemy
return self.generator_settings.no_carrier or not faction.carriers return self.generator_settings.no_carrier or not faction.carriers
def should_remove_lha(self, player: bool) -> bool: def should_remove_lha(self, player: Player) -> bool:
faction = self.player if player else self.enemy faction = self.player if player.is_blue else self.enemy
return self.generator_settings.no_lha or not [ return self.generator_settings.no_lha or not [
x for x in faction.carriers if x.unit_class == UnitClass.HELICOPTER_CARRIER x for x in faction.carriers if x.unit_class == UnitClass.HELICOPTER_CARRIER
] ]
@ -174,11 +175,13 @@ class GameGenerator:
# Remove carrier and lha, invert situation if needed # Remove carrier and lha, invert situation if needed
for cp in self.theater.controlpoints: for cp in self.theater.controlpoints:
if self.generator_settings.inverted: if self.generator_settings.inverted:
cp.starts_blue = cp.captured_invert cp.starting_coalition = (
Player.RED if not cp.captured_invert else Player.BLUE
)
if cp.is_carrier and self.should_remove_carrier(cp.starts_blue): if cp.is_carrier and self.should_remove_carrier(cp.starting_coalition):
to_remove.append(cp) to_remove.append(cp)
elif cp.is_lha and self.should_remove_lha(cp.starts_blue): elif cp.is_lha and self.should_remove_lha(cp.starting_coalition):
to_remove.append(cp) to_remove.append(cp)
# do remove # do remove
@ -230,11 +233,13 @@ class ControlPointGroundObjectGenerator:
self.control_point.connected_objectives.append(ground_object) self.control_point.connected_objectives.append(ground_object)
def generate_navy(self) -> None: def generate_navy(self) -> None:
if self.control_point.captured.is_neutral:
return
skip_player_navy = self.generator_settings.no_player_navy skip_player_navy = self.generator_settings.no_player_navy
if self.control_point.captured and skip_player_navy: if self.control_point.captured.is_blue and skip_player_navy:
return return
skip_enemy_navy = self.generator_settings.no_enemy_navy skip_enemy_navy = self.generator_settings.no_enemy_navy
if not self.control_point.captured and skip_enemy_navy: if self.control_point.captured.is_red and skip_enemy_navy:
return return
for position in self.control_point.preset_locations.ships: for position in self.control_point.preset_locations.ships:
unit_group = self.armed_forces.random_group_for_task(GroupTask.NAVY) unit_group = self.armed_forces.random_group_for_task(GroupTask.NAVY)
@ -352,7 +357,7 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
self.control_point.name, self.control_point.name,
self.control_point.position, self.control_point.position,
self.game.theater, self.game.theater,
self.control_point.starts_blue, self.control_point.starting_coalition,
) )
self.control_point.finish_init(self.game) self.control_point.finish_init(self.game)
self.game.theater.controlpoints.append(self.control_point) self.game.theater.controlpoints.append(self.control_point)

View File

@ -21,6 +21,7 @@ from game.sidc import (
) )
from game.theater.presetlocation import PresetLocation from game.theater.presetlocation import PresetLocation
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
from .player import Player
from ..data.groups import GroupTask from ..data.groups import GroupTask
from ..utils import Distance, Heading, meters from ..utils import Distance, Heading, meters
@ -98,11 +99,12 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
@property @property
def standard_identity(self) -> StandardIdentity: def standard_identity(self) -> StandardIdentity:
return ( if self.control_point.captured.is_blue:
StandardIdentity.FRIEND return StandardIdentity.FRIEND
if self.control_point.captured elif self.control_point.captured.is_neutral:
else StandardIdentity.HOSTILE_FAKER return StandardIdentity.UNKNOWN
) else:
return StandardIdentity.HOSTILE_FAKER
@property @property
def is_dead(self) -> bool: def is_dead(self) -> bool:
@ -154,10 +156,12 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
def faction_color(self) -> str: def faction_color(self) -> str:
return "BLUE" if self.control_point.captured else "RED" return "BLUE" if self.control_point.captured else "RED"
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: Player) -> bool:
if self.control_point.captured.is_neutral:
return False
return self.control_point.is_friendly(to_player) return self.control_point.is_friendly(to_player)
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if self.is_friendly(for_player): if self.is_friendly(for_player):
@ -360,7 +364,7 @@ class BuildingGroundObject(TheaterGroundObject):
class NavalGroundObject(TheaterGroundObject, ABC): class NavalGroundObject(TheaterGroundObject, ABC):
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -466,7 +470,7 @@ class MissileSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool: def should_head_to_conflict(self) -> bool:
return True return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -507,7 +511,7 @@ class CoastalSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool: def should_head_to_conflict(self) -> bool:
return True return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -534,7 +538,7 @@ class IadsGroundObject(TheaterGroundObject, ABC):
task=task, task=task,
) )
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -585,7 +589,7 @@ class SamGroundObject(IadsGroundObject):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.LAND_UNIT, LandUnitEntity.AIR_DEFENSE return SymbolSet.LAND_UNIT, LandUnitEntity.AIR_DEFENSE
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -642,7 +646,7 @@ class VehicleGroupGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool: def should_head_to_conflict(self) -> bool:
return True return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):
@ -697,7 +701,7 @@ class ShipGroundObject(NavalGroundObject):
class IadsBuildingGroundObject(BuildingGroundObject): class IadsBuildingGroundObject(BuildingGroundObject):
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType from game.ato import FlightType
if not self.is_friendly(for_player): if not self.is_friendly(for_player):

View File

@ -9,6 +9,7 @@ from typing import Dict, Iterator, List, Optional, Set, Tuple
from .conflicttheater import ConflictTheater from .conflicttheater import ConflictTheater
from .controlpoint import ControlPoint from .controlpoint import ControlPoint
from .player import Player
class NoPathError(RuntimeError): class NoPathError(RuntimeError):
@ -152,7 +153,7 @@ class TransitNetwork:
class TransitNetworkBuilder: class TransitNetworkBuilder:
def __init__(self, theater: ConflictTheater, for_player: bool) -> None: def __init__(self, theater: ConflictTheater, for_player: Player) -> None:
self.control_points = list(theater.control_points_for(for_player)) self.control_points = list(theater.control_points_for(for_player))
self.network = TransitNetwork() self.network = TransitNetwork()
self.airports: Set[ControlPoint] = { self.airports: Set[ControlPoint] = {

View File

@ -26,6 +26,7 @@ from game.utils import Distance, meters, nautical_miles
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
from game.theater.player import Player
ThreatPoly = Union[MultiPolygon, Polygon] ThreatPoly = Union[MultiPolygon, Polygon]
@ -187,7 +188,7 @@ class ThreatZones:
return min(cap_threat_range, max_distance) return min(cap_threat_range, max_distance)
@classmethod @classmethod
def for_faction(cls, game: Game, player: bool) -> ThreatZones: def for_faction(cls, game: Game, player: Player) -> ThreatZones:
"""Generates the threat zones projected by the given coalition. """Generates the threat zones projected by the given coalition.
Args: Args:

View File

@ -50,7 +50,14 @@ from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.naming import namegen from game.naming import namegen
from game.procurement import AircraftProcurementRequest from game.procurement import AircraftProcurementRequest
from game.theater import ControlPoint, MissionTarget, ParkingType, Carrier, Airfield from game.theater import (
ControlPoint,
MissionTarget,
ParkingType,
Carrier,
Airfield,
Player,
)
from game.theater.transitnetwork import ( from game.theater.transitnetwork import (
TransitConnection, TransitConnection,
TransitNetwork, TransitNetwork,
@ -92,7 +99,7 @@ class TransferOrder:
position: ControlPoint = field(init=False) position: ControlPoint = field(init=False)
#: True if the transfer order belongs to the player. #: True if the transfer order belongs to the player.
player: bool = field(init=False) player: Player = field(init=False)
#: The units being transferred. #: The units being transferred.
units: dict[GroundUnitType, int] units: dict[GroundUnitType, int]
@ -111,7 +118,7 @@ class TransferOrder:
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.position = self.origin self.position = self.origin
self.player = self.origin.is_friendly(to_player=True) self.player = self.origin.captured
@property @property
def description(self) -> str: def description(self) -> str:
@ -239,7 +246,10 @@ class Airlift(Transport):
@property @property
def player_owned(self) -> bool: def player_owned(self) -> bool:
return self.transfer.player if self.transfer.player.is_blue:
return True
else:
return False
def find_escape_route(self) -> Optional[ControlPoint]: def find_escape_route(self) -> Optional[ControlPoint]:
# TODO: Move units to closest base. # TODO: Move units to closest base.
@ -384,8 +394,11 @@ class MultiGroupTransport(MissionTarget, Transport):
self.origin = origin self.origin = origin
self.transfers: List[TransferOrder] = [] self.transfers: List[TransferOrder] = []
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: Player) -> bool:
return self.origin.captured if self.origin.captured.is_blue:
return True
else:
return False
def add_units(self, transfer: TransferOrder) -> None: def add_units(self, transfer: TransferOrder) -> None:
self.transfers.append(transfer) self.transfers.append(transfer)
@ -432,7 +445,7 @@ class MultiGroupTransport(MissionTarget, Transport):
yield unit_type yield unit_type
@property @property
def player_owned(self) -> bool: def player_owned(self) -> Player:
return self.origin.captured return self.origin.captured
def find_escape_route(self) -> Optional[ControlPoint]: def find_escape_route(self) -> Optional[ControlPoint]:
@ -450,7 +463,7 @@ class Convoy(MultiGroupTransport):
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
super().__init__(namegen.next_convoy_name(), origin, destination) super().__init__(namegen.next_convoy_name(), origin, destination)
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
if self.is_friendly(for_player): if self.is_friendly(for_player):
return return
@ -476,7 +489,7 @@ class CargoShip(MultiGroupTransport):
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
super().__init__(namegen.next_cargo_ship_name(), origin, destination) super().__init__(namegen.next_cargo_ship_name(), origin, destination)
def mission_types(self, for_player: bool) -> Iterator[FlightType]: def mission_types(self, for_player: Player) -> Iterator[FlightType]:
if self.is_friendly(for_player): if self.is_friendly(for_player):
return return
@ -570,7 +583,7 @@ class CargoShipMap(TransportMap[CargoShip]):
class PendingTransfers: class PendingTransfers:
def __init__(self, game: Game, player: bool) -> None: def __init__(self, game: Game, player: Player) -> None:
self.game = game self.game = game
self.player = player self.player = player
self.convoys = ConvoyMap() self.convoys = ConvoyMap()

View File

@ -24,7 +24,7 @@ from game.radio.tacan import TacanChannel
from game.server import EventStream from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents from game.sim.gameupdateevents import GameUpdateEvents
from game.squadrons.squadron import Pilot, Squadron from game.squadrons.squadron import Pilot, Squadron
from game.theater import NavalControlPoint from game.theater import NavalControlPoint, Player
from game.theater.missiontarget import MissionTarget from game.theater.missiontarget import MissionTarget
from game.transfers import PendingTransfers, TransferOrder from game.transfers import PendingTransfers, TransferOrder
from qt_ui.simcontroller import SimController from qt_ui.simcontroller import SimController
@ -564,8 +564,8 @@ class GameModel:
self.allocated_icls: list[int] = list() self.allocated_icls: list[int] = list()
self.init_comms_registry() self.init_comms_registry()
def ato_model_for(self, player: bool) -> AtoModel: def ato_model_for(self, player: Player) -> AtoModel:
if player: if player.is_blue:
return self.ato_model return self.ato_model
return self.red_ato_model return self.red_ato_model

View File

@ -10,6 +10,7 @@ from PySide6.QtWidgets import (
from game import Game from game import Game
from game.income import Income from game.income import Income
from game.theater import Player
from qt_ui.windows.intel import IntelWindow from qt_ui.windows.intel import IntelWindow
@ -76,8 +77,8 @@ class QIntelBox(QGroupBox):
def economic_strength_text(self) -> str: def economic_strength_text(self) -> str:
assert self.game is not None assert self.game is not None
own = Income(self.game, player=True).total own = Income(self.game, player=Player.BLUE).total
enemy = Income(self.game, player=False).total enemy = Income(self.game, player=Player.RED).total
if not enemy: if not enemy:
return "enemy economy ruined" return "enemy economy ruined"

View File

@ -6,6 +6,7 @@ from game.ato.flightwaypointtype import FlightWaypointType
from game.missiongenerator.frontlineconflictdescription import ( from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription, FrontLineConflictDescription,
) )
from game.theater.player import Player
from game.theater.controlpoint import ControlPointType from game.theater.controlpoint import ControlPointType
from game.utils import Distance from game.utils import Distance
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
@ -93,7 +94,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
wpt.targets.append(target) wpt.targets.append(target)
wpt.obj_name = tgo.obj_name wpt.obj_name = tgo.obj_name
wpt.waypoint_type = FlightWaypointType.CUSTOM wpt.waypoint_type = FlightWaypointType.CUSTOM
if tgo.is_friendly(to_player=True): if tgo.is_friendly(to_player=Player.BLUE):
wpt.description = f"Friendly unit: {target.name}" wpt.description = f"Friendly unit: {target.name}"
else: else:
wpt.description = f"Enemy unit: {target.name}" wpt.description = f"Enemy unit: {target.name}"

View File

@ -790,7 +790,7 @@ class AirWingConfigurationDialog(QDialog):
self.tabs = [] self.tabs = []
for coalition in game.coalitions: for coalition in game.coalitions:
coalition_tab = AirWingConfigurationTab(coalition, game, aircraft_present) coalition_tab = AirWingConfigurationTab(coalition, game, aircraft_present)
name = "Blue" if coalition.player else "Red" name = "Blue" if coalition.player.is_blue else "Red"
self.tab_widget.addTab(coalition_tab, name) self.tab_widget.addTab(coalition_tab, name)
self.tabs.append(coalition_tab) self.tabs.append(coalition_tab)

View File

@ -14,13 +14,14 @@ from PySide6.QtWidgets import (
) )
from game.debriefing import Debriefing from game.debriefing import Debriefing
from game.theater import Player
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
T = TypeVar("T") T = TypeVar("T")
class LossGrid(QGridLayout): class LossGrid(QGridLayout):
def __init__(self, debriefing: Debriefing, player: bool) -> None: def __init__(self, debriefing: Debriefing, player: Player) -> None:
super().__init__() super().__init__()
self.add_loss_rows( self.add_loss_rows(
@ -57,8 +58,10 @@ class LossGrid(QGridLayout):
class ScrollingCasualtyReportContainer(QGroupBox): class ScrollingCasualtyReportContainer(QGroupBox):
def __init__(self, debriefing: Debriefing, player: bool) -> None: def __init__(self, debriefing: Debriefing, player: Player) -> None:
country = debriefing.player_country if player else debriefing.enemy_country country = (
debriefing.player_country if player.is_blue else debriefing.enemy_country
)
super().__init__(f"{country}'s lost units:") super().__init__(f"{country}'s lost units:")
scroll_content = QWidget() scroll_content = QWidget()
scroll_content.setLayout(LossGrid(debriefing, player)) scroll_content.setLayout(LossGrid(debriefing, player))
@ -91,10 +94,14 @@ class QDebriefingWindow(QDialog):
title = QLabel("<b>Casualty report</b>") title = QLabel("<b>Casualty report</b>")
layout.addWidget(title) layout.addWidget(title)
player_lost_units = ScrollingCasualtyReportContainer(debriefing, player=True) player_lost_units = ScrollingCasualtyReportContainer(
debriefing, player=Player.BLUE
)
layout.addWidget(player_lost_units) layout.addWidget(player_lost_units)
enemy_lost_units = ScrollingCasualtyReportContainer(debriefing, player=False) enemy_lost_units = ScrollingCasualtyReportContainer(
debriefing, player=Player.RED
)
layout.addWidget(enemy_lost_units, 1) layout.addWidget(enemy_lost_units, 1)
okay = QPushButton("Okay") okay = QPushButton("Okay")

View File

@ -25,7 +25,7 @@ from dcs.unittype import UnitType
from game import Game from game import Game
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint from game.theater import ControlPoint, Player
from game.transfers import TransferOrder from game.transfers import TransferOrder
from qt_ui.models import GameModel from qt_ui.models import GameModel
from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.QLabeledWidget import QLabeledWidget
@ -40,7 +40,7 @@ class TransferDestinationComboBox(QComboBox):
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if ( if (
cp != self.origin cp != self.origin
and cp.is_friendly(to_player=True) and cp.is_friendly(to_player=Player.BLUE)
and cp.can_deploy_ground_units and cp.can_deploy_ground_units
): ):
self.addItem(cp.name, cp) self.addItem(cp.name, cp)

View File

@ -27,6 +27,7 @@ from game.theater import (
FREE_FRONTLINE_UNIT_SUPPLY, FREE_FRONTLINE_UNIT_SUPPLY,
NavalControlPoint, NavalControlPoint,
ParkingType, ParkingType,
Player,
) )
from qt_ui.dialogs import Dialog from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel from qt_ui.models import GameModel
@ -85,7 +86,7 @@ class QBaseMenu2(QDialog):
self.freq_widget = None self.freq_widget = None
self.link4_widget = None self.link4_widget = None
is_friendly = cp.is_friendly(True) is_friendly = cp.is_friendly(Player.BLUE)
if is_friendly and isinstance(cp, RadioFrequencyContainer): if is_friendly and isinstance(cp, RadioFrequencyContainer):
self.freq_widget = QFrequencyWidget(cp, self.game_model) self.freq_widget = QFrequencyWidget(cp, self.game_model)
cp_settings.addWidget(self.freq_widget, counter // 2, counter % 2) cp_settings.addWidget(self.freq_widget, counter // 2, counter % 2)

View File

@ -19,7 +19,7 @@ from game.config import REWARDS
from game.data.building_data import FORTIFICATION_BUILDINGS from game.data.building_data import FORTIFICATION_BUILDINGS
from game.server import EventStream from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents from game.sim.gameupdateevents import GameUpdateEvents
from game.theater import ControlPoint, TheaterGroundObject from game.theater import ControlPoint, TheaterGroundObject, Player
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
) )
@ -87,12 +87,12 @@ class QGroundObjectMenu(QDialog):
if isinstance(self.ground_object, BuildingGroundObject): if isinstance(self.ground_object, BuildingGroundObject):
self.mainLayout.addWidget(self.buildingBox) self.mainLayout.addWidget(self.buildingBox)
if self.cp.captured: if self.cp.captured.is_blue:
self.mainLayout.addWidget(self.financesBox) self.mainLayout.addWidget(self.financesBox)
else: else:
self.mainLayout.addWidget(self.intelBox) self.mainLayout.addWidget(self.intelBox)
self.mainLayout.addWidget(self.orientationBox) self.mainLayout.addWidget(self.orientationBox)
if self.ground_object.is_iads and self.cp.is_friendly(to_player=False): if self.ground_object.is_iads and self.cp.is_friendly(to_player=Player.RED):
self.mainLayout.addWidget(self.hiddenBox) self.mainLayout.addWidget(self.hiddenBox)
self.actionLayout = QHBoxLayout() self.actionLayout = QHBoxLayout()
@ -118,8 +118,10 @@ class QGroundObjectMenu(QDialog):
@property @property
def show_buy_sell_actions(self) -> bool: def show_buy_sell_actions(self) -> bool:
if self.cp.captured.is_neutral:
return False
buysell_allowed = self.game.settings.enable_enemy_buy_sell buysell_allowed = self.game.settings.enable_enemy_buy_sell
buysell_allowed |= self.cp.captured buysell_allowed |= self.cp.captured.is_blue
return buysell_allowed return buysell_allowed
def doLayout(self): def doLayout(self):
@ -133,7 +135,7 @@ class QGroundObjectMenu(QDialog):
QLabel(f"<b>Unit {str(unit.display_name)}</b>"), i, 0 QLabel(f"<b>Unit {str(unit.display_name)}</b>"), i, 0
) )
if not unit.alive and unit.repairable and self.cp.captured: if not unit.alive and unit.repairable and self.cp.captured.is_blue:
price = unit.unit_type.price if unit.unit_type else 0 price = unit.unit_type.price if unit.unit_type else 0
repair = QPushButton(f"Repair [{price}M]") repair = QPushButton(f"Repair [{price}M]")
repair.setProperty("style", "btn-success") repair.setProperty("style", "btn-success")

View File

@ -33,7 +33,11 @@ pluggy==1.5.0
pre-commit==4.2.0 pre-commit==4.2.0
pydantic==2.11.0b2 pydantic==2.11.0b2
pydantic-settings==2.8.1 pydantic-settings==2.8.1
<<<<<<< HEAD
pydcs @ git+https://github.com/dcs-retribution/pydcs@60cddd944d454f59f5c866a2ec2ae3e91526da25 pydcs @ git+https://github.com/dcs-retribution/pydcs@60cddd944d454f59f5c866a2ec2ae3e91526da25
=======
pydcs @ git+https://github.com/Druss99/pydcs@efbd67fc87de6efe40316523cdd96d1c91711f43
>>>>>>> 9f9f53b26 (refactor of previous commits)
pyinstaller==5.13.2 pyinstaller==5.13.2
pyinstaller-hooks-contrib==2024.0 pyinstaller-hooks-contrib==2024.0
pyparsing==3.2.1 pyparsing==3.2.1

View File

@ -0,0 +1,147 @@
---
name: Persian Gulf - Scenic Route - Neutral
theater: Persian Gulf
authors: Fuzzle
description: <p>A lightweight naval campaign involving a US Navy carrier group pushing across the coast of Iran. <strong>This is a purely naval campaign, meaning you will need to use the Air Assault mission type with transports to take the first FOB. Ensure you soften it up enough first!</strong></p><p><strong>Backstory:</strong> Iran has declared war on all US forces in the Gulf resulting in all local allies withdrawing their support for American troops. A lone carrier group must pacify the southern coast of Iran and hold out until backup can arrive lest the US and her interests be ejected from the region permanently.</p>
version: "10.7"
advanced_iads: true
recommended_player_faction: US Navy 2005
recommended_enemy_faction: Iran 2015
miz: scenic_route_neutral.miz
performance: 1
recommended_start_date: 2005-04-26
recommended_player_money: 1000
recommended_enemy_money: 1300
recommended_player_income_multiplier: 1.2
recommended_enemy_income_multiplier: 0.7
squadrons:
#BLUFOR CVN
Naval-1:
- primary: BARCAP
secondary: air-to-air
aircraft:
- VF-143
size: 14
- primary: SEAD
secondary: air-to-ground
aircraft:
- VFA-113
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
# BLUFOR LHA
Naval-2:
- primary: BAI
secondary: air-to-ground
aircraft:
- VMA-223
size: 10
- primary: Transport
secondary: air-to-ground
aircraft:
- HMLA-169 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
- HMLA-169 (AH-1W)
size: 6
# OPFOR CVN
Naval-3:
- primary: BARCAP
secondary: any
- primary: Strike
secondary: any
- primary: BAI
secondary: any
- primary: Refueling
# Kish Intl
24:
- primary: AEW&C
aircraft:
- A-50
size: 1
- primary: BARCAP
secondary: any
aircraft:
- F-14A Tomcat (Block 135-GR Late)
- primary: Strike
secondary: air-to-ground
aircraft:
- Su-24MK Fencer-D
- primary: BARCAP
secondary: any
aircraft:
- F-4E Phantom II
# Havadarya
9:
- primary: BARCAP
secondary: any
aircraft:
- F-4E Phantom II
size: 10
- primary: CAS
secondary: air-to-ground
aircraft:
- Su-25 Frogfoot
size: 12
# Bandar Abbas Intl
2:
- primary: TARCAP
secondary: any
aircraft:
- F-5E Tiger II
- primary: BARCAP
secondary: air-to-ground
aircraft:
- MiG-29A Fulcrum-A
- primary: Strike
secondary: air-to-ground
aircraft:
- Su-24MK
- primary: BARCAP
secondary: air-to-ground
aircraft:
- F-4E Phantom II
# OPFOR First FOB
FOB Seerik:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-24V Hind-E
size: 6
# OPFOR Second FOB
FOB Kohnehshahr:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-24V Hind-F
size: 6
# OPFOR Third FOB
FOB Khvosh:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-28N Havoc
size: 6
# OPFOR Last FOB
FOB Charak:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-28N Havoc
size: 6

Binary file not shown.

View File

@ -17,6 +17,7 @@ from game.theater.controlpoint import (
OffMapSpawn, OffMapSpawn,
Fob, Fob,
ParkingType, ParkingType,
Player,
) )
from game.utils import Heading from game.utils import Heading
@ -30,8 +31,8 @@ def test_mission_types_friendly(mocker: Any) -> None:
mocker.patch("game.theater.controlpoint.Airfield.is_friendly", return_value=True) mocker.patch("game.theater.controlpoint.Airfield.is_friendly", return_value=True)
airport = Airport(None, None) # type: ignore airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__ airport.name = "test" # required for Airfield.__init__
airfield = Airfield(airport, theater=None, starts_blue=True) # type: ignore airfield = Airfield(airport, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(airfield.mission_types(for_player=True)) mission_types = list(airfield.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3 assert len(mission_types) == 3
assert FlightType.AEWC in mission_types assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types assert FlightType.REFUELING in mission_types
@ -39,8 +40,8 @@ def test_mission_types_friendly(mocker: Any) -> None:
# Carrier # Carrier
mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=True) mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=True)
carrier = Carrier(name="test", at=None, theater=None, starts_blue=True) # type: ignore carrier = Carrier(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(carrier.mission_types(for_player=True)) mission_types = list(carrier.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3 assert len(mission_types) == 3
assert FlightType.AEWC in mission_types assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types assert FlightType.REFUELING in mission_types
@ -48,8 +49,8 @@ def test_mission_types_friendly(mocker: Any) -> None:
# LHA # LHA
mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=True) mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=True)
lha = Lha(name="test", at=None, theater=None, starts_blue=True) # type: ignore lha = Lha(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(lha.mission_types(for_player=True)) mission_types = list(lha.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3 assert len(mission_types) == 3
assert FlightType.AEWC in mission_types assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types assert FlightType.REFUELING in mission_types
@ -57,16 +58,16 @@ def test_mission_types_friendly(mocker: Any) -> None:
# Fob # Fob
mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=True) mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=True)
fob = Fob(name="test", at=None, theater=None, starts_blue=True) # type: ignore fob = Fob(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(fob.mission_types(for_player=True)) mission_types = list(fob.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 2 assert len(mission_types) == 2
assert FlightType.AEWC in mission_types assert FlightType.AEWC in mission_types
assert FlightType.BARCAP in mission_types assert FlightType.BARCAP in mission_types
# Off map spawn # Off map spawn
mocker.patch("game.theater.controlpoint.OffMapSpawn.is_friendly", return_value=True) mocker.patch("game.theater.controlpoint.OffMapSpawn.is_friendly", return_value=True)
off_map_spawn = OffMapSpawn(name="test", position=None, theater=None, starts_blue=True) # type: ignore off_map_spawn = OffMapSpawn(name="test", position=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(off_map_spawn.mission_types(for_player=True)) mission_types = list(off_map_spawn.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 0 assert len(mission_types) == 0
@ -79,8 +80,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
mocker.patch("game.theater.controlpoint.Airfield.is_friendly", return_value=False) mocker.patch("game.theater.controlpoint.Airfield.is_friendly", return_value=False)
airport = Airport(None, None) # type: ignore airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__ airport.name = "test" # required for Airfield.__init__
airfield = Airfield(airport, theater=None, starts_blue=True) # type: ignore airfield = Airfield(airport, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(airfield.mission_types(for_player=True)) mission_types = list(airfield.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 8 assert len(mission_types) == 8
assert FlightType.OCA_AIRCRAFT in mission_types assert FlightType.OCA_AIRCRAFT in mission_types
assert FlightType.OCA_RUNWAY in mission_types assert FlightType.OCA_RUNWAY in mission_types
@ -93,8 +94,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# Carrier # Carrier
mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=False) mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=False)
carrier = Carrier(name="test", at=None, theater=None, starts_blue=True) # type: ignore carrier = Carrier(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(carrier.mission_types(for_player=True)) mission_types = list(carrier.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 5 assert len(mission_types) == 5
assert FlightType.ANTISHIP in mission_types assert FlightType.ANTISHIP in mission_types
assert FlightType.ESCORT in mission_types assert FlightType.ESCORT in mission_types
@ -104,8 +105,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# LHA # LHA
mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=False) mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=False)
lha = Lha(name="test", at=None, theater=None, starts_blue=True) # type: ignore lha = Lha(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(lha.mission_types(for_player=True)) mission_types = list(lha.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 5 assert len(mission_types) == 5
assert FlightType.ANTISHIP in mission_types assert FlightType.ANTISHIP in mission_types
assert FlightType.ESCORT in mission_types assert FlightType.ESCORT in mission_types
@ -115,8 +116,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# Fob # Fob
mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=False) mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=False)
fob = Fob(name="test", at=None, theater=None, starts_blue=True) # type: ignore fob = Fob(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(fob.mission_types(for_player=True)) mission_types = list(fob.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 6 assert len(mission_types) == 6
assert FlightType.AIR_ASSAULT in mission_types assert FlightType.AIR_ASSAULT in mission_types
assert FlightType.ESCORT in mission_types assert FlightType.ESCORT in mission_types
@ -129,8 +130,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
mocker.patch( mocker.patch(
"game.theater.controlpoint.OffMapSpawn.is_friendly", return_value=False "game.theater.controlpoint.OffMapSpawn.is_friendly", return_value=False
) )
off_map_spawn = OffMapSpawn(name="test", position=None, theater=None, starts_blue=True) # type: ignore off_map_spawn = OffMapSpawn(name="test", position=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(off_map_spawn.mission_types(for_player=True)) mission_types = list(off_map_spawn.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 0 assert len(mission_types) == 0
@ -144,7 +145,7 @@ def test_control_point_parking(mocker: Any) -> None:
airport = Airport(None, None) # type: ignore airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__ airport.name = "test" # required for Airfield.__init__
point = Point(0, 0, None) # type: ignore point = Point(0, 0, None) # type: ignore
control_point = Airfield(airport, theater=None, starts_blue=True) # type: ignore control_point = Airfield(airport, theater=None, starts_blue=Player.BLUE) # type: ignore
parking_type_ground_start = ParkingType( parking_type_ground_start = ParkingType(
fixed_wing=False, fixed_wing_stol=True, rotary_wing=False fixed_wing=False, fixed_wing_stol=True, rotary_wing=False
) )

View File

@ -4,7 +4,7 @@ import pytest
from dcs.mapping import Point from dcs.mapping import Point
from game.ato.flighttype import FlightType from game.ato.flighttype import FlightType
from game.theater.controlpoint import OffMapSpawn from game.theater.controlpoint import OffMapSpawn, Player
from game.theater.presetlocation import PresetLocation from game.theater.presetlocation import PresetLocation
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
@ -34,7 +34,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
name="dummy_control_point", name="dummy_control_point",
position=Point(0, 0, None), # type: ignore position=Point(0, 0, None), # type: ignore
theater=None, # type: ignore theater=None, # type: ignore
starts_blue=True, starts_blue=Player.BLUE,
) )
# Patch is_friendly as it's difficult to set up a proper ControlPoint # Patch is_friendly as it's difficult to set up a proper ControlPoint
@ -56,7 +56,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(ground_object.mission_types(for_player=True)) mission_types = list(ground_object.mission_types(for_player=Player.BLUE))
assert mission_types == [FlightType.BARCAP] assert mission_types == [FlightType.BARCAP]
for ground_object_type in [BuildingGroundObject, IadsBuildingGroundObject]: for ground_object_type in [BuildingGroundObject, IadsBuildingGroundObject]:
@ -67,7 +67,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(ground_object.mission_types(for_player=True)) mission_types = list(ground_object.mission_types(for_player=Player.BLUE))
assert mission_types == [FlightType.BARCAP] assert mission_types == [FlightType.BARCAP]
@ -84,7 +84,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
name="dummy_control_point", name="dummy_control_point",
position=Point(0, 0, None), # type: ignore position=Point(0, 0, None), # type: ignore
theater=None, # type: ignore theater=None, # type: ignore
starts_blue=True, starts_blue=Player.BLUE,
) )
# Patch is_friendly as it's difficult to set up a proper ControlPoint # Patch is_friendly as it's difficult to set up a proper ControlPoint
@ -99,7 +99,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(building.mission_types(for_player=False)) mission_types = list(building.mission_types(for_player=Player.RED))
assert len(mission_types) == 6 assert len(mission_types) == 6
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types
assert FlightType.REFUELING in mission_types assert FlightType.REFUELING in mission_types
@ -115,7 +115,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(iads_building.mission_types(for_player=False)) mission_types = list(iads_building.mission_types(for_player=Player.RED))
assert len(mission_types) == 7 assert len(mission_types) == 7
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types
assert FlightType.REFUELING in mission_types assert FlightType.REFUELING in mission_types
@ -136,7 +136,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(ground_object.mission_types(for_player=False)) mission_types = list(ground_object.mission_types(for_player=Player.RED))
assert len(mission_types) == 7 assert len(mission_types) == 7
assert FlightType.ANTISHIP in mission_types assert FlightType.ANTISHIP in mission_types
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types
@ -152,7 +152,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(sam.mission_types(for_player=False)) mission_types = list(sam.mission_types(for_player=Player.RED))
assert len(mission_types) == 8 assert len(mission_types) == 8
assert FlightType.DEAD in mission_types assert FlightType.DEAD in mission_types
assert FlightType.SEAD in mission_types assert FlightType.SEAD in mission_types
@ -168,7 +168,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
location=dummy_location, location=dummy_location,
control_point=dummy_control_point, control_point=dummy_control_point,
) )
mission_types = list(ewr.mission_types(for_player=False)) mission_types = list(ewr.mission_types(for_player=Player.RED))
assert len(mission_types) == 7 assert len(mission_types) == 7
assert FlightType.DEAD in mission_types assert FlightType.DEAD in mission_types
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types
@ -187,7 +187,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
location=dummy_location, location=dummy_location,
control_point=dummy_control_point, control_point=dummy_control_point,
) )
mission_types = list(ground_object.mission_types(for_player=False)) mission_types = list(ground_object.mission_types(for_player=Player.RED))
assert len(mission_types) == 7 assert len(mission_types) == 7
assert FlightType.BAI in mission_types assert FlightType.BAI in mission_types
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types
@ -203,7 +203,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point, control_point=dummy_control_point,
task=None, task=None,
) )
mission_types = list(vehicles.mission_types(for_player=False)) mission_types = list(vehicles.mission_types(for_player=Player.RED))
assert len(mission_types) == 7 assert len(mission_types) == 7
assert FlightType.BAI in mission_types assert FlightType.BAI in mission_types
assert FlightType.STRIKE in mission_types assert FlightType.STRIKE in mission_types