Improve Ground Unit Behavior

* Prevent spinning units
* Prevent units spawning in exclusion zone
* Prevent units from moving into exclusion zone
This commit is contained in:
walterroach 2020-12-12 15:33:26 -06:00
parent 817d6a0e15
commit c8e71d269b
3 changed files with 71 additions and 23 deletions

View File

@ -9,6 +9,9 @@ from itertools import tee
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
from shapely import geometry
from shapely import ops
from dcs import Mission from dcs import Mission
from dcs.countries import ( from dcs.countries import (
CombinedJointTaskForcesBlue, CombinedJointTaskForcesBlue,
@ -472,6 +475,32 @@ class ConflictTheater:
return True return True
def nearest_land_pos(self, point: Point, extend_dist: int = 50) -> Point:
"""Returns the nearest point inside a land exclusion zone from point
`extend_dist` determines how far inside the zone the point should be placed"""
if self.is_on_land(point):
return point
point = geometry.Point(point.x, point.y)
nearest_points = []
for inclusion_zone in self.landmap[0]:
nearest_pair = ops.nearest_points(point, inclusion_zone)
nearest_points.append(nearest_pair[1])
min_distance = None # type: Optional[geometry.Point]
nearest_point = None # type: Optional[geometry.Point]
for pt in nearest_points:
distance = point.distance(pt)
if not min_distance or distance < min_distance:
min_distance = distance
nearest_point = pt
assert isinstance(nearest_point, geometry.Point)
point = Point(point.x, point.y)
nearest_point = Point(nearest_point.x, nearest_point.y)
new_point = point.point_from_heading(
point.heading_between_point(nearest_point),
point.distance_to_point(nearest_point) + extend_dist
)
return new_point
def player_points(self) -> List[ControlPoint]: def player_points(self) -> List[ControlPoint]:
return [point for point in self.controlpoints if point.captured] return [point for point in self.controlpoints if point.captured]

View File

@ -177,7 +177,12 @@ class GroundConflictGenerator:
forward_heading: int forward_heading: int
) -> None: ) -> None:
infantry_position = group.points[0].position.random_point_within(250, 50) infantry_position = self.conflict.find_ground_position(
group.points[0].position.random_point_within(250, 50),
500,
forward_heading,
self.conflict.theater
)
if side == self.conflict.attackers_country: if side == self.conflict.attackers_country:
cp = self.conflict.from_cp cp = self.conflict.from_cp
@ -236,7 +241,7 @@ class GroundConflictGenerator:
"""Setting a waypoint close to the spawn position allows the group to reform gracefully """Setting a waypoint close to the spawn position allows the group to reform gracefully
rather than spin rather than spin
""" """
reform_point = dcs_group.position.point_from_heading(forward_heading, 30) reform_point = dcs_group.position.point_from_heading(forward_heading, 50)
dcs_group.add_waypoint(reform_point) dcs_group.add_waypoint(reform_point)
def _plan_artillery_action( def _plan_artillery_action(
@ -326,7 +331,10 @@ class GroundConflictGenerator:
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK -RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
) )
) )
dcs_group.add_waypoint(target.points[0].position + rand_offset, PointAction.OffRoad) target_point = self.conflict.theater.nearest_land_pos(
target.points[0].position + rand_offset
)
dcs_group.add_waypoint(target_point)
dcs_group.points[2].tasks.append(AttackGroup(target.id)) dcs_group.points[2].tasks.append(AttackGroup(target.id))
if ( if (
@ -334,7 +342,9 @@ class GroundConflictGenerator:
<= <=
AGGRESIVE_MOVE_DISTANCE AGGRESIVE_MOVE_DISTANCE
): ):
attack_point = to_cp.position.random_point_within(500, 0) attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
else: else:
attack_point = self.find_offensive_point( attack_point = self.find_offensive_point(
dcs_group, dcs_group,
@ -347,7 +357,9 @@ class GroundConflictGenerator:
# If the enemy base is close enough, the units will attack the base # If the enemy base is close enough, the units will attack the base
if to_cp.position.distance_to_point( if to_cp.position.distance_to_point(
dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE: dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE:
attack_point = to_cp.position.random_point_within(500, 0) attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
else: else:
attack_point = self.find_offensive_point(dcs_group, forward_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE) attack_point = self.find_offensive_point(dcs_group, forward_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad) dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
@ -364,10 +376,15 @@ class GroundConflictGenerator:
RANDOM_OFFSET_ATTACK RANDOM_OFFSET_ATTACK
) )
) )
dcs_group.add_waypoint(target.points[0].position+rand_offset, PointAction.OffRoad) target_point = self.conflict.theater.nearest_land_pos(
target.points[0].position+rand_offset
)
dcs_group.add_waypoint(target_point, PointAction.OffRoad)
dcs_group.points[i + 1].tasks.append(AttackGroup(target.id)) dcs_group.points[i + 1].tasks.append(AttackGroup(target.id))
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
attack_point = to_cp.position.random_point_within(500, 0) attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
dcs_group.add_waypoint(attack_point) dcs_group.add_waypoint(attack_point)
if stance != CombatStance.RETREAT: if stance != CombatStance.RETREAT:
@ -390,7 +407,7 @@ class GroundConflictGenerator:
if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]: if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]:
# APC & ATGM will never move too much forward, but will follow along any offensive # APC & ATGM will never move too much forward, but will follow along any offensive
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
attack_point = to_cp.position.random_point_within(500, 0) attack_point = self.conflict.theater.nearest_land_pos(to_cp.position.random_point_within(500, 0))
else: else:
attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE) attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad) dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
@ -491,12 +508,9 @@ class GroundConflictGenerator:
:return: dcs.mapping.Point object with the desired position :return: dcs.mapping.Point object with the desired position
""" """
desired_point = dcs_group.points[0].position.point_from_heading(heading_sum(frontline_heading, +180), distance) desired_point = dcs_group.points[0].position.point_from_heading(heading_sum(frontline_heading, +180), distance)
return Conflict.find_ground_position( if self.conflict.theater.is_on_land(desired_point):
desired_point, return desired_point
1000000, return self.conflict.theater.nearest_land_pos(desired_point)
heading_sum(frontline_heading, 90),
self.conflict.theater,
)
def find_offensive_point( def find_offensive_point(
@ -513,12 +527,9 @@ class GroundConflictGenerator:
:return: dcs.mapping.Point object with the desired position :return: dcs.mapping.Point object with the desired position
""" """
desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance) desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance)
return Conflict.find_ground_position( if self.conflict.theater.is_on_land(desired_point):
desired_point, return desired_point
1000000, return self.conflict.theater.nearest_land_pos(desired_point)
heading_sum(frontline_heading, 90),
self.conflict.theater,
)
@staticmethod @staticmethod
def find_n_nearest_enemy_groups( def find_n_nearest_enemy_groups(
@ -681,7 +692,7 @@ class GroundConflictGenerator:
group = self.mission.vehicle_group( group = self.mission.vehicle_group(
side, side,
namegen.next_unit_name(side, cp.id, unit), unit, namegen.next_unit_name(side, cp.id, unit), unit,
position=self._group_point(at, distance_from_frontline), position=at,
group_size=count, group_size=count,
heading=heading, heading=heading,
move_formation=move_formation) move_formation=move_formation)

View File

@ -91,8 +91,12 @@ class Conflict:
return pos return pos
@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, coerce=True) -> Optional[Point]:
"""Finds the nearest valid ground position along a provided heading and it's inverse""" """
Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance.
`coerce=True` will return the closest land position to `initial` regardless of heading or distance
`coerce=False` will return None if a point isn't found
"""
pos = initial pos = initial
if theater.is_on_land(pos): if theater.is_on_land(pos):
return pos return pos
@ -101,5 +105,9 @@ class Conflict:
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)
if coerce:
pos = theater.nearest_land_pos(initial)
return pos
logging.error("Didn't find ground position ({})!".format(initial)) logging.error("Didn't find ground position ({})!".format(initial))
return None return None