mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
initial multi segment frontline implementation
This commit is contained in:
parent
5719b136fe
commit
33885e2216
@ -393,8 +393,7 @@ class Game:
|
||||
|
||||
# By default, use the existing frontline conflict position
|
||||
for front_line in self.theater.conflicts():
|
||||
position = Conflict.frontline_position(self.theater,
|
||||
front_line.control_point_a,
|
||||
position = Conflict.frontline_position(front_line.control_point_a,
|
||||
front_line.control_point_b)
|
||||
points.append(position[0])
|
||||
points.append(front_line.control_point_a.position)
|
||||
|
||||
@ -93,7 +93,7 @@ class GroundConflictGenerator:
|
||||
if combat_width < 35000:
|
||||
combat_width = 35000
|
||||
|
||||
position = Conflict.frontline_position(self.game.theater, self.conflict.from_cp, self.conflict.to_cp)
|
||||
position = Conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp)
|
||||
|
||||
# Create player groups at random position
|
||||
for group in self.player_planned_combat_groups:
|
||||
|
||||
@ -6,6 +6,7 @@ from dcs.country import Country
|
||||
from dcs.mapping import Point
|
||||
|
||||
from theater import ConflictTheater, ControlPoint
|
||||
from theater.frontline import FrontLine
|
||||
|
||||
AIR_DISTANCE = 40000
|
||||
|
||||
@ -134,14 +135,11 @@ class Conflict:
|
||||
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
||||
return from_cp.has_frontline and to_cp.has_frontline
|
||||
|
||||
@classmethod
|
||||
def frontline_position(cls, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint) -> Tuple[Point, int]:
|
||||
attack_heading = from_cp.position.heading_between_point(to_cp.position)
|
||||
attack_distance = from_cp.position.distance_to_point(to_cp.position)
|
||||
middle_point = from_cp.position.point_from_heading(attack_heading, attack_distance / 2)
|
||||
|
||||
strength_delta = (from_cp.base.strength - to_cp.base.strength) / 1.0
|
||||
position = middle_point.point_from_heading(attack_heading, strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
|
||||
@staticmethod
|
||||
def frontline_position(from_cp: ControlPoint, to_cp: ControlPoint) -> Tuple[Point, int]:
|
||||
frontline = FrontLine(from_cp, to_cp)
|
||||
attack_heading = frontline.attack_heading
|
||||
position = frontline.position
|
||||
return position, _opposite_heading(attack_heading)
|
||||
|
||||
|
||||
@ -162,7 +160,7 @@ class Conflict:
|
||||
|
||||
return Point(*intersection.xy[0]), _heading_sum(heading, 90), intersection.length
|
||||
"""
|
||||
frontline = cls.frontline_position(theater, from_cp, to_cp)
|
||||
frontline = cls.frontline_position(from_cp, to_cp)
|
||||
center_position, heading = frontline
|
||||
left_position, right_position = None, None
|
||||
|
||||
@ -479,7 +477,7 @@ class Conflict:
|
||||
|
||||
@classmethod
|
||||
def transport_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||
frontline_position, heading = cls.frontline_position(theater, from_cp, to_cp)
|
||||
frontline_position, heading = cls.frontline_position(from_cp, to_cp)
|
||||
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
|
||||
dest = cls._find_ground_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
|
||||
if not dest:
|
||||
|
||||
@ -370,7 +370,7 @@ class GroundObjectsGenerator:
|
||||
center = self.conflict.center
|
||||
heading = self.conflict.heading - 90
|
||||
else:
|
||||
center, heading = self.conflict.frontline_position(self.conflict.theater, self.conflict.from_cp, self.conflict.to_cp)
|
||||
center, heading = self.conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp)
|
||||
heading -= 90
|
||||
|
||||
initial_position = center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE)
|
||||
|
||||
@ -104,7 +104,7 @@ class VisualGenerator:
|
||||
if from_cp.is_global or to_cp.is_global:
|
||||
continue
|
||||
|
||||
frontline = Conflict.frontline_position(self.game.theater, from_cp, to_cp)
|
||||
frontline = Conflict.frontline_position(from_cp, to_cp)
|
||||
if not frontline:
|
||||
continue
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
if cp.captured:
|
||||
enemy_cp = [ecp for ecp in cp.connected_points if ecp.captured != cp.captured]
|
||||
for ecp in enemy_cp:
|
||||
pos = Conflict.frontline_position(self.game.theater, cp, ecp)[0]
|
||||
pos = Conflict.frontline_position(cp, ecp)[0]
|
||||
wpt = FlightWaypoint(
|
||||
FlightWaypointType.CUSTOM,
|
||||
pos.x,
|
||||
|
||||
@ -38,7 +38,8 @@ from qt_ui.widgets.map.QLiberationScene import QLiberationScene
|
||||
from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint
|
||||
from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from theater import ControlPoint, FrontLine
|
||||
from theater import ControlPoint
|
||||
from theater.frontline import FrontLine
|
||||
from theater.theatergroundobject import (
|
||||
EwrGroundObject,
|
||||
MissileSiteGroundObject,
|
||||
@ -407,13 +408,21 @@ class QLiberationMap(QGraphicsView):
|
||||
pen = QPen(brush=color)
|
||||
pen.setColor(color)
|
||||
pen.setWidth(6)
|
||||
frontline = FrontLine(cp, connected_cp)
|
||||
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
|
||||
if not cp.captured:
|
||||
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
||||
else:
|
||||
posx, h = Conflict.frontline_position(self.game.theater, cp, connected_cp)
|
||||
# pass
|
||||
# frontline = FrontLine(cp, connected_cp, self.game.theater.terrain)
|
||||
posx = frontline.position
|
||||
h = frontline.attack_heading
|
||||
pos2 = self._transform_point(posx)
|
||||
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
||||
for segment in frontline.segments:
|
||||
seg_a = self._transform_point(segment.point_a)
|
||||
seg_b = self._transform_point(segment.point_b)
|
||||
scene.addLine(seg_a[0], seg_a[1], seg_b[0], seg_b[1], pen=pen)
|
||||
# scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
||||
|
||||
p1 = point_from_heading(pos2[0], pos2[1], h+180, 25)
|
||||
p2 = point_from_heading(pos2[0], pos2[1], h, 25)
|
||||
@ -421,7 +430,11 @@ class QLiberationMap(QGraphicsView):
|
||||
FrontLine(cp, connected_cp)))
|
||||
|
||||
else:
|
||||
scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
||||
for segment in frontline.segments:
|
||||
seg_a = self._transform_point(segment.point_a)
|
||||
seg_b = self._transform_point(segment.point_b)
|
||||
scene.addLine(seg_a[0], seg_a[1], seg_b[0], seg_b[1], pen=pen)
|
||||
# scene.addLine(pos[0], pos[1], pos2[0], pos2[1], pen=pen)
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent):
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ from dcs.terrain.terrain import Terrain
|
||||
|
||||
from .controlpoint import ControlPoint
|
||||
from .landmap import Landmap, load_landmap, poly_contains
|
||||
from .frontline import FrontLine
|
||||
from .frontline import FrontLine, ComplexFrontLine
|
||||
|
||||
SIZE_TINY = 150
|
||||
SIZE_SMALL = 600
|
||||
@ -61,6 +61,7 @@ class ConflictTheater:
|
||||
reference_points: Dict[Tuple[float, float], Tuple[float, float]]
|
||||
overview_image: str
|
||||
landmap: Optional[Landmap]
|
||||
frontline_data: Optional[Dict[str, ComplexFrontLine]] = None
|
||||
"""
|
||||
land_poly = None # type: Polygon
|
||||
"""
|
||||
@ -68,6 +69,7 @@ class ConflictTheater:
|
||||
|
||||
def __init__(self):
|
||||
self.controlpoints: List[ControlPoint] = []
|
||||
ConflictTheater.frontline_data = FrontLine.load_json_frontlines(self)
|
||||
"""
|
||||
self.land_poly = geometry.Polygon(self.landmap[0][0])
|
||||
for x in self.landmap[1]:
|
||||
@ -196,7 +198,7 @@ class ConflictTheater:
|
||||
cps[l[1]].connect(cps[l[0]])
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
||||
class CaucasusTheater(ConflictTheater):
|
||||
terrain = caucasus.Caucasus()
|
||||
@ -228,7 +230,6 @@ class PersianGulfTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class NevadaTheater(ConflictTheater):
|
||||
terrain = nevada.Nevada()
|
||||
overview_image = "nevada.gif"
|
||||
@ -242,7 +243,6 @@ class NevadaTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class NormandyTheater(ConflictTheater):
|
||||
terrain = normandy.Normandy()
|
||||
overview_image = "normandy.gif"
|
||||
@ -256,7 +256,6 @@ class NormandyTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class TheChannelTheater(ConflictTheater):
|
||||
terrain = thechannel.TheChannel()
|
||||
overview_image = "thechannel.gif"
|
||||
@ -270,7 +269,6 @@ class TheChannelTheater(ConflictTheater):
|
||||
"night": (0, 5),
|
||||
}
|
||||
|
||||
|
||||
class SyriaTheater(ConflictTheater):
|
||||
terrain = syria.Syria()
|
||||
overview_image = "syria.gif"
|
||||
@ -282,4 +280,4 @@ class SyriaTheater(ConflictTheater):
|
||||
"day": (8, 16),
|
||||
"dusk": (16, 18),
|
||||
"night": (0, 5),
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,21 @@
|
||||
"""Battlefield front lines."""
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
import logging
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from itertools import tee
|
||||
from typing import Tuple, List, Union, Dict, Optional
|
||||
from typing import Tuple, List, Union, Dict, Optional, TYPE_CHECKING
|
||||
|
||||
from dcs.mapping import Point
|
||||
|
||||
from .controlpoint import ControlPoint, MissionTarget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from theater.conflicttheater import ConflictTheater
|
||||
|
||||
Numeric = Union[int, float]
|
||||
|
||||
# TODO: Dedup by moving everything to using this class.
|
||||
@ -19,7 +23,10 @@ FRONTLINE_MIN_CP_DISTANCE = 5000
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
"""
|
||||
itertools recipe
|
||||
s -> (s0,s1), (s1,s2), (s2, s3), ...
|
||||
"""
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
@ -48,10 +55,12 @@ class FrontLineSegment:
|
||||
|
||||
@property
|
||||
def attack_heading(self) -> Numeric:
|
||||
"""The heading of the frontline segment from player to enemy control point"""
|
||||
return self.point_a.heading_between_point(self.point_b)
|
||||
|
||||
@property
|
||||
def attack_distance(self) -> Numeric:
|
||||
"""Length of the segment"""
|
||||
return self.point_a.distance_to_point(self.point_b)
|
||||
|
||||
|
||||
@ -61,7 +70,8 @@ class FrontLine(MissionTarget):
|
||||
Overwrites the entirety of MissionTarget __init__ method to allow for
|
||||
dynamic position calculation.
|
||||
"""
|
||||
frontline_data: Optional[Dict[str, ComplexFrontLine]] = None
|
||||
|
||||
theater: ConflictTheater
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -73,7 +83,6 @@ class FrontLine(MissionTarget):
|
||||
self.segments: List[FrontLineSegment] = []
|
||||
self._build_segments()
|
||||
self.name = f"Front line {control_point_a}/{control_point_b}"
|
||||
print(f"FRONTLINE SEGMENTS {len(self.segments)}")
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
@ -90,10 +99,12 @@ class FrontLine(MissionTarget):
|
||||
|
||||
@property
|
||||
def attack_distance(self):
|
||||
"""The total distance of all segments"""
|
||||
return sum(i.attack_distance for i in self.segments)
|
||||
|
||||
@property
|
||||
def attack_heading(self):
|
||||
"""The heading of the active attack segment from player to enemy control point"""
|
||||
return self.active_segment.attack_heading
|
||||
|
||||
@property
|
||||
@ -113,6 +124,30 @@ class FrontLine(MissionTarget):
|
||||
)
|
||||
return self.segments[0]
|
||||
|
||||
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 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 segment.point_a.point_from_heading(
|
||||
segment.attack_heading, remaining_dist
|
||||
)
|
||||
else:
|
||||
remaining_dist -= segment.attack_distance
|
||||
|
||||
@property
|
||||
def _position_distance(self) -> float:
|
||||
"""
|
||||
@ -122,44 +157,37 @@ class FrontLine(MissionTarget):
|
||||
total_strength = (
|
||||
self.control_point_a.base.strength + self.control_point_b.base.strength
|
||||
)
|
||||
if total_strength == 0:
|
||||
if self.control_point_a.base.strength == 0:
|
||||
return self._adjust_for_min_dist(0)
|
||||
if self.control_point_b.base.strength == 0:
|
||||
return self._adjust_for_min_dist(self.attack_distance)
|
||||
strength_pct = self.control_point_a.base.strength / total_strength
|
||||
return self._adjust_for_min_dist(strength_pct * self.attack_distance)
|
||||
|
||||
@classmethod
|
||||
def load_json_frontlines(cls, terrain_name: str) -> None:
|
||||
try:
|
||||
path = Path(f"resources/frontlines/{terrain_name.lower()}.json")
|
||||
with open(path, "r") as file:
|
||||
logging.debug(f"Loading frontline from {path}...")
|
||||
data = json.load(file)
|
||||
cls.frontline_data = {
|
||||
frontline: ComplexFrontLine(
|
||||
data[frontline]["start_cp"],
|
||||
[Point(i[0], i[1]) for i in data[frontline]["points"]],
|
||||
)
|
||||
for frontline in data
|
||||
}
|
||||
print(cls.frontline_data)
|
||||
except OSError:
|
||||
logging.warning(f"Unable to load preset frontlines for {terrain_name}")
|
||||
|
||||
def _calculate_position(self) -> Point:
|
||||
def _adjust_for_min_dist(self, distance: Numeric) -> Numeric:
|
||||
"""
|
||||
The position where the conflict should occur
|
||||
according to the current strength of each control point.
|
||||
Ensures the frontline conflict is never located within the minimum distance
|
||||
constant of either end control point.
|
||||
"""
|
||||
return self.point_from_a(self._position_distance)
|
||||
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 _build_segments(self) -> None:
|
||||
"""Create line segments for the frontline"""
|
||||
control_point_ids = "|".join(
|
||||
[str(self.control_point_a.id), str(self.control_point_b.id)]
|
||||
)
|
||||
) # from_cp.id|to_cp.id
|
||||
reversed_cp_ids = "|".join(
|
||||
[str(self.control_point_b.id), str(self.control_point_a.id)]
|
||||
)
|
||||
complex_frontlines = FrontLine.frontline_data
|
||||
complex_frontlines = self.theater.frontline_data
|
||||
if (complex_frontlines) and (
|
||||
(control_point_ids in complex_frontlines)
|
||||
or (reversed_cp_ids in complex_frontlines)
|
||||
@ -186,34 +214,26 @@ class FrontLine(MissionTarget):
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
@classmethod
|
||||
def load_json_frontlines(
|
||||
cls, theater: ConflictTheater
|
||||
) -> Optional[Dict[str, ComplexFrontLine]]:
|
||||
"""Load complex frontlines from json and set the theater class variable to current theater instance"""
|
||||
cls.theater = theater
|
||||
try:
|
||||
path = Path(f"resources/frontlines/{theater.terrain.name.lower()}.json")
|
||||
with open(path, "r") as file:
|
||||
logging.debug(f"Loading frontline from {path}...")
|
||||
data = json.load(file)
|
||||
return {
|
||||
frontline: ComplexFrontLine(
|
||||
data[frontline]["start_cp"],
|
||||
[Point(i[0], i[1]) for i in data[frontline]["points"]],
|
||||
)
|
||||
else:
|
||||
remaining_dist -= segment.attack_distance
|
||||
for frontline in data
|
||||
}
|
||||
except OSError:
|
||||
logging.warning(
|
||||
f"Unable to load preset frontlines for {theater.terrain.name}"
|
||||
)
|
||||
return None
|
||||
|
||||
@ -74,7 +74,6 @@ class GameGenerator:
|
||||
namegen.reset()
|
||||
self.prepare_theater()
|
||||
self.populate_red_airbases()
|
||||
FrontLine.load_json_frontlines(self.theater.terrain.name)
|
||||
game = Game(player_name=self.player,
|
||||
enemy_name=self.enemy,
|
||||
theater=self.theater,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user