diff --git a/game/ato/flightplans/cas.py b/game/ato/flightplans/cas.py index e44cf571..fa508929 100644 --- a/game/ato/flightplans/cas.py +++ b/game/ato/flightplans/cas.py @@ -77,11 +77,16 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]): FrontLineConflictDescription, ) - ingress, heading, distance = FrontLineConflictDescription.frontline_vector( + bounds = FrontLineConflictDescription.frontline_bounds( location, self.theater, self.coalition.game.settings ) - center = ingress.point_from_heading(heading.degrees, distance / 2) - egress = ingress.point_from_heading(heading.degrees, distance) + ingress = bounds.left_position + center = ingress.point_from_heading( + bounds.heading_from_left_to_right.degrees, bounds.length / 2 + ) + egress = ingress.point_from_heading( + bounds.heading_from_left_to_right.degrees, bounds.length + ) ingress_distance = ingress.distance_to_point(self.flight.departure.position) egress_distance = egress.distance_to_point(self.flight.departure.position) diff --git a/game/missiongenerator/airsupportgenerator.py b/game/missiongenerator/airsupportgenerator.py index a605ff55..f850e4e2 100644 --- a/game/missiongenerator/airsupportgenerator.py +++ b/game/missiongenerator/airsupportgenerator.py @@ -15,14 +15,14 @@ from dcs.task import ( ) from dcs.unittype import UnitType +from game.ato.ai_flight_planner_db import AEWC_CAPABLE from game.callsigns import callsign_for_support_unit from game.naming import namegen from game.radio.radios import RadioRegistry from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage from game.utils import Heading -from game.ato.ai_flight_planner_db import AEWC_CAPABLE -from .missiondata import MissionData, AwacsInfo, TankerInfo from .frontlineconflictdescription import FrontLineConflictDescription +from .missiondata import AwacsInfo, MissionData, TankerInfo if TYPE_CHECKING: from game import Game diff --git a/game/missiongenerator/drawingsgenerator.py b/game/missiongenerator/drawingsgenerator.py index c2a88424..aba80a8a 100644 --- a/game/missiongenerator/drawingsgenerator.py +++ b/game/missiongenerator/drawingsgenerator.py @@ -85,18 +85,16 @@ class DrawingsGenerator: Generate a frontline "line" for each active frontline """ for front_line in self.game.theater.conflicts(): - ( - plane_start, - heading, - distance, - ) = FrontLineConflictDescription.frontline_vector( + bounds = FrontLineConflictDescription.frontline_bounds( front_line, self.game.theater, self.game.settings ) - end_point = plane_start.point_from_heading(heading.degrees, distance) + end_point = bounds.left_position.point_from_heading( + bounds.heading_from_left_to_right.degrees, bounds.length + ) shape = self.player_layer.add_line_segment( - plane_start, - end_point - plane_start, + bounds.left_position, + end_point - bounds.left_position, line_thickness=16, color=FRONTLINE_COLORS, line_style=LineStyle.Triangle, diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 20af789a..834b9f66 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -43,9 +43,9 @@ from game.radio.radios import RadioRegistry from game.theater.controlpoint import ControlPoint from game.unitmap import UnitMap from game.utils import Heading -from .missiondata import MissionData, JtacInfo from .frontlineconflictdescription import FrontLineConflictDescription from .lasercoderegistry import LaserCodeRegistry +from .missiondata import JtacInfo, MissionData if TYPE_CHECKING: from game import Game @@ -100,18 +100,14 @@ class FlotGenerator: self.conflict.front_line, self.game.theater, self.game.settings ) - frontline_vector = FrontLineConflictDescription.frontline_vector( - self.conflict.front_line, self.game.theater, self.game.settings - ) - # Create player groups at random position player_groups = self._generate_groups( - self.player_planned_combat_groups, frontline_vector, True + self.player_planned_combat_groups, is_player=True ) # Create enemy groups at random position enemy_groups = self._generate_groups( - self.enemy_planned_combat_groups, frontline_vector, False + self.enemy_planned_combat_groups, is_player=False ) # TODO: Differentiate AirConflict and GroundConflict classes. @@ -698,33 +694,33 @@ class FlotGenerator: return rg def get_valid_position_for_group( - self, - conflict_position: Point, - combat_width: int, - distance_from_frontline: int, - heading: Heading, - spawn_heading: Heading, - ) -> Optional[Point]: - shifted = conflict_position.point_from_heading( - heading.degrees, random.randint(0, combat_width) + self, distance_from_frontline: int, spawn_heading: Heading + ) -> Point | None: + assert self.conflict.heading is not None + assert self.conflict.size is not None + shifted = self.conflict.position.point_from_heading( + self.conflict.heading.degrees, + random.randint(0, self.conflict.size), ) desired_point = shifted.point_from_heading( spawn_heading.degrees, distance_from_frontline ) return FrontLineConflictDescription.find_ground_position( - desired_point, combat_width, heading, self.conflict.theater + desired_point, + self.conflict.size, + self.conflict.heading, + self.conflict.theater, ) def _generate_groups( - self, - groups: list[CombatGroup], - frontline_vector: Tuple[Point, Heading, int], - is_player: bool, + 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 = [] - position, heading, combat_width = frontline_vector - spawn_heading = heading.left if is_player else heading.right + assert self.conflict.heading is not None + spawn_heading = ( + self.conflict.heading.left if is_player else self.conflict.heading.right + ) country = self.game.coalition_for(is_player).country_name for group in groups: if group.role == CombatGroupRole.ARTILLERY: @@ -738,7 +734,7 @@ class FlotGenerator: ) final_position = self.get_valid_position_for_group( - position, combat_width, distance_from_frontline, heading, spawn_heading + distance_from_frontline, spawn_heading ) if final_position is not None: diff --git a/game/missiongenerator/frontlineconflictdescription.py b/game/missiongenerator/frontlineconflictdescription.py index 7355c563..37894624 100644 --- a/game/missiongenerator/frontlineconflictdescription.py +++ b/game/missiongenerator/frontlineconflictdescription.py @@ -1,7 +1,8 @@ from __future__ import annotations import logging -from typing import Tuple, Optional +from dataclasses import dataclass +from typing import Optional, Tuple from dcs.country import Country from dcs.mapping import Point @@ -12,6 +13,15 @@ from game.theater.conflicttheater import ConflictTheater, FrontLine from game.theater.controlpoint import ControlPoint from game.utils import Heading +FRONTLINE_LENGTH = 80000 + + +@dataclass(frozen=True) +class FrontLineBounds: + left_position: Point + heading_from_left_to_right: Heading + length: int + class FrontLineConflictDescription: def __init__( @@ -26,7 +36,6 @@ class FrontLineConflictDescription: heading: Optional[Heading] = None, size: Optional[int] = None, ): - self.attackers_side = attackers_side self.defenders_side = defenders_side self.attackers_country = attackers_country @@ -66,9 +75,9 @@ class FrontLineConflictDescription: return position, attack_heading.opposite @classmethod - def frontline_vector( + def frontline_bounds( cls, front_line: FrontLine, theater: ConflictTheater, settings: Settings - ) -> Tuple[Point, Heading, int]: + ) -> FrontLineBounds: """ Returns a vector for a valid frontline location avoiding exclusion zones. """ @@ -88,7 +97,7 @@ class FrontLineConflictDescription: theater, ) distance = int(left_position.distance_to_point(right_position)) - return left_position, right_heading, distance + return FrontLineBounds(left_position, right_heading, distance) @classmethod def frontline_cas_conflict( @@ -102,19 +111,20 @@ class FrontLineConflictDescription: settings: Settings, ) -> FrontLineConflictDescription: assert cls.has_frontline_between(front_line.blue_cp, front_line.red_cp) - position, heading, distance = cls.frontline_vector( - front_line, theater, settings - ) + # TODO: Break apart the front-line and air conflict descriptions. + # We're wastefully not caching the front-line bounds here because air conflicts + # can't compute bounds, only a position. + bounds = cls.frontline_bounds(front_line, theater, settings) conflict = cls( - position=position, - heading=heading, + position=bounds.left_position, + heading=bounds.heading_from_left_to_right, theater=theater, front_line=front_line, attackers_side=attacker_name, defenders_side=defender_name, attackers_country=attacker, defenders_country=defender, - size=distance, + size=bounds.length, ) return conflict diff --git a/game/missiongenerator/visualsgenerator.py b/game/missiongenerator/visualsgenerator.py index 04245622..5661a10d 100644 --- a/game/missiongenerator/visualsgenerator.py +++ b/game/missiongenerator/visualsgenerator.py @@ -1,7 +1,7 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING, Any +from typing import Any, TYPE_CHECKING from dcs.mission import Mission from dcs.unit import Static @@ -79,18 +79,16 @@ class VisualsGenerator: if from_cp.is_global or to_cp.is_global: continue - ( - plane_start, - heading, - distance, - ) = FrontLineConflictDescription.frontline_vector( + bounds = FrontLineConflictDescription.frontline_bounds( front_line, self.game.theater, self.game.settings ) - if not plane_start: - continue - for offset in range(0, distance, self.game.settings.perf_smoke_spacing): - position = plane_start.point_from_heading(heading.degrees, offset) + for offset in range( + 0, bounds.length, self.game.settings.perf_smoke_spacing + ): + position = bounds.left_position.point_from_heading( + bounds.heading_from_left_to_right.degrees, offset + ) for k, v in FRONT_SMOKE_TYPE_CHANCES.items(): if random.randint(0, 100) <= k: