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 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.countries import (
CombinedJointTaskForcesBlue,
@ -472,6 +475,32 @@ class ConflictTheater:
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]:
return [point for point in self.controlpoints if point.captured]

View File

@ -177,7 +177,12 @@ class GroundConflictGenerator:
forward_heading: int
) -> 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:
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
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)
def _plan_artillery_action(
@ -326,7 +331,10 @@ 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)
dcs_group.points[2].tasks.append(AttackGroup(target.id))
if (
@ -334,7 +342,9 @@ class GroundConflictGenerator:
<=
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:
attack_point = self.find_offensive_point(
dcs_group,
@ -347,7 +357,9 @@ class GroundConflictGenerator:
# If the enemy base is close enough, the units will attack the base
if to_cp.position.distance_to_point(
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:
attack_point = self.find_offensive_point(dcs_group, forward_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
@ -364,10 +376,15 @@ class GroundConflictGenerator:
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))
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)
if stance != CombatStance.RETREAT:
@ -390,7 +407,7 @@ class GroundConflictGenerator:
if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]:
# 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:
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:
attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
@ -491,12 +508,9 @@ class GroundConflictGenerator:
: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)
return Conflict.find_ground_position(
desired_point,
1000000,
heading_sum(frontline_heading, 90),
self.conflict.theater,
)
if self.conflict.theater.is_on_land(desired_point):
return desired_point
return self.conflict.theater.nearest_land_pos(desired_point)
def find_offensive_point(
@ -513,12 +527,9 @@ class GroundConflictGenerator:
:return: dcs.mapping.Point object with the desired position
"""
desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance)
return Conflict.find_ground_position(
desired_point,
1000000,
heading_sum(frontline_heading, 90),
self.conflict.theater,
)
if self.conflict.theater.is_on_land(desired_point):
return desired_point
return self.conflict.theater.nearest_land_pos(desired_point)
@staticmethod
def find_n_nearest_enemy_groups(
@ -681,7 +692,7 @@ class GroundConflictGenerator:
group = self.mission.vehicle_group(
side,
namegen.next_unit_name(side, cp.id, unit), unit,
position=self._group_point(at, distance_from_frontline),
position=at,
group_size=count,
heading=heading,
move_formation=move_formation)

View File

@ -91,8 +91,12 @@ class Conflict:
return pos
@classmethod
def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
"""Finds the nearest valid ground position along a provided heading and it's inverse"""
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 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
if theater.is_on_land(pos):
return pos
@ -101,5 +105,9 @@ class Conflict:
if theater.is_on_land(pos):
return pos
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))
return None