randomized strike objects with templates; forbid ground objects and vehicles placement on mountains and in forests; updated push trigger so it include player group; adjacent CP missions could be initiated from carriers

This commit is contained in:
Vasyl Horbachenko 2018-10-11 03:45:20 +03:00
parent 8431c7745d
commit e28a24c875
24 changed files with 228 additions and 62 deletions

View File

@ -70,7 +70,8 @@ try:
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
conflicttheater.controlpoints[i].captured = True
start_generator.generate_initial(conflicttheater, enemy_name, sams, multiplier)
start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
start_generator.generate_groundobjects(conflicttheater)
game = Game(player_name=player_name,
enemy_name=enemy_name,
theater=conflicttheater)

View File

@ -112,14 +112,18 @@ class Game:
enemy_generated_types = []
for player_cp, enemy_cp in self.theater.conflicts(True):
if player_cp.is_global or enemy_cp.is_global:
if enemy_cp.is_global:
continue
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent:
if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]:
if not Conflict.has_frontline_between(player_cp, enemy_cp):
continue
if player_cp.is_global:
if event_class not in [InterceptEvent, StrikeEvent, NavalInterceptEvent]:
continue
if player_probability == 100 or self._roll(player_probability, player_cp.base.strength):
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
pass

View File

@ -47,7 +47,9 @@ class FrontlineAttackOperation(Operation):
self.briefinggen.append_frequency("FARP", "127.5 MHz AM")
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), at=farp, escort=False)
self.airgen.generate_cas_strikegroup(*assigned_units_split(dict),
at=farp,
escort=len(planes_flights) == 0)
self.briefinggen.title = "Frontline CAS"
self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."

View File

@ -52,12 +52,22 @@ class StrikeOperation(Operation):
targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[1]))
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(self.strikegroup),
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights),
targets=targets,
at=self.attackers_starting_position)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
if heli_flights:
self.briefinggen.append_frequency("FARP", "127.5 MHz AM")
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(dict),
targets=targets,
at=farp,
escort=len(planes_flights) == 0)
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position)
self.briefinggen.title = "Strike"

View File

@ -21,6 +21,10 @@ class AirSupportConflictGenerator:
self.conflict = conflict
self.game = game
@classmethod
def support_tasks(cls) -> typing.Collection[typing.Type[MainTask]]:
return [Refueling, AWACS]
def generate(self, is_awacs_enabled):
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
tanker_unit = db.find_unittype(Refueling, self.conflict.attackers_side.name)[0]

View File

@ -130,6 +130,7 @@ class Conflict:
return self.to_cp.size * GROUND_DISTANCE_FACTOR
def find_insertion_point(self, other_point: Point) -> Point:
if self.is_vector:
dx = self.position.x - self.tail.x
dy = self.position.y - self.tail.y
dr2 = float(dx ** 2 + dy ** 2)
@ -143,6 +144,8 @@ class Conflict:
x = lerp * dx + self.tail.x
y = lerp * dy + self.tail.y
return Point(x, y)
else:
return self.position
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]:
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
@ -153,7 +156,10 @@ class Conflict:
@classmethod
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]:
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE)
cp_distance = from_cp.position.distance_to_point(to_cp.position)
cp_distance -= cp_distance * to_cp.frontline_offset + cp_distance * from_cp.frontline_offset
distance = max(cp_distance * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE)
heading = to_cp.position.heading_between_point(from_cp.position)
return to_cp.position.point_from_heading(heading, distance), heading
@ -172,7 +178,6 @@ class Conflict:
if pos:
left_position = pos
center_position = pos
print("{} - {} {}".format(from_cp, to_cp, center_position))
if left_position is None:
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater)

View File

@ -19,11 +19,15 @@ class GroundObjectsGenerator:
self.game = game
def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]:
assert self.conflict.is_vector, "FARP could be generated only on frontline conflicts!"
for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)):
if self.conflict.is_vector:
center = self.conflict.center
heading = self.conflict.heading - 90
position = self.conflict.find_ground_position(self.conflict.center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading)
else:
center, heading = self.conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp)
heading -= 90
position = self.conflict.find_ground_position(center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading)
for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)):
position = position.point_from_heading(0, i * 275)
yield self.m.farp(
@ -64,6 +68,10 @@ class GroundObjectsGenerator:
else:
static_type = fortification_map[ground_object.dcs_identifier]
if not static_type:
print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier))
continue
group = self.m.static_group(
country=side,
name=ground_object.string_identifier,

View File

@ -12,9 +12,11 @@ from dcs.action import *
from game import db
from theater import *
from gen.airsupportgen import AirSupportConflictGenerator
from gen import *
PUSH_TRIGGER_SIZE = 3000
PUSH_TRIGGER_ACTIVATION_AGL = 100
REGROUP_ZONE_DISTANCE = 12000
REGROUP_ALT = 5000
@ -51,6 +53,11 @@ class TriggersGenerator:
vehicle_group.late_activation = True
activate_by_trigger.append(vehicle_group)
for plane_group in country.plane_group:
if plane_group.task in [x.name for x in AirSupportConflictGenerator.support_tasks()]:
plane_group.late_activation = True
activate_by_trigger.append(plane_group)
conflict_distance = player_cp.position.distance_to_point(self.conflict.position)
minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM)
if minimum_radius < 0:
@ -79,9 +86,6 @@ class TriggersGenerator:
if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3:
continue
if group.units[0].is_human():
continue
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
pos1 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
@ -109,7 +113,9 @@ class TriggersGenerator:
push_trigger = TriggerOnce(Event.NoEvent, "Push trigger")
for group in push_by_trigger:
push_trigger.add_condition(AllOfGroupOutsideZone(group.id, push_trigger_zone.id))
for unit in group.units:
push_trigger.add_condition(UnitAltitudeHigherAGL(unit.id, PUSH_TRIGGER_ACTIVATION_AGL))
push_trigger.add_action(AITaskPush(group.id, 1))
message_string = self.mission.string("Task force is in the air, proceed with the objective (activate waypoint 3).")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
import pickle
import typing
from dcs.mission import Mission
from dcs.mapping import Point
from dcs.unit import *
from dcs.statics import warehouse_map, fortification_map
def load_templates():
temp_mis = Mission()
temp_mis.load_file("resources/tools/groundobject_templates.miz")
groups = {} # type: typing.Dict[str, typing.Dict[int, typing.Collection[Static]]]
for static_group in temp_mis.country("USA").static_group:
for static in static_group.units:
static_name = str(static.name).split()[0]
tpl_name, tpl_idx = static_name[:-1], int(static_name[-1])
groups[tpl_name] = groups.get(tpl_name, {})
groups[tpl_name][tpl_idx] = groups[tpl_name].get(tpl_idx, [])
groups[tpl_name][tpl_idx].append(static)
tpls = {name: {idx: [] for idx in groups[name].keys()} for name in groups.keys()}
for category_name, category_groups in groups.items():
for idx, static_groups in category_groups.items():
dist = -1
a, b = None, None
for aa in static_groups:
for bb in static_groups:
if aa.position.distance_to_point(bb.position) > dist:
dist = aa.position.distance_to_point(bb.position)
a = aa
b = bb
center = a.position.point_from_heading(a.position.heading_between_point(b.position), dist / 2)
for static in static_groups:
tpls[category_name][idx].append({
"type": static.type,
"offset": Point(center.x - static.position.x, center.y - static.position.y),
"heading": static.heading,
})
return tpls
with open("resources/groundobject_templates.p", "wb") as f:
pickle.dump(load_templates(), f)

View File

@ -5,6 +5,7 @@ from dcs.mission import Mission
from dcs.mapping import Point
from dcs.terrain import *
from dcs.unitgroup import VehicleGroup, StaticGroup
from dcs import vehicles
from dcs.unit import *
from dcs.statics import warehouse_map, fortification_map
@ -51,7 +52,7 @@ if __name__ == "__main__":
theater_object.position = unit.position
theater_object.heading = unit.heading
if isinstance(unit, Vehicle):
if isinstance(unit, Vehicle) and unit.type in vehicles.AirDefence.__dict__.values():
theater_object.dcs_identifier = "AA"
else:
theater_object.dcs_identifier = unit.type

View File

@ -1,14 +1,26 @@
import pickle
from dcs.mission import Mission
from dcs.planes import A_10C
for terrain in ["cau", "gulf"]:
m = Mission()
m.load_file("./{}_terrain.miz".format(terrain))
landmap = []
inclusion_zones = []
exclusion_zones = []
for plane_group in m.country("USA").plane_group:
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
zone = [(x.position.x, x.position.y) for x in plane_group.points]
if terrain == "cau" and inclusion_zones:
# legacy
exclusion_zones.append(zone)
else:
if plane_group.units[0].type == "F-15C":
exclusion_zones.append(zone)
else:
inclusion_zones.append(zone)
with open("../{}landmap.p".format(terrain), "wb") as f:
pickle.dump(landmap, f)
print(len(inclusion_zones), len(exclusion_zones))
pickle.dump((inclusion_zones, exclusion_zones), f)

Binary file not shown.

Binary file not shown.

View File

@ -47,6 +47,9 @@ class CaucasusTheater(ConflictTheater):
def __init__(self, load_ground_objects=True):
super(CaucasusTheater, self).__init__()
self.soganlug.frontline_offset = 0.5
self.soganlug.base.strength = 1
self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan])
self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik])
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
@ -73,10 +76,6 @@ class CaucasusTheater(ConflictTheater):
self.carrier_1.captured = True
self.soganlug.captured = True
if load_ground_objects:
with open("resources/cau_groundobjects.p", "rb") as f:
self.set_groundobject(pickle.load(f))
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
point.name = " ".join(re.split(r"[ -]", point.name)[:1])

View File

@ -18,6 +18,8 @@ IMPORTANCE_LOW = 1
IMPORTANCE_MEDIUM = 1.2
IMPORTANCE_HIGH = 1.4
GLOBAL_CP_CONFLICT_DISTANCE_MIN = 340000
"""
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
COAST_NS_E = [45, 90, 135, ]
@ -58,31 +60,28 @@ class ConflictTheater:
def __init__(self):
self.controlpoints = []
def set_groundobject(self, dictionary: typing.Dict[int, typing.Collection[TheaterGroundObject]]):
for id, value in dictionary.items():
for cp in self.controlpoints:
if cp.id == id:
cp.ground_objects = value
break
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
for connected_point in connected_to:
point.connect(to=connected_point)
self.controlpoints.append(point)
def is_in_sea(self, point: Point) -> bool:
if not self.landmap:
return False
return poly_contains(point.x, point.y, self.landmap[0][0])
def is_on_land(self, point: Point) -> bool:
if not self.landmap:
return True
# check first poly (main land poly)
if not poly_contains(point.x, point.y, self.landmap[0]):
for inclusion_zone in self.landmap[0]:
if not poly_contains(point.x, point.y, inclusion_zone):
return False
# check others polys (exclusion zones from main)
for poly in self.landmap[1:]:
if poly_contains(point.x, point.y, poly):
# point is in one of the exclusion zones, meaning that it's in the lake or something
for exclusion_zone in self.landmap[1]:
if poly_contains(point.x, point.y, exclusion_zone):
return False
return True
@ -95,5 +94,9 @@ class ConflictTheater:
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
yield (cp, connected_point)
for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]:
if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN:
yield (global_cp, connected_point)
def enemy_points(self) -> typing.Collection[ControlPoint]:
return [point for point in self.controlpoints if not point.captured]

View File

@ -9,15 +9,20 @@ from .theatergroundobject import TheaterGroundObject
class ControlPoint:
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.Collection[TheaterGroundObject]
position = None # type: Point
captured = False
has_frontline = True
id = 0
position = None # type: Point
name = None # type: str
full_name = None # type: str
base = None # type: theater.base.Base
at = None # type: db.StartPosition
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.List[TheaterGroundObject]
captured = False
has_frontline = True
frontline_offset = 0.0
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
import theater.base

View File

@ -1,7 +1,8 @@
import pickle
import typing
Landmap = typing.Collection[typing.Collection[typing.Tuple[float, float]]]
Zone = typing.Collection[typing.Tuple[float, float]]
Landmap = typing.Tuple[typing.Collection[Zone], typing.Collection[Zone]]
def load_landmap(filename: str) -> Landmap:

View File

@ -1,4 +1,7 @@
import math
import pickle
import random
import typing
from theater.base import *
from theater.conflicttheater import *
@ -15,7 +18,7 @@ COUNT_BY_TASK = {
}
def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float):
def generate_inital_units(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float):
for cp in theater.enemy_points():
if cp.captured:
continue
@ -37,3 +40,49 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie
for unit_type in unittypes:
logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type))
cp.base.commision_units({unit_type: count_per_type})
def generate_groundobjects(theater: ConflictTheater):
with open("resources/groundobject_templates.p", "rb") as f:
tpls = pickle.load(f)
def find_ground_location(near, theater, max, min) -> typing.Optional[Point]:
for _ in range(500):
p = near.random_point_within(max, min)
if theater.is_on_land(p):
return p
return None
group_id = 0
for cp in theater.enemy_points():
for _ in range(0, random.randrange(3, 6)):
point = find_ground_location(cp.position, theater, 120000, 5000)
if point is None:
print("Couldn't find point for {}".format(cp))
continue
dist = point.distance_to_point(cp.position)
for another_cp in theater.enemy_points():
if another_cp.position.distance_to_point(point) < dist:
cp = another_cp
tpl = random.choice(list(random.choice(list(tpls.values())).values()))
random_heading = random.randrange(0, 360)
group_id += 1
object_id = 0
for object in tpl:
object_id += 1
g = TheaterGroundObject()
g.group_id = group_id
g.object_id = object_id
g.cp_id = cp.id
g.dcs_identifier = object["type"]
g.heading = object["heading"] + random_heading
g.position = Point(point.x + object["offset"].x, point.y + object["offset"].y)
cp.ground_objects.append(g)

View File

@ -1,6 +1,7 @@
import typing
from dcs.mapping import Point
from dcs.statics import *
NAME_BY_CATEGORY = {
"power": "Power plant",
@ -9,6 +10,8 @@ NAME_BY_CATEGORY = {
"aa": "AA Defense Site",
"warehouse": "Warehouse",
"farp": "FARP",
"fob": "FOB",
"factory": "Factory",
"comms": "Comms. tower",
"oil": "Oil platform"
}
@ -20,6 +23,8 @@ ABBREV_NAME = {
"aa": "AA",
"warehouse": "WARE",
"farp": "FARP",
"fob": "FOB",
"factory": "FACTORY",
"comms": "COMMST",
"oil": "OILP"
}
@ -27,12 +32,14 @@ ABBREV_NAME = {
CATEGORY_MAP = {
"aa": ["AA"],
"power": ["Workshop A"],
"warehouse": ["Warehouse"],
"fuel": ["Tank"],
"ammo": [".Ammunition depot"],
"farp": ["FARP Tent"],
"comms": ["TV tower", "Comms tower"],
"power": ["Workshop A", "Electric power box", "Garage A"],
"warehouse": ["Warehouse", "Hangar A"],
"fuel": ["Tank", "Tank 2", "Fuel tank"],
"ammo": [".Ammunition depot", "Hangar B"],
"farp": ["FARP Tent", "FARP Ammo Dump Coating", "FARP Fuel Depot", "FARP Command Post", "FARP CP Blindage"],
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
"factory": ["Tech combine", "Tech hangar A"],
"comms": ["TV tower", "Comms tower M"],
"oil": ["Oil platform"],
}