mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
frontline
This commit is contained in:
parent
9620ac7e7e
commit
ede5ee60c3
@ -1,44 +1,199 @@
|
|||||||
"""Battlefield front lines."""
|
"""Battlefield front lines."""
|
||||||
from typing import Tuple
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from itertools import tee
|
||||||
|
from typing import Tuple, List, Union, Dict
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from . import ControlPoint, MissionTarget
|
|
||||||
|
from .controlpoint import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
Numeric = Union[int, float]
|
||||||
|
|
||||||
# TODO: Dedup by moving everything to using this class.
|
# TODO: Dedup by moving everything to using this class.
|
||||||
FRONTLINE_MIN_CP_DISTANCE = 5000
|
FRONTLINE_MIN_CP_DISTANCE = 5000
|
||||||
|
|
||||||
|
|
||||||
def compute_position(control_point_a: ControlPoint,
|
def pairwise(iterable):
|
||||||
control_point_b: ControlPoint) -> Point:
|
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||||
a = control_point_a.position
|
a, b = tee(iterable)
|
||||||
b = control_point_b.position
|
next(b, None)
|
||||||
attack_heading = a.heading_between_point(b)
|
return zip(a, b)
|
||||||
attack_distance = a.distance_to_point(b)
|
|
||||||
middle_point = a.point_from_heading(attack_heading, attack_distance / 2)
|
|
||||||
|
|
||||||
strength_delta = float(control_point_a.base.strength -
|
|
||||||
control_point_b.base.strength)
|
@dataclass
|
||||||
position = middle_point.point_from_heading(attack_heading,
|
class ComplexFrontLine:
|
||||||
strength_delta *
|
"""
|
||||||
attack_distance / 2 -
|
Stores data necessary for building a multi-segment frontline.
|
||||||
FRONTLINE_MIN_CP_DISTANCE)
|
"points" should be ordered from closest to farthest distance originating from start_cp.position
|
||||||
return position
|
"""
|
||||||
|
|
||||||
|
start_cp: ControlPoint
|
||||||
|
points: List[Point]
|
||||||
|
# control_points: List[ControlPoint]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FrontLineSegment:
|
||||||
|
"""
|
||||||
|
Describes a line segment of a FrontLine
|
||||||
|
"""
|
||||||
|
|
||||||
|
point_a: Point
|
||||||
|
point_b: Point
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack_heading(self) -> Numeric:
|
||||||
|
return self.point_a.heading_between_point(self.point_b)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack_distance(self) -> Numeric:
|
||||||
|
return self.point_a.distance_to_point(self.point_b)
|
||||||
|
|
||||||
|
|
||||||
class FrontLine(MissionTarget):
|
class FrontLine(MissionTarget):
|
||||||
"""Defines a front line location between two control points.
|
"""Defines a front line location between two control points.
|
||||||
|
|
||||||
Front lines are the area where ground combat happens.
|
Front lines are the area where ground combat happens.
|
||||||
|
Overwrites the entirety of MissionTarget __init__ method to allow for
|
||||||
|
dynamic position calculation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, control_point_a: ControlPoint,
|
def __init__(
|
||||||
control_point_b: ControlPoint) -> None:
|
self,
|
||||||
super().__init__(f"Front line {control_point_a}/{control_point_b}",
|
control_point_a: ControlPoint,
|
||||||
compute_position(control_point_a, control_point_b))
|
control_point_b: ControlPoint,
|
||||||
|
frontline_data: Dict[str, ComplexFrontLine],
|
||||||
|
) -> None:
|
||||||
self.control_point_a = control_point_a
|
self.control_point_a = control_point_a
|
||||||
self.control_point_b = control_point_b
|
self.control_point_b = control_point_b
|
||||||
|
self.segments: List[FrontLineSegment] = []
|
||||||
|
self._build_segments(frontline_data)
|
||||||
|
self.name = f"Front line {control_point_a}/{control_point_b}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self):
|
||||||
|
return self._calculate_position()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||||
"""Returns a tuple of the two control points."""
|
"""Returns a tuple of the two control points."""
|
||||||
return self.control_point_a, self.control_point_b
|
return self.control_point_a, self.control_point_b
|
||||||
|
|
||||||
|
@property
|
||||||
|
def middle_point(self):
|
||||||
|
self.point_from_a(self.attack_distance / 2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack_distance(self):
|
||||||
|
return sum(i.attack_distance for i in self.segments)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack_heading(self):
|
||||||
|
return self.active_segment.attack_heading
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_segment(self) -> FrontLineSegment:
|
||||||
|
"""The FrontLine segment where there can be an active conflict"""
|
||||||
|
if self._position_distance <= self.segments[0].attack_distance:
|
||||||
|
return self.segments[0]
|
||||||
|
|
||||||
|
remaining_dist = self._position_distance
|
||||||
|
for segment in self.segments:
|
||||||
|
if remaining_dist <= segment.attack_distance:
|
||||||
|
return segment
|
||||||
|
else:
|
||||||
|
remaining_dist -= segment.attack_distance
|
||||||
|
logging.error(
|
||||||
|
"Frontline attack distance is greater than the sum of its segments"
|
||||||
|
)
|
||||||
|
return self.segments[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _position_distance(self) -> float:
|
||||||
|
"""
|
||||||
|
The distance from point a where the conflict should occur
|
||||||
|
according to the current strength of each control point
|
||||||
|
"""
|
||||||
|
total_strength = (
|
||||||
|
self.control_point_a.base.strength + self.control_point_b.base.strength
|
||||||
|
)
|
||||||
|
if total_strength == 0:
|
||||||
|
return self._adjust_for_min_dist(0)
|
||||||
|
strength_pct = self.control_point_a.base.strength / total_strength
|
||||||
|
return self._adjust_for_min_dist(strength_pct * self.attack_distance)
|
||||||
|
|
||||||
|
def _calculate_position(self) -> Point:
|
||||||
|
"""
|
||||||
|
The position where the conflict should occur
|
||||||
|
according to the current strength of each control point.
|
||||||
|
"""
|
||||||
|
return self.point_from_a(self._position_distance)
|
||||||
|
|
||||||
|
def _build_segments(self, frontline_data: Dict[str, ComplexFrontLine]) -> None:
|
||||||
|
control_point_ids = "|".join(
|
||||||
|
[str(self.control_point_a.id), str(self.control_point_b.id)]
|
||||||
|
)
|
||||||
|
reversed_cp_ids = "|".join(
|
||||||
|
[str(self.control_point_b.id), str(self.control_point_a.id)]
|
||||||
|
)
|
||||||
|
complex_frontlines = frontline_data
|
||||||
|
if (complex_frontlines) and (
|
||||||
|
(control_point_ids in complex_frontlines)
|
||||||
|
or (reversed_cp_ids in complex_frontlines)
|
||||||
|
):
|
||||||
|
# The frontline segments must be stored in the correct order for the distance algorithms to work.
|
||||||
|
# The points in the frontline are ordered from the id before the | to the id after.
|
||||||
|
# First, check if control point id pair matches in order, and create segments if a match is found.
|
||||||
|
if control_point_ids in complex_frontlines:
|
||||||
|
point_pairs = pairwise(complex_frontlines[control_point_ids].points)
|
||||||
|
for i in point_pairs:
|
||||||
|
self.segments.append(FrontLineSegment(i[0], i[1]))
|
||||||
|
# Check the reverse order and build in reverse if found.
|
||||||
|
elif reversed_cp_ids in complex_frontlines:
|
||||||
|
point_pairs = pairwise(
|
||||||
|
reversed(complex_frontlines[reversed_cp_ids].points)
|
||||||
|
)
|
||||||
|
for i in point_pairs:
|
||||||
|
self.segments.append(FrontLineSegment(i[0], i[1]))
|
||||||
|
# If no complex frontline has been configured, fall back to the old straight line method.
|
||||||
|
else:
|
||||||
|
self.segments.append(
|
||||||
|
FrontLineSegment(
|
||||||
|
self.control_point_a.position, self.control_point_b.position
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _adjust_for_min_dist(self, distance: Numeric) -> Numeric:
|
||||||
|
"""
|
||||||
|
Ensures the frontline conflict is never located within the minimum distance
|
||||||
|
constant of either end control point.
|
||||||
|
"""
|
||||||
|
if (distance > self.attack_distance / 2) and (
|
||||||
|
distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance
|
||||||
|
):
|
||||||
|
distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE
|
||||||
|
elif (distance < self.attack_distance / 2) and (
|
||||||
|
distance < FRONTLINE_MIN_CP_DISTANCE
|
||||||
|
):
|
||||||
|
distance = FRONTLINE_MIN_CP_DISTANCE
|
||||||
|
return distance
|
||||||
|
|
||||||
|
def point_from_a(self, distance: Numeric) -> Point:
|
||||||
|
"""
|
||||||
|
Returns a point {distance} away from control_point_a along the frontline segments.
|
||||||
|
"""
|
||||||
|
if distance < self.segments[0].attack_distance:
|
||||||
|
return self.control_point_a.position.point_from_heading(
|
||||||
|
self.segments[0].attack_heading, distance
|
||||||
|
)
|
||||||
|
remaining_dist = distance
|
||||||
|
for segment in self.segments:
|
||||||
|
if remaining_dist < segment.attack_distance:
|
||||||
|
return self.control_point_a.position.point_from_heading(
|
||||||
|
segment.attack_heading, remaining_dist
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
remaining_dist -= segment.attack_distance
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user