mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Fix armor groups spawning bugs
* Prevent common cases where ground units do not spawn due to frontline position being in exclusion zone * Fix case where ground units will spawn inside exclusion zone due to random offset from frontline center being fixed * Remove dead code from `conflictgen.py` * Start cleanup of `GroundConflictGenerator`
This commit is contained in:
parent
edbe2d86f2
commit
a9f1de13b1
@ -43,3 +43,15 @@ def mps_to_knots(value_in_mps: float) -> int:
|
|||||||
:arg value_in_mps Meters Per Second
|
:arg value_in_mps Meters Per Second
|
||||||
"""
|
"""
|
||||||
return int(value_in_mps * 1.943)
|
return int(value_in_mps * 1.943)
|
||||||
|
|
||||||
|
def heading_sum(h, a) -> int:
|
||||||
|
h += a
|
||||||
|
if h > 360:
|
||||||
|
return h - 360
|
||||||
|
elif h < 0:
|
||||||
|
return 360 + h
|
||||||
|
else:
|
||||||
|
return h
|
||||||
|
|
||||||
|
def opposite_heading(h):
|
||||||
|
return h+180
|
||||||
134
gen/armor.py
134
gen/armor.py
@ -1,8 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
|
from pydcs.dcs.unitgroup import VehicleGroup
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import List, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
from dcs.action import AITaskPush
|
from dcs.action import AITaskPush
|
||||||
@ -29,13 +30,14 @@ from dcs.unittype import VehicleType
|
|||||||
from game import db
|
from game import db
|
||||||
from .naming import namegen
|
from .naming import namegen
|
||||||
from gen.ground_forces.ai_ground_planner import (
|
from gen.ground_forces.ai_ground_planner import (
|
||||||
CombatGroupRole,
|
CombatGroup, CombatGroupRole,
|
||||||
DISTANCE_FROM_FRONTLINE,
|
DISTANCE_FROM_FRONTLINE,
|
||||||
)
|
)
|
||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .ground_forces.combat_stance import CombatStance
|
from .ground_forces.combat_stance import CombatStance
|
||||||
from game.plugins import LuaPluginManager
|
from game.plugins import LuaPluginManager
|
||||||
|
from game.utils import heading_sum, opposite_heading
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -69,7 +71,15 @@ class JtacInfo:
|
|||||||
|
|
||||||
class GroundConflictGenerator:
|
class GroundConflictGenerator:
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game: Game, player_planned_combat_groups, enemy_planned_combat_groups, player_stance):
|
def __init__(
|
||||||
|
self,
|
||||||
|
mission: Mission,
|
||||||
|
conflict: Conflict,
|
||||||
|
game: Game,
|
||||||
|
player_planned_combat_groups: List[CombatGroup],
|
||||||
|
enemy_planned_combat_groups: List[CombatGroup],
|
||||||
|
player_stance: CombatStance
|
||||||
|
):
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.enemy_planned_combat_groups = enemy_planned_combat_groups
|
self.enemy_planned_combat_groups = enemy_planned_combat_groups
|
||||||
@ -102,7 +112,7 @@ class GroundConflictGenerator:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _group_point(self, point) -> Point:
|
def _group_point(self, point: Point) -> Point:
|
||||||
distance = random.randint(
|
distance = random.randint(
|
||||||
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
|
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
|
||||||
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
|
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
|
||||||
@ -110,59 +120,18 @@ class GroundConflictGenerator:
|
|||||||
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR)
|
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
player_groups = []
|
|
||||||
enemy_groups = []
|
|
||||||
|
|
||||||
combat_width = self.conflict.distance/2
|
|
||||||
if combat_width > 500000:
|
|
||||||
combat_width = 500000
|
|
||||||
if combat_width < 35000:
|
|
||||||
combat_width = 35000
|
|
||||||
|
|
||||||
position = Conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater)
|
position = Conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater)
|
||||||
|
frontline_vector = Conflict.frontline_vector(
|
||||||
|
self.conflict.from_cp,
|
||||||
|
self.conflict.to_cp,
|
||||||
|
self.game.theater
|
||||||
|
)
|
||||||
|
|
||||||
# Create player groups at random position
|
# Create player groups at random position
|
||||||
for group in self.player_planned_combat_groups:
|
player_groups = self._generate_groups(self.player_planned_combat_groups, frontline_vector, True)
|
||||||
if group.role == CombatGroupRole.ARTILLERY:
|
|
||||||
distance_from_frontline = self.get_artilery_group_distance_from_frontline(group)
|
|
||||||
else:
|
|
||||||
distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role]
|
|
||||||
final_position = self.get_valid_position_for_group(position, True, combat_width, distance_from_frontline)
|
|
||||||
|
|
||||||
if final_position is not None:
|
|
||||||
g = self._generate_group(
|
|
||||||
side=self.mission.country(self.game.player_country),
|
|
||||||
unit=group.units[0],
|
|
||||||
heading=self.conflict.heading+90,
|
|
||||||
count=len(group.units),
|
|
||||||
at=final_position)
|
|
||||||
g.set_skill(self.game.settings.player_skill)
|
|
||||||
player_groups.append((g,group))
|
|
||||||
|
|
||||||
self.gen_infantry_group_for_group(g, True, self.mission.country(self.game.player_country), self.conflict.heading + 90)
|
|
||||||
else:
|
|
||||||
logging.warning(f"Unable to get valid position for {group}")
|
|
||||||
|
|
||||||
# Create enemy groups at random position
|
# Create enemy groups at random position
|
||||||
for group in self.enemy_planned_combat_groups:
|
enemy_groups = self._generate_groups(self.enemy_planned_combat_groups, frontline_vector, False)
|
||||||
if group.role == CombatGroupRole.ARTILLERY:
|
|
||||||
distance_from_frontline = self.get_artilery_group_distance_from_frontline(group)
|
|
||||||
else:
|
|
||||||
distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role]
|
|
||||||
final_position = self.get_valid_position_for_group(position, False, combat_width, distance_from_frontline)
|
|
||||||
|
|
||||||
if final_position is not None:
|
|
||||||
g = self._generate_group(
|
|
||||||
side=self.mission.country(self.game.enemy_country),
|
|
||||||
unit=group.units[0],
|
|
||||||
heading=self.conflict.heading - 90,
|
|
||||||
count=len(group.units),
|
|
||||||
at=final_position)
|
|
||||||
g.set_skill(self.game.settings.enemy_vehicle_skill)
|
|
||||||
enemy_groups.append((g, group))
|
|
||||||
|
|
||||||
self.gen_infantry_group_for_group(g, False, self.mission.country(self.game.enemy_country), self.conflict.heading - 90)
|
|
||||||
|
|
||||||
# Plan combat actions for groups
|
# Plan combat actions for groups
|
||||||
self.plan_action_for_groups(self.player_stance, player_groups, enemy_groups, self.conflict.heading + 90, self.conflict.from_cp, self.conflict.to_cp)
|
self.plan_action_for_groups(self.player_stance, player_groups, enemy_groups, self.conflict.heading + 90, self.conflict.from_cp, self.conflict.to_cp)
|
||||||
@ -481,13 +450,18 @@ class GroundConflictGenerator:
|
|||||||
return rg
|
return rg
|
||||||
|
|
||||||
|
|
||||||
def get_valid_position_for_group(self, conflict_position, isplayer, combat_width, distance_from_frontline):
|
def get_valid_position_for_group(
|
||||||
|
self,
|
||||||
|
conflict_position: Point,
|
||||||
|
combat_width: int,
|
||||||
|
distance_from_frontline: int,
|
||||||
|
heading: int,
|
||||||
|
spawn_heading: int
|
||||||
|
):
|
||||||
i = 0
|
i = 0
|
||||||
while i < 1000:
|
while i < 1000:
|
||||||
heading_diff = -90 if isplayer else 90
|
shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width))
|
||||||
shifted = conflict_position[0].point_from_heading(self.conflict.heading,
|
final_position = shifted.point_from_heading(spawn_heading, distance_from_frontline)
|
||||||
random.randint((int)(-combat_width / 2), (int)(combat_width / 2)))
|
|
||||||
final_position = shifted.point_from_heading(self.conflict.heading + heading_diff, distance_from_frontline)
|
|
||||||
|
|
||||||
if self.conflict.theater.is_on_land(final_position):
|
if self.conflict.theater.is_on_land(final_position):
|
||||||
return final_position
|
return final_position
|
||||||
@ -495,7 +469,51 @@ class GroundConflictGenerator:
|
|||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, move_formation: PointAction = PointAction.OffRoad, heading=0):
|
def _generate_groups(self, groups: List[CombatGroup], frontline_vector: Tuple[Point, int, int], is_player: bool):
|
||||||
|
"""Finds valid positions for planned groups and generates a pydcs group for them"""
|
||||||
|
positioned_groups = []
|
||||||
|
position, heading, combat_width = frontline_vector
|
||||||
|
spawn_heading = int(heading_sum(heading, -90)) if is_player else int(heading_sum(heading, 90))
|
||||||
|
country = self.game.player_country if is_player else self.game.enemy_country
|
||||||
|
for group in groups:
|
||||||
|
if group.role == CombatGroupRole.ARTILLERY:
|
||||||
|
distance_from_frontline = self.get_artilery_group_distance_from_frontline(group)
|
||||||
|
else:
|
||||||
|
distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role]
|
||||||
|
|
||||||
|
final_position = self.get_valid_position_for_group(
|
||||||
|
position,
|
||||||
|
combat_width,
|
||||||
|
distance_from_frontline,
|
||||||
|
heading,
|
||||||
|
spawn_heading
|
||||||
|
)
|
||||||
|
|
||||||
|
if final_position is not None:
|
||||||
|
g = self._generate_group(
|
||||||
|
self.mission.country(country),
|
||||||
|
group.units[0],
|
||||||
|
len(group.units),
|
||||||
|
final_position,
|
||||||
|
heading=opposite_heading(spawn_heading)
|
||||||
|
)
|
||||||
|
g.set_skill(self.game.settings.player_skill)
|
||||||
|
positioned_groups.append((g,group))
|
||||||
|
self.gen_infantry_group_for_group(g, True, self.mission.country(country), opposite_heading(spawn_heading))
|
||||||
|
else:
|
||||||
|
logging.warning(f"Unable to get valid position for {group}")
|
||||||
|
|
||||||
|
return positioned_groups
|
||||||
|
|
||||||
|
def _generate_group(
|
||||||
|
self,
|
||||||
|
side: Country,
|
||||||
|
unit: VehicleType,
|
||||||
|
count: int,
|
||||||
|
at: Point,
|
||||||
|
move_formation: PointAction = PointAction.OffRoad,
|
||||||
|
heading=0
|
||||||
|
) -> VehicleGroup:
|
||||||
|
|
||||||
if side == self.conflict.attackers_country:
|
if side == self.conflict.attackers_country:
|
||||||
cp = self.conflict.from_cp
|
cp = self.conflict.from_cp
|
||||||
|
|||||||
@ -7,25 +7,11 @@ from dcs.mapping import Point
|
|||||||
|
|
||||||
from game.theater.conflicttheater import ConflictTheater, FrontLine
|
from game.theater.conflicttheater import ConflictTheater, FrontLine
|
||||||
from game.theater.controlpoint import ControlPoint
|
from game.theater.controlpoint import ControlPoint
|
||||||
|
from game.utils import heading_sum, opposite_heading
|
||||||
|
|
||||||
|
|
||||||
FRONTLINE_LENGTH = 80000
|
FRONTLINE_LENGTH = 80000
|
||||||
|
|
||||||
|
|
||||||
def _opposite_heading(h):
|
|
||||||
return h+180
|
|
||||||
|
|
||||||
|
|
||||||
def _heading_sum(h, a) -> int:
|
|
||||||
h += a
|
|
||||||
if h > 360:
|
|
||||||
return h - 360
|
|
||||||
elif h < 0:
|
|
||||||
return 360 + h
|
|
||||||
else:
|
|
||||||
return h
|
|
||||||
|
|
||||||
|
|
||||||
class Conflict:
|
class Conflict:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
@ -37,7 +23,7 @@ class Conflict:
|
|||||||
defenders_country: Country,
|
defenders_country: Country,
|
||||||
position: Point,
|
position: Point,
|
||||||
heading=None,
|
heading=None,
|
||||||
distance=None,
|
size=None
|
||||||
):
|
):
|
||||||
|
|
||||||
self.attackers_side = attackers_side
|
self.attackers_side = attackers_side
|
||||||
@ -50,49 +36,18 @@ class Conflict:
|
|||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.position = position
|
self.position = position
|
||||||
self.heading = heading
|
self.heading = heading
|
||||||
self.distance = distance
|
self.size = size
|
||||||
self.size = to_cp.size
|
|
||||||
|
|
||||||
@property
|
|
||||||
def center(self) -> Point:
|
|
||||||
return self.position.point_from_heading(self.heading, self.distance / 2)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tail(self) -> Point:
|
|
||||||
return self.position.point_from_heading(self.heading, self.distance)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_vector(self) -> bool:
|
|
||||||
return self.heading is not None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def opposite_heading(self) -> int:
|
|
||||||
return _heading_sum(self.heading, 180)
|
|
||||||
|
|
||||||
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> Point:
|
|
||||||
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
||||||
return from_cp.has_frontline and to_cp.has_frontline
|
return from_cp.has_frontline and to_cp.has_frontline
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def frontline_position(from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int]:
|
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int]:
|
||||||
frontline = FrontLine(from_cp, to_cp, theater)
|
frontline = FrontLine(from_cp, to_cp, theater)
|
||||||
attack_heading = frontline.attack_heading
|
attack_heading = frontline.attack_heading
|
||||||
position = frontline.position
|
position = cls.find_ground_position(frontline.position, FRONTLINE_LENGTH, heading_sum(attack_heading, 90), theater)
|
||||||
return position, _opposite_heading(attack_heading)
|
return position, opposite_heading(attack_heading)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def flight_frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
|
|
||||||
"""Returns the frontline vector without regard for exclusion zones, used in CAS flight plan"""
|
|
||||||
frontline = cls.frontline_position(from_cp, to_cp, theater)
|
|
||||||
center_position, heading = frontline
|
|
||||||
left_position = center_position.point_from_heading(_heading_sum(heading, -90), int(FRONTLINE_LENGTH/2))
|
|
||||||
right_position = center_position.point_from_heading(_heading_sum(heading, 90), int(FRONTLINE_LENGTH/2))
|
|
||||||
|
|
||||||
return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position))
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
|
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
|
||||||
@ -100,11 +55,10 @@ class Conflict:
|
|||||||
Returns a vector for a valid frontline location avoiding exclusion zones.
|
Returns a vector for a valid frontline location avoiding exclusion zones.
|
||||||
"""
|
"""
|
||||||
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
|
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
|
||||||
center_position = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, 90), theater)
|
left_heading = heading_sum(heading, -90)
|
||||||
left_heading = _heading_sum(heading, 90)
|
right_heading = heading_sum(heading, 90)
|
||||||
right_heading = _heading_sum(heading, -90)
|
left_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater)
|
||||||
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater)
|
right_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
|
||||||
right_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
|
|
||||||
distance = int(left_position.distance_to_point(right_position))
|
distance = int(left_position.distance_to_point(right_position))
|
||||||
return left_position, right_heading, distance
|
return left_position, right_heading, distance
|
||||||
|
|
||||||
@ -112,11 +66,9 @@ class Conflict:
|
|||||||
def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
assert cls.has_frontline_between(from_cp, to_cp)
|
assert cls.has_frontline_between(from_cp, to_cp)
|
||||||
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||||
|
conflict = cls(
|
||||||
return cls(
|
|
||||||
position=position,
|
position=position,
|
||||||
heading=heading,
|
heading=heading,
|
||||||
distance=distance,
|
|
||||||
theater=theater,
|
theater=theater,
|
||||||
from_cp=from_cp,
|
from_cp=from_cp,
|
||||||
to_cp=to_cp,
|
to_cp=to_cp,
|
||||||
@ -124,10 +76,12 @@ class Conflict:
|
|||||||
defenders_side=defender_name,
|
defenders_side=defender_name,
|
||||||
attackers_country=attacker,
|
attackers_country=attacker,
|
||||||
defenders_country=defender,
|
defenders_country=defender,
|
||||||
|
size=distance
|
||||||
)
|
)
|
||||||
|
return conflict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
def extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||||
"""Finds a valid ground position in one heading from an initial point"""
|
"""Finds a valid ground position in one heading from an initial point"""
|
||||||
pos = initial
|
pos = initial
|
||||||
for distance in range(0, int(max_distance), 100):
|
for distance in range(0, int(max_distance), 100):
|
||||||
@ -140,7 +94,7 @@ class Conflict:
|
|||||||
return initial
|
return initial
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||||
"""Finds the nearest ground position along a provided heading and it's inverse"""
|
"""Finds the nearest ground position along a provided heading and it's inverse"""
|
||||||
pos = initial
|
pos = initial
|
||||||
for distance in range(0, int(max_distance), 100):
|
for distance in range(0, int(max_distance), 100):
|
||||||
@ -149,6 +103,6 @@ class Conflict:
|
|||||||
pos = initial.point_from_heading(heading, distance)
|
pos = initial.point_from_heading(heading, distance)
|
||||||
if theater.is_on_land(pos):
|
if theater.is_on_land(pos):
|
||||||
return pos
|
return pos
|
||||||
pos = initial.point_from_heading(_opposite_heading(heading), distance)
|
pos = initial.point_from_heading(opposite_heading(heading), distance)
|
||||||
logging.error("Didn't find ground position ({})!".format(initial))
|
logging.error("Didn't find ground position ({})!".format(initial))
|
||||||
return initial
|
return initial
|
||||||
|
|||||||
@ -1038,7 +1038,7 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, FrontLine):
|
if not isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
ingress, heading, distance = Conflict.flight_frontline_vector(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
location.control_points[0], location.control_points[1],
|
location.control_points[0], location.control_points[1],
|
||||||
self.game.theater
|
self.game.theater
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user