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(
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
ground_group = ground_object.group_by_name(group_name)
if ground_group is not None:

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ from game.theater.controlpoint import (
Carrier,
ControlPoint,
ControlPointType,
Player,
Fob,
Lha,
OffMapSpawn,
@ -123,9 +124,13 @@ class MizCampaignLoader:
def control_point_from_airport(
self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
) -> ControlPoint:
cp = Airfield(
airport, self.theater, starts_blue=airport.is_blue(), ctld_zones=ctld_zones
)
if airport.dynamic_spawn:
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
# be owned by the player when the campaign is "inverted".
@ -133,43 +138,44 @@ class MizCampaignLoader:
return cp
def country(self, blue: bool) -> Country:
country = self.mission.country(
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name
)
def country(self, blue: Player) -> Country:
if blue.is_blue:
country = self.mission.country(self.BLUE_COUNTRY.name)
else:
country = self.mission.country(self.RED_COUNTRY.name)
# Should be guaranteed because we initialized them.
assert country
return country
@property
def blue(self) -> Country:
return self.country(blue=True)
return self.country(blue=Player.BLUE)
@property
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:
if group.units[0].type == self.OFF_MAP_UNIT_TYPE:
yield group
def carriers(self, blue: bool) -> Iterator[ShipGroup]:
def carriers(self, blue: Player) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group:
if group.units[0].type == self.CV_UNIT_TYPE:
yield group
def lhas(self, blue: bool) -> Iterator[ShipGroup]:
def lhas(self, blue: Player) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group:
if group.units[0].type == self.LHA_UNIT_TYPE:
yield group
def fobs(self, blue: bool) -> Iterator[VehicleGroup]:
def fobs(self, blue: Player) -> Iterator[VehicleGroup]:
for group in self.country(blue).vehicle_group:
if group.units[0].type == self.FOB_UNIT_TYPE:
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:
if group.units[0].type == self.INVISIBLE_FOB_UNIT_TYPE:
yield group
@ -290,12 +296,12 @@ class MizCampaignLoader:
def control_points(self) -> dict[UUID, ControlPoint]:
control_points = {}
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)
control_point = self.control_point_from_airport(airport, ctld_zones)
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):
control_point = OffMapSpawn(
str(group.name), group.position, self.theater, starts_blue=blue
@ -348,13 +354,13 @@ class MizCampaignLoader:
@property
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:
yield group
@property
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:
yield group
@ -378,7 +384,7 @@ class MizCampaignLoader:
@property
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:
yield group

View File

@ -17,6 +17,7 @@ from game.procurement import AircraftProcurementRequest, ProcurementAi
from game.profiling import MultiEventTracer, logged_duration
from game.squadrons import AirWing
from game.theater.bullseye import Bullseye
from game.theater.player import Player
from game.theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from game.threatzones import ThreatZones
from game.transfers import PendingTransfers
@ -32,7 +33,7 @@ if TYPE_CHECKING:
class Coalition:
def __init__(
self, game: Game, faction: Faction, budget: float, player: bool
self, game: Game, faction: Faction, budget: float, player: Player
) -> None:
self.game = game
self.player = player
@ -68,9 +69,11 @@ class Coalition:
@property
def coalition_id(self) -> int:
if self.player:
if self.player.is_blue:
return 2
elif self.player.is_red:
return 1
return 0
@property
def opponent(self) -> Coalition:
@ -95,6 +98,7 @@ class Coalition:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
if state["player"] != Player.NEUTRAL:
del state["_threat_zone"]
del state["_navmesh"]
del state["faker"]
@ -203,7 +207,7 @@ class Coalition:
squadron.refund_orders()
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 tracer.trace(f"{color} mission planning"):
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
# 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_front_line = self.game.settings.automate_front_line_reinforcements
manage_aircraft = self.game.settings.automate_aircraft_reinforcements

View File

@ -16,6 +16,7 @@ from game.theater import (
OffMapSpawn,
ParkingType,
NavalControlPoint,
Player,
)
from game.theater.theatergroundobject import (
BuildingGroundObject,
@ -35,7 +36,7 @@ MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder:
"""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.is_player = is_player
@ -154,7 +155,7 @@ class ObjectiveFinder:
airfields_in_proximity = self.closest_airfields_to(cp)
airbase_threat_range = self.game.settings.airbase_threat_range
if (
not self.is_player
self.is_player.is_red
and randint(1, 100)
> self.game.settings.opfor_autoplanner_aggressiveness
):
@ -216,7 +217,7 @@ class ObjectiveFinder:
def farthest_friendly_control_point(self) -> ControlPoint:
"""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
max_distance = meters(0)
@ -234,7 +235,7 @@ class ObjectiveFinder:
def closest_friendly_control_point(self) -> ControlPoint:
"""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
min_distance = meters(math.inf)
@ -258,14 +259,14 @@ class ObjectiveFinder:
return (
c
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]:
prioritized = []
capturable_later = []
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:
isolated.append(cp)
continue

View File

@ -43,7 +43,9 @@ class PackageFulfiller:
@property
def is_player(self) -> bool:
return self.coalition.player
if self.coalition.player.is_blue:
return True
return False
@property
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.theaterstate import TheaterState
from game.htn import CompoundTask, Method
from game.theater import ControlPoint, FrontLine
from game.theater import ControlPoint, FrontLine, Player
@dataclass(frozen=True)
@ -26,12 +26,12 @@ class CaptureBase(CompoundTask[TheaterState]):
def enemy_cp(self, state: TheaterState) -> ControlPoint:
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)
ammo_depots = list(state.ammo_dumps_at(cp))
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)
ammo_depots = list(state.ammo_dumps_at(cp))
return cp.front_line_capacity_with(len(ammo_depots))

View File

@ -11,10 +11,11 @@ from game.theater import FrontLine
if TYPE_CHECKING:
from game.coalition import Coalition
from game.theater.player import Player
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.friendly_cp = self.front_line.control_point_friendly_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.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import FrontLine
from game.theater import FrontLine, Player
@dataclass
@ -19,9 +19,8 @@ class PlanCas(PackagePlanningTask[FrontLine]):
# 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*
# be ground units) and what aircraft should be ordered.
enemy_cp = self.target.control_point_friendly_to(
player=not state.context.coalition.player
)
player = state.context.coalition.player.opponent
enemy_cp = self.target.control_point_friendly_to(player)
if enemy_cp.deployable_front_line_units == 0 and state.context.turn > 0:
return False
return super().preconditions_met(state)

View File

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

View File

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

View File

@ -16,7 +16,7 @@ from uuid import UUID
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.theater import Airfield, ControlPoint
from game.theater import Airfield, ControlPoint, Player
if TYPE_CHECKING:
from game import Game
@ -45,9 +45,9 @@ class AirLosses:
def losses(self) -> Iterator[FlyingUnit]:
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 = self.player if player else self.enemy
losses = self.player if player.is_blue else self.enemy
for loss in losses:
losses_by_type[loss.flight.unit_type] += 1
return losses_by_type
@ -87,7 +87,7 @@ class GroundLosses:
@dataclass(frozen=True)
class BaseCaptureEvent:
control_point: ControlPoint
captured_by_player: bool
captured_by_player: Player
@dataclass(frozen=True)
@ -166,7 +166,7 @@ class Debriefing:
def merge_simulation_results(self, results: SimulationResults) -> None:
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)
else:
self.air_losses.enemy.append(air_loss)
@ -209,9 +209,9 @@ class Debriefing:
def casualty_count(self, control_point: ControlPoint) -> int:
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)
if player:
if player.is_blue:
losses = self.ground_losses.player_front_line
else:
losses = self.ground_losses.enemy_front_line
@ -219,9 +219,9 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1
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)
if player:
if player.is_blue:
losses = self.ground_losses.player_convoy
else:
losses = self.ground_losses.enemy_convoy
@ -229,9 +229,9 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1
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)
if player:
if player.is_blue:
ships = self.ground_losses.player_cargo_ships
else:
ships = self.ground_losses.enemy_cargo_ships
@ -240,9 +240,9 @@ class Debriefing:
losses_by_type[unit_type] += count
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)
if player:
if player.is_blue:
losses = self.ground_losses.player_airlifts
else:
losses = self.ground_losses.enemy_airlifts
@ -251,9 +251,9 @@ class Debriefing:
losses_by_type[unit_type] += 1
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)
if player:
if player.is_blue:
losses = self.ground_losses.player_ground_objects
else:
losses = self.ground_losses.enemy_ground_objects
@ -261,9 +261,9 @@ class Debriefing:
losses_by_type[loss.theater_unit.type.id] += 1
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)
if player:
if player.is_blue:
losses = self.ground_losses.player_scenery
else:
losses = self.ground_losses.enemy_scenery
@ -279,7 +279,7 @@ class Debriefing:
if aircraft is None:
logging.error(f"Could not find Flight matching {unit_name}")
continue
if aircraft.flight.departure.captured:
if aircraft.flight.departure.captured.is_blue:
player_losses.append(aircraft)
else:
enemy_losses.append(aircraft)
@ -290,7 +290,7 @@ class Debriefing:
for unit_name in self.state_data.killed_ground_units:
front_line_unit = self.unit_map.front_line_unit(unit_name)
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)
else:
losses.enemy_front_line.append(front_line_unit)
@ -298,7 +298,7 @@ class Debriefing:
convoy_unit = self.unit_map.convoy_unit(unit_name)
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)
else:
losses.enemy_convoy.append(convoy_unit)
@ -306,7 +306,7 @@ class Debriefing:
cargo_ship = self.unit_map.cargo_ship(unit_name)
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)
else:
losses.enemy_cargo_ships.append(cargo_ship)
@ -314,7 +314,9 @@ class Debriefing:
ground_object = self.unit_map.theater_units(unit_name)
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)
else:
losses.enemy_ground_objects.append(ground_object)
@ -323,7 +325,9 @@ class Debriefing:
scenery_object = self.unit_map.scenery_object(unit_name)
# Try appending object to the name, because we do this for building statics.
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)
else:
losses.enemy_scenery.append(scenery_object)
@ -331,9 +335,9 @@ class Debriefing:
airfield = self.unit_map.airfield(unit_name)
if airfield is not None:
if airfield.captured:
if airfield.captured.is_blue:
losses.player_airfields.append(airfield)
else:
elif airfield.captured.is_red:
losses.enemy_airfields.append(airfield)
continue
@ -349,7 +353,7 @@ class Debriefing:
for unit_name in self.state_data.killed_aircraft:
airlift_unit = self.unit_map.airlift_unit(unit_name)
if airlift_unit is not None:
if airlift_unit.transfer.player:
if airlift_unit.transfer.player.is_blue:
losses.player_airlifts.append(airlift_unit)
else:
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
# bases are near the conflict. Nothing to do.
continue
captured_by_player = int(new_owner_id_str) == blue_coalition_id
if 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):
# Base is currently friendly to the new owner. Was captured and
# recaptured in the same mission. Nothing to do.

View File

@ -6,7 +6,7 @@ import math
from collections.abc import Iterator
from datetime import date, datetime, time, timedelta
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 dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
@ -32,7 +32,7 @@ from .infos.information import Information
from .lasercodes.lasercoderegistry import LaserCodeRegistry
from .profiling import logged_duration
from .settings import Settings
from .theater import ConflictTheater
from .theater import ConflictTheater, Player
from .theater.bullseye import Bullseye
from .theater.theatergroundobject import (
EwrGroundObject,
@ -138,8 +138,11 @@ class Game:
self.conditions = self.generate_conditions(forced_time=start_time)
self.sanitize_sides(player_faction, enemy_faction)
self.blue = Coalition(self, player_faction, player_budget, player=True)
self.red = Coalition(self, enemy_faction, enemy_budget, player=False)
self.blue = Coalition(self, player_faction, player_budget, player=Player.BLUE)
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.red.set_opponent(self.blue)
@ -178,10 +181,10 @@ class Game:
def point_in_world(self, x: float, y: float) -> Point:
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
def transit_network_for(self, player: bool) -> TransitNetwork:
def transit_network_for(self, player: Player) -> TransitNetwork:
return self.coalition_for(player).transit_network
def generate_conditions(self, forced_time: time | None = None) -> Conditions:
@ -209,32 +212,35 @@ class Game:
else:
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
def faker_for(self, player: bool) -> Faker:
def faker_for(self, player: 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
@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"""
countries_in_use = {self.red.faction.country, self.blue.faction.country}
if UnitedNationsPeacekeepers not in countries_in_use:
return UnitedNationsPeacekeepers
return UnitedNationsPeacekeepers()
elif Switzerland.name not in countries_in_use:
return Switzerland
return Switzerland()
else:
return USAFAggressors
return USAFAggressors()
def coalition_for(self, player: bool) -> Coalition:
if player:
def coalition_for(self, player: Player) -> Coalition:
if player.is_neutral:
return self.neutral
elif player.is_blue:
return self.blue
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)
def on_load(self, game_still_initializing: bool = False) -> None:
@ -489,7 +495,7 @@ class Game:
self.current_group_id += 1
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()
def compute_threat_zones(self, events: GameUpdateEvents) -> None:
@ -498,10 +504,10 @@ class Game:
self.blue.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
def navmesh_for(self, player: bool) -> NavMesh:
def navmesh_for(self, player: Player) -> NavMesh:
return self.coalition_for(player).nav_mesh
def compute_unculled_zones(self, events: GameUpdateEvents) -> None:

View File

@ -66,7 +66,7 @@ class GroundUnitOrders:
bought_units: dict[GroundUnitType, int] = {}
units_needing_transfer: dict[GroundUnitType, int] = {}
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]
if self.destination != ground_unit_source:
source = ground_unit_source

View File

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

View File

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

View File

@ -14,6 +14,7 @@ if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType
from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater.player import Player
@dataclass(frozen=True)
@ -42,7 +43,7 @@ class FlightData:
size: int
#: 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.
departure_delay: timedelta

View File

@ -299,7 +299,7 @@ class FlightGroupConfigurator:
unit.set_player()
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)
else:
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.theater import ControlPoint, FrontLine
from .aircraft.flightdata import FlightData
from .missiondata import AwacsInfo, TankerInfo
from .flotgenerator import JtacInfo
from .missiondata import AwacsInfo, TankerInfo
if TYPE_CHECKING:
from game import Game
@ -182,7 +182,7 @@ class BriefingGenerator(MissionInfoGenerator):
def generate_allied_flights_by_departure(self) -> None:
"""Create iterable to display allied flights grouped by departure airfield."""
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
if (
name in self.allied_flights_by_departure

View File

@ -18,6 +18,7 @@ from game.utils import kph
if TYPE_CHECKING:
from game import Game
from game.theater.player import Player
class ConvoyGenerator:
@ -94,7 +95,7 @@ class ConvoyGenerator:
name: str,
position: Point,
units: dict[GroundUnitType, int],
for_player: bool,
for_player: Player,
) -> VehicleGroup:
unit_types = list(units.items())
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)
CP_RED = Rgba(255, 0, 0, 80)
CP_BLUE = Rgba(0, 0, 255, 80)
CP_NEUTRAL = Rgba(128, 128, 128, 80)
BLUE_PATH_COLOR = Rgba(0, 0, 255, 100)
RED_PATH_COLOR = Rgba(255, 0, 0, 100)
ACTIVE_PATH_COLOR = Rgba(255, 80, 80, 100)
@ -35,10 +36,12 @@ class DrawingsGenerator:
Generate cps as circles
"""
for cp in self.game.theater.controlpoints:
if cp.captured:
if cp.captured.is_blue:
color = CP_BLUE
else:
elif cp.captured.is_red:
color = CP_RED
else:
color = CP_NEUTRAL
shape = self.player_layer.add_circle(
cp.position,
TRIGGER_RADIUS_CAPTURE,
@ -61,9 +64,9 @@ class DrawingsGenerator:
continue
else:
# Determine path color
if cp.captured and destination.captured:
if cp.captured.is_blue and destination.captured.is_blue:
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
else:
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.naming import namegen
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.utils import Heading
from .frontlineconflictdescription import FrontLineConflictDescription
@ -101,12 +101,12 @@ class FlotGenerator:
# Create player groups at random position
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
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.
@ -193,7 +193,7 @@ class FlotGenerator:
callsign=callsign,
region=frontline,
code=str(code),
blue=True,
blue=Player.BLUE,
freq=freq,
)
)
@ -215,7 +215,7 @@ class FlotGenerator:
def gen_infantry_group_for_group(
self,
group: VehicleGroup,
is_player: bool,
is_player: Player,
side: Country,
forward_heading: Heading,
) -> None:
@ -294,7 +294,7 @@ class FlotGenerator:
GroundForcePainter(faction, vehicle).apply_livery()
vg.hidden_on_mfd = True
def _earliest_tot_on_flot(self, player: bool) -> timedelta:
def _earliest_tot_on_flot(self, player: Player) -> timedelta:
tots = [
x.time_over_target
for x in self.game.ato_for(player).packages
@ -413,7 +413,7 @@ class FlotGenerator:
"""
duration = timedelta()
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)
if stance == CombatStance.AGGRESSIVE:
# Attack nearest enemy if any
@ -503,7 +503,7 @@ class FlotGenerator:
"""
duration = timedelta()
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)
if stance in [
CombatStance.AGGRESSIVE,
@ -762,7 +762,7 @@ class FlotGenerator:
)
def _generate_groups(
self, groups: list[CombatGroup], is_player: bool
self, groups: list[CombatGroup], is_player: Player
) -> List[Tuple[VehicleGroup, CombatGroup]]:
"""Finds valid positions for planned groups and generates a pydcs group for them"""
positioned_groups = []
@ -795,7 +795,7 @@ class FlotGenerator:
final_position,
heading=spawn_heading.opposite,
)
if is_player:
if is_player == Player.BLUE:
g.set_skill(Skill(self.game.settings.player_skill))
else:
g.set_skill(Skill(self.game.settings.enemy_vehicle_skill))
@ -813,7 +813,7 @@ class FlotGenerator:
def _generate_group(
self,
player: bool,
player: Player,
side: Country,
unit_type: GroundUnitType,
count: int,

View File

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

View File

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

View File

@ -391,9 +391,12 @@ class MissionGenerator:
tmu.theater_unit.position,
self.mission.terrain,
).dict()
warehouse["coalition"] = (
"blue" if tmu.theater_unit.ground_object.coalition.player else "red"
)
if tmu.theater_unit.ground_object.coalition.player.is_neutral:
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
if tmu.theater_unit.is_ship or tmu.dcs_unit.category == "Heliports": # type: ignore
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.naming import namegen
from game.theater import Player
if TYPE_CHECKING:
from game import Game
@ -25,12 +26,12 @@ class RebellionGenerator:
def generate(self) -> None:
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:
self._generate_rebel_zone(ownfor_country, rz)
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:
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.theater import (
ControlPoint,
Player,
TheaterGroundObject,
TheaterUnit,
NavalControlPoint,
@ -408,7 +409,7 @@ class GroundObjectGenerator:
# Align the trigger zones to the faction color on the DCS briefing/F10 map.
color = (
{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}
)
@ -878,7 +879,12 @@ class HelipadGenerator:
pad.position,
self.m.terrain,
).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?
self.m.warehouses.warehouses[pad.id] = warehouse
@ -1005,7 +1011,12 @@ class GroundSpawnRoadbaseGenerator:
pad.position,
self.m.terrain,
).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?
self.m.warehouses.warehouses[pad.id] = warehouse
@ -1131,7 +1142,12 @@ class GroundSpawnLargeGenerator:
pad.position,
self.m.terrain,
).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?
self.m.warehouses.warehouses[pad.id] = warehouse
@ -1275,7 +1291,12 @@ class GroundSpawnGenerator:
pad.position,
self.m.terrain,
).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?
self.m.warehouses.warehouses[pad.id] = warehouse

View File

@ -12,7 +12,6 @@ from dcs.action import (
RemoveSceneObjects,
RemoveSceneObjectsMask,
SceneryDestructionZone,
Smoke,
)
from dcs.condition import (
AllOfCoalitionOutsideZone,
@ -99,9 +98,12 @@ class TriggerGenerator:
raise RuntimeError(
f"Could not find {airfield.airport.name} in the mission"
)
cp_airport.set_coalition(
airfield.captured and player_coalition or enemy_coalition
)
if airfield.captured.is_neutral:
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:
"""
@ -138,7 +140,7 @@ class TriggerGenerator:
zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK"
)
if cp.captured:
if cp.captured.is_blue:
name = ground_object.obj_name + " [ALLY]"
else:
name = ground_object.obj_name + " [ENEMY]"
@ -186,7 +188,7 @@ class TriggerGenerator:
"""
for cp in self.game.theater.controlpoints:
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured:
if cp.captured.is_blue:
attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition
@ -242,6 +244,51 @@ class TriggerGenerator:
recapture_trigger.add_action(ClearFlag(flag=flag))
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:
player_coalition = "blue"
enemy_coalition = "red"

View File

@ -55,7 +55,7 @@ class GameStats:
turn_data = GameTurnMetadata()
for cp in game.theater.controlpoints:
if cp.captured:
if cp.captured.is_blue:
for squadron in cp.squadrons:
turn_data.allied_units.aircraft_count += squadron.owned_aircraft
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.squadrons import AirWing
from game.squadrons import Squadron
from game.theater.player import Player
from game.theater.controlpoint import (
ControlPoint,
OffMapSpawn,
@ -833,7 +834,7 @@ class PretenseAircraftGenerator:
"""
self.initialize_pretense_data_structures(cp)
is_player = True
is_player = Player.BLUE
if country == cp.coalition.faction.country:
offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp)
@ -868,7 +869,7 @@ class PretenseAircraftGenerator:
coalition = (
self.game.coalition_for(is_player)
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)

View File

@ -25,7 +25,7 @@ from game.missiongenerator.aircraft.flightgroupspawner import (
)
from game.missiongenerator.missiondata import MissionData
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):
@ -87,7 +87,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner):
def insert_into_pretense(self, name: str) -> None:
cp = self.flight.departure
is_player = True
is_player = Player.BLUE
cp_side = (
2
if self.flight.coalition

View File

@ -1495,13 +1495,13 @@ class PretenseLuaGenerator(LuaGenerator):
cp_name.replace("ä", "a")
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):
continue
elif (
cp.is_fleet
and cp.captured
and cp.captured.is_blue
and self.game.settings.pretense_controllable_carrier
):
# Friendly carrier, generate carrier parameters
@ -1591,7 +1591,7 @@ class PretenseLuaGenerator(LuaGenerator):
# Also connect carrier and LHA control points to adjacent friendly points
if cp.is_fleet and (
not self.game.settings.pretense_controllable_carrier
or not cp.captured
or cp.captured.is_red
):
num_of_carrier_connections = 0
for (
@ -1616,7 +1616,7 @@ class PretenseLuaGenerator(LuaGenerator):
try:
if (
cp.is_fleet
and cp.captured
and cp.captured.is_blue
and self.game.settings.pretense_controllable_carrier
):
break

View File

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

View File

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

View File

@ -1,9 +1,7 @@
from __future__ import annotations
import logging
import math
import random
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING
from dcs import Point
from dcs.action import (
@ -11,10 +9,6 @@ from dcs.action import (
DoScript,
MarkToAll,
SetFlag,
RemoveSceneObjects,
RemoveSceneObjectsMask,
SceneryDestructionZone,
Smoke,
)
from dcs.condition import (
AllOfCoalitionOutsideZone,
@ -22,7 +16,6 @@ from dcs.condition import (
FlagIsTrue,
PartOfCoalitionInZone,
TimeAfter,
TimeSinceFlag,
)
from dcs.mission import Mission
from dcs.task import Option
@ -31,12 +24,11 @@ from dcs.terrain.syria.airports import Damascus, Khalkhalah
from dcs.translation import String
from dcs.triggers import Event, TriggerCondition, TriggerOnce
from dcs.unit import Skill
from numpy import cross, einsum, arctan2
from shapely import MultiPolygon, Point as ShapelyPoint
from game.naming import ALPHA_MILITARY
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
if TYPE_CHECKING:
@ -157,7 +149,7 @@ class PretenseTriggerGenerator:
zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK"
)
if cp.captured:
if cp.captured.is_blue:
name = ground_object.obj_name + " [ALLY]"
else:
name = ground_object.obj_name + " [ENEMY]"
@ -174,7 +166,7 @@ class PretenseTriggerGenerator:
"""
for cp in self.game.theater.controlpoints:
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured:
if cp.captured.is_blue:
attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition
@ -243,7 +235,7 @@ class PretenseTriggerGenerator:
self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh"
)
sea_zones_landmap = self.game.coalition_for(
player=False
player=Player.RED
).nav_mesh.theater.landmap
if (
self.game.settings.pretense_controllable_carrier
@ -251,7 +243,7 @@ class PretenseTriggerGenerator:
):
navmesh_number = 0
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:
navmesh_number += 1
if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly):
@ -325,7 +317,7 @@ class PretenseTriggerGenerator:
if (
cp.is_fleet
and self.game.settings.pretense_controllable_carrier
and cp.captured
and cp.captured.is_blue
):
# Friendly carrier zones are generated above
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.data.units import UnitClass
from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint, MissionTarget, ParkingType
from game.theater import ControlPoint, MissionTarget, ParkingType, Player
if TYPE_CHECKING:
from game import Game
@ -34,20 +34,20 @@ class ProcurementAi:
def __init__(
self,
game: Game,
for_player: bool,
owner: Player,
faction: Faction,
manage_runways: bool,
manage_front_line: bool,
manage_aircraft: bool,
) -> None:
self.game = game
self.is_player = for_player
self.air_wing = game.air_wing_for(for_player)
self.is_player = owner
self.air_wing = game.air_wing_for(owner)
self.faction = faction
self.manage_runways = manage_runways
self.manage_front_line = manage_front_line
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:
armor_investment = 0
@ -114,7 +114,7 @@ class ProcurementAi:
if control_point.runway_can_be_repaired:
control_point.begin_runway_repair()
budget -= RUNWAY_REPAIR_COST
if self.is_player:
if self.is_player.is_blue:
self.game.message(
"We have begun repairing the runway at " f"{control_point}"
)
@ -223,7 +223,7 @@ class ProcurementAi:
@property
def owned_points(self) -> List[ControlPoint]:
if self.is_player:
if self.is_player.is_blue:
return self.game.theater.player_points()
else:
return self.game.theater.enemy_points()

View File

@ -29,12 +29,16 @@ class ControlPointJs(BaseModel):
destination = None
if control_point.target_position is not None:
destination = control_point.target_position.latlng()
if control_point.captured.is_blue:
blue = True
else:
blue = False
return ControlPointJs(
id=control_point.id,
name=control_point.name,
blue=control_point.captured,
blue=blue,
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,
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 game import Game
from game.theater.player import Player
from .models import ControlPointJs
from ..dependencies import GameContext
from ..leaflet import LeafletPoint
@ -75,7 +76,7 @@ def set_destination(
)
if not cp.moveable:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile")
if not cp.captured:
if not cp.captured.is_blue:
raise HTTPException(
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:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile")
if not cp.captured:
if not cp.captured.is_blue:
raise HTTPException(
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.supplyroutes.models import SupplyRouteJs
from game.server.tgos.models import TgoJs
from game.theater import Player
if TYPE_CHECKING:
from game import Game
@ -26,9 +27,9 @@ class GameUpdateEventsJs(BaseModel):
new_combats: list[FrozenCombatJs]
updated_combats: list[FrozenCombatJs]
ended_combats: list[UUID]
navmesh_updates: dict[bool, NavMeshJs]
navmesh_updates: dict[Player, NavMeshJs]
updated_unculled_zones: list[UnculledZoneJs]
threat_zones_updated: dict[bool, ThreatZonesJs]
threat_zones_updated: dict[Player, ThreatZonesJs]
new_flights: list[FlightJs]
updated_flights: list[FlightJs]
deleted_flights: set[UUID]

View File

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

View File

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

View File

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
from pydantic import BaseModel
from game.server.leaflet import LeafletPoint, LeafletPoly, ShapelyUtil
from game.theater import ConflictTheater
from game.theater import ConflictTheater, Player
from game.threatzones import ThreatZones
if TYPE_CHECKING:
@ -85,9 +85,9 @@ class ThreatZoneContainerJs(BaseModel):
def for_game(game: Game) -> ThreatZoneContainerJs:
return ThreatZoneContainerJs(
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(
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.server import GameContext
from game.theater.player import Player
from .models import NavMeshJs
router: APIRouter = APIRouter(prefix="/navmesh")
@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
return NavMeshJs.from_navmesh(mesh, game)

View File

@ -76,6 +76,10 @@ class SupplyRouteJs(BaseModel):
def for_link(
game: Game, a: ControlPoint, b: ControlPoint, points: list[Point], sea: bool
) -> SupplyRouteJs:
if a.captured.is_blue:
blue = True
else:
blue = False
return SupplyRouteJs(
# 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
@ -93,7 +97,7 @@ class SupplyRouteJs(BaseModel):
points=[p.latlng() for p in points],
front_active=not sea and a.front_is_active(b),
is_sea=sea,
blue=a.captured,
blue=blue,
active_transports=TransportFinder(game, a, b).describe_active_transports(
sea
),

View File

@ -34,12 +34,16 @@ class TgoJs(BaseModel):
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
threat_ranges = [group.max_threat_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(
id=tgo.id,
name=tgo.name,
control_point_name=tgo.control_point.name,
category=tgo.category,
blue=tgo.control_point.captured,
blue=blue,
position=tgo.position.latlng(),
units=[unit.display_name for unit in tgo.units],
threat_ranges=threat_ranges,

View File

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

View File

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

View File

@ -9,7 +9,7 @@ from shapely.ops import unary_union
from game.utils import dcs_to_shapely_point
if TYPE_CHECKING:
from game.theater import ConflictTheater, TheaterGroundObject
from game.theater import ConflictTheater, TheaterGroundObject, Player
from game.threatzones import ThreatPoly
@ -31,7 +31,9 @@ class SamEngagementZones:
yield tgo
@classmethod
def from_theater(cls, theater: ConflictTheater, player: bool) -> SamEngagementZones:
def from_theater(
cls, theater: ConflictTheater, player: Player
) -> SamEngagementZones:
commit_regions = []
individual_zones = []
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.navmesh import NavMesh
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.theater.iadsnetwork.iadsnetwork import IadsNetworkNode
@ -24,9 +24,9 @@ class GameUpdateEvents:
updated_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)
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)
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)
updated_flights: set[Flight] = 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))
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
return self
@ -78,7 +78,9 @@ class GameUpdateEvents:
self.unculled_zones_updated = zones
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
return self

View File

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

View File

@ -14,12 +14,13 @@ from ..utils import Distance
if TYPE_CHECKING:
from game.game import Game
from game.theater.player import Player
from ..ato.flighttype import FlightType
from .squadron import Squadron
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.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
self.squadron_defs = SquadronDefLoader(game, faction).load()

View File

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

View File

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

View File

@ -68,6 +68,7 @@ from .base import Base
from .frontline import FrontLine
from .interfaces.CTLD import CTLD
from .missiontarget import MissionTarget
from .player import Player
from .theatergroundobject import (
GenericCarrierGroundObject,
TheaterGroundObject,
@ -377,7 +378,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
position: Point,
at: StartingPosition,
theater: ConflictTheater,
starts_blue: bool,
starting_coalition: Player,
cptype: ControlPointType = ControlPointType.AIRBASE,
is_invisible: bool = False,
) -> None:
@ -386,8 +387,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self.full_name = name
self.at = at
self.theater = theater
self.starts_blue = starts_blue
self.is_invisible = is_invisible
self.starting_coalition = starting_coalition
self.connected_objectives: List[TheaterGroundObject] = []
self.preset_locations = PresetLocations()
self.helipads: List[PointWithHeading] = []
@ -435,7 +436,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def finish_init(self, game: Game) -> 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
self._front_line_db = game.db.front_lines
@ -444,6 +445,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
# the entire game state when it comes up.
from game.sim import GameUpdateEvents
if self.captured != Player.NEUTRAL:
self._create_missing_front_lines(laser_code_registry, GameUpdateEvents())
@property
@ -455,9 +457,11 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self, laser_code_registry: LaserCodeRegistry, events: GameUpdateEvents
) -> None:
for connection in self.convoy_routes.keys():
if not connection.front_line_active_with(
self
) and not connection.is_friendly_to(self):
if (
not connection.front_line_active_with(self)
and not connection.is_friendly_to(self)
and connection.captured != Player.NEUTRAL
):
self._create_front_line_with(laser_code_registry, connection, events)
def _create_front_line_with(
@ -491,6 +495,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
@property
def has_frontline(self) -> bool:
if self.captured.is_neutral:
return False
return bool(self.front_lines)
def front_line_active_with(self, other: ControlPoint) -> bool:
@ -500,14 +506,17 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
return self.front_lines[other]
@property
def captured(self) -> bool:
def captured(self) -> Player:
return self.coalition.player
@property
def standard_identity(self) -> StandardIdentity:
return (
StandardIdentity.FRIEND if self.captured else StandardIdentity.HOSTILE_FAKER
)
if self.captured.is_neutral:
return StandardIdentity.UNKNOWN
elif self.captured.is_blue:
return StandardIdentity.FRIEND
else:
return StandardIdentity.HOSTILE_FAKER
@property
def sidc_status(self) -> Status:
@ -819,7 +828,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
found.append(g)
return found
def is_friendly(self, to_player: bool) -> bool:
def is_friendly(self, to_player: Player) -> bool:
return self.captured == to_player
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:
total = self.base.total_armor_value
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(
f"{self.name} is not connected to any friendly points. Ground "
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:
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(
f"No valid retreat destination in range of {self.name} for {airframe} "
f"{count} aircraft have been captured and sold for ${value}M."
@ -960,7 +973,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
pass
# 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)
self.ground_unit_orders.refund_all(self.coalition)
self.retreat_ground_units(game)
@ -1125,6 +1138,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
@property
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)
def front_is_active(self, other: ControlPoint) -> bool:
@ -1211,7 +1226,7 @@ class Airfield(ControlPoint, CTLD):
self,
airport: Airport,
theater: ConflictTheater,
starts_blue: bool,
starting_coalition: Player,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
influence_zone: Optional[List[Tuple[Point, float]]] = None,
) -> None:
@ -1220,7 +1235,7 @@ class Airfield(ControlPoint, CTLD):
airport.position,
airport,
theater,
starts_blue,
starting_coalition,
cptype=ControlPointType.AIRBASE,
)
self.airport = airport
@ -1252,7 +1267,7 @@ class Airfield(ControlPoint, CTLD):
return True
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
if not self.is_friendly(for_player):
@ -1373,7 +1388,7 @@ class NavalControlPoint(
def is_fleet(self) -> bool:
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
if self.is_friendly(for_player):
@ -1482,7 +1497,7 @@ class NavalControlPoint(
class Carrier(NavalControlPoint):
def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
):
super().__init__(
name,
@ -1497,7 +1512,7 @@ class Carrier(NavalControlPoint):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
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")
@property
@ -1522,7 +1537,7 @@ class EssexCarrier(Carrier):
class Lha(NavalControlPoint):
def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
):
super().__init__(
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]:
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")
@property
@ -1555,7 +1570,7 @@ class OffMapSpawn(ControlPoint):
return True
def __init__(
self, name: str, position: Point, theater: ConflictTheater, starts_blue: bool
self, name: str, position: Point, theater: ConflictTheater, starts_blue: Player
):
super().__init__(
name,
@ -1570,10 +1585,10 @@ class OffMapSpawn(ControlPoint):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
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")
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
yield from []
def total_aircraft_parking(self, parking_type: ParkingType) -> int:
@ -1630,7 +1645,7 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
name: str,
at: Point,
theater: ConflictTheater,
starts_blue: bool,
starts_blue: Player,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
is_invisible: bool = False,
influence_zone: Optional[List[Tuple[Point, float]]] = None,
@ -1667,7 +1682,7 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
def runway_status(self) -> 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
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 .missiontarget import MissionTarget
from .player import Player
from ..lasercodes.lasercode import LaserCode
from ..utils import Heading, pairwise
@ -89,19 +90,22 @@ class FrontLine(MissionTarget):
def update_position(self) -> None:
self.position = self._compute_position()
def control_point_friendly_to(self, player: bool) -> ControlPoint:
if player:
def control_point_friendly_to(self, player: Player) -> ControlPoint:
if player.is_blue:
return self.blue_cp
return self.red_cp
def control_point_hostile_to(self, player: bool) -> ControlPoint:
return self.control_point_friendly_to(not player)
def control_point_hostile_to(self, player: Player) -> ControlPoint:
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."""
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
yield from [
@ -210,6 +214,6 @@ class FrontLine(MissionTarget):
raise ValueError(
"Cannot sort control points that are friendly to each other"
)
if a.captured:
if a.captured.is_blue:
return a, b
return b, a

View File

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

View File

@ -6,7 +6,7 @@ from dcs.mapping import Point
if TYPE_CHECKING:
from game.ato.flighttype import FlightType
from game.theater import TheaterUnit, Coalition
from game.theater import TheaterUnit, Coalition, Player
class MissionTarget:
@ -24,11 +24,11 @@ class MissionTarget:
"""Computes the distance to the given mission target."""
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."""
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
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,
OffMapSpawn,
)
from .player import Player
from .theatergroup import (
IadsGroundGroup,
IadsRole,
@ -158,12 +159,12 @@ class GameGenerator:
game.settings.version = VERSION
return game
def should_remove_carrier(self, player: bool) -> bool:
faction = self.player if player else self.enemy
def should_remove_carrier(self, player: Player) -> bool:
faction = self.player if player.is_blue else self.enemy
return self.generator_settings.no_carrier or not faction.carriers
def should_remove_lha(self, player: bool) -> bool:
faction = self.player if player else self.enemy
def should_remove_lha(self, player: Player) -> bool:
faction = self.player if player.is_blue else self.enemy
return self.generator_settings.no_lha or not [
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
for cp in self.theater.controlpoints:
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)
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)
# do remove
@ -230,11 +233,13 @@ class ControlPointGroundObjectGenerator:
self.control_point.connected_objectives.append(ground_object)
def generate_navy(self) -> None:
if self.control_point.captured.is_neutral:
return
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
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
for position in self.control_point.preset_locations.ships:
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.position,
self.game.theater,
self.control_point.starts_blue,
self.control_point.starting_coalition,
)
self.control_point.finish_init(self.game)
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 .missiontarget import MissionTarget
from .player import Player
from ..data.groups import GroupTask
from ..utils import Distance, Heading, meters
@ -98,11 +99,12 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
@property
def standard_identity(self) -> StandardIdentity:
return (
StandardIdentity.FRIEND
if self.control_point.captured
else StandardIdentity.HOSTILE_FAKER
)
if self.control_point.captured.is_blue:
return StandardIdentity.FRIEND
elif self.control_point.captured.is_neutral:
return StandardIdentity.UNKNOWN
else:
return StandardIdentity.HOSTILE_FAKER
@property
def is_dead(self) -> bool:
@ -154,10 +156,12 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
def faction_color(self) -> str:
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)
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
from game.ato import FlightType
if self.is_friendly(for_player):
@ -360,7 +364,7 @@ class BuildingGroundObject(TheaterGroundObject):
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
if not self.is_friendly(for_player):
@ -466,7 +470,7 @@ class MissileSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
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
if not self.is_friendly(for_player):
@ -507,7 +511,7 @@ class CoastalSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
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
if not self.is_friendly(for_player):
@ -534,7 +538,7 @@ class IadsGroundObject(TheaterGroundObject, ABC):
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
if not self.is_friendly(for_player):
@ -585,7 +589,7 @@ class SamGroundObject(IadsGroundObject):
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
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
if not self.is_friendly(for_player):
@ -642,7 +646,7 @@ class VehicleGroupGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
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
if not self.is_friendly(for_player):
@ -697,7 +701,7 @@ class ShipGroundObject(NavalGroundObject):
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
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 .controlpoint import ControlPoint
from .player import Player
class NoPathError(RuntimeError):
@ -152,7 +153,7 @@ class TransitNetwork:
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.network = TransitNetwork()
self.airports: Set[ControlPoint] = {

View File

@ -26,6 +26,7 @@ from game.utils import Distance, meters, nautical_miles
if TYPE_CHECKING:
from game import Game
from game.theater.player import Player
ThreatPoly = Union[MultiPolygon, Polygon]
@ -187,7 +188,7 @@ class ThreatZones:
return min(cap_threat_range, max_distance)
@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.
Args:

View File

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

View File

@ -24,7 +24,7 @@ from game.radio.tacan import TacanChannel
from game.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents
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.transfers import PendingTransfers, TransferOrder
from qt_ui.simcontroller import SimController
@ -564,8 +564,8 @@ class GameModel:
self.allocated_icls: list[int] = list()
self.init_comms_registry()
def ato_model_for(self, player: bool) -> AtoModel:
if player:
def ato_model_for(self, player: Player) -> AtoModel:
if player.is_blue:
return self.ato_model
return self.red_ato_model

View File

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

View File

@ -6,6 +6,7 @@ from game.ato.flightwaypointtype import FlightWaypointType
from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription,
)
from game.theater.player import Player
from game.theater.controlpoint import ControlPointType
from game.utils import Distance
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
@ -93,7 +94,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
wpt.targets.append(target)
wpt.obj_name = tgo.obj_name
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}"
else:
wpt.description = f"Enemy unit: {target.name}"

View File

@ -790,7 +790,7 @@ class AirWingConfigurationDialog(QDialog):
self.tabs = []
for coalition in game.coalitions:
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.tabs.append(coalition_tab)

View File

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

View File

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

View File

@ -27,6 +27,7 @@ from game.theater import (
FREE_FRONTLINE_UNIT_SUPPLY,
NavalControlPoint,
ParkingType,
Player,
)
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
@ -85,7 +86,7 @@ class QBaseMenu2(QDialog):
self.freq_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):
self.freq_widget = QFrequencyWidget(cp, self.game_model)
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.server import EventStream
from game.sim.gameupdateevents import GameUpdateEvents
from game.theater import ControlPoint, TheaterGroundObject
from game.theater import ControlPoint, TheaterGroundObject, Player
from game.theater.theatergroundobject import (
BuildingGroundObject,
)
@ -87,12 +87,12 @@ class QGroundObjectMenu(QDialog):
if isinstance(self.ground_object, BuildingGroundObject):
self.mainLayout.addWidget(self.buildingBox)
if self.cp.captured:
if self.cp.captured.is_blue:
self.mainLayout.addWidget(self.financesBox)
else:
self.mainLayout.addWidget(self.intelBox)
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.actionLayout = QHBoxLayout()
@ -118,8 +118,10 @@ class QGroundObjectMenu(QDialog):
@property
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.cp.captured
buysell_allowed |= self.cp.captured.is_blue
return buysell_allowed
def doLayout(self):
@ -133,7 +135,7 @@ class QGroundObjectMenu(QDialog):
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
repair = QPushButton(f"Repair [{price}M]")
repair.setProperty("style", "btn-success")

View File

@ -33,7 +33,11 @@ pluggy==1.5.0
pre-commit==4.2.0
pydantic==2.11.0b2
pydantic-settings==2.8.1
<<<<<<< HEAD
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-hooks-contrib==2024.0
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,
Fob,
ParkingType,
Player,
)
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)
airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__
airfield = Airfield(airport, theater=None, starts_blue=True) # type: ignore
mission_types = list(airfield.mission_types(for_player=True))
airfield = Airfield(airport, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(airfield.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3
assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types
@ -39,8 +40,8 @@ def test_mission_types_friendly(mocker: Any) -> None:
# Carrier
mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=True)
carrier = Carrier(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(carrier.mission_types(for_player=True))
carrier = Carrier(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(carrier.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3
assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types
@ -48,8 +49,8 @@ def test_mission_types_friendly(mocker: Any) -> None:
# LHA
mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=True)
lha = Lha(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(lha.mission_types(for_player=True))
lha = Lha(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(lha.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 3
assert FlightType.AEWC in mission_types
assert FlightType.REFUELING in mission_types
@ -57,16 +58,16 @@ def test_mission_types_friendly(mocker: Any) -> None:
# Fob
mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=True)
fob = Fob(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(fob.mission_types(for_player=True))
fob = Fob(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(fob.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 2
assert FlightType.AEWC in mission_types
assert FlightType.BARCAP in mission_types
# Off map spawn
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
mission_types = list(off_map_spawn.mission_types(for_player=True))
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=Player.BLUE))
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)
airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__
airfield = Airfield(airport, theater=None, starts_blue=True) # type: ignore
mission_types = list(airfield.mission_types(for_player=True))
airfield = Airfield(airport, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(airfield.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 8
assert FlightType.OCA_AIRCRAFT in mission_types
assert FlightType.OCA_RUNWAY in mission_types
@ -93,8 +94,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# Carrier
mocker.patch("game.theater.controlpoint.Carrier.is_friendly", return_value=False)
carrier = Carrier(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(carrier.mission_types(for_player=True))
carrier = Carrier(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(carrier.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 5
assert FlightType.ANTISHIP in mission_types
assert FlightType.ESCORT in mission_types
@ -104,8 +105,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# LHA
mocker.patch("game.theater.controlpoint.Lha.is_friendly", return_value=False)
lha = Lha(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(lha.mission_types(for_player=True))
lha = Lha(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(lha.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 5
assert FlightType.ANTISHIP in mission_types
assert FlightType.ESCORT in mission_types
@ -115,8 +116,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
# Fob
mocker.patch("game.theater.controlpoint.Fob.is_friendly", return_value=False)
fob = Fob(name="test", at=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(fob.mission_types(for_player=True))
fob = Fob(name="test", at=None, theater=None, starts_blue=Player.BLUE) # type: ignore
mission_types = list(fob.mission_types(for_player=Player.BLUE))
assert len(mission_types) == 6
assert FlightType.AIR_ASSAULT in mission_types
assert FlightType.ESCORT in mission_types
@ -129,8 +130,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
mocker.patch(
"game.theater.controlpoint.OffMapSpawn.is_friendly", return_value=False
)
off_map_spawn = OffMapSpawn(name="test", position=None, theater=None, starts_blue=True) # type: ignore
mission_types = list(off_map_spawn.mission_types(for_player=True))
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=Player.BLUE))
assert len(mission_types) == 0
@ -144,7 +145,7 @@ def test_control_point_parking(mocker: Any) -> None:
airport = Airport(None, None) # type: ignore
airport.name = "test" # required for Airfield.__init__
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(
fixed_wing=False, fixed_wing_stol=True, rotary_wing=False
)

View File

@ -4,7 +4,7 @@ import pytest
from dcs.mapping import Point
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.theatergroundobject import (
BuildingGroundObject,
@ -34,7 +34,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
name="dummy_control_point",
position=Point(0, 0, 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
@ -56,7 +56,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
control_point=dummy_control_point,
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]
for ground_object_type in [BuildingGroundObject, IadsBuildingGroundObject]:
@ -67,7 +67,7 @@ def test_mission_types_friendly(mocker: Any) -> None:
control_point=dummy_control_point,
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]
@ -84,7 +84,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
name="dummy_control_point",
position=Point(0, 0, 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
@ -99,7 +99,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point,
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 FlightType.STRIKE 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,
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 FlightType.STRIKE 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,
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 FlightType.ANTISHIP 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,
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 FlightType.DEAD in mission_types
assert FlightType.SEAD in mission_types
@ -168,7 +168,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
location=dummy_location,
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 FlightType.DEAD in mission_types
assert FlightType.STRIKE in mission_types
@ -187,7 +187,7 @@ def test_mission_types_enemy(mocker: Any) -> None:
location=dummy_location,
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 FlightType.BAI 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,
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 FlightType.BAI in mission_types
assert FlightType.STRIKE in mission_types