10 Commits
dev ... v1.4.1

Author SHA1 Message Date
Raffson
cc65e32493 Update h11 to 0.16.0 2025-06-07 19:41:29 +02:00
Raffson
382f8df39f Update most Python packages 2025-06-07 19:41:10 +02:00
Raffson
a12ab86e71 Changelog for v1.4.1 2025-06-07 19:31:23 +02:00
Raffson
fd1337bb46 Versioning for v1.4.1 2025-06-07 19:31:10 +02:00
Raffson
1bc876ab61 Fix missing check in handle_ew_jamming w.r.t. plugin option 2025-06-07 19:25:37 +02:00
Raffson
ab2391e745 Update actions/checkout to v4 2025-04-20 23:24:25 +02:00
Raffson
356fcf9532 Update actions/setup-node to v4 2025-04-20 23:24:25 +02:00
Raffson
88c18b65ff Update actions/setup-python to v5? 2025-04-20 23:24:25 +02:00
Raffson
29470085d3 Another attempt at fixing the pipeline -_- 2025-04-20 23:24:24 +02:00
Raffson
1df1325f55 Pin python version for black in lint.yml 2025-04-20 23:24:24 +02:00
620 changed files with 32874 additions and 163448 deletions

View File

@@ -31,7 +31,7 @@ body:
If the bug was found in a test/development build, select "Test build" or "Development build"
and provide a link to the applicable build in the field below.
options:
- v1.4.1
- v1.3.1
- Test build
- type: input
attributes:

View File

@@ -39,7 +39,7 @@ body:
If the bug was found in a test/development build, select "Test build" or "Development build"
and provide a link to the applicable build in the field below.
options:
- 1.4.1
- 1.3.1
- Test build
- type: input
attributes:

View File

@@ -68,7 +68,7 @@ Our plans for future releases can be found on our
[Projects page](https://github.com/dcs-retribution/dcs-retribution/projects).
Each planned release has a Project, and the page for that project has columns for to do,
in progress, and done. Items in the Done column are in the
[preview build](https://github.com/dcs-retribution/dcs-retribution/wiki/Preview-builds)
[preview build](https://github.com/dcs-retribution/dcs-retribution/wiki/Betas)
for that release. Items in the To do column are planned to be added to that release.
## Resources

View File

@@ -1,41 +1,3 @@
# Retribution v1.5.0
## Features/Improvements
* **[Campaigns]** Ability to define invisible FOBs
* **[Campaigns]** Ability to define influence zones for Control Points
* **[Plugins]** Improvements to AI support for EW Script 2.0
* **[Plugins]** Add moose as a base plugin
* **[Config]** New preference setting to trigger the first-start window on every start (could help in scenarios multiple Retribution instances need to run concurrently)
* **[Modding]** Update Grinelli Designs F-22A Mod to 2.0.0 (May 2025)
* **[Campaign Design]** Added support for Germany Cold War terrain by Ugra Media
* **[Options]** New option to control EPLRS-task injection in mission generator
* **[Modding]** Add Tornado F3 ADV (v1.0, requires FC3 fix)
* **[Modding]** Add Military Aircraft Mod 1.7.2 (See VSN Discord)
* **[Payload Editor]** Add Sniper POD
* **[Modding]** Update SU-30 mod to v2.8.04 Beta + CWS 3.72
* **[Plugins]** EW Script - DEAD added to applicable flight types.
* **[Plugins]** EW Script - Offensive Jamming restricted to aircraft with ALQ99/249 pods, or "has_built_in_jamming: true" in aircarft yaml (AI and Player)
* **[Modding]** Update to CJS Super Hornet Mod v2.4.4
* **[Engine]** Support for DCS 2.9.20.15010, including MiG-29A Fulcrum support.
* **[Campaigns]** Ability to define neutral FOBs and Airfields
* **[Modding]** Add Airboss Moose Module
* **[AirWing]** Use aircraft display names for easier differentiation between modules
## Fixes
* **[Flight Plans]** Fixed bugs wrt planning escort flights
* **[Flight Plans]** Added AntiShipStrike as a fallback task for OCA/Aircraft to fix a bug where the S-3B could not do OCA/Aircraft
* **[Squadrons]** Fixed a bug where loading an air wing config would not properly load all squadrons
* **[Flight Plans]** Fixed a bug where SEAD flights would fire one ARM and RTB
* **[Plugins]** EW Script - Fix radar detection routine.
* **[Campaign]** Fixed a bug where sinking a destroyer in a carrier group would cause squadrons to be removed from the carrier
* **[Engine]** Fixed a bug with transfers to Helipads
* **[Engine]** Fixed a bug with parking allocation
* **[Flight Plans]** Fixed a bug where divert airfield was broken for opfor
* **[Engine]** Fixed a bug with state.json loading wrt transfers
* **[Engine]** Fixed a bug wrt pretense generation and moose script conflicts
* **[Engine]** Fixed a bug where frontline debriefing was not properly calculated
* **[Mission Generation]** Fixed an issue where blue and red units spawned next to each other on frontlines
# Retribution v1.4.1 (hotfix)
## Fixes

22911
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@
"@types/leaflet": "^1.9.8",
"@types/redux-logger": "^3.0.13",
"@types/websocket": "^1.0.10",
"electron": "^28.3.2",
"electron": "^22.3.27",
"electron-is-dev": "^2.0.0",
"generate-license-file": "^2.0.0",
"identity-obj-proxy": "^3.0.0",

View File

@@ -60,7 +60,7 @@ const WaypointMarker = (props: WaypointMarkerProps) => {
useEffect(() => {
const waypoint = props.waypoint;
marker.current?.setTooltipContent(
`${props.number-1} ${waypoint.name}<br />` +
`${props.number} ${waypoint.name}<br />` +
`${waypoint.altitude_ft.toFixed()} ft ${waypoint.altitude_reference}<br />` +
waypoint.timing
);

View File

@@ -9,7 +9,7 @@
project = "DCS Retribution"
copyright = "2025, DCS Retribution Team"
author = "DCS Retribution Team"
release = "1.5.0"
release = "1.4.1"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

View File

@@ -40,7 +40,7 @@ class ArmedForces:
# all layouts which are usable by the given faction.
for layout in LAYOUTS.layouts:
if layout.generic and layout.usable_by_faction(faction):
# Creates a faction compatible ForceGroup
# Creates a faction compatible GorceGroup
self.add_or_update_force_group(ForceGroup.for_layout(layout, faction))
# Add all preset groups afterwards to prevent them being merged with generics

View File

@@ -258,9 +258,6 @@ class ForceGroup:
if unit_count == 0:
# No units to be created so dont create a theater group for them
return
# If the control point is neutral, don't create any units at all
if ground_object.control_point.captured.is_neutral:
return
# Generate Units
fixed_pos = FIXED_POS_ARG in unit_group.name
fixed_hdg = FIXED_HDG_ARG in unit_group.name

View File

@@ -35,7 +35,6 @@ 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
@@ -175,7 +174,7 @@ class Flight(
self.roster = FlightMembers.from_roster(self, self.roster)
@property
def blue(self) -> Player:
def blue(self) -> bool:
return self.squadron.player
@property

View File

@@ -23,13 +23,7 @@ class EscortFlightPlan(FormationAttackFlightPlan):
@property
def split_time(self) -> datetime:
# Avoid infinite recursion when this escort flight is itself the primary flight
# This can happen when only escort flights remain in a package
if (
self.package.primary_flight
and self.package.primary_flight != self.flight
and self.package.primary_flight.flight_plan
):
if self.package.primary_flight and self.package.primary_flight.flight_plan:
return self.package.primary_flight.flight_plan.mission_departure_time
else:
return super().split_time

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, Player
from game.theater import ConflictTheater
from game.threatzones import ThreatZones
from ..flight import Flight
from ..package import Package
@@ -38,7 +38,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
self._generate_package_waypoints_if_needed(dump_debug_info)
self._flight_plan = self.build(dump_debug_info)
except NavMeshError as ex:
color = "blue" if self.flight.squadron.player.is_blue else "red"
color = "blue" if self.flight.squadron.player else "red"
raise PlanningError(
f"Could not plan {color} {self.flight.flight_type.value} from "
f"{self.flight.departure} to {self.package.target}"
@@ -71,7 +71,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
return self.flight.coalition
@property
def is_player(self) -> Player:
def is_player(self) -> bool:
return self.coalition.player
@property

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Optional, TYPE_CHECKING, Union
@@ -25,7 +26,6 @@ class SquadronConfig:
name: Optional[str]
nickname: Optional[str]
female_pilot_percentage: Optional[int]
aircraft_type: Optional[str]
@property
def auto_assignable(self) -> set[FlightType]:
@@ -51,7 +51,6 @@ class SquadronConfig:
data.get("name", None),
data.get("nickname", None),
data.get("female_pilot_percentage", None),
data.get("aircraft_type", None),
)
@staticmethod
@@ -84,14 +83,15 @@ class CampaignAirWingConfig:
try:
base = theater.control_point_named(base_id)
except:
logging.warning(
f"Control point {base_id} not found, trying to match by full name"
)
if not base:
try:
base = theater.control_point_by_full_name(base_id)
except KeyError:
logging.error(f"Control point {base_id} not found, skipping")
if base_id == "Red CV":
base = next((c for c in carriers if not c.captured), None)
elif base_id == "Blue CV":
base = next((c for c in carriers if c.captured), None)
elif base_id == "Red LHA":
base = next((l for l in lhas if not l.captured), None)
elif base_id == "Blue LHA":
base = next((l for l in lhas if l.captured), None)
for squadron_data in squadron_configs:
if base is None:
logging.warning(

View File

@@ -60,7 +60,7 @@ class DefaultSquadronAssigner:
) -> Optional[SquadronDef]:
for preferred_aircraft in config.aircraft:
squadron_def = self.find_preferred_squadron(
preferred_aircraft, config.aircraft_type, config.primary, control_point
preferred_aircraft, config.primary, control_point
)
if squadron_def is not None:
return squadron_def
@@ -78,11 +78,7 @@ class DefaultSquadronAssigner:
)
def find_preferred_squadron(
self,
preferred_aircraft: str,
aircraft_type: Optional[str],
task: FlightType,
control_point: ControlPoint,
self, preferred_aircraft: str, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
# Attempt to find a squadron with the name in the request.
squadron_def = self.find_squadron_by_name(
@@ -94,7 +90,7 @@ class DefaultSquadronAssigner:
# If the name didn't match a squadron available to this coalition, try to find
# an aircraft with the matching name that meets the requirements.
try:
aircraft = AircraftType.named(aircraft_type or preferred_aircraft)
aircraft = AircraftType.named(preferred_aircraft)
except KeyError:
logging.warning(
"%s is neither a compatible squadron or a known aircraft type, "

View File

@@ -16,7 +16,6 @@ from dcs.terrain import Airport
from dcs.unitgroup import PlaneGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from game.controlpoint_influenceradius import ControlPointInfluenceRadius, point_in_zone
from game.point_with_heading import PointWithHeading
from game.positioned import Positioned
from game.profiling import logged_duration
@@ -25,8 +24,6 @@ from game.theater.controlpoint import (
Airfield,
Carrier,
ControlPoint,
ControlPointType,
Player,
Fob,
Lha,
OffMapSpawn,
@@ -56,8 +53,6 @@ class MizCampaignLoader:
FOB_UNIT_TYPE = Unarmed.SKP_11.id
FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD", "FARP"]
INVISIBLE_FOB_UNIT_TYPE = Unarmed.M_818.id
NEUTRAL_FOB_UNIT_TYPE = Unarmed.KrAZ6322.id
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id
@@ -125,13 +120,9 @@ class MizCampaignLoader:
def control_point_from_airport(
self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
) -> ControlPoint:
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)
cp = Airfield(
airport, self.theater, starts_blue=airport.is_blue(), 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".
@@ -139,54 +130,42 @@ class MizCampaignLoader:
return cp
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)
def country(self, blue: bool) -> Country:
country = self.mission.country(
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name
)
# Should be guaranteed because we initialized them.
assert country
return country
@property
def blue(self) -> Country:
return self.country(blue=Player.BLUE)
return self.country(blue=True)
@property
def red(self) -> Country:
return self.country(blue=Player.RED)
return self.country(blue=False)
def off_map_spawns(self, blue: Player) -> Iterator[PlaneGroup]:
def off_map_spawns(self, blue: bool) -> 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: Player) -> Iterator[ShipGroup]:
def carriers(self, blue: bool) -> 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: Player) -> Iterator[ShipGroup]:
def lhas(self, blue: bool) -> 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: Player) -> Iterator[VehicleGroup]:
def fobs(self, blue: bool) -> 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: Player) -> Iterator[VehicleGroup]:
for group in self.country(blue).vehicle_group:
if group.units[0].type == self.INVISIBLE_FOB_UNIT_TYPE:
yield group
@property
def neutral_fobs(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type == self.NEUTRAL_FOB_UNIT_TYPE:
yield group
@property
def ships(self) -> Iterator[ShipGroup]:
for group in self.red.ship_group:
@@ -293,22 +272,16 @@ class MizCampaignLoader:
def scenery(self) -> List[SceneryGroup]:
return SceneryGroup.from_trigger_zones(self.mission.triggers._zones)
@cached_property
def cp_influence_zones(self) -> List[ControlPointInfluenceRadius]:
return ControlPointInfluenceRadius.from_trigger_zones(
self.mission.triggers._zones
)
@cached_property
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() or airport.is_neutral():
if airport.is_blue() or airport.is_red():
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 (Player.RED, Player.BLUE):
for blue in (False, True):
for group in self.off_map_spawns(blue):
control_point = OffMapSpawn(
str(group.name), group.position, self.theater, starts_blue=blue
@@ -339,47 +312,17 @@ class MizCampaignLoader:
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
for fob in self.invisible_fobs(blue):
ctld_zones = self.get_ctld_zones(fob.name)
control_point = Fob(
str(fob.name),
fob.position,
self.theater,
starts_blue=blue,
ctld_zones=ctld_zones,
is_invisible=True,
)
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
for fob in self.neutral_fobs:
ctld_zones = self.get_ctld_zones(fob.name)
control_point = Fob(
str(fob.name),
fob.position,
self.theater,
starts_blue=Player.NEUTRAL,
ctld_zones=ctld_zones,
)
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
if self.cp_influence_zones:
for cp in control_points.values():
for influence_radius in self.cp_influence_zones:
if cp.full_name == influence_radius.cp_name:
cp.influence_radius = influence_radius
return control_points
@property
def front_line_path_groups(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=Player.BLUE).vehicle_group:
for group in self.country(blue=True).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=Player.BLUE).ship_group:
for group in self.country(blue=True).ship_group:
if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE:
yield group
@@ -403,7 +346,7 @@ class MizCampaignLoader:
@property
def cp_convoy_spawns(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=Player.BLUE).vehicle_group:
for group in self.country(blue=True).vehicle_group:
if group.units[0].type == self.CP_CONVOY_SPAWN_TYPE:
yield group
@@ -528,59 +471,7 @@ class MizCampaignLoader:
def objective_info(
self, near: Positioned, allow_naval: bool = False
) -> Tuple[ControlPoint, Distance]:
zones_containing_point = [
z
for z in self.cp_influence_zones
if point_in_zone(z.zone_def, near.position)
]
# Ensure we only consider naval control points if allow_naval is True
candidates = [
self.theater.control_point_named(z.cp_name) for z in zones_containing_point
]
if not allow_naval:
candidates = [
cp
for cp in candidates
if cp.cptype
not in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
]
]
if candidates:
closest = min(
candidates, key=lambda cp: cp.position.distance_to_point(near.position)
)
distance = meters(closest.position.distance_to_point(near.position))
return closest, distance
# If no zones contain the point, find the closest control point without an influence radius
if not allow_naval:
fallback_candidates = [
cp
for cp in self.theater.controlpoints
if cp.cptype
not in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
]
]
else:
fallback_candidates = self.theater.controlpoints
fallback_candidates = [
cp for cp in fallback_candidates if not cp.influence_radius
]
if not fallback_candidates:
raise RuntimeError(
f"All control points have an influence zone but no zones contain {near} at {near.position}"
)
closest = min(
fallback_candidates,
key=lambda cp: cp.position.distance_to_point(near.position),
)
closest = self.theater.closest_control_point(near.position, allow_naval)
distance = meters(closest.position.distance_to_point(near.position))
return closest, distance

View File

@@ -17,7 +17,6 @@ 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
@@ -33,7 +32,7 @@ if TYPE_CHECKING:
class Coalition:
def __init__(
self, game: Game, faction: Faction, budget: float, player: Player
self, game: Game, faction: Faction, budget: float, player: bool
) -> None:
self.game = game
self.player = player
@@ -69,11 +68,9 @@ class Coalition:
@property
def coalition_id(self) -> int:
if self.player.is_blue:
if self.player:
return 2
elif self.player.is_red:
return 1
return 0
return 1
@property
def opponent(self) -> Coalition:
@@ -98,23 +95,12 @@ class Coalition:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
del state["_threat_zone"]
del state["_navmesh"]
del state["faker"]
# TODO: Figure out why this is needed after adding neutral point support
if state["player"] != Player.NEUTRAL:
del state["_threat_zone"]
del state["_navmesh"]
return state
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
# Regenerate any state that was not persisted.
self.on_load()
@@ -217,7 +203,7 @@ class Coalition:
squadron.refund_orders()
def plan_missions(self, now: datetime) -> None:
color = "Blue" if self.player.is_blue else "Red"
color = "Blue" if self.player else "Red"
with MultiEventTracer() as tracer:
with tracer.trace(f"{color} mission planning"):
with tracer.trace(f"{color} mission identification"):
@@ -234,7 +220,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.is_blue:
if self.player:
manage_runways = self.game.settings.automate_runway_repair
manage_front_line = self.game.settings.automate_front_line_reinforcements
manage_aircraft = self.game.settings.automate_aircraft_reinforcements

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
import itertools
import math
import operator
from collections.abc import Iterable, Iterator
@@ -17,7 +16,6 @@ from game.theater import (
OffMapSpawn,
ParkingType,
NavalControlPoint,
Player,
)
from game.theater.theatergroundobject import (
BuildingGroundObject,
@@ -37,7 +35,7 @@ MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder:
"""Identifies potential objectives for the mission planner."""
def __init__(self, game: Game, is_player: Player) -> None:
def __init__(self, game: Game, is_player: bool) -> None:
self.game = game
self.is_player = is_player
@@ -156,7 +154,7 @@ class ObjectiveFinder:
airfields_in_proximity = self.closest_airfields_to(cp)
airbase_threat_range = self.game.settings.airbase_threat_range
if (
self.is_player.is_red
not self.is_player
and randint(1, 100)
> self.game.settings.opfor_autoplanner_aggressiveness
):
@@ -218,7 +216,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(self.is_player.opponent)
threat_zones = self.game.threat_zone_for(not self.is_player)
farthest = None
max_distance = meters(0)
@@ -236,7 +234,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(self.is_player.opponent)
threat_zones = self.game.threat_zone_for(not self.is_player)
closest = None
min_distance = meters(math.inf)
@@ -260,37 +258,14 @@ class ObjectiveFinder:
return (
c
for c in self.game.theater.controlpoints
if not c.is_friendly(self.is_player) and c.captured != Player.NEUTRAL
if not c.is_friendly(self.is_player)
)
def prioritized_points(self) -> list[ControlPoint]:
prioritized = []
capturable_later = []
isolated = []
for cp in self.game.theater.control_points_for(self.is_player.opponent):
if cp.is_isolated:
isolated.append(cp)
continue
if cp.has_active_frontline:
prioritized.append(cp)
else:
capturable_later.append(cp)
prioritized.extend(self._targets_by_range(capturable_later))
prioritized.extend(self._targets_by_range(isolated))
return prioritized
def air_assault_targets(self) -> list[ControlPoint]:
"""Returns control points suitable for air assault missions, including neutral bases."""
prioritized = []
capturable_later = []
isolated = []
combined_control_points = itertools.chain(
self.game.theater.control_points_for(self.is_player.opponent),
self.game.theater.control_points_for(Player.NEUTRAL),
)
for cp in combined_control_points:
for cp in self.game.theater.control_points_for(not self.is_player):
if cp.is_isolated:
isolated.append(cp)
continue

View File

@@ -43,9 +43,7 @@ class PackageFulfiller:
@property
def is_player(self) -> bool:
if self.coalition.player.is_blue:
return True
return False
return self.coalition.player
@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, Player
from game.theater import ControlPoint, FrontLine
@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: Player) -> int:
def units_deployable(self, state: TheaterState, player: bool) -> 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: Player) -> int:
def unit_cap(self, state: TheaterState, player: bool) -> 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,11 +11,10 @@ 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: Player) -> None:
def __init__(self, front_line: FrontLine, player: bool) -> 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)
@@ -27,7 +26,7 @@ class FrontLineStanceTask(TheaterCommanderTask, ABC):
@staticmethod
def management_allowed(state: TheaterState) -> bool:
return (
not state.context.coalition.player.is_blue
not state.context.coalition.player
or state.context.settings.automate_front_line_stance
)

View File

@@ -45,7 +45,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
def preconditions_met(self, state: TheaterState) -> bool:
if (
state.context.coalition.player.is_blue
state.context.coalition.player
and state.context.settings.auto_ato_behavior is AutoAtoBehavior.Disabled
):
return False
@@ -102,7 +102,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
return 1
def fulfill_mission(self, state: TheaterState) -> bool:
color = "blue" if state.context.coalition.player.is_blue else "red"
color = "blue" if state.context.coalition.player else "red"
self.propose_flights()
fulfiller = PackageFulfiller(
state.context.coalition,
@@ -214,7 +214,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
settings = state.context.coalition.game.settings
margin = 100 - (
settings.ownfor_autoplanner_aggressiveness
if state.context.coalition.player.is_blue
if state.context.coalition.player
else settings.opfor_autoplanner_aggressiveness
)
threat_range = iads_threat.max_threat_range() * (margin / 100)

View File

@@ -12,7 +12,7 @@ from game.theater import MissionTarget
class PlanAewc(PackagePlanningTask[MissionTarget]):
def preconditions_met(self, state: TheaterState) -> bool:
if (
state.context.coalition.player.is_blue
state.context.coalition.player
and not state.context.settings.auto_ato_behavior_awacs
):
return False

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, Player
from game.theater import FrontLine
@dataclass
@@ -19,8 +19,9 @@ 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.
player = state.context.coalition.player.opponent
enemy_cp = self.target.control_point_friendly_to(player)
enemy_cp = self.target.control_point_friendly_to(
player=not state.context.coalition.player
)
if enemy_cp.deployable_front_line_units == 0 and state.context.turn > 0:
return False
return super().preconditions_met(state)

View File

@@ -14,7 +14,7 @@ MARGIN = 4 # assume 4 aircraft can land without refueling
class PlanRecovery(PackagePlanningTask[ControlPoint]):
def preconditions_met(self, state: TheaterState) -> bool:
if (
state.context.coalition.player.is_blue
state.context.coalition.player
and not state.context.settings.auto_ato_behavior_tankers
):
return False

View File

@@ -12,7 +12,7 @@ from game.theater import MissionTarget
class PlanRefueling(PackagePlanningTask[MissionTarget]):
def preconditions_met(self, state: TheaterState) -> bool:
if (
state.context.coalition.player.is_blue
state.context.coalition.player
and not state.context.settings.auto_ato_behavior_tankers
):
return False

View File

@@ -56,7 +56,7 @@ https://en.wikipedia.org/wiki/Hierarchical_task_network
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
from game.ato.starttype import StartType
from game.commander.tasks.compound.nextaction import PlanNextAction
@@ -67,11 +67,10 @@ 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: Player) -> None:
def __init__(self, game: Game, player: bool) -> None:
super().__init__(
PlanNextAction(
aircraft_cold_start=game.settings.default_start_type is StartType.COLD
@@ -80,18 +79,6 @@ class TheaterCommander(Planner[TheaterState, TheaterCommanderTask]):
self.game = game
self.player = player
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
def plan_missions(self, now: datetime, tracer: MultiEventTracer) -> None:
state = TheaterState.from_game(self.game, self.player, now, tracer)
while True:

View File

@@ -20,7 +20,6 @@ from game.theater import (
ControlPoint,
FrontLine,
MissionTarget,
Player,
)
from game.theater.theatergroundobject import (
BuildingGroundObject,
@@ -153,12 +152,11 @@ class TheaterState(WorldState["TheaterState"]):
@classmethod
def from_game(
cls, game: Game, player: Player, now: datetime, tracer: MultiEventTracer
cls, game: Game, player: bool, now: datetime, tracer: MultiEventTracer
) -> TheaterState:
coalition = game.coalition_for(player)
finder = ObjectiveFinder(game, player)
ordered_capturable_points = finder.prioritized_points()
air_assault_capturable_points = finder.air_assault_targets()
context = PersistentContext(
game.db,
@@ -178,7 +176,7 @@ class TheaterState(WorldState["TheaterState"]):
battle_postitions: Dict[ControlPoint, BattlePositions] = {
cp: BattlePositions.for_control_point(cp)
for cp in air_assault_capturable_points
for cp in ordered_capturable_points
}
vulnerable_control_points = [
@@ -215,8 +213,8 @@ class TheaterState(WorldState["TheaterState"]):
)
),
strike_targets=list(finder.strike_targets()),
enemy_barcaps=list(game.theater.control_points_for(player.opponent)),
threat_zones=game.threat_zone_for(player.opponent),
enemy_barcaps=list(game.theater.control_points_for(not player)),
threat_zones=game.threat_zone_for(not player),
vulnerable_control_points=vulnerable_control_points,
control_point_priority_queue=ordered_capturable_points,
priority_cp=(

View File

@@ -1,65 +0,0 @@
from __future__ import annotations
from typing import Iterable, List, TYPE_CHECKING
from functools import cached_property
from dcs.mapping import Polygon
from dcs.triggers import TriggerZone, TriggerZoneCircular, TriggerZoneQuadPoint
from game.theater.theatergroundobject import NAME_BY_CATEGORY
if TYPE_CHECKING:
from dcs.mapping import Point
class ControlPointInfluenceRadiusError(RuntimeError):
"""Error for when there are insufficient conditions to create a ControlPointInfluenceRadius."""
pass
class ControlPointInfluenceRadius:
"""Store information about a scenery objective."""
def __init__(self, zone_def: TriggerZone, cp_name: str) -> None:
self.zone_def = zone_def
self.position = zone_def.position
self.cp_name = cp_name
@staticmethod
def from_trigger_zones(
trigger_zones: Iterable[TriggerZone],
) -> List[ControlPointInfluenceRadius]:
"""Define scenery objectives based on their encompassing blue/red circle."""
zone_definitions = []
cp_influence_zones = []
for zone in trigger_zones:
if ControlPointInfluenceRadius.is_red(zone):
zone_definitions.append(zone)
# For each objective definition.
for zone_def in zone_definitions:
if len(zone_def.properties) == 0:
raise ControlPointInfluenceRadiusError(
"Undefined ControlPointInfluenceRadius category in TriggerZone: "
+ zone_def.name
)
zone_def_cp_name = zone_def.properties[1].get("value")
cp_influence_zones.append(
ControlPointInfluenceRadius(zone_def, zone_def_cp_name)
)
return cp_influence_zones
@staticmethod
def is_red(zone: TriggerZone) -> bool:
# Red in RGB is [1 Red], [0 Green], [0 Blue]. Ignore the fourth position: Transparency.
return zone.color[1] == 1 and zone.color[2] == 0 and zone.color[3] == 0
def point_in_zone(zone: TriggerZone, pos: Point) -> bool:
if isinstance(zone, TriggerZoneCircular):
return zone.position.distance_to_point(pos) < zone.radius
elif isinstance(zone, TriggerZoneQuadPoint):
return Polygon(pos._terrain, zone.verticies).point_in_poly(pos)
raise RuntimeError(f"Invalid trigger-zone: {zone.name}")

View File

@@ -6,7 +6,6 @@ REQUIRED_BUILDINGS = [
"ammo",
"factory",
"fob",
"invisiblefob",
"oil",
]

View File

@@ -60,7 +60,6 @@ class GroupTask(Enum):
FARP = ("Farp", GroupRole.BUILDING)
FOB = ("FOB", GroupRole.BUILDING)
FUEL = ("Fuel", GroupRole.BUILDING)
INVISIBLE_FOB = ("InvisibleFOB", GroupRole.BUILDING)
OFFSHORE_STRIKE_TARGET = ("OffShoreStrikeTarget", GroupRole.BUILDING)
OIL = ("Oil", GroupRole.BUILDING)

View File

@@ -123,7 +123,6 @@ class WeaponType(Enum):
TGP = "TGP"
DECOY = "DECOY"
JAMMER = "JAMMER"
OFFENSIVE_JAMMER = "OFFENSIVE_JAMMER"
UNKNOWN = "unknown"

View File

@@ -41,9 +41,6 @@ from game.radio.channels import (
WarthogChannelNamer,
PhantomChannelNamer,
KiowaChannelNamer,
ARC5RadioChannelAllocator,
ARC5ChannelNamer,
FulcrumChannelNamer,
)
from game.utils import (
Distance,
@@ -97,7 +94,6 @@ class RadioConfig:
return None
allocator_type: Type[RadioChannelAllocator] = {
"SCR-522": SCR522RadioChannelAllocator,
"ARC-5": ARC5RadioChannelAllocator,
"common": CommonRadioChannelAllocator,
"farmer": FarmerRadioChannelAllocator,
"noop": NoOpChannelAllocator,
@@ -109,7 +105,6 @@ class RadioConfig:
def make_namer(cls, config: dict[str, Any]) -> Type[ChannelNamer]:
return {
"SCR-522": SCR522ChannelNamer,
"ARC-5": ARC5ChannelNamer,
"default": ChannelNamer,
"huey": HueyChannelNamer,
"mirage": MirageChannelNamer,
@@ -123,7 +118,6 @@ class RadioConfig:
"a10c-ii": WarthogChannelNamer,
"phantom": PhantomChannelNamer,
"kiowa": KiowaChannelNamer,
"fulcrum": FulcrumChannelNamer,
}[config.get("namer", "default")]
@@ -235,14 +229,6 @@ class AircraftType(UnitType[Type[FlyingType]]):
# when no TGP is mounted on any station.
has_built_in_target_pod: bool
# indicates if the aircraft has a built-in jammer allowing Defensive EWJamming to be used
# without the need for a jamming pod
has_built_in_ecm: bool
# Indicates if the aircraft has a built in jammer allowing Offensive EWJamming to be used
# without the need for a jamming pod
has_built_in_jamming: bool
task_priorities: dict[FlightType, int]
laser_code_configs: list[LaserCodeConfig]
@@ -605,8 +591,6 @@ class AircraftType(UnitType[Type[FlyingType]]):
can_carry_crates=data.get("can_carry_crates", aircraft.helicopter),
task_priorities=task_priorities,
has_built_in_target_pod=data.get("has_built_in_target_pod", False),
has_built_in_ecm=data.get("has_built_in_ecm", False),
has_built_in_jamming=data.get("has_built_in_jamming", False),
laser_code_configs=[
LaserCodeConfig.from_yaml(d) for d in data.get("laser_codes", [])
],

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, Player
from game.theater import Airfield, ControlPoint
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: Player) -> Dict[AircraftType, int]:
def by_type(self, player: bool) -> Dict[AircraftType, int]:
losses_by_type: Dict[AircraftType, int] = defaultdict(int)
losses = self.player if player.is_blue else self.enemy
losses = self.player if player 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: Player
captured_by_player: bool
@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.is_blue:
if air_loss.flight.squadron.player:
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: Player) -> dict[GroundUnitType, int]:
def front_line_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player.is_blue:
if player:
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: Player) -> dict[GroundUnitType, int]:
def convoy_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player.is_blue:
if player:
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: Player) -> dict[GroundUnitType, int]:
def cargo_ship_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player.is_blue:
if player:
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: Player) -> dict[GroundUnitType, int]:
def airlift_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player.is_blue:
if player:
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: Player) -> Dict[str, int]:
def ground_object_losses_by_type(self, player: bool) -> Dict[str, int]:
losses_by_type: Dict[str, int] = defaultdict(int)
if player.is_blue:
if player:
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: Player) -> Dict[str, int]:
def scenery_losses_by_type(self, player: bool) -> Dict[str, int]:
losses_by_type: Dict[str, int] = defaultdict(int)
if player.is_blue:
if player:
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.is_blue:
if aircraft.flight.departure.captured:
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.is_blue:
if front_line_unit.origin.captured:
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.is_blue:
if convoy_unit.convoy.player_owned:
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.is_blue:
if cargo_ship.player_owned:
losses.player_cargo_ships.append(cargo_ship)
else:
losses.enemy_cargo_ships.append(cargo_ship)
@@ -314,9 +314,7 @@ 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=Player.BLUE
):
if ground_object.theater_unit.ground_object.is_friendly(to_player=True):
losses.player_ground_objects.append(ground_object)
else:
losses.enemy_ground_objects.append(ground_object)
@@ -325,9 +323,7 @@ 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=Player.BLUE
):
if scenery_object.ground_unit.ground_object.is_friendly(to_player=True):
losses.player_scenery.append(scenery_object)
else:
losses.enemy_scenery.append(scenery_object)
@@ -335,9 +331,9 @@ class Debriefing:
airfield = self.unit_map.airfield(unit_name)
if airfield is not None:
if airfield.captured.is_blue:
if airfield.captured:
losses.player_airfields.append(airfield)
elif airfield.captured.is_red:
else:
losses.enemy_airfields.append(airfield)
continue
@@ -353,7 +349,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.is_blue:
if airlift_unit.transfer.player:
losses.player_airlifts.append(airlift_unit)
else:
losses.enemy_airlifts.append(airlift_unit)
@@ -381,10 +377,8 @@ class Debriefing:
# Captured base is not a part of the campaign. This happens when neutral
# bases are near the conflict. Nothing to do.
continue
if int(new_owner_id_str) == blue_coalition_id:
captured_by_player = Player.BLUE
else:
captured_by_player = Player.RED
captured_by_player = int(new_owner_id_str) == blue_coalition_id
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

@@ -865,19 +865,6 @@ class Faction:
# MiG-31BM
if not mod_settings.mig31bm_foxhound:
self.remove_aircraft("MiG-31BM")
# Mlitary Aircraft Mod 1.7.1
if not mod_settings.mam:
self.remove_aircraft("A400M_Atlas")
self.remove_aircraft("B2_Spirit")
self.remove_aircraft("C2A_Greyhound")
self.remove_aircraft("C5_Galaxy")
self.remove_aircraft("KC_10_Extender")
self.remove_aircraft("KC_10_Extender_D")
self.remove_aircraft("P3C_Orion")
self.remove_aircraft("V22_Osprey")
# Tornado f.3 ADV (1.0)
if not mod_settings.tornado_adv:
self.remove_aircraft("Tornado_ADV")
def remove_aircraft(self, name: str) -> None:
for aircraft_set in [self.aircraft, self.awacs, self.tankers]:

View File

@@ -4,10 +4,9 @@ import itertools
import logging
import math
from collections.abc import Iterator
from copy import deepcopy
from datetime import date, datetime, time, timedelta
from enum import Enum
from typing import Any, List, TYPE_CHECKING, Union, cast
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
from uuid import UUID
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
@@ -33,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, Player
from .theater import ConflictTheater
from .theater.bullseye import Bullseye
from .theater.theatergroundobject import (
EwrGroundObject,
@@ -139,11 +138,8 @@ 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=Player.BLUE)
self.red = Coalition(self, enemy_faction, enemy_budget, player=Player.RED)
neutral_faction = deepcopy(player_faction)
neutral_faction.country = self.neutral_country
self.neutral = Coalition(self, neutral_faction, 0, player=Player.NEUTRAL)
self.blue = Coalition(self, player_faction, player_budget, player=True)
self.red = Coalition(self, enemy_faction, enemy_budget, player=False)
self.blue.set_opponent(self.red)
self.red.set_opponent(self.blue)
@@ -182,10 +178,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: Player) -> AirTaskingOrder:
def ato_for(self, player: bool) -> AirTaskingOrder:
return self.coalition_for(player).ato
def transit_network_for(self, player: Player) -> TransitNetwork:
def transit_network_for(self, player: bool) -> TransitNetwork:
return self.coalition_for(player).transit_network
def generate_conditions(self, forced_time: time | None = None) -> Conditions:
@@ -213,35 +209,32 @@ class Game:
else:
enemy_faction.country = country_with_name("Russia")
def faction_for(self, player: Player) -> Faction:
def faction_for(self, player: bool) -> Faction:
return self.coalition_for(player).faction
def faker_for(self, player: Player) -> Faker:
def faker_for(self, player: bool) -> Faker:
return self.coalition_for(player).faker
def air_wing_for(self, player: Player) -> AirWing:
def air_wing_for(self, player: bool) -> AirWing:
return self.coalition_for(player).air_wing
@property
def neutral_country(self) -> Country:
def neutral_country(self) -> Type[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()
elif Switzerland() not in countries_in_use:
return Switzerland()
if UnitedNationsPeacekeepers not in countries_in_use:
return UnitedNationsPeacekeepers
elif Switzerland.name not in countries_in_use:
return Switzerland
else:
return USAFAggressors()
return USAFAggressors
def coalition_for(self, player: Player) -> Coalition:
if player.is_neutral:
return self.neutral
elif player.is_blue:
def coalition_for(self, player: bool) -> Coalition:
if player:
return self.blue
else:
return self.red
return self.red
def adjust_budget(self, amount: float, player: Player) -> None:
def adjust_budget(self, amount: float, player: bool) -> None:
self.coalition_for(player).adjust_budget(amount)
def on_load(self, game_still_initializing: bool = False) -> None:
@@ -496,7 +489,7 @@ class Game:
self.current_group_id += 1
return self.current_group_id
def compute_transit_network_for(self, player: Player) -> TransitNetwork:
def compute_transit_network_for(self, player: bool) -> TransitNetwork:
return TransitNetworkBuilder(self.theater, player).build()
def compute_threat_zones(self, events: GameUpdateEvents) -> None:
@@ -505,10 +498,10 @@ class Game:
self.blue.compute_nav_meshes(events)
self.red.compute_nav_meshes(events)
def threat_zone_for(self, player: Player) -> ThreatZones:
def threat_zone_for(self, player: bool) -> ThreatZones:
return self.coalition_for(player).threat_zone
def navmesh_for(self, player: Player) -> NavMesh:
def navmesh_for(self, player: bool) -> 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.is_blue else "Enemy"
allegiance = "Ally" if self.destination.captured else "Enemy"
d: dict[GroundUnitType, int]
if self.destination != ground_unit_source:
source = ground_unit_source

View File

@@ -4,7 +4,6 @@ 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
@@ -23,8 +22,8 @@ class BuildingIncome:
class Income:
def __init__(self, game: Game, player: Player) -> None:
if player.is_blue:
def __init__(self, game: Game, player: bool) -> None:
if player:
self.multiplier = game.settings.player_income_multiplier
else:
self.multiplier = game.settings.enemy_income_multiplier

View File

@@ -288,8 +288,6 @@ class BuildingLayout(TgoLayout):
@property
def category(self) -> str:
for task in self.tasks:
if task is GroupTask.INVISIBLE_FOB:
return "fob"
if task not in [GroupTask.STRIKE_TARGET, GroupTask.OFFSHORE_STRIKE_TARGET]:
return task.description.lower()
raise RuntimeError(f"Building Template {self.name} has no building category")

View File

@@ -192,8 +192,7 @@ class AircraftBehavior:
@staticmethod
def configure_eplrs(group: FlyingGroup[Any], flight: Flight) -> None:
eplrs_enabled = flight.coalition.game.settings.eplrs_enabled
if eplrs_enabled and flight.unit_type.eplrs_capable:
if flight.unit_type.eplrs_capable:
group.points[0].tasks.append(EPLRS(group.id))
def configure_cap(self, group: FlyingGroup[Any], flight: Flight) -> None:
@@ -307,7 +306,7 @@ class AircraftBehavior:
)
def configure_oca_strike(self, group: FlyingGroup[Any], flight: Flight) -> None:
self.configure_task(flight, group, CAS, [AFAC, SEAD, AntishipStrike])
self.configure_task(flight, group, CAS, [AFAC, SEAD])
self.configure_behavior(
flight,
group,

View File

@@ -13,7 +13,6 @@ from dcs.datalinks.datalink import DataLinkType
from dcs.datalinks.datalinkbase import DataLinkSettingsWithFlightLead
from dcs.datalinks.link16 import Link16Network, ViperLink16NetworkMemberLink
from dcs.mission import Mission
from dcs.task import RunScript
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from dcs.unit import Skill
@@ -104,23 +103,6 @@ class AircraftGenerator:
for parking_slot in cp.parking_slots:
parking_slot.unit_id = None
def _prioritized_packages(self, ato: AirTaskingOrder) -> List[Package]:
"""Returns the packages in the order they should be generated."""
return sorted(
ato.packages,
key=lambda p: (
(
1
if any(
f.flight_type in [FlightType.AEWC, FlightType.REFUELING]
for f in p.flights
)
else 0
),
p.time_over_target,
),
)
def generate_flights(
self,
country: Country,
@@ -141,7 +123,7 @@ class AircraftGenerator:
self._reserve_frequencies_and_tacan(ato)
self.mission_data.packages.clear()
for package in reversed(self._prioritized_packages(ato)):
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
logging.info(f"Generating package for target: {package.target.name}")
if not package.flights:
continue
@@ -220,7 +202,7 @@ class AircraftGenerator:
):
continue
if control_point.captured.is_blue:
if control_point.captured:
country = player_country
else:
country = enemy_country
@@ -237,12 +219,12 @@ class AircraftGenerator:
squadron.location, Fob
)
if (
squadron.coalition.player.is_blue
squadron.coalition.player
and self.game.settings.perf_disable_untasked_blufor_aircraft
):
return
elif (
squadron.coalition.player.is_red
not squadron.coalition.player
and self.game.settings.perf_disable_untasked_opfor_aircraft
):
return
@@ -274,7 +256,7 @@ class AircraftGenerator:
).create_idle_aircraft()
if group:
if (
squadron.coalition.player.is_red
not squadron.coalition.player
and squadron.aircraft.flyable
and (
self.game.settings.enable_squadron_pilot_limits
@@ -359,51 +341,16 @@ class AircraftGenerator:
def _track_ewrj_flight(self, flight: Flight, group: FlyingGroup[Any]) -> None:
if not self.ewrj_package_dict.get(id(flight.package)):
self.ewrj_package_dict[id(flight.package)] = []
added = False
if (
not flight.flight_type.is_air_to_air
and any(
[
wpt
for wpt in group.points
if wpt.name in ["JOIN", "SPLIT", "RACETRACK START", "RACETRACK END"]
and any(
[
t
for t in wpt.tasks
if isinstance(t, RunScript)
and (
"Djamming" in t.params["action"]["params"]["command"]
or "EWjamm" in t.params["action"]["params"]["command"]
)
]
)
]
)
flight.package.primary_flight
and flight is flight.package.primary_flight
or flight.client_count
and (
not self.need_ecm
or flight.any_member_has_weapon_of_type(WeaponType.JAMMER)
or flight.any_member_has_weapon_of_type(WeaponType.OFFENSIVE_JAMMER)
or flight.squadron.aircraft.has_built_in_ecm
or flight.squadron.aircraft.has_built_in_jamming
)
):
self.ewrj_package_dict[id(flight.package)].append(group)
added = True
if (
added
or not flight.flight_type.is_air_to_air
and self.ewrj_package_dict[id(flight.package)]
):
for f in flight.package.flights:
if f is flight or f.group_id == 0 or f.flight_type.is_air_to_air:
continue
g = self.mission.find_group_by_id(f.group_id)
if (
isinstance(g, FlyingGroup)
and g not in self.ewrj_package_dict[id(flight.package)]
):
self.ewrj_package_dict[id(flight.package)].append(g)
def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None:
for package in ato.packages:

View File

@@ -14,7 +14,6 @@ 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)
@@ -43,7 +42,7 @@ class FlightData:
size: int
#: True if this flight belongs to the player's coalition.
friendly: Player
friendly: bool
#: Number of seconds after mission start the flight is set to depart.
departure_delay: timedelta

View File

@@ -189,17 +189,8 @@ class FlightGroupConfigurator:
return
# Check if ecm_required option is enabled
jammer_required = settings.plugin_option("ewrj.ecm_required")
offensive_jammer = member.loadout.has_weapon_of_type(
WeaponType.OFFENSIVE_JAMMER
)
offensive_inbuilt = self.flight.squadron.aircraft.has_built_in_jamming
has_jammer = (
member.loadout.has_weapon_of_type(WeaponType.JAMMER) or offensive_jammer
)
built_in_jammer = (
self.flight.squadron.aircraft.has_built_in_ecm or offensive_inbuilt
)
if jammer_required and not (has_jammer or built_in_jammer):
has_jammer = member.loadout.has_weapon_of_type(WeaponType.JAMMER)
if jammer_required and not has_jammer:
return
# Create the original ewrj_menu_trigger for player flight members
ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}")
@@ -208,20 +199,6 @@ class FlightGroupConfigurator:
self.group.points[0].tasks[0] = OptReactOnThreat(
OptReactOnThreat.Values.PassiveDefense
)
# Create LUA Flags for Offensive Jamming in EW Script for Player Flights
if not (offensive_jammer or offensive_inbuilt):
return
ewrj_offensive_trigger = TriggerStart(
comment=f"Offensive Jammer Flag {unit.name}"
)
ewrj_offensive_trigger.add_action(
DoScript(
String(
f'trigger.action.setUserFlag("offensive_jamming_{unit.name}", 1)'
)
)
)
self.mission.triggerrules.triggers.append(ewrj_offensive_trigger)
def setup_radios(self) -> RadioFrequency:
freq = self.flight.frequency
@@ -299,7 +276,7 @@ class FlightGroupConfigurator:
unit.set_player()
def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill:
if self.flight.squadron.player.is_blue:
if self.flight.squadron.player:
base_skill = Skill(self.game.settings.player_skill)
else:
base_skill = Skill(self.game.settings.enemy_skill)

View File

@@ -9,10 +9,13 @@ from dcs.task import (
OptFormation,
Targets,
SetUnlimitedFuelCommand,
RunScript,
OptReactOnThreat,
)
from game.ato import FlightType
from game.data.doctrine import Doctrine
from game.data.weapons import WeaponType
from game.theater import NavalControlPoint
from game.utils import nautical_miles, feet
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -60,14 +63,8 @@ class JoinPointBuilder(PydcsWaypointBuilder):
FlightType.SEAD_SWEEP,
FlightType.SEAD,
FlightType.SEAD_ESCORT,
FlightType.DEAD,
]:
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
self.offensive_jamming(waypoint, "start")
self.defensive_jamming(waypoint, "start")
self.start_defensive_jamming(waypoint)
if self.flight.flight_type == FlightType.SEAD_ESCORT:
self.handle_sead_escort(doctrine, waypoint)
# Let the AI use ECM to preemptively defend themselves.
@@ -107,6 +104,28 @@ class JoinPointBuilder(PydcsWaypointBuilder):
vertical_spacing=doctrine.sead_escort_spacing.feet,
)
def start_defensive_jamming(self, waypoint: MovingPoint) -> None:
# Start Defensive Jamming
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
ecm_required = settings.plugin_option("ewrj.ecm_required")
has_jammer = False
for unit, member in zip(self.group.units, self.flight.iter_members()):
has_jammer = member.loadout.has_weapon_of_type(WeaponType.JAMMER)
if ecm_required and not has_jammer:
continue
if not member.is_player:
script_content = f'startDjamming("{unit.name}")'
start_jamming_script = RunScript(script_content)
waypoint.tasks.append(start_jamming_script)
has_jammer = True
if has_jammer:
passive_defense = OptReactOnThreat(
OptReactOnThreat.Values.PassiveDefense
)
waypoint.tasks.append(passive_defense)
def configure_escort_tasks(
self,
waypoint: MovingPoint,

View File

@@ -13,7 +13,6 @@ from game.ato import Flight, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.starttype import StartType
from game.ato.traveltime import GroundSpeed
from game.data.weapons import WeaponType
from game.missiongenerator.missiondata import MissionData
from game.theater import MissionTarget, TheaterUnit, OffMapSpawn
@@ -158,36 +157,3 @@ class PydcsWaypointBuilder:
self.group.units[0].unit_type in (F_14A_135_GR, F_14B)
):
self.group.add_nav_target_point(self.waypoint.position, "IP")
def defensive_jamming(self, waypoint: MovingPoint, action: str) -> None:
# Explodes incoming missiles within the jamming bubble through the EW-Jamming script
settings = self.flight.coalition.game.settings
ecm_required = settings.plugin_option("ewrj.ecm_required")
for unit, member in zip(self.group.units, self.flight.iter_members()):
has_jammer = member.loadout.has_weapon_of_type(
WeaponType.JAMMER
) or member.loadout.has_weapon_of_type(WeaponType.OFFENSIVE_JAMMER)
built_in_jammer = (
self.flight.squadron.aircraft.has_built_in_ecm
or self.flight.squadron.aircraft.has_built_in_jamming
)
if ecm_required and not (has_jammer or built_in_jammer):
continue
if not member.is_player:
script_content = f'{action}IAdefjamming("{unit.name}")'
jamming_script = RunScript(script_content)
waypoint.tasks.append(jamming_script)
def offensive_jamming(self, waypoint: MovingPoint, action: str) -> None:
# Silences enemy radars through the EW-Jamming script
settings = self.flight.coalition.game.settings
ecm_required = settings.plugin_option("ewrj.ecm_required")
for unit, member in zip(self.group.units, self.flight.iter_members()):
has_jammer = member.loadout.has_weapon_of_type(WeaponType.OFFENSIVE_JAMMER)
built_in_jammer = self.flight.squadron.aircraft.has_built_in_jamming
if ecm_required and not (has_jammer or built_in_jammer):
continue
if not member.is_player:
script_content = f'{action}EWjamm("{unit.name}")'
stop_jamming_script = RunScript(script_content)
waypoint.tasks.append(stop_jamming_script)

View File

@@ -9,6 +9,8 @@ from dcs.task import (
Tanker,
Targets,
SetUnlimitedFuelCommand,
RunScript,
OptReactOnThreat,
)
from game.ato import FlightType
@@ -38,12 +40,21 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
)
return
# Start Defensive Jamming for all flights
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
self.defensive_jamming(waypoint, "start")
self.offensive_jamming(waypoint, "start")
if self.flight.flight_type == FlightType.AEWC:
# Start Offensive Jamming for all AWACS flights
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
# all units in group are AWACS, no specific checks needed
for unit, member in zip(self.group.units, self.flight.iter_members()):
script_content = f'startEWjamm("{unit.name}")'
start_jamming_script = RunScript(script_content)
waypoint.tasks.append(start_jamming_script)
passive_defense = OptReactOnThreat(
OptReactOnThreat.Values.PassiveDefense
)
waypoint.tasks.append(passive_defense)
# NB: It's important that the engage task comes before the orbit task.
# Though they're on the same waypoint, if the orbit task comes first it

View File

@@ -3,6 +3,8 @@ import logging
from dcs.point import MovingPoint
from dcs.task import (
SetUnlimitedFuelCommand,
RunScript,
OptReactOnThreat,
)
from game.ato import FlightType
@@ -18,12 +20,18 @@ class RaceTrackEndBuilder(PydcsWaypointBuilder):
# Disable Offensive Jamming at Racetrack End
if self.flight.flight_type == FlightType.AEWC:
# Stop Defensive Jamming for all AWACS flights
# Stop Offensive Jamming for all AWACS flights
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
self.defensive_jamming(waypoint, "stop")
self.offensive_jamming(waypoint, "stop")
# all units in group are AWACS, no specific checks needed
for unit, member in zip(self.group.units, self.flight.iter_members()):
script_content = f'stopEWjamming("{unit.name}")'
stop_jamming_script = RunScript(script_content)
waypoint.tasks.append(stop_jamming_script)
evade_fire = OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
waypoint.tasks.append(evade_fire)
def build(self) -> MovingPoint:
waypoint = super().build()

View File

@@ -62,7 +62,6 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
# into the SAM instead of waiting for it to come alive
engage_task = EngageGroup(miz_group.id)
engage_task.params["weaponType"] = DcsWeaponType.ARM.value
engage_task.params["expend"] = Expend.All.value
waypoint.tasks.append(engage_task)
# Use other Air-to-Surface Missiles at last

View File

@@ -5,9 +5,11 @@ from dcs.task import (
SetUnlimitedFuelCommand,
SwitchWaypoint,
RunScript,
OptReactOnThreat,
)
from game.ato import FlightType
from game.data.weapons import WeaponType
from game.utils import knots
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -47,15 +49,28 @@ class SplitPointBuilder(PydcsWaypointBuilder):
FlightType.SEAD_SWEEP,
FlightType.SEAD,
FlightType.SEAD_ESCORT,
FlightType.DEAD,
]:
if self.flight.flight_type == FlightType.SEAD_ESCORT:
# Moved previous escort split tasks
if self.flight.flight_type.is_escort_type:
index = len(self.group.points)
self.group.add_trigger_action(SwitchWaypoint(None, index))
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
self.offensive_jamming(waypoint, "stop")
self.defensive_jamming(waypoint, "stop")
self.stop_defensive_jamming(waypoint)
def stop_defensive_jamming(self, waypoint: MovingPoint) -> None:
# Stop Defensive Jamming
settings = self.flight.coalition.game.settings
ai_jammer = settings.plugin_option("ewrj.ai_jammer_enabled")
if settings.plugins.get("ewrj") and ai_jammer:
for unit, member in zip(self.group.units, self.flight.iter_members()):
if settings.plugin_option("ewrj.ecm_required"):
ecm = WeaponType.JAMMER
if not member.loadout.has_weapon_of_type(ecm):
continue
if not member.is_player:
script_content = f'stopDjamming("{unit.name}")'
stop_jamming_script = RunScript(script_content)
waypoint.tasks.append(stop_jamming_script)
evaide_fire = OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)
waypoint.tasks.append(evaide_fire)

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 .flotgenerator import JtacInfo
from .missiondata import AwacsInfo, TankerInfo
from .flotgenerator import JtacInfo
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.is_blue:
if not flight.client_units and flight.friendly:
name = flight.departure.airfield_name
if (
name in self.allied_flights_by_departure

View File

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

View File

@@ -15,7 +15,6 @@ 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)
@@ -36,12 +35,10 @@ class DrawingsGenerator:
Generate cps as circles
"""
for cp in self.game.theater.controlpoints:
if cp.captured.is_blue:
if cp.captured:
color = CP_BLUE
elif cp.captured.is_red:
color = CP_RED
else:
color = CP_NEUTRAL
color = CP_RED
shape = self.player_layer.add_circle(
cp.position,
TRIGGER_RADIUS_CAPTURE,
@@ -64,9 +61,9 @@ class DrawingsGenerator:
continue
else:
# Determine path color
if cp.captured.is_blue and destination.captured.is_blue:
if cp.captured and destination.captured:
color = BLUE_PATH_COLOR
elif cp.captured.is_red and destination.captured.is_red:
elif not cp.captured and not destination.captured:
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, Player
from game.theater.controlpoint import ControlPoint
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=Player.BLUE
self.player_planned_combat_groups, is_player=True
)
# Create enemy groups at random position
enemy_groups = self._generate_groups(
self.enemy_planned_combat_groups, is_player=Player.RED
self.enemy_planned_combat_groups, is_player=False
)
# TODO: Differentiate AirConflict and GroundConflict classes.
@@ -193,7 +193,7 @@ class FlotGenerator:
callsign=callsign,
region=frontline,
code=str(code),
blue=Player.BLUE,
blue=True,
freq=freq,
)
)
@@ -215,7 +215,7 @@ class FlotGenerator:
def gen_infantry_group_for_group(
self,
group: VehicleGroup,
is_player: Player,
is_player: bool,
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: Player) -> timedelta:
def _earliest_tot_on_flot(self, player: bool) -> 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(to_cp.coalition.player.opponent)
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
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(to_cp.coalition.player.opponent)
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
self._set_reform_waypoint(dcs_group, forward_heading, duration)
if stance in [
CombatStance.AGGRESSIVE,
@@ -762,15 +762,13 @@ class FlotGenerator:
)
def _generate_groups(
self, groups: list[CombatGroup], is_player: Player
self, groups: list[CombatGroup], is_player: bool
) -> List[Tuple[VehicleGroup, CombatGroup]]:
"""Finds valid positions for planned groups and generates a pydcs group for them"""
positioned_groups = []
assert self.conflict.heading is not None
spawn_heading = (
self.conflict.heading.left
if is_player.is_blue
else self.conflict.heading.right
self.conflict.heading.left if is_player else self.conflict.heading.right
)
country = self.game.coalition_for(is_player).faction.country
country = self.mission.country(country.name)
@@ -797,7 +795,7 @@ class FlotGenerator:
final_position,
heading=spawn_heading.opposite,
)
if is_player == Player.BLUE:
if is_player:
g.set_skill(Skill(self.game.settings.player_skill))
else:
g.set_skill(Skill(self.game.settings.enemy_vehicle_skill))
@@ -815,7 +813,7 @@ class FlotGenerator:
def _generate_group(
self,
player: Player,
player: bool,
side: Country,
unit_type: GroundUnitType,
count: int,

View File

@@ -10,14 +10,15 @@ 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:
@@ -101,15 +102,13 @@ class LuaGenerator:
for logistic_info in self.mission_data.logistics:
if logistic_info.transport not in transports:
transports.append(logistic_info.transport)
coalition_color = "blue" if logistic_info.blue.is_blue else "red"
coalition_color = "blue" if logistic_info.blue else "red"
logistics_item = logistics_flights.add_item()
logistics_item.add_data_array("pilot_names", logistic_info.pilot_names)
logistics_item.add_key_value("pickup_zone", logistic_info.pickup_zone)
logistics_item.add_key_value("drop_off_zone", logistic_info.drop_off_zone)
logistics_item.add_key_value("target_zone", logistic_info.target_zone)
logistics_item.add_key_value(
"side", str(2 if logistic_info.blue.is_blue else 1)
)
logistics_item.add_key_value("side", str(2 if logistic_info.blue else 1))
logistics_item.add_key_value("logistic_unit", logistic_info.logistic_unit)
logistics_item.add_key_value(
"aircraft_type", logistic_info.transport.dcs_id
@@ -145,7 +144,7 @@ class LuaGenerator:
target_points = lua_data.add_item("TargetPoints")
for flight in self.mission_data.flights:
if flight.friendly.is_blue and flight.flight_type in [
if flight.friendly and flight.flight_type in [
FlightType.ANTISHIP,
FlightType.DEAD,
FlightType.SEAD,
@@ -179,7 +178,7 @@ class LuaGenerator:
for cp in self.game.theater.controlpoints:
coalition_object = (
lua_data.get_or_create_item("BlueAA")
if cp.captured.is_blue
if cp.captured
else lua_data.get_or_create_item("RedAA")
)
for ground_object in cp.ground_objects:
@@ -203,9 +202,7 @@ class LuaGenerator:
# Should probably do the same with all the roles... but the script is already
# tolerant of those being empty.
for node in self.game.theater.iads_network.skynet_nodes(self.game):
coalition = iads_object.get_or_create_item(
"BLUE" if node.player.is_blue else "RED"
)
coalition = iads_object.get_or_create_item("BLUE" if node.player else "RED")
iads_type = coalition.get_or_create_item(node.iads_role.value)
iads_element = iads_type.add_item()
iads_element.add_key_value("dcsGroupName", node.dcs_name)

View File

@@ -15,7 +15,6 @@ 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
@@ -25,7 +24,7 @@ class GroupInfo:
group_name: str
callsign: str
freq: RadioFrequency
blue: Player
blue: bool
@dataclass
@@ -86,7 +85,7 @@ class LogisticsInfo:
pilot_names: list[str]
transport: AircraftType
blue: Player
blue: bool
logistic_unit: str = field(default_factory=str)
pickup_zone: str = field(default_factory=str)

View File

@@ -9,7 +9,6 @@ import dcs.lua
from dcs import Mission, Point
from dcs.coalition import Coalition
from dcs.countries import country_dict
from dcs.point import MovingPoint
from dcs.task import OptReactOnThreat
from dcs.terrain import Airport
from dcs.unit import Static
@@ -132,42 +131,28 @@ class MissionGenerator:
return self.unit_map
@staticmethod
def _configure_react_to_threat_for_ew_jamming_packages(
gen: AircraftGenerator,
) -> None:
def _configure_ewrj(gen: AircraftGenerator) -> None:
for groups in gen.ewrj_package_dict.values():
optrot = [
task
for task in groups[0].points[0].tasks
if isinstance(task, OptReactOnThreat)
][0]
assert isinstance(optrot, OptReactOnThreat)
if (
len(groups) == 1
and optrot.value != OptReactOnThreat.Values.PassiveDefense
):
# primary flight with no EWR-Jamming capability
continue
for group in groups:
start_point = [
p for p in group.points if p.name in ["JOIN", "RACETRACK START"]
]
if not start_point:
sp = group.points[0]
else:
sp = start_point[0]
MissionGenerator.set_react_to_threat(
OptReactOnThreat.Values.PassiveDefense, sp
)
stop_point = [
p for p in group.points if p.name in ["SPLIT", "RACETRACK END"]
]
if not stop_point:
sp = group.points[0]
else:
sp = stop_point[0]
MissionGenerator.set_react_to_threat(
OptReactOnThreat.Values.EvadeFire, sp
)
@staticmethod
def set_react_to_threat(
optrot: OptReactOnThreat.Values, start_point: MovingPoint
) -> None:
tasks = start_point.tasks
for i in range(len(tasks)):
if isinstance(tasks[i], OptReactOnThreat):
tasks[i] = OptReactOnThreat(optrot)
return
tasks.append(OptReactOnThreat(optrot))
tasks = group.points[0].tasks
for i in range(len(tasks)):
if isinstance(tasks[i], OptReactOnThreat):
tasks[i] = OptReactOnThreat(
OptReactOnThreat.Values.PassiveDefense
)
break
def setup_mission_coalitions(self) -> None:
self.mission.coalition["blue"] = Coalition(
@@ -306,7 +291,7 @@ class MissionGenerator:
flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data)
if self.game.settings.plugins.get("ewrj"):
self._configure_react_to_threat_for_ew_jamming_packages(aircraft_generator)
self._configure_ewrj(aircraft_generator)
def generate_destroyed_units(self) -> None:
"""Add destroyed units to the Mission"""
@@ -391,12 +376,9 @@ class MissionGenerator:
tmu.theater_unit.position,
self.mission.terrain,
).dict()
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["coalition"] = (
"blue" if tmu.theater_unit.ground_object.coalition.player else "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,7 +13,6 @@ 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
@@ -26,12 +25,12 @@ class RebellionGenerator:
def generate(self) -> None:
ownfor_country = self.mission.country(
self.game.coalition_for(player=Player.BLUE).faction.country.name
self.game.coalition_for(player=True).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=Player.RED).faction.country.name
self.game.coalition_for(player=False).faction.country.name
)
for rz in self.game.theater.opfor_rebel_zones:
self._generate_rebel_zone(opfor_country, rz)

View File

@@ -67,7 +67,6 @@ from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
from game.runways import RunwayData
from game.theater import (
ControlPoint,
Player,
TheaterGroundObject,
TheaterUnit,
NavalControlPoint,
@@ -387,9 +386,9 @@ class GroundObjectGenerator:
)
self._register_theater_unit(unit, static_group.units[0])
def enable_eplrs(self, group: VehicleGroup, unit_type: Type[VehicleType]) -> None:
eplrs_enabled = self.game.settings.eplrs_enabled
if eplrs_enabled and unit_type.eplrs:
@staticmethod
def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None:
if unit_type.eplrs:
group.points[0].tasks.append(EPLRS(group.id))
def set_alarm_state(self, group: MovingGroup[Any]) -> None:
@@ -409,7 +408,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=Player.BLUE)
if scenery.ground_object.is_friendly(to_player=True)
else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15}
)
@@ -810,12 +809,9 @@ class HelipadGenerator:
# capture triggers
pad: BaseFARP
neutral_country = self.m.country(self.game.neutral_country.name)
if self.cp.captured is Player.NEUTRAL:
country = neutral_country
else:
country = self.m.country(
self.game.coalition_for(self.cp.captured).faction.country.name
)
country = self.m.country(
self.game.coalition_for(self.cp.captured).faction.country.name
)
name = f"{self.cp.name} {helipad_type} {i}"
logging.info("Generating helipad static : " + name)
@@ -866,7 +862,7 @@ class HelipadGenerator:
if self.game.position_culled(helipad):
cull_farp_statics = True
if self.cp.coalition.player.is_blue:
if self.cp.coalition.player:
for package in self.cp.coalition.ato.packages:
for flight in package.flights:
if flight.squadron.location == self.cp:
@@ -882,12 +878,7 @@ class HelipadGenerator:
pad.position,
self.m.terrain,
).dict()
if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse
@@ -998,7 +989,7 @@ class GroundSpawnRoadbaseGenerator:
cull_farp_statics = True
elif self.game.position_culled(ground_spawn[0]):
cull_farp_statics = True
if self.cp.coalition.player.is_blue:
if self.cp.coalition.player:
for package in self.cp.coalition.ato.packages:
for flight in package.flights:
if flight.squadron.location == self.cp:
@@ -1014,12 +1005,7 @@ class GroundSpawnRoadbaseGenerator:
pad.position,
self.m.terrain,
).dict()
if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse
@@ -1145,12 +1131,7 @@ class GroundSpawnLargeGenerator:
pad.position,
self.m.terrain,
).dict()
if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse
@@ -1277,7 +1258,7 @@ class GroundSpawnGenerator:
cull_farp_statics = True
elif self.game.position_culled(vtol_pad[0]):
cull_farp_statics = True
if self.cp.coalition.player.is_blue:
if self.cp.coalition.player:
for package in self.cp.coalition.ato.packages:
for flight in package.flights:
if flight.squadron.location == self.cp:
@@ -1294,12 +1275,7 @@ class GroundSpawnGenerator:
pad.position,
self.m.terrain,
).dict()
if self.cp.coalition.player.is_neutral:
warehouse["coalition"] = "neutral"
elif self.cp.coalition.player.is_blue:
warehouse["coalition"] = "blue"
else:
warehouse["coalition"] = "red"
warehouse["coalition"] = "blue" if self.cp.coalition.player else "red"
# configure dynamic spawn + hot start of DS, plus dynamic cargo?
self.m.warehouses.warehouses[pad.id] = warehouse
@@ -1406,11 +1382,7 @@ class TgoGenerator:
def generate(self) -> None:
for cp in self.game.theater.controlpoints:
# Use neutral country for neutral control points
if cp.captured is Player.NEUTRAL:
country = self.m.country(self.game.neutral_country.name)
else:
country = self.m.country(cp.coalition.faction.country.name)
country = self.m.country(cp.coalition.faction.country.name)
# Generate helipads
helipad_gen = HelipadGenerator(

View File

@@ -12,6 +12,7 @@ from dcs.action import (
RemoveSceneObjects,
RemoveSceneObjectsMask,
SceneryDestructionZone,
Smoke,
)
from dcs.condition import (
AllOfCoalitionOutsideZone,
@@ -32,7 +33,6 @@ from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE
if TYPE_CHECKING:
from game.game import Game
from game.theater.controlpoint import ControlPoint
PUSH_TRIGGER_SIZE = 3000
PUSH_TRIGGER_ACTIVATION_AGL = 25
@@ -99,12 +99,9 @@ class TriggerGenerator:
raise RuntimeError(
f"Could not find {airfield.airport.name} in the mission"
)
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)
cp_airport.set_coalition(
airfield.captured and player_coalition or enemy_coalition
)
def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None:
"""
@@ -141,7 +138,7 @@ class TriggerGenerator:
zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK"
)
if cp.captured.is_blue:
if cp.captured:
name = ground_object.obj_name + " [ALLY]"
else:
name = ground_object.obj_name + " [ENEMY]"
@@ -180,57 +177,26 @@ class TriggerGenerator:
# clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),))
self.mission.triggerrules.triggers.append(enable_clear_trigger)
def _create_capture_trigger(
self,
trigger_zone_id: int,
flag: int,
capturing_coalition: str,
losing_coalition: str,
capturing_coalition_int: int,
cp: ControlPoint,
flag_condition_true: bool = False,
) -> TriggerCondition:
"""Helper method to create a single capture trigger with the given parameters."""
trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
trigger.add_condition(
AllOfCoalitionOutsideZone(
losing_coalition, trigger_zone_id, unit_type="GROUND"
)
)
trigger.add_condition(
PartOfCoalitionInZone(
capturing_coalition, trigger_zone_id, unit_type="GROUND"
)
)
if flag_condition_true:
trigger.add_condition(FlagIsTrue(flag=flag))
trigger.add_action(ClearFlag(flag=flag))
else:
trigger.add_condition(FlagIsFalse(flag=flag))
trigger.add_action(SetFlag(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{capturing_coalition_int}||{cp.full_name}"'
)
trigger.add_action(DoScript(script_string))
return trigger
def _generate_capture_triggers(self) -> None:
def _generate_capture_triggers(
self, player_coalition: str, enemy_coalition: str
) -> None:
"""Creates a pair of triggers for each control point of `cls.capture_zone_types`.
One for the initial capture of a control point, and one if it is recaptured.
Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua`
"""
for cp in self.game.theater.controlpoints:
attacking_coalition = str.lower(cp.captured.opponent.name)
defending_coalition = str.lower(cp.captured.opponent.opponent.name)
if attacking_coalition == "red":
attack_coalition_int = 1 # 1 is the Event int for Red
defend_coalition_int = 2 # 2 is the Event int for Blue
else:
attack_coalition_int = 2
defend_coalition_int = 1
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured:
attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition
defend_coalition_int = 2 # 2 is the Event int for Blue
else:
attacking_coalition = player_coalition
attack_coalition_int = 2
defending_coalition = enemy_coalition
defend_coalition_int = 1
trigger_zone = self.mission.triggers.add_triggerzone(
cp.position,
radius=TRIGGER_RADIUS_CAPTURE,
@@ -238,48 +204,43 @@ class TriggerGenerator:
name="CAPTURE",
)
flag = self.get_capture_zone_flag()
capture = self._create_capture_trigger(
trigger_zone.id,
flag,
attacking_coalition,
defending_coalition,
attack_coalition_int,
cp,
capture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
capture_trigger.add_condition(
AllOfCoalitionOutsideZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
recapture = self._create_capture_trigger(
trigger_zone.id,
flag,
defending_coalition,
attacking_coalition,
defend_coalition_int,
cp,
flag_condition_true=True,
capture_trigger.add_condition(
PartOfCoalitionInZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
self.mission.triggerrules.triggers.extend([capture, recapture])
capture_trigger.add_condition(FlagIsFalse(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"'
)
capture_trigger.add_action(DoScript(script_string))
capture_trigger.add_action(SetFlag(flag=flag))
self.mission.triggerrules.triggers.append(capture_trigger)
if cp.captured.is_neutral:
# In this case attacking_coalition is blue and defending_coalition is red because
# neutral control points always have blue set as the opponent
red_capture = self._create_capture_trigger(
trigger_zone.id,
flag,
defending_coalition,
attacking_coalition,
defend_coalition_int,
cp,
recapture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
recapture_trigger.add_condition(
AllOfCoalitionOutsideZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
red_recapture = self._create_capture_trigger(
trigger_zone.id,
flag,
defending_coalition,
attacking_coalition,
defend_coalition_int,
cp,
flag_condition_true=True,
)
self.mission.triggerrules.triggers.extend(
[red_capture, red_recapture]
)
recapture_trigger.add_condition(
PartOfCoalitionInZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
recapture_trigger.add_condition(FlagIsTrue(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"'
)
recapture_trigger.add_action(DoScript(script_string))
recapture_trigger.add_action(ClearFlag(flag=flag))
self.mission.triggerrules.triggers.append(recapture_trigger)
def generate(self) -> None:
player_coalition = "blue"
@@ -288,7 +249,7 @@ class TriggerGenerator:
self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition)
self._gen_markers()
self._generate_capture_triggers()
self._generate_capture_triggers(player_coalition, enemy_coalition)
if self.game.settings.ground_start_scenery_remove_triggers:
try:
self._generate_clear_statics_trigger(self.game.scenery_clear_zones)

View File

@@ -55,7 +55,7 @@ class GameStats:
turn_data = GameTurnMetadata()
for cp in game.theater.controlpoints:
if cp.captured.is_blue:
if cp.captured:
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

@@ -36,18 +36,6 @@ class DummyObject:
class MigrationUnpickler(pickle.Unpickler):
"""Custom unpickler to migrate campaign save-files for when components have been moved"""
def find_class(self, module: Any, name: str) -> Any:
if name == "Su_30MKA_AG":
from pydcs_extensions.su30 import Su_30MKA
return Su_30MKA
if name == "Su_30MKI_AG":
from pydcs_extensions.su30 import Su_30MKI
return Su_30MKI
if name == "Su_30SM_AG":
from pydcs_extensions.su30 import Su_30SM
return Su_30SM
if name == "Su_30MKM_AG":
from pydcs_extensions.su30 import Su_30MKM
return Su_30MKM
if name == "NightMissions":
from game.settings import NightMissions
return NightMissions
@@ -120,16 +108,6 @@ class MigrationUnpickler(pickle.Unpickler):
elif name == "Bas_100":
from dcs.terrain.kola.airports import Vuojarvi
return Vuojarvi
elif name == "Alakourtti":
from dcs.terrain.kola.airports import Alakurtti
return Alakurtti
if module == "dcs.terrain.sinai.airports":
if name == "Borj_El_Arab_International_Airport":
from dcs.terrain.sinai.airports import Borg_El_Arab_International_Airport
return Borg_El_Arab_International_Airport
elif name == "Palmahim":
from dcs.terrain.sinai.airports import Palmachim
return Palmachim
if module == "dcs.terrain.syria.airports":
if name == "Amman":
from dcs.terrain.syria.airports import Marka
@@ -141,10 +119,6 @@ class MigrationUnpickler(pickle.Unpickler):
"Helipad_218"
]:
return dcs.terrain.Airport # use base-class if airport was removed
if module == "dcs.terrain.afghanistan.airports":
if name == "Khost_Heliport":
from dcs.terrain.afghanistan.airports import FOB_Salerno
return FOB_Salerno
if module == "dcs.terrain.falklands.airports":
if name == "Aerodromo_De_Tolhuin":
from dcs.terrain.falklands.airports import Tolhuin

View File

@@ -35,7 +35,6 @@ 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,
@@ -834,7 +833,7 @@ class PretenseAircraftGenerator:
"""
self.initialize_pretense_data_structures(cp)
is_player = Player.BLUE
is_player = True
if country == cp.coalition.faction.country:
offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp)
@@ -869,7 +868,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(Player.RED)
else self.game.coalition_for(False)
)
self.generate_pretense_aircraft_for_other_side(cp, coalition, ato)
@@ -1058,7 +1057,6 @@ class PretenseAircraftGenerator:
and (
not self.need_ecm
or flight.any_member_has_weapon_of_type(WeaponType.JAMMER)
or flight.any_member_has_weapon_of_type(WeaponType.OFFENSIVE_JAMMER)
)
):
self.ewrj_package_dict[id(flight.package)].append(group)

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, Player
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint
class PretenseNameGenerator(NameGenerator):
@@ -87,7 +87,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner):
def insert_into_pretense(self, name: str) -> None:
cp = self.flight.departure
is_player = Player.BLUE
is_player = True
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.is_blue else 1
cp_side = 2 if cp.captured else 1
if isinstance(cp, OffMapSpawn):
continue
elif (
cp.is_fleet
and cp.captured.is_blue
and cp.captured
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 cp.captured.is_red
or not cp.captured
):
num_of_carrier_connections = 0
for (
@@ -1616,13 +1616,13 @@ class PretenseLuaGenerator(LuaGenerator):
try:
if (
cp.is_fleet
and cp.captured.is_blue
and cp.captured
and self.game.settings.pretense_controllable_carrier
):
break
elif (
closest_cps[extra_connection].is_fleet
and closest_cps[extra_connection].captured.is_blue
and closest_cps[extra_connection].captured
and self.game.settings.pretense_controllable_carrier
):
break
@@ -1647,7 +1647,7 @@ class PretenseLuaGenerator(LuaGenerator):
if isinstance(cp, OffMapSpawn):
continue
cp_side_captured = cp_side == 2
if cp_side_captured != cp.captured.is_blue:
if cp_side_captured != cp.captured:
continue
cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(
cp.name
@@ -1723,13 +1723,6 @@ class PretenseLuaGenerator(LuaGenerator):
def inject_plugin_script(
self, plugin_mnemonic: str, script: str, script_mnemonic: str
) -> None:
# Hard block MOOSE injection for Pretense missions
if script_mnemonic.lower() == "moose" or "moose" in script.lower():
logging.info(
"PretenseLuaGenerator: Skipping hard-blocked Moose.lua injection"
)
return
if script_mnemonic in self.plugin_scripts:
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
return
@@ -1737,6 +1730,7 @@ class PretenseLuaGenerator(LuaGenerator):
self.plugin_scripts.append(script_mnemonic)
plugin_path = Path("./resources/plugins", plugin_mnemonic)
script_path = Path(plugin_path, script)
if not script_path.exists():
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")

View File

@@ -28,7 +28,6 @@ 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
@@ -234,7 +233,7 @@ class PretenseMissionGenerator(MissionGenerator):
callsign=callsign,
region=frontline,
code=str(code),
blue=Player.BLUE,
blue=True,
freq=freq,
)
)

View File

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

View File

@@ -1,7 +1,9 @@
from __future__ import annotations
import logging
import math
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List
from dcs import Point
from dcs.action import (
@@ -9,6 +11,10 @@ from dcs.action import (
DoScript,
MarkToAll,
SetFlag,
RemoveSceneObjects,
RemoveSceneObjectsMask,
SceneryDestructionZone,
Smoke,
)
from dcs.condition import (
AllOfCoalitionOutsideZone,
@@ -16,6 +22,7 @@ from dcs.condition import (
FlagIsTrue,
PartOfCoalitionInZone,
TimeAfter,
TimeSinceFlag,
)
from dcs.mission import Mission
from dcs.task import Option
@@ -24,11 +31,12 @@ 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, Player
from game.theater import Airfield
from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn
if TYPE_CHECKING:
@@ -111,7 +119,7 @@ class PretenseTriggerGenerator:
f"Could not find {airfield.airport.name} in the mission"
)
cp_airport.set_coalition(
airfield.captured.is_blue and player_coalition or enemy_coalition
airfield.captured and player_coalition or enemy_coalition
)
def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None:
@@ -149,7 +157,7 @@ class PretenseTriggerGenerator:
zone = self.mission.triggers.add_triggerzone(
location, radius=10, hidden=True, name="MARK"
)
if cp.captured.is_blue:
if cp.captured:
name = ground_object.obj_name + " [ALLY]"
else:
name = ground_object.obj_name + " [ENEMY]"
@@ -166,7 +174,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.is_blue:
if cp.captured:
attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition
@@ -235,7 +243,7 @@ class PretenseTriggerGenerator:
self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh"
)
sea_zones_landmap = self.game.coalition_for(
player=Player.RED
player=False
).nav_mesh.theater.landmap
if (
self.game.settings.pretense_controllable_carrier
@@ -243,7 +251,7 @@ class PretenseTriggerGenerator:
):
navmesh_number = 0
for navmesh_poly in self.game.coalition_for(
player=Player.BLUE if use_blue_navmesh else Player.RED
player=use_blue_navmesh
).nav_mesh.polys:
navmesh_number += 1
if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly):
@@ -317,7 +325,7 @@ class PretenseTriggerGenerator:
if (
cp.is_fleet
and self.game.settings.pretense_controllable_carrier
and cp.captured.is_blue
and cp.captured
):
# 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, Player
from game.theater import ControlPoint, MissionTarget, ParkingType
if TYPE_CHECKING:
from game import Game
@@ -34,20 +34,20 @@ class ProcurementAi:
def __init__(
self,
game: Game,
owner: Player,
for_player: bool,
faction: Faction,
manage_runways: bool,
manage_front_line: bool,
manage_aircraft: bool,
) -> None:
self.game = game
self.is_player = owner
self.air_wing = game.air_wing_for(owner)
self.is_player = for_player
self.air_wing = game.air_wing_for(for_player)
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(self.is_player.opponent)
self.threat_zones = self.game.threat_zone_for(not self.is_player)
def calculate_ground_unit_budget_share(self) -> float:
armor_investment = 0
@@ -78,7 +78,7 @@ class ProcurementAi:
balance = (
self.game.settings.auto_procurement_balance
if self.is_player.is_blue
if self.is_player
else self.game.settings.auto_procurement_balance_red
)
air = balance / 100.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.is_blue:
if self.is_player:
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.is_blue:
if self.is_player:
return self.game.theater.player_points()
else:
return self.game.theater.enemy_points()
@@ -269,7 +269,7 @@ class ProcurementAi:
reserves_factor = (
self.game.settings.frontline_reserves_factor
if self.is_player.is_blue
if self.is_player
else self.game.settings.frontline_reserves_factor_red
)
fr_factor = reserves_factor / 100.0
@@ -308,7 +308,7 @@ class ProcurementAi:
)
target = (
self.game.settings.reserves_procurement_target
if self.is_player.is_blue
if self.is_player
else self.game.settings.reserves_procurement_target_red
)
if allocated.total >= target:

View File

@@ -219,27 +219,6 @@ class SCR522RadioChannelAllocator(RadioChannelAllocator):
return "SCR-522"
@dataclass(frozen=True)
class ARC5RadioChannelAllocator(RadioChannelAllocator):
"""Preset channel allocator for the ARC-5 WW2 radios. (4 channels)"""
def assign_channels_for_flight(
self, flight: FlightData, mission_data: MissionData
) -> None:
radio_id = 1
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
if flight.departure.atc is not None:
flight.assign_channel(radio_id, 2, flight.departure.atc)
if flight.arrival.atc is not None:
flight.assign_channel(radio_id, 3, flight.arrival.atc)
# TODO : Some GCI on Channel 4 ?
@classmethod
def name(cls) -> str:
return "ARC-5"
class ChannelNamer:
"""Base class allowing channel name customization per-aircraft.
@@ -397,23 +376,6 @@ class SCR522ChannelNamer(ChannelNamer):
return "SCR-522"
class ARC5ChannelNamer(ChannelNamer):
"""
Channel namer for F4U-1D Corsair
"""
@staticmethod
def channel_name(radio_id: int, channel_id: int) -> str:
if channel_id > 3:
return "?"
else:
return f"Channel " + "1234"[channel_id - 1]
@classmethod
def name(cls) -> str:
return "ARC-5"
class LegacyWarthogChannelNamer(ChannelNamer):
"""Channel namer for the legacy A-10C."""
@@ -472,15 +434,3 @@ class KiowaChannelNamer(ChannelNamer):
@classmethod
def name(cls) -> str:
return "kiowa"
class FulcrumChannelNamer(ChannelNamer):
"""Channel namer for the MiG-29 Fulcrum A"""
@staticmethod
def channel_name(radio_id: int, channel_id: int) -> str:
return f"Ch {channel_id -1}"
@classmethod
def name(cls) -> str:
return "fulcrum"

View File

@@ -84,10 +84,6 @@ def kHz(
return RadioFrequency(num * 1000 + hz, modulation)
def Hz(num: int, hz: int = 0, modulation: Modulation = Modulation.AM) -> RadioFrequency:
return RadioFrequency(num + hz, modulation)
@dataclass(frozen=True)
class RadioRange:
"""Defines the minimum (inclusive) and maximum (exclusive) range of the radio."""
@@ -235,15 +231,6 @@ RADIOS: List[Radio] = [
# P-51 / P-47 Radio
# 4 preset channels (A/B/C/D)
Radio("SCR522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
# F4U-1D Radio
# 4 preset channels (1/2/3/4)
Radio(
"ARC-5",
(
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(220), MHz(390), kHz(25), Modulation.AM),
),
),
# JF-17 Radios should use AM
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), kHz(25), Modulation.AM),)),
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
@@ -356,20 +343,6 @@ RADIOS: List[Radio] = [
"AN/ARC-27",
(RadioRange(MHz(225), MHz(400), kHz(100), Modulation.AM),),
),
# MiG-29 Fulcrum A
Radio(
"R-862",
(
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.FM),
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.FM),
),
),
Radio(
"ARK-19",
(RadioRange(kHz(150), kHz(1300), Hz(500), Modulation.AM),),
),
]

View File

@@ -29,16 +29,12 @@ 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=blue,
blue=control_point.captured,
position=control_point.position.latlng(),
mobile=control_point.moveable and control_point.captured.is_blue,
mobile=control_point.moveable and control_point.captured,
destination=destination,
sidc=str(control_point.sidc()),
)

View File

@@ -6,7 +6,6 @@ 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
@@ -76,7 +75,7 @@ def set_destination(
)
if not cp.moveable:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"{cp} is not mobile")
if not cp.captured.is_blue:
if not cp.captured:
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player"
)
@@ -121,7 +120,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.is_blue:
if not cp.captured:
raise HTTPException(
status.HTTP_403_FORBIDDEN, detail=f"{cp} is not owned by the player"
)

View File

@@ -15,7 +15,6 @@ 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
@@ -27,9 +26,9 @@ class GameUpdateEventsJs(BaseModel):
new_combats: list[FrozenCombatJs]
updated_combats: list[FrozenCombatJs]
ended_combats: list[UUID]
navmesh_updates: dict[Player, NavMeshJs]
navmesh_updates: dict[bool, NavMeshJs]
updated_unculled_zones: list[UnculledZoneJs]
threat_zones_updated: dict[Player, ThreatZonesJs]
threat_zones_updated: dict[bool, ThreatZonesJs]
new_flights: list[FlightJs]
updated_flights: list[FlightJs]
deleted_flights: set[UUID]

View File

@@ -41,13 +41,9 @@ 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=blue,
blue=flight.blue,
position=position,
sidc=str(flight.sidc()),
waypoints=waypoints,

View File

@@ -49,9 +49,7 @@ class GameJs(BaseModel):
iads_network=IadsNetworkJs.from_network(game.theater.iads_network),
threat_zones=ThreatZoneContainerJs.for_game(game),
navmeshes=NavMeshesJs.from_game(game),
map_center=LeafletPoint.from_latlng(
game.theater.terrain.map_view_default.position.latlng()
),
map_center=game.theater.terrain.map_view_default.position.latlng(),
unculled_zones=UnculledZoneJs.from_game(game),
map_zones=MapZonesJs.from_game(game),
)

View File

@@ -5,7 +5,6 @@ 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
@@ -35,16 +34,8 @@ 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(Player.BLUE) != tgo.is_friendly(
Player.BLUE
):
if connection.ground_object.is_friendly(True) != tgo.is_friendly(True):
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,
@@ -58,7 +49,7 @@ class IadsConnectionJs(BaseModel):
network_node.group.alive_units > 0
and connection.alive_units > 0
),
blue=blue,
blue=tgo.is_friendly(True),
is_power="power"
in [tgo.category, connection.ground_object.category],
)

View File

@@ -19,10 +19,6 @@ class LeafletPoint(BaseModel):
title = "LatLng"
@staticmethod
def from_latlng(latlng: LatLng) -> LeafletPoint:
return LeafletPoint(lat=latlng.lat, lng=latlng.lng)
LeafletLine = list[LeafletPoint]

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, Player
from game.theater import ConflictTheater
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=Player.BLUE), game.theater
game.threat_zone_for(player=True), game.theater
),
red=ThreatZonesJs.from_zones(
game.threat_zone_for(player=Player.RED), game.theater
game.threat_zone_for(player=False), game.theater
),
)

View File

@@ -2,13 +2,12 @@ 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: Player, game: Game = Depends(GameContext.require)) -> NavMeshJs:
def get(for_player: bool, game: Game = Depends(GameContext.require)) -> NavMeshJs:
mesh = game.coalition_for(for_player).nav_mesh
return NavMeshJs.from_navmesh(mesh, game)

View File

@@ -76,10 +76,6 @@ 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
@@ -97,7 +93,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=blue,
blue=a.captured,
active_transports=TransportFinder(game, a, b).describe_active_transports(
sea
),

View File

@@ -34,16 +34,12 @@ 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=blue,
blue=tgo.control_point.captured,
position=tgo.position.latlng(),
units=[unit.display_name for unit in tgo.units],
threat_ranges=threat_ranges,

View File

@@ -816,12 +816,6 @@ class Settings:
GAMEPLAY_SECTION,
default=True,
)
eplrs_enabled: bool = boolean_option(
"Enable EPLRS",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=True,
)
generate_dark_kneeboard: bool = boolean_option(
"Generate dark kneeboard",
MISSION_GENERATOR_PAGE,

View File

@@ -46,7 +46,7 @@ class AirCombat(JoinableCombat):
blue_flights = []
red_flights = []
for flight in self.flights:
if flight.squadron.player.is_blue:
if flight.squadron.player:
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.is_blue:
if flight.squadron.player:
blue.append(flight)
else:
red.append(flight)

View File

@@ -6,7 +6,6 @@ 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
@@ -32,10 +31,8 @@ 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=Player.BLUE
)
red_sam = SamEngagementZones.from_theater(self.game.theater, player=Player.RED)
blue_sam = SamEngagementZones.from_theater(self.game.theater, player=True)
red_sam = SamEngagementZones.from_theater(self.game.theater, player=False)
# 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
@@ -49,7 +46,7 @@ class CombatInitiator:
if flight.state.in_combat:
return
if flight.squadron.player.is_blue:
if flight.squadron.player:
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, Player
from game.theater import ConflictTheater, TheaterGroundObject
from game.threatzones import ThreatPoly
@@ -31,9 +31,7 @@ class SamEngagementZones:
yield tgo
@classmethod
def from_theater(
cls, theater: ConflictTheater, player: Player
) -> SamEngagementZones:
def from_theater(cls, theater: ConflictTheater, player: bool) -> 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, Player
from game.theater import ControlPoint, FrontLine, TheaterGroundObject
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[Player, NavMesh] = field(default_factory=dict)
navmesh_updates: dict[bool, NavMesh] = field(default_factory=dict)
unculled_zones_updated: list[Point] = field(default_factory=list)
threat_zones_updated: dict[Player, ThreatZones] = field(default_factory=dict)
threat_zones_updated: dict[bool, 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: Player, navmesh: NavMesh) -> GameUpdateEvents:
def update_navmesh(self, player: bool, navmesh: NavMesh) -> GameUpdateEvents:
self.navmesh_updates[player] = navmesh
return self
@@ -78,9 +78,7 @@ class GameUpdateEvents:
self.unculled_zones_updated = zones
return self
def update_threat_zones(
self, player: Player, zones: ThreatZones
) -> GameUpdateEvents:
def update_threat_zones(self, player: bool, 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.is_blue:
if captured.captured_by_player:
self.game.message(
f"{captured.control_point} captured!",
f"We took control of {captured.control_point}.",
@@ -174,7 +174,7 @@ class MissionResultsProcessor:
self, debriefing: Debriefing, events: GameUpdateEvents
) -> None:
for cp in self.game.theater.player_points():
enemy_cps = [e for e in cp.connected_points if e.captured.is_red]
enemy_cps = [e for e in cp.connected_points if not e.captured]
for enemy_cp in enemy_cps:
front_line = cp.front_line_with(enemy_cp)
front_line.update_position()
@@ -321,7 +321,7 @@ class MissionResultsProcessor:
settings = cp.coalition.game.settings
factor = (
settings.frontline_reserves_factor
if cp.captured.is_blue
if cp.captured
else settings.frontline_reserves_factor_red
)
@@ -345,7 +345,7 @@ class MissionResultsProcessor:
1,
(
settings.reserves_procurement_target
if source.captured.is_blue
if source.captured
else settings.reserves_procurement_target_red
),
)
@@ -367,7 +367,7 @@ class MissionResultsProcessor:
source.base.commit_losses(moved_units)
# Also transfer pending deliveries.
for unit_type, count in list(source.ground_unit_orders.units.items()):
for unit_type, count in source.ground_unit_orders.units.items():
move_count = int(count * move_factor)
source.ground_unit_orders.sell({unit_type: move_count})
destination.ground_unit_orders.order({unit_type: move_count})

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import itertools
from collections import defaultdict
from typing import Iterator, Optional, Sequence, TYPE_CHECKING, Any
from typing import Iterator, Optional, Sequence, TYPE_CHECKING
from game.ato.closestairfields import ObjectiveDistanceCache
from game.dcs.aircrafttype import AircraftType
@@ -14,31 +14,18 @@ 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: Player, game: Game, faction: Faction) -> None:
def __init__(self, player: bool, game: Game, faction: Faction) -> None:
self.player = player
self.squadrons: dict[AircraftType, list[Squadron]] = defaultdict(list)
self.squadron_defs = SquadronDefLoader(game, faction).load()
self.squadron_def_generator = SquadronDefGenerator(faction)
self.settings = game.settings
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
def unclaim_squadron_def(self, squadron: Squadron) -> None:
if squadron.aircraft in self.squadron_defs:
for squadron_def in self.squadron_defs[squadron.aircraft]:

View File

@@ -15,7 +15,6 @@ from faker import Faker
from game.ato import Flight, FlightType, Package
from game.settings import AutoAtoBehavior, Settings
from game.theater import ParkingType
from game.theater.player import Player
from .pilot import Pilot, PilotStatus
from ..db.database import Database
from ..radio.radios import RadioFrequency
@@ -97,7 +96,7 @@ class Squadron:
self._livery_pool: list[str] = []
@property
def player(self) -> Player:
def player(self) -> bool:
return self.coalition.player
def assign_to_base(self, base: ControlPoint) -> None:
@@ -135,7 +134,7 @@ class Squadron:
return self.claim_new_pilot_if_allowed()
# For opfor, so player/AI option is irrelevant.
if self.player != Player.BLUE:
if not self.player:
return self.available_pilots.pop()
preference = self.settings.auto_ato_behavior

View File

@@ -2,6 +2,5 @@ from .base import *
from .conflicttheater import *
from .controlpoint import *
from .frontline import FrontLine
from .landmap import *
from .missiontarget import MissionTarget
from .theatergroundobject import SamGroundObject

View File

@@ -15,7 +15,6 @@ 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
@@ -171,10 +170,10 @@ class ConflictTheater:
return new_point
def control_points_for(
self, player: Player, state_check: bool = False
self, player: bool, state_check: bool = False
) -> Iterator[ControlPoint]:
for point in self.controlpoints:
if point.captured is player:
if point.captured == player:
if not state_check:
yield point
elif point.is_carrier and point.runway_is_operational():
@@ -183,21 +182,14 @@ class ConflictTheater:
yield point
def player_points(self, state_check: bool = False) -> List[ControlPoint]:
return list(
self.control_points_for(player=Player.BLUE, state_check=state_check)
)
return list(self.control_points_for(player=True, 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=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)
)
return list(self.control_points_for(player=False, state_check=state_check))
def closest_control_point(
self, point: Point, allow_naval: bool = False
@@ -267,12 +259,10 @@ class ConflictTheater:
"""
closest_cps = list()
distances_to_cp = dict()
if cp.captured.is_blue:
if cp.captured:
control_points = self.player_points()
elif cp.captured.is_red:
else:
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
@@ -302,12 +292,6 @@ class ConflictTheater:
return cp
raise KeyError(f"Cannot find ControlPoint named {name}")
def control_point_by_full_name(self, full_name: str) -> ControlPoint:
for cp in self.controlpoints:
if cp.full_name == full_name:
return cp
raise KeyError(f"Cannot find ControlPoint with full name {full_name}")
def find_carriers(self) -> List[ControlPoint]:
try:
carriers = [cp for cp in self.controlpoints if cp.is_carrier]

View File

@@ -40,14 +40,12 @@ from dcs.ships import (
Stennis,
Type_071,
hms_invincible,
Essex,
)
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import ShipType
from game.ato.closestairfields import ObjectiveDistanceCache
from game.controlpoint_influenceradius import ControlPointInfluenceRadius
from game.ground_forces.combat_stance import CombatStance
from game.point_with_heading import PointWithHeading
from game.runways import RunwayAssigner, RunwayData
@@ -68,7 +66,6 @@ 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,
@@ -378,17 +375,15 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
position: Point,
at: StartingPosition,
theater: ConflictTheater,
starting_coalition: Player,
starts_blue: bool,
cptype: ControlPointType = ControlPointType.AIRBASE,
is_invisible: bool = False,
) -> None:
super().__init__(name, position)
self.id = uuid.uuid4()
self.full_name = name
self.at = at
self.theater = theater
self.is_invisible = is_invisible
self.starting_coalition = starting_coalition
self.starts_blue = starts_blue
self.connected_objectives: List[TheaterGroundObject] = []
self.preset_locations = PresetLocations()
self.helipads: List[PointWithHeading] = []
@@ -419,8 +414,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
# Initialized late because ControlPoints are constructed before the game is.
self._front_line_db: Database[FrontLine] | None = None
self.influence_radius: ControlPointInfluenceRadius | None = None
def __repr__(self) -> str:
return f"<{self.__class__}: {self.name}>"
@@ -436,7 +429,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def finish_init(self, game: Game) -> None:
assert self._coalition is None
self._coalition = game.coalition_for(self.starting_coalition)
self._coalition = game.coalition_for(self.starts_blue)
assert self._front_line_db is None
self._front_line_db = game.db.front_lines
@@ -445,8 +438,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())
self._create_missing_front_lines(laser_code_registry, GameUpdateEvents())
@property
def front_line_db(self) -> Database[FrontLine]:
@@ -457,11 +449,9 @@ 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)
and connection.captured != Player.NEUTRAL
):
if not connection.front_line_active_with(
self
) and not connection.is_friendly_to(self):
self._create_front_line_with(laser_code_registry, connection, events)
def _create_front_line_with(
@@ -495,8 +485,6 @@ 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:
@@ -506,17 +494,14 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
return self.front_lines[other]
@property
def captured(self) -> Player:
def captured(self) -> bool:
return self.coalition.player
@property
def standard_identity(self) -> StandardIdentity:
if self.captured.is_neutral:
return StandardIdentity.UNKNOWN
elif self.captured.is_blue:
return StandardIdentity.FRIEND
else:
return StandardIdentity.HOSTILE_FAKER
return (
StandardIdentity.FRIEND if self.captured else StandardIdentity.HOSTILE_FAKER
)
@property
def sidc_status(self) -> Status:
@@ -828,7 +813,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
found.append(g)
return found
def is_friendly(self, to_player: Player) -> bool:
def is_friendly(self, to_player: bool) -> bool:
return self.captured == to_player
def is_friendly_to(self, control_point: ControlPoint) -> bool:
@@ -837,9 +822,7 @@ 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=Player.BLUE if self.captured.is_red else Player.RED
)
game.adjust_budget(total, player=not self.captured)
game.message(
f"{self.name} is not connected to any friendly points. Ground "
f"vehicles have been captured and sold for ${total}M."
@@ -868,9 +851,7 @@ 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=Player.BLUE if self.captured.is_red else Player.RED
)
game.adjust_budget(value, player=not self.captured)
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."
@@ -973,7 +954,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
pass
# TODO: Should be Airbase specific.
def capture(self, game: Game, events: GameUpdateEvents, for_player: Player) -> None:
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
new_coalition = game.coalition_for(for_player)
self.ground_unit_orders.refund_all(self.coalition)
self.retreat_ground_units(game)
@@ -1138,8 +1119,6 @@ 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:
@@ -1226,22 +1205,20 @@ class Airfield(ControlPoint, CTLD):
self,
airport: Airport,
theater: ConflictTheater,
starting_coalition: Player,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
influence_zone: Optional[List[Tuple[Point, float]]] = None,
) -> None:
super().__init__(
airport.name,
airport.position,
airport,
theater,
starting_coalition,
starts_blue,
cptype=ControlPointType.AIRBASE,
)
self.airport = airport
self._runway_status = RunwayStatus()
self.ctld_zones = ctld_zones
self.influence_zone = influence_zone
@property
def dcs_airport(self) -> Airport:
@@ -1267,7 +1244,7 @@ class Airfield(ControlPoint, CTLD):
return True
return self.runway_is_operational()
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -1388,7 +1365,7 @@ class NavalControlPoint(
def is_fleet(self) -> bool:
return True
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if self.is_friendly(for_player):
@@ -1449,7 +1426,6 @@ class NavalControlPoint(
CVN_72,
CVN_73,
CVN_75,
Essex,
]:
return True
return False
@@ -1497,7 +1473,7 @@ class NavalControlPoint(
class Carrier(NavalControlPoint):
def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
):
super().__init__(
name,
@@ -1512,7 +1488,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: Player) -> None:
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
raise RuntimeError("Carriers cannot be captured")
@property
@@ -1530,14 +1506,9 @@ class Carrier(NavalControlPoint):
return "cv"
class EssexCarrier(Carrier):
def can_operate(self, aircraft: AircraftType) -> bool:
return aircraft.lha_capable
class Lha(NavalControlPoint):
def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: Player
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
):
super().__init__(
name, at, at, theater, starts_blue, cptype=ControlPointType.LHA_GROUP
@@ -1547,7 +1518,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: Player) -> None:
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
raise RuntimeError("LHAs cannot be captured")
@property
@@ -1570,7 +1541,7 @@ class OffMapSpawn(ControlPoint):
return True
def __init__(
self, name: str, position: Point, theater: ConflictTheater, starts_blue: Player
self, name: str, position: Point, theater: ConflictTheater, starts_blue: bool
):
super().__init__(
name,
@@ -1585,10 +1556,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: Player) -> None:
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
raise RuntimeError("Off map control points cannot be captured")
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from []
def total_aircraft_parking(self, parking_type: ParkingType) -> int:
@@ -1645,18 +1616,14 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
name: str,
at: Point,
theater: ConflictTheater,
starts_blue: Player,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
is_invisible: bool = False,
influence_zone: Optional[List[Tuple[Point, float]]] = None,
) -> None:
super().__init__(
name, at, at, theater, starts_blue, cptype=ControlPointType.FOB
)
self.name = name
self.ctld_zones = ctld_zones
self.is_invisible = is_invisible
self.influence_zone = influence_zone
@property
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
@@ -1682,7 +1649,7 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
def runway_status(self) -> RunwayStatus:
return RunwayStatus()
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):

View File

@@ -8,7 +8,6 @@ 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
@@ -90,22 +89,19 @@ class FrontLine(MissionTarget):
def update_position(self) -> None:
self.position = self._compute_position()
def control_point_friendly_to(self, player: Player) -> ControlPoint:
if player.is_blue:
def control_point_friendly_to(self, player: bool) -> ControlPoint:
if player:
return self.blue_cp
return self.red_cp
def control_point_hostile_to(self, player: Player) -> ControlPoint:
if player.is_blue:
return self.red_cp
else:
return self.blue_cp
def control_point_hostile_to(self, player: bool) -> ControlPoint:
return self.control_point_friendly_to(not player)
def is_friendly(self, to_player: Player) -> bool:
def is_friendly(self, to_player: bool) -> bool:
"""Returns True if the objective is in friendly territory."""
return False
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
yield from [
@@ -214,6 +210,6 @@ class FrontLine(MissionTarget):
raise ValueError(
"Cannot sort control points that are friendly to each other"
)
if a.captured.is_blue:
if a.captured:
return a, b
return b, a

View File

@@ -16,7 +16,6 @@ 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
@@ -32,7 +31,7 @@ class SkynetNode:
"""Dataclass for a SkynetNode used in the LUA Data table by the luagenerator"""
dcs_name: str
player: Player
player: bool
iads_role: IadsRole
properties: dict[str, str] = field(default_factory=dict)
connections: dict[str, list[str]] = field(default_factory=lambda: defaultdict(list))
@@ -63,7 +62,7 @@ class SkynetNode:
def from_group(cls, group: IadsGroundGroup) -> SkynetNode:
node = cls(
cls.dcs_name_for_group(group),
group.ground_object.coalition.player,
group.ground_object.is_friendly(True),
group.iads_role,
)
unit_type = group.units[0].unit_type
@@ -315,8 +314,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(Player.BLUE)
tgo_friendly = tgo.is_friendly(Player.BLUE)
node_friendly = node.group.ground_object.is_friendly(True)
tgo_friendly = tgo.is_friendly(True)
return node_friendly == tgo_friendly
def _update_network(

View File

@@ -12,7 +12,6 @@ from dcs.mission import Mission
from dcs.terrain.terrain import Terrain
from shapely import geometry, LineString
from shapely.geometry import MultiPolygon, Polygon
import shapely as shp
@dataclass(frozen=True)
@@ -29,11 +28,6 @@ class Landmap:
if not self.sea_zones.is_valid:
raise RuntimeError("Sea zones not valid")
# Generate Spatial Index using `prepare` to improve performance
shp.prepare(self.inclusion_zones)
shp.prepare(self.exclusion_zones)
shp.prepare(self.sea_zones)
@cached_property
def inclusion_zone_only(self) -> MultiPolygon:
return self.inclusion_zones - self.exclusion_zones - self.sea_zones

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, Player
from game.theater import TheaterUnit, Coalition
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: Player) -> bool:
def is_friendly(self, to_player: bool) -> bool:
"""Returns True if the objective is in friendly territory."""
raise NotImplementedError
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if self.is_friendly(for_player):

View File

@@ -1,30 +0,0 @@
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

@@ -13,12 +13,7 @@ from game import Game
from game.factions.faction import Faction
from game.naming import namegen
from game.scenery_group import SceneryGroup
from game.theater import (
PointWithHeading,
PresetLocation,
NavalControlPoint,
EssexCarrier,
)
from game.theater import PointWithHeading, PresetLocation, NavalControlPoint
from game.theater.theatergroundobject import (
BuildingGroundObject,
IadsBuildingGroundObject,
@@ -32,7 +27,6 @@ from . import (
Fob,
OffMapSpawn,
)
from .player import Player
from .theatergroup import (
IadsGroundGroup,
IadsRole,
@@ -96,13 +90,11 @@ class ModSettings:
uh_60l: bool = False
jas39_gripen: bool = False
sk_60: bool = False
mam: bool = False
mirage_3: bool = False
super_etendard: bool = False
su15_flagon: bool = False
su30_flanker_h: bool = False
su57_felon: bool = False
tornado_adv: bool = False
frenchpack: bool = False
high_digit_sams: bool = False
ov10a_bronco: bool = False
@@ -159,12 +151,12 @@ class GameGenerator:
game.settings.version = VERSION
return game
def should_remove_carrier(self, player: Player) -> bool:
faction = self.player if player.is_blue else self.enemy
def should_remove_carrier(self, player: bool) -> bool:
faction = self.player if player else self.enemy
return self.generator_settings.no_carrier or not faction.carriers
def should_remove_lha(self, player: Player) -> bool:
faction = self.player if player.is_blue else self.enemy
def should_remove_lha(self, player: bool) -> bool:
faction = self.player if player else self.enemy
return self.generator_settings.no_lha or not [
x for x in faction.carriers if x.unit_class == UnitClass.HELICOPTER_CARRIER
]
@@ -175,14 +167,11 @@ class GameGenerator:
# Remove carrier and lha, invert situation if needed
for cp in self.theater.controlpoints:
if self.generator_settings.inverted:
if not cp.starting_coalition.is_neutral:
cp.starting_coalition = (
Player.RED if not cp.captured_invert else Player.BLUE
)
cp.starts_blue = cp.captured_invert
if cp.is_carrier and self.should_remove_carrier(cp.starting_coalition):
if cp.is_carrier and self.should_remove_carrier(cp.starts_blue):
to_remove.append(cp)
elif cp.is_lha and self.should_remove_lha(cp.starting_coalition):
elif cp.is_lha and self.should_remove_lha(cp.starts_blue):
to_remove.append(cp)
# do remove
@@ -234,13 +223,11 @@ 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.is_blue and skip_player_navy:
if self.control_point.captured and skip_player_navy:
return
skip_enemy_navy = self.generator_settings.no_enemy_navy
if self.control_point.captured.is_red and skip_enemy_navy:
if not self.control_point.captured 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)
@@ -309,9 +296,7 @@ class GenericCarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
if go.category in ["CARRIER", "LHA"]
][0]
groups = [
g
for g in carrier_go.groups
if "carrier" in g.name.lower() or "lha" in g.name.lower()
g for g in carrier_go.groups if "Carrier" in g.name or "LHA" in g.name
]
return groups[0].units[0]
@@ -334,7 +319,6 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
logging.error(f"{self.faction_name} has no access to AircraftCarrier")
return False
self.transform_to_essex_if_needed(unit_group)
self.generate_ground_object_from_group(
unit_group,
PresetLocation(
@@ -347,25 +331,6 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
self.apply_carrier_config()
return True
def transform_to_essex_if_needed(self, unit_group: ForceGroup) -> None:
classes = [u.unit_class for u in unit_group.units]
if any([c for c in classes if c == UnitClass.HELICOPTER_CARRIER]) and not any(
[c for c in classes if c == UnitClass.AIRCRAFT_CARRIER]
):
self.game.theater.controlpoints.remove(self.control_point)
sqdrns = self.control_point.squadrons
self.control_point = EssexCarrier(
self.control_point.name,
self.control_point.position,
self.game.theater,
self.control_point.starting_coalition,
)
self.control_point.finish_init(self.game)
self.game.theater.controlpoints.append(self.control_point)
for sqdrn in sqdrns:
if sqdrn.aircraft.lha_capable:
sqdrn.location = self.control_point
class LhaGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
def generate(self) -> bool:
@@ -621,24 +586,14 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
return False
def generate_fob(self) -> None:
if self.control_point.is_invisible:
self.generate_building_at(
GroupTask.INVISIBLE_FOB,
PresetLocation(
self.control_point.name,
self.control_point.position,
self.control_point.heading,
),
)
else:
self.generate_building_at(
GroupTask.FOB,
PresetLocation(
self.control_point.name,
self.control_point.position,
self.control_point.heading,
),
)
self.generate_building_at(
GroupTask.FOB,
PresetLocation(
self.control_point.name,
self.control_point.position,
self.control_point.heading,
),
)
class GroundObjectGenerator:

View File

@@ -21,7 +21,6 @@ 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
@@ -90,8 +89,6 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
@property
def sidc_status(self) -> Status:
if self.control_point.captured.is_neutral:
return Status.PRESENT
if self.is_dead:
return Status.PRESENT_DESTROYED
elif self.dead_units:
@@ -101,12 +98,11 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
@property
def standard_identity(self) -> StandardIdentity:
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
return (
StandardIdentity.FRIEND
if self.control_point.captured
else StandardIdentity.HOSTILE_FAKER
)
@property
def is_dead(self) -> bool:
@@ -158,12 +154,10 @@ 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: Player) -> bool:
if self.control_point.captured.is_neutral:
return False
def is_friendly(self, to_player: bool) -> bool:
return self.control_point.is_friendly(to_player)
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if self.is_friendly(for_player):
@@ -366,7 +360,7 @@ class BuildingGroundObject(TheaterGroundObject):
class NavalGroundObject(TheaterGroundObject, ABC):
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -472,7 +466,7 @@ class MissileSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
return True
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -513,7 +507,7 @@ class CoastalSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
return True
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -540,7 +534,7 @@ class IadsGroundObject(TheaterGroundObject, ABC):
task=task,
)
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -577,8 +571,6 @@ class SamGroundObject(IadsGroundObject):
@property
def sidc_status(self) -> Status:
if self.control_point.captured.is_neutral:
return Status.PRESENT
if self.is_dead:
return Status.PRESENT_DESTROYED
elif self.dead_units:
@@ -593,7 +585,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: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -650,7 +642,7 @@ class VehicleGroupGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
return True
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
@@ -705,7 +697,7 @@ class ShipGroundObject(NavalGroundObject):
class IadsBuildingGroundObject(BuildingGroundObject):
def mission_types(self, for_player: Player) -> Iterator[FlightType]:
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):

View File

@@ -8,7 +8,7 @@ from dcs.triggers import TriggerZone
from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType, VehicleType
from game.data.radar_db import LAUNCHER_TRACKER_PAIRS, TELARS, TRACK_RADARS
from game.data.units import ANTI_AIR_UNIT_CLASSES, UnitClass
from game.data.units import ANTI_AIR_UNIT_CLASSES
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
@@ -82,17 +82,9 @@ class TheaterUnit:
iads = self.ground_object.control_point.coalition.game.theater.iads_network
iads.update_tgo(self.ground_object, events)
if self.ground_object.is_naval_control_point:
for unit in self.ground_object.units:
if (
unit.unit_type
and unit.unit_type.unit_class is UnitClass.AIRCRAFT_CARRIER
and not unit.alive
):
cp = self.ground_object.control_point
for squadron in cp.squadrons:
cp.coalition.air_wing.squadrons[squadron.aircraft].remove(
squadron
)
cp = self.ground_object.control_point
for squadron in cp.squadrons:
cp.coalition.air_wing.squadrons[squadron.aircraft].remove(squadron)
def revive(self, events: GameUpdateEvents) -> None:
self.alive = True

Some files were not shown because too many files have changed in this diff Show More