From df80ec635f2ea1684d6fc3965c1d447f31523619 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 17 Nov 2020 20:17:29 -0800 Subject: [PATCH] Add a new miz file based campaign generator. Defining a campaign using a miz file instead of as JSON has a number of advantages: * Much easier for players to mod their campaigns. * Easier to see the big picture of how objective locations will be laid out, since every control point can be seen at once. * No need to associate objective locations to control points explicitly; the campaign generator can claim objectives for control points based on distance. * Easier to create an IADS that performs well. * Non-random campaigns are easier to make. The downside is duplication across campaigns, and a less structured data format for complex objects. The former is annoying if we have to fix a bug that appears in a dozen campaigns. It's less an annoyance for needing to start from scratch since the easiest way to create a campaign will be to copy the "full" campaign for the given theater and prune it. So far I've implemented control points, base defenses, and front lines. Still need to add support for non-base defense TGOs. This currently doesn't do anything for the `radials` property of the `ControlPoint` because I'm not sure what those are. --- game/theater/conflicttheater.py | 249 +++++++++++++++++++++- game/theater/controlpoint.py | 46 +++- game/theater/start_generator.py | 60 +++--- qt_ui/windows/newgame/QCampaignList.py | 6 +- resources/campaigns/inherent_resolve.json | 79 +------ resources/campaigns/inherent_resolve.miz | Bin 0 -> 28105 bytes 6 files changed, 322 insertions(+), 118 deletions(-) create mode 100644 resources/campaigns/inherent_resolve.miz diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index c0b373ce..d6605c47 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -1,13 +1,22 @@ from __future__ import annotations -import logging +import itertools import json +import logging from dataclasses import dataclass +from functools import cached_property from itertools import tee from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Union +from dcs import Mission +from dcs.countries import ( + CombinedJointTaskForcesBlue, + CombinedJointTaskForcesRed, +) +from dcs.country import Country from dcs.mapping import Point +from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa from dcs.terrain import ( caucasus, nevada, @@ -16,11 +25,14 @@ from dcs.terrain import ( syria, thechannel, ) -from dcs.terrain.terrain import Terrain +from dcs.terrain.terrain import Airport, Terrain +from dcs.unitgroup import MovingGroup, ShipGroup, VehicleGroup +from dcs.vehicles import AirDefence, Armor from gen.flights.flight import FlightType from .controlpoint import ControlPoint, MissionTarget from .landmap import Landmap, load_landmap, poly_contains +from ..utils import nm_to_meter Numeric = Union[int, float] @@ -73,6 +85,193 @@ def pairwise(iterable): return zip(a, b) +class MizCampaignLoader: + BLUE_COUNTRY = CombinedJointTaskForcesBlue() + RED_COUNTRY = CombinedJointTaskForcesRed() + + CV_UNIT_TYPE = CVN_74_John_C__Stennis.id + LHA_UNIT_TYPE = LHA_1_Tarawa.id + FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id + + EWR_UNIT_TYPE = AirDefence.EWR_55G6.id + SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id + GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id + + BASE_DEFENSE_RADIUS = nm_to_meter(2) + + def __init__(self, miz: Path, theater: ConflictTheater) -> None: + self.theater = theater + self.mission = Mission() + self.mission.load_file(str(miz)) + self.control_point_id = itertools.count(1000) + + # If there are no red carriers there usually aren't red units. Make sure + # both countries are initialized so we don't have to deal with None. + if self.mission.country(self.BLUE_COUNTRY.name) is None: + self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) + if self.mission.country(self.RED_COUNTRY.name) is None: + self.mission.coalition["red"].add_country(self.RED_COUNTRY) + + @staticmethod + def control_point_from_airport(airport: Airport) -> ControlPoint: + # TODO: Radials? + radials = LAND + + # The wiki says this is a legacy property and to just use regular. + size = SIZE_REGULAR + + # The importance is taken from the periodicity of the airport's + # warehouse divided by 10. 30 is the default, and out of range (valid + # values are between 1.0 and 1.4). If it is used, pick the default + # importance. + if airport.periodicity == 30: + importance = IMPORTANCE_MEDIUM + else: + importance = airport.periodicity / 10 + + cp = ControlPoint.from_airport(airport, radials, size, importance) + cp.captured = airport.is_blue() + + # Use the unlimited aircraft option to determine if an airfield should + # be owned by the player when the campaign is "inverted". + cp.captured_invert = airport.unlimited_aircrafts + + return cp + + def country(self, blue: bool) -> Country: + country = self.mission.country( + self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name) + # Should be guaranteed because we initialized them. + assert country + return country + + @property + def blue(self) -> Country: + return self.country(blue=True) + + @property + def red(self) -> Country: + return self.country(blue=False) + + def carriers(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.CV_UNIT_TYPE: + yield group + + def lhas(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.LHA_UNIT_TYPE: + yield group + + @property + def ewrs(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.EWR_UNIT_TYPE: + yield group + + @property + def sams(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.SAM_UNIT_TYPE: + yield group + + @property + def garrisons(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.GARRISON_UNIT_TYPE: + yield group + + @cached_property + def control_points(self) -> Dict[int, ControlPoint]: + control_points = {} + for airport in self.mission.terrain.airport_list(): + if airport.is_blue() or airport.is_red(): + control_point = self.control_point_from_airport(airport) + control_points[control_point.id] = control_point + + for blue in (False, True): + for group in self.carriers(blue): + control_point = ControlPoint.carrier( + "carrier", group.position, next(self.control_point_id)) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point + for group in self.lhas(blue): + control_point = ControlPoint.lha( + "lha", group.position, next(self.control_point_id)) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point + + return control_points + + @property + def front_line_path_groups(self) -> Iterator[VehicleGroup]: + for group in self.country(blue=True).vehicle_group: + if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: + yield group + + @cached_property + def front_lines(self) -> Dict[str, ComplexFrontLine]: + # Dict of front line ID to a front line. + front_lines = {} + for group in self.front_line_path_groups: + # The unit will have its first waypoint at the source CP and the + # final waypoint at the destination CP. Intermediate waypoints + # define the curve of the front line. + waypoints = [p.position for p in group.points] + origin = self.mission.terrain.nearest_airport(waypoints[0]) + if origin is None: + raise RuntimeError( + f"No airport near the first waypoint of {group.name}") + destination = self.mission.terrain.nearest_airport(waypoints[-1]) + if destination is None: + raise RuntimeError( + f"No airport near the final waypoint of {group.name}") + + # Snap the begin and end points to the control points. + waypoints[0] = origin.position + waypoints[-1] = destination.position + front_line_id = f"{origin.id}|{destination.id}" + front_lines[front_line_id] = ComplexFrontLine(origin, waypoints) + self.control_points[origin.id].connect( + self.control_points[destination.id]) + self.control_points[destination.id].connect( + self.control_points[origin.id]) + return front_lines + + def objective_info(self, group: MovingGroup) -> Tuple[ControlPoint, int]: + closest = self.theater.closest_control_point(group.position) + distance = closest.position.distance_to_point(group.position) + return closest, distance + + def add_preset_locations(self) -> None: + for group in self.garrisons: + closest, distance = self.objective_info(group) + if distance < self.BASE_DEFENSE_RADIUS: + closest.preset_locations.base_garrisons.append(group.position) + else: + logging.warning( + f"Found garrison unit too far from base: {group.name}") + + for group in self.sams: + closest, distance = self.objective_info(group) + if distance < self.BASE_DEFENSE_RADIUS: + closest.preset_locations.base_air_defense.append(group.position) + else: + closest.preset_locations.sams.append(group.position) + + for group in self.ewrs: + closest, distance = self.objective_info(group) + closest.preset_locations.ewrs.append(group.position) + + def populate_theater(self) -> None: + for control_point in self.control_points.values(): + self.theater.add_controlpoint(control_point) + self.add_preset_locations() + self.theater.set_frontline_data(self.front_lines) + + class ConflictTheater: terrain: Terrain @@ -83,17 +282,35 @@ class ConflictTheater: land_poly = None # type: Polygon """ daytime_map: Dict[str, Tuple[int, int]] - frontline_data: Optional[Dict[str, ComplexFrontLine]] = None + _frontline_data: Optional[Dict[str, ComplexFrontLine]] = None def __init__(self): self.controlpoints: List[ControlPoint] = [] - self.frontline_data = FrontLine.load_json_frontlines(self) + self._frontline_data: Optional[Dict[str, ComplexFrontLine]] = None """ self.land_poly = geometry.Polygon(self.landmap[0][0]) for x in self.landmap[1]: self.land_poly = self.land_poly.difference(geometry.Polygon(x)) """ + @property + def frontline_data(self) -> Optional[Dict[str, ComplexFrontLine]]: + if self._frontline_data is None: + self.load_frontline_data_from_file() + return self._frontline_data + + def load_frontline_data_from_file(self) -> None: + if self._frontline_data is not None: + logging.warning("Replacing existing frontline data from file") + self._frontline_data = FrontLine.load_json_frontlines(self) + if self._frontline_data is None: + self._frontline_data = {} + + def set_frontline_data(self, data: Dict[str, ComplexFrontLine]) -> None: + if self._frontline_data is not None: + logging.warning("Replacing existing frontline data") + self._frontline_data = data + def add_controlpoint(self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None): if connected_to is None: @@ -153,11 +370,21 @@ class ConflictTheater: def enemy_points(self) -> List[ControlPoint]: return [point for point in self.controlpoints if not point.captured] + def closest_control_point(self, point: Point) -> ControlPoint: + closest = self.controlpoints[0] + closest_distance = point.distance_to_point(closest.position) + for control_point in self.controlpoints[1:]: + distance = point.distance_to_point(control_point.position) + if distance < closest_distance: + closest = control_point + closest_distance = distance + return closest + def add_json_cp(self, theater, p: dict) -> ControlPoint: if p["type"] == "airbase": - airbase = theater.terrain.airports[p["id"]].__class__ + airbase = theater.terrain.airports[p["id"]] if "radials" in p.keys(): radials = p["radials"] @@ -188,7 +415,7 @@ class ConflictTheater: return cp @staticmethod - def from_json(data: Dict[str, Any]) -> ConflictTheater: + def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater: theaters = { "Caucasus": CaucasusTheater, "Nevada": NevadaTheater, @@ -199,6 +426,12 @@ class ConflictTheater: } theater = theaters[data["theater"]] t = theater() + + miz = data.get("miz", None) + if miz is not None: + MizCampaignLoader(directory / miz, t).populate_theater() + return t + cps = {} for p in data["player_points"]: cp = t.add_json_cp(theater, p) @@ -376,10 +609,6 @@ class FrontLine(MissionTarget): """Returns a tuple of the two control points.""" return self.control_point_a, self.control_point_b - @property - def middle_point(self): - self.point_from_a(self.attack_distance / 2) - @property def attack_distance(self): """The total distance of all segments""" diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 46ac7e00..2e682107 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -1,9 +1,11 @@ from __future__ import annotations import itertools +import random import re +from dataclasses import dataclass, field from enum import Enum -from typing import Dict, Iterator, List, TYPE_CHECKING +from typing import Dict, Iterator, List, Optional, TYPE_CHECKING from dcs.mapping import Point from dcs.ships import ( @@ -36,6 +38,43 @@ class ControlPointType(Enum): FOB = 5 # A FOB (ground units only) +@dataclass +class PresetLocations: + base_garrisons: List[Point] = field(default_factory=list) + base_air_defense: List[Point] = field(default_factory=list) + + ewrs: List[Point] = field(default_factory=list) + sams: List[Point] = field(default_factory=list) + coastal_defenses: List[Point] = field(default_factory=list) + strike_locations: List[Point] = field(default_factory=list) + + @staticmethod + def _random_from(points: List[Point]) -> Optional[Point]: + if not points: + return None + point = random.choice(points) + points.remove(point) + return point + + def random_garrison(self) -> Optional[Point]: + return self._random_from(self.base_garrisons) + + def random_base_sam(self) -> Optional[Point]: + return self._random_from(self.base_air_defense) + + def random_ewr(self) -> Optional[Point]: + return self._random_from(self.ewrs) + + def random_sam(self) -> Optional[Point]: + return self._random_from(self.sams) + + def random_coastal_defense(self) -> Optional[Point]: + return self._random_from(self.coastal_defenses) + + def random_strike_location(self) -> Optional[Point]: + return self._random_from(self.strike_locations) + + class ControlPoint(MissionTarget): position = None # type: Point @@ -57,6 +96,7 @@ class ControlPoint(MissionTarget): self.at = at self.connected_objectives: List[TheaterGroundObject] = [] self.base_defenses: List[BaseDefenseGroundObject] = [] + self.preset_locations = PresetLocations() self.size = size self.importance = importance @@ -79,7 +119,7 @@ class ControlPoint(MissionTarget): def from_airport(cls, airport: Airport, radials: List[int], size: int, importance: float, has_frontline=True): assert airport obj = cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE) - obj.airport = airport() + obj.airport = airport return obj @classmethod @@ -157,7 +197,7 @@ class ControlPoint(MissionTarget): else: return 0 - def connect(self, to): + def connect(self, to: ControlPoint) -> None: self.connected_points.append(to) self.stances[to.id] = CombatStance.DEFENSIVE diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 95bc1c69..b16302d9 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -4,7 +4,7 @@ import logging import math import pickle import random -from typing import Any, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional from dcs.mapping import Point from dcs.task import CAP, CAS, PinpointStrike @@ -13,6 +13,17 @@ from dcs.vehicles import AirDefence from game import Game, db from game.factions.faction import Faction from game.settings import Settings +from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW +from game.theater.theatergroundobject import ( + BuildingGroundObject, + CarrierGroundObject, + EwrGroundObject, + LhaGroundObject, + MissileSiteGroundObject, + SamGroundObject, + ShipGroundObject, + VehicleGroupGroundObject, +) from game.version import VERSION from gen import namegen from gen.defenses.armor_group_generator import generate_armor_group @@ -34,17 +45,6 @@ from theater import ( ControlPointType, TheaterGroundObject, ) -from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW -from game.theater.theatergroundobject import ( - EwrGroundObject, - SamGroundObject, - BuildingGroundObject, - CarrierGroundObject, - LhaGroundObject, - MissileSiteGroundObject, - ShipGroundObject, - VehicleGroupGroundObject, -) GroundObjectTemplates = Dict[str, Dict[str, Any]] @@ -317,10 +317,9 @@ class BaseDefenseGenerator: self.generate_base_defenses() def generate_ewr(self) -> None: - position = self._find_location() + position = self._find_location( + "EWR", self.control_point.preset_locations.random_ewr) if position is None: - logging.error("Could not find position for " - f"{self.control_point} EWR") return group_id = self.game.next_group_id() @@ -350,10 +349,9 @@ class BaseDefenseGenerator: self.generate_garrison() def generate_garrison(self) -> None: - position = self._find_location() + position = self._find_location( + "garrison", self.control_point.preset_locations.random_garrison) if position is None: - logging.error("Could not find position for " - f"{self.control_point} garrison") return group_id = self.game.next_group_id() @@ -368,10 +366,9 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_sam(self) -> None: - position = self._find_location() + position = self._find_location( + "SAM", self.control_point.preset_locations.random_base_sam) if position is None: - logging.error("Could not find position for " - f"{self.control_point} SAM") return group_id = self.game.next_group_id() @@ -385,10 +382,9 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_shorad(self) -> None: - position = self._find_location() + position = self._find_location( + "SHORAD", self.control_point.preset_locations.random_garrison) if position is None: - logging.error("Could not find position for " - f"{self.control_point} SHORAD") return group_id = self.game.next_group_id() @@ -401,7 +397,21 @@ class BaseDefenseGenerator: g.groups.append(group) self.control_point.base_defenses.append(g) - def _find_location(self) -> Optional[Point]: + def _find_location(self, position_type: str, + get_preset: Callable[[], None]) -> Optional[Point]: + position = get_preset() + if position is None: + logging.warning( + f"Found no preset location for {self.control_point} " + f"{position_type}. Falling back to random location." + ) + position = self._find_random_location() + if position is None: + logging.error("Could not find position for " + f"{self.control_point} {position_type}.") + return position + + def _find_random_location(self) -> Optional[Point]: position = find_location(True, self.control_point.position, self.game.theater, 400, 3200, [], True) diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 617869bc..822cfaca 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -29,8 +29,10 @@ class Campaign: data = json.load(campaign_file) sanitized_theater = data["theater"].replace(" ", "") - return cls(data["name"], f"Terrain_{sanitized_theater}", data.get("authors", "???"), - data.get("description", ""), ConflictTheater.from_json(data)) + return cls(data["name"], f"Terrain_{sanitized_theater}", + data.get("authors", "???"), + data.get("description", ""), + ConflictTheater.from_json(path.parent, data)) def load_campaigns() -> List[Campaign]: diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index fc5969a5..66befcd5 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -3,82 +3,5 @@ "theater": "Syria", "authors": "Khopa", "description": "

In this scenario, you start from Jordan, and have to fight your way through eastern Syria.

", - "player_points": [ - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4, - "captured_invert": true - }, - { - "type": "carrier", - "id": 1001, - "x": -210000, - "y": -200000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -131000, - "y": -161000, - "captured_invert": true - } - ], - "enemy_points": [ - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1, - "captured_invert": true - } - ], - "links": [ - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Incirlik", - "Incirlik" - ], - [ - "Khalkhalah", - "Palmyra" - ], - [ - "Palmyra", - "Tabqa" - ], - [ - "Jirah", - "Tabqa" - ] - ] + "miz": "inherent_resolve.miz" } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz new file mode 100644 index 0000000000000000000000000000000000000000..bee1a2d2a853e6b69c8fe5f1b1eba47b809bdbc6 GIT binary patch literal 28105 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 literal 0 HcmV?d00001