mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Added coastal defenses sites generator for Iran and China.
This commit is contained in:
parent
643dd65113
commit
2a6e2d470d
@ -60,6 +60,9 @@ class Faction:
|
|||||||
# Possible Missile site generators for this faction
|
# Possible Missile site generators for this faction
|
||||||
missiles: List[str] = field(default_factory=list)
|
missiles: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
# Possible costal site generators for this faction
|
||||||
|
coastal_defenses: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
# Required mods or asset packs
|
# Required mods or asset packs
|
||||||
requirements: Dict[str, str] = field(default_factory=dict)
|
requirements: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
@ -90,6 +93,9 @@ class Faction:
|
|||||||
# How many missiles group should we try to generate per CP on startup for this faction
|
# How many missiles group should we try to generate per CP on startup for this faction
|
||||||
missiles_group_count: int = field(default=1)
|
missiles_group_count: int = field(default=1)
|
||||||
|
|
||||||
|
# How many coastal group should we try to generate per CP on startup for this faction
|
||||||
|
coastal_group_count: int = field(default=1)
|
||||||
|
|
||||||
# Whether this faction has JTAC access
|
# Whether this faction has JTAC access
|
||||||
has_jtac: bool = field(default=False)
|
has_jtac: bool = field(default=False)
|
||||||
|
|
||||||
@ -153,6 +159,7 @@ class Faction:
|
|||||||
faction.air_defenses.extend(json.get("shorads", []))
|
faction.air_defenses.extend(json.get("shorads", []))
|
||||||
|
|
||||||
faction.missiles = json.get("missiles", [])
|
faction.missiles = json.get("missiles", [])
|
||||||
|
faction.coastal_defenses = json.get("coastal_defenses", [])
|
||||||
faction.requirements = json.get("requirements", {})
|
faction.requirements = json.get("requirements", {})
|
||||||
|
|
||||||
faction.carrier_names = json.get("carrier_names", [])
|
faction.carrier_names = json.get("carrier_names", [])
|
||||||
@ -173,6 +180,7 @@ class Faction:
|
|||||||
faction.jtac_unit = None
|
faction.jtac_unit = None
|
||||||
faction.navy_group_count = int(json.get("navy_group_count", 1))
|
faction.navy_group_count = int(json.get("navy_group_count", 1))
|
||||||
faction.missiles_group_count = int(json.get("missiles_group_count", 0))
|
faction.missiles_group_count = int(json.get("missiles_group_count", 0))
|
||||||
|
faction.coastal_group_count = int(json.get("coastal_group_count", 0))
|
||||||
|
|
||||||
# Load doctrine
|
# Load doctrine
|
||||||
doctrine = json.get("doctrine", "modern")
|
doctrine = json.get("doctrine", "modern")
|
||||||
|
|||||||
16
game/point_with_heading.py
Normal file
16
game/point_with_heading.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from dcs import Point
|
||||||
|
|
||||||
|
|
||||||
|
class PointWithHeading(Point):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PointWithHeading, self).__init__(0, 0)
|
||||||
|
self.heading = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_point(point: Point, heading: int):
|
||||||
|
p = PointWithHeading()
|
||||||
|
p.x = point.x
|
||||||
|
p.y = point.y
|
||||||
|
p.heading = heading
|
||||||
|
return p
|
||||||
@ -55,6 +55,7 @@ from .controlpoint import (
|
|||||||
Fob,
|
Fob,
|
||||||
)
|
)
|
||||||
from .landmap import Landmap, load_landmap, poly_contains
|
from .landmap import Landmap, load_landmap, poly_contains
|
||||||
|
from ..point_with_heading import PointWithHeading
|
||||||
from ..utils import Distance, meters, nautical_miles
|
from ..utils import Distance, meters, nautical_miles
|
||||||
|
|
||||||
Numeric = Union[int, float]
|
Numeric = Union[int, float]
|
||||||
@ -71,6 +72,7 @@ IMPORTANCE_HIGH = 1.4
|
|||||||
|
|
||||||
FRONTLINE_MIN_CP_DISTANCE = 5000
|
FRONTLINE_MIN_CP_DISTANCE = 5000
|
||||||
|
|
||||||
|
|
||||||
def pairwise(iterable):
|
def pairwise(iterable):
|
||||||
"""
|
"""
|
||||||
itertools recipe
|
itertools recipe
|
||||||
@ -183,7 +185,7 @@ class MizCampaignLoader:
|
|||||||
for group in self.country(blue).ship_group:
|
for group in self.country(blue).ship_group:
|
||||||
if group.units[0].type == self.LHA_UNIT_TYPE:
|
if group.units[0].type == self.LHA_UNIT_TYPE:
|
||||||
yield group
|
yield group
|
||||||
|
|
||||||
def fobs(self, blue: bool) -> Iterator[VehicleGroup]:
|
def fobs(self, blue: bool) -> Iterator[VehicleGroup]:
|
||||||
for group in self.country(blue).vehicle_group:
|
for group in self.country(blue).vehicle_group:
|
||||||
if group.units[0].type == self.FOB_UNIT_TYPE:
|
if group.units[0].type == self.FOB_UNIT_TYPE:
|
||||||
@ -297,7 +299,7 @@ class MizCampaignLoader:
|
|||||||
# final waypoint at the destination CP. Intermediate waypoints
|
# final waypoint at the destination CP. Intermediate waypoints
|
||||||
# define the curve of the front line.
|
# define the curve of the front line.
|
||||||
waypoints = [p.position for p in group.points]
|
waypoints = [p.position for p in group.points]
|
||||||
origin = self.theater.closest_control_point(waypoints[0])
|
origin = self.theater.closest_control_point(waypoints[0])
|
||||||
if origin is None:
|
if origin is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"No control point near the first waypoint of {group.name}")
|
f"No control point near the first waypoint of {group.name}")
|
||||||
@ -326,7 +328,8 @@ class MizCampaignLoader:
|
|||||||
for group in self.garrisons:
|
for group in self.garrisons:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
if distance < self.BASE_DEFENSE_RADIUS:
|
if distance < self.BASE_DEFENSE_RADIUS:
|
||||||
closest.preset_locations.base_garrisons.append(group.position)
|
closest.preset_locations.base_garrisons.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Found garrison unit too far from base: {group.name}")
|
f"Found garrison unit too far from base: {group.name}")
|
||||||
@ -334,42 +337,44 @@ class MizCampaignLoader:
|
|||||||
for group in self.sams:
|
for group in self.sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
if distance < self.BASE_DEFENSE_RADIUS:
|
if distance < self.BASE_DEFENSE_RADIUS:
|
||||||
closest.preset_locations.base_air_defense.append(group.position)
|
closest.preset_locations.base_air_defense.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
else:
|
else:
|
||||||
closest.preset_locations.strike_locations.append(group.position)
|
closest.preset_locations.strike_locations.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.ewrs:
|
for group in self.ewrs:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.ewrs.append(group.position)
|
closest.preset_locations.ewrs.append(PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.offshore_strike_targets:
|
for group in self.offshore_strike_targets:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.offshore_strike_locations.append(
|
closest.preset_locations.offshore_strike_locations.append(
|
||||||
group.position)
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.ships:
|
for group in self.ships:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.ships.append(group.position)
|
closest.preset_locations.ships.append(PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.missile_sites:
|
for group in self.missile_sites:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.missile_sites.append(group.position)
|
closest.preset_locations.missile_sites.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.coastal_defenses:
|
for group in self.coastal_defenses:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.coastal_defenses.append(group.position)
|
closest.preset_locations.coastal_defenses.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
|
|
||||||
for group in self.required_long_range_sams:
|
for group in self.required_long_range_sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.required_long_range_sams.append(
|
closest.preset_locations.required_long_range_sams.append(
|
||||||
group.position
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
)
|
|
||||||
|
|
||||||
for group in self.required_medium_range_sams:
|
for group in self.required_medium_range_sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.required_medium_range_sams.append(
|
closest.preset_locations.required_medium_range_sams.append(
|
||||||
group.position
|
PointWithHeading.from_point(group.position, group.units[0].heading))
|
||||||
)
|
|
||||||
|
|
||||||
def populate_theater(self) -> None:
|
def populate_theater(self) -> None:
|
||||||
for control_point in self.control_points.values():
|
for control_point in self.control_points.values():
|
||||||
@ -486,8 +491,8 @@ class ConflictTheater:
|
|||||||
for inclusion_zone in self.landmap.inclusion_zones:
|
for inclusion_zone in self.landmap.inclusion_zones:
|
||||||
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
||||||
nearest_points.append(nearest_pair[1])
|
nearest_points.append(nearest_pair[1])
|
||||||
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
||||||
nearest_point = nearest_points[0] # type: geometry.Point
|
nearest_point = nearest_points[0] # type: geometry.Point
|
||||||
for pt in nearest_points[1:]:
|
for pt in nearest_points[1:]:
|
||||||
distance = point.distance(pt)
|
distance = point.distance(pt)
|
||||||
if distance < min_distance:
|
if distance < min_distance:
|
||||||
@ -547,7 +552,7 @@ class ConflictTheater:
|
|||||||
closest = conflict
|
closest = conflict
|
||||||
closest_distance = distance
|
closest_distance = distance
|
||||||
return closest
|
return closest
|
||||||
|
|
||||||
def closest_opposing_control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
def closest_opposing_control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||||
"""
|
"""
|
||||||
Returns a tuple of the two nearest opposing ControlPoints in theater.
|
Returns a tuple of the two nearest opposing ControlPoints in theater.
|
||||||
@ -572,7 +577,7 @@ class ConflictTheater:
|
|||||||
self.find_control_point_by_id(i)
|
self.find_control_point_by_id(i)
|
||||||
for i
|
for i
|
||||||
in min(all_cp_min_distances, key=all_cp_min_distances.get) # type: ignore
|
in min(all_cp_min_distances, key=all_cp_min_distances.get) # type: ignore
|
||||||
] # type: List[ControlPoint]
|
] # type: List[ControlPoint]
|
||||||
assert len(closest_opposing_cps) == 2
|
assert len(closest_opposing_cps) == 2
|
||||||
if closest_opposing_cps[0].captured:
|
if closest_opposing_cps[0].captured:
|
||||||
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
||||||
@ -613,7 +618,7 @@ class ConflictTheater:
|
|||||||
cp.captured_invert = False
|
cp.captured_invert = False
|
||||||
|
|
||||||
return cp
|
return cp
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater:
|
def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater:
|
||||||
theaters = {
|
theaters = {
|
||||||
@ -649,7 +654,7 @@ class ConflictTheater:
|
|||||||
cps[l[1]].connect(cps[l[0]])
|
cps[l[1]].connect(cps[l[0]])
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
class CaucasusTheater(ConflictTheater):
|
class CaucasusTheater(ConflictTheater):
|
||||||
terrain = caucasus.Caucasus()
|
terrain = caucasus.Caucasus()
|
||||||
@ -788,10 +793,10 @@ class FrontLine(MissionTarget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
control_point_a: ControlPoint,
|
control_point_a: ControlPoint,
|
||||||
control_point_b: ControlPoint,
|
control_point_b: ControlPoint,
|
||||||
theater: ConflictTheater
|
theater: ConflictTheater
|
||||||
) -> None:
|
) -> None:
|
||||||
self.control_point_a = control_point_a
|
self.control_point_a = control_point_a
|
||||||
self.control_point_b = control_point_b
|
self.control_point_b = control_point_b
|
||||||
@ -876,7 +881,7 @@ class FrontLine(MissionTarget):
|
|||||||
according to the current strength of each control point
|
according to the current strength of each control point
|
||||||
"""
|
"""
|
||||||
total_strength = (
|
total_strength = (
|
||||||
self.control_point_a.base.strength + self.control_point_b.base.strength
|
self.control_point_a.base.strength + self.control_point_b.base.strength
|
||||||
)
|
)
|
||||||
if self.control_point_a.base.strength == 0:
|
if self.control_point_a.base.strength == 0:
|
||||||
return self._adjust_for_min_dist(0)
|
return self._adjust_for_min_dist(0)
|
||||||
@ -891,11 +896,11 @@ class FrontLine(MissionTarget):
|
|||||||
constant of either end control point.
|
constant of either end control point.
|
||||||
"""
|
"""
|
||||||
if (distance > self.attack_distance / 2) and (
|
if (distance > self.attack_distance / 2) and (
|
||||||
distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance
|
distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance
|
||||||
):
|
):
|
||||||
distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE
|
distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE
|
||||||
elif (distance < self.attack_distance / 2) and (
|
elif (distance < self.attack_distance / 2) and (
|
||||||
distance < FRONTLINE_MIN_CP_DISTANCE
|
distance < FRONTLINE_MIN_CP_DISTANCE
|
||||||
):
|
):
|
||||||
distance = FRONTLINE_MIN_CP_DISTANCE
|
distance = FRONTLINE_MIN_CP_DISTANCE
|
||||||
return distance
|
return distance
|
||||||
@ -910,8 +915,8 @@ class FrontLine(MissionTarget):
|
|||||||
)
|
)
|
||||||
complex_frontlines = self.theater.frontline_data
|
complex_frontlines = self.theater.frontline_data
|
||||||
if (complex_frontlines) and (
|
if (complex_frontlines) and (
|
||||||
(control_point_ids in complex_frontlines)
|
(control_point_ids in complex_frontlines)
|
||||||
or (reversed_cp_ids in complex_frontlines)
|
or (reversed_cp_ids in complex_frontlines)
|
||||||
):
|
):
|
||||||
# The frontline segments must be stored in the correct order for the distance algorithms to work.
|
# The frontline segments must be stored in the correct order for the distance algorithms to work.
|
||||||
# The points in the frontline are ordered from the id before the | to the id after.
|
# The points in the frontline are ordered from the id before the | to the id after.
|
||||||
@ -935,10 +940,9 @@ class FrontLine(MissionTarget):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_json_frontlines(
|
def load_json_frontlines(
|
||||||
theater: ConflictTheater
|
theater: ConflictTheater
|
||||||
) -> Optional[Dict[str, ComplexFrontLine]]:
|
) -> Optional[Dict[str, ComplexFrontLine]]:
|
||||||
"""Load complex frontlines from json"""
|
"""Load complex frontlines from json"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import heapq
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import re
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -28,6 +27,7 @@ from gen.ground_forces.combat_stance import CombatStance
|
|||||||
from gen.runways import RunwayAssigner, RunwayData
|
from gen.runways import RunwayAssigner, RunwayData
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .missiontarget import MissionTarget
|
from .missiontarget import MissionTarget
|
||||||
|
from game.point_with_heading import PointWithHeading
|
||||||
from .theatergroundobject import (
|
from .theatergroundobject import (
|
||||||
BaseDefenseGroundObject,
|
BaseDefenseGroundObject,
|
||||||
EwrGroundObject,
|
EwrGroundObject,
|
||||||
@ -71,44 +71,43 @@ class LocationType(Enum):
|
|||||||
Shorad = "SHORAD"
|
Shorad = "SHORAD"
|
||||||
StrikeTarget = "strike target"
|
StrikeTarget = "strike target"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PresetLocations:
|
class PresetLocations:
|
||||||
"""Defines the preset locations loaded from the campaign mission file."""
|
"""Defines the preset locations loaded from the campaign mission file."""
|
||||||
|
|
||||||
#: Locations used for spawning ground defenses for bases.
|
#: Locations used for spawning ground defenses for bases.
|
||||||
base_garrisons: List[Point] = field(default_factory=list)
|
base_garrisons: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used for spawning air defenses for bases. Used by SAMs, AAA,
|
#: Locations used for spawning air defenses for bases. Used by SAMs, AAA,
|
||||||
#: and SHORADs.
|
#: and SHORADs.
|
||||||
base_air_defense: List[Point] = field(default_factory=list)
|
base_air_defense: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by EWRs.
|
#: Locations used by EWRs.
|
||||||
ewrs: List[Point] = field(default_factory=list)
|
ewrs: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by non-carrier ships. Carriers and LHAs are not random.
|
#: Locations used by non-carrier ships. Carriers and LHAs are not random.
|
||||||
ships: List[Point] = field(default_factory=list)
|
ships: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by coastal defenses.
|
#: Locations used by coastal defenses.
|
||||||
coastal_defenses: List[Point] = field(default_factory=list)
|
coastal_defenses: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by ground based strike objectives.
|
#: Locations used by ground based strike objectives.
|
||||||
strike_locations: List[Point] = field(default_factory=list)
|
strike_locations: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by offshore strike objectives.
|
#: Locations used by offshore strike objectives.
|
||||||
offshore_strike_locations: List[Point] = field(default_factory=list)
|
offshore_strike_locations: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations used by missile sites like scuds and V-2s.
|
#: Locations used by missile sites like scuds and V-2s.
|
||||||
missile_sites: List[Point] = field(default_factory=list)
|
missile_sites: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations of long range SAMs which should always be spawned.
|
#: Locations of long range SAMs which should always be spawned.
|
||||||
required_long_range_sams: List[Point] = field(default_factory=list)
|
required_long_range_sams: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
#: Locations of medium range SAMs which should always be spawned.
|
#: Locations of medium range SAMs which should always be spawned.
|
||||||
required_medium_range_sams: List[Point] = field(default_factory=list)
|
required_medium_range_sams: List[PointWithHeading] = field(default_factory=list)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _random_from(points: List[Point]) -> Optional[Point]:
|
def _random_from(points: List[PointWithHeading]) -> Optional[PointWithHeading]:
|
||||||
"""Finds, removes, and returns a random position from the given list."""
|
"""Finds, removes, and returns a random position from the given list."""
|
||||||
if not points:
|
if not points:
|
||||||
return None
|
return None
|
||||||
@ -116,7 +115,7 @@ class PresetLocations:
|
|||||||
points.remove(point)
|
points.remove(point)
|
||||||
return point
|
return point
|
||||||
|
|
||||||
def random_for(self, location_type: LocationType) -> Optional[Point]:
|
def random_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
||||||
"""Returns a position suitable for the given location type.
|
"""Returns a position suitable for the given location type.
|
||||||
|
|
||||||
The location, if found, will be claimed by the caller and not available
|
The location, if found, will be claimed by the caller and not available
|
||||||
@ -376,20 +375,18 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
# TODO: Should be Airbase specific.
|
# TODO: Should be Airbase specific.
|
||||||
def clear_base_defenses(self) -> None:
|
def clear_base_defenses(self) -> None:
|
||||||
for base_defense in self.base_defenses:
|
for base_defense in self.base_defenses:
|
||||||
|
p = PointWithHeading.from_point(base_defense.position, base_defense.heading)
|
||||||
if isinstance(base_defense, EwrGroundObject):
|
if isinstance(base_defense, EwrGroundObject):
|
||||||
self.preset_locations.ewrs.append(base_defense.position)
|
self.preset_locations.ewrs.append(p)
|
||||||
elif isinstance(base_defense, SamGroundObject):
|
elif isinstance(base_defense, SamGroundObject):
|
||||||
self.preset_locations.base_air_defense.append(
|
self.preset_locations.base_air_defense.append(p)
|
||||||
base_defense.position)
|
|
||||||
elif isinstance(base_defense, VehicleGroupGroundObject):
|
elif isinstance(base_defense, VehicleGroupGroundObject):
|
||||||
self.preset_locations.base_garrisons.append(
|
self.preset_locations.base_garrisons.append(p)
|
||||||
base_defense.position)
|
|
||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Could not determine preset location type for "
|
"Could not determine preset location type for "
|
||||||
f"{base_defense}. Assuming garrison type.")
|
f"{base_defense}. Assuming garrison type.")
|
||||||
self.preset_locations.base_garrisons.append(
|
self.preset_locations.base_garrisons.append(p)
|
||||||
base_defense.position)
|
|
||||||
self.base_defenses = []
|
self.base_defenses = []
|
||||||
|
|
||||||
def capture_equipment(self, game: Game) -> None:
|
def capture_equipment(self, game: Game) -> None:
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from dcs.vehicles import AirDefence
|
|||||||
|
|
||||||
from game import Game, db
|
from game import Game, db
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
from game.theater import Carrier, Lha, LocationType
|
from game.theater import Carrier, Lha, LocationType, PointWithHeading
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
BuildingGroundObject,
|
BuildingGroundObject,
|
||||||
CarrierGroundObject,
|
CarrierGroundObject,
|
||||||
@ -22,10 +22,11 @@ from game.theater.theatergroundobject import (
|
|||||||
MissileSiteGroundObject,
|
MissileSiteGroundObject,
|
||||||
SamGroundObject,
|
SamGroundObject,
|
||||||
ShipGroundObject,
|
ShipGroundObject,
|
||||||
VehicleGroupGroundObject,
|
VehicleGroupGroundObject, CoastalSiteGroundObject,
|
||||||
)
|
)
|
||||||
from game.version import VERSION
|
from game.version import VERSION
|
||||||
from gen import namegen
|
from gen import namegen
|
||||||
|
from gen.coastal.coastal_group_generator import generate_coastal_group
|
||||||
from gen.defenses.armor_group_generator import generate_armor_group
|
from gen.defenses.armor_group_generator import generate_armor_group
|
||||||
from gen.fleet.ship_group_generator import (
|
from gen.fleet.ship_group_generator import (
|
||||||
generate_carrier_group,
|
generate_carrier_group,
|
||||||
@ -142,12 +143,12 @@ class LocationFinder:
|
|||||||
self.miz_data = MizDataLocationFinder.compute_possible_locations(
|
self.miz_data = MizDataLocationFinder.compute_possible_locations(
|
||||||
game.theater.terrain.name, control_point.full_name)
|
game.theater.terrain.name, control_point.full_name)
|
||||||
|
|
||||||
def location_for(self, location_type: LocationType) -> Optional[Point]:
|
def location_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
||||||
position = self.control_point.preset_locations.random_for(location_type)
|
position = self.control_point.preset_locations.random_for(location_type)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
return position
|
return position
|
||||||
|
|
||||||
logging.warning(f"No campaign location for %s at %s",
|
logging.warning(f"No campaign location for %s Mat %s",
|
||||||
location_type.value, self.control_point)
|
location_type.value, self.control_point)
|
||||||
position = self.random_from_miz_data(
|
position = self.random_from_miz_data(
|
||||||
location_type == LocationType.OffshoreStrikeTarget)
|
location_type == LocationType.OffshoreStrikeTarget)
|
||||||
@ -164,7 +165,7 @@ class LocationFinder:
|
|||||||
location_type.value, self.control_point)
|
location_type.value, self.control_point)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def random_from_miz_data(self, offshore: bool) -> Optional[Point]:
|
def random_from_miz_data(self, offshore: bool) -> Optional[PointWithHeading]:
|
||||||
if offshore:
|
if offshore:
|
||||||
locations = self.miz_data.offshore_locations
|
locations = self.miz_data.offshore_locations
|
||||||
else:
|
else:
|
||||||
@ -172,11 +173,16 @@ class LocationFinder:
|
|||||||
if self.miz_data.offshore_locations:
|
if self.miz_data.offshore_locations:
|
||||||
preset = random.choice(locations)
|
preset = random.choice(locations)
|
||||||
locations.remove(preset)
|
locations.remove(preset)
|
||||||
return preset.position
|
return PointWithHeading.from_point(preset.position, preset.heading)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def random_position(self, location_type: LocationType) -> Optional[Point]:
|
def random_position(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
||||||
# TODO: Flesh out preset locations so we never hit this case.
|
# TODO: Flesh out preset locations so we never hit this case.
|
||||||
|
|
||||||
|
if location_type == LocationType.Coastal:
|
||||||
|
# No coastal locations generated randomly
|
||||||
|
return None
|
||||||
|
|
||||||
logging.warning("Falling back to random location for %s at %s",
|
logging.warning("Falling back to random location for %s at %s",
|
||||||
location_type.value, self.control_point)
|
location_type.value, self.control_point)
|
||||||
|
|
||||||
@ -228,7 +234,7 @@ class LocationFinder:
|
|||||||
|
|
||||||
def _find_random_position(self, min_range: int, max_range: int,
|
def _find_random_position(self, min_range: int, max_range: int,
|
||||||
on_ground: bool, is_base_defense: bool,
|
on_ground: bool, is_base_defense: bool,
|
||||||
avoid_others: bool) -> Optional[Point]:
|
avoid_others: bool) -> Optional[PointWithHeading]:
|
||||||
"""
|
"""
|
||||||
Find a valid ground object location
|
Find a valid ground object location
|
||||||
:param on_ground: Whether it should be on ground or on sea (True = on
|
:param on_ground: Whether it should be on ground or on sea (True = on
|
||||||
@ -241,7 +247,7 @@ class LocationFinder:
|
|||||||
near = self.control_point.position
|
near = self.control_point.position
|
||||||
others = self.control_point.ground_objects
|
others = self.control_point.ground_objects
|
||||||
|
|
||||||
def is_valid(point: Optional[Point]) -> bool:
|
def is_valid(point: Optional[PointWithHeading]) -> bool:
|
||||||
if point is None:
|
if point is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -272,9 +278,9 @@ class LocationFinder:
|
|||||||
|
|
||||||
for _ in range(300):
|
for _ in range(300):
|
||||||
# Check if on land or sea
|
# Check if on land or sea
|
||||||
p = near.random_point_within(max_range, min_range)
|
p = PointWithHeading.from_point(near.random_point_within(max_range, min_range), random.randint(0, 360))
|
||||||
if is_valid(p):
|
if is_valid(p):
|
||||||
return p
|
return
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -544,6 +550,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
if self.faction.missiles:
|
if self.faction.missiles:
|
||||||
self.generate_missile_sites()
|
self.generate_missile_sites()
|
||||||
|
|
||||||
|
if self.faction.coastal_defenses:
|
||||||
|
self.generate_coastal_sites()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def generate_ground_points(self) -> None:
|
def generate_ground_points(self) -> None:
|
||||||
@ -668,6 +677,26 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
self.control_point.connected_objectives.append(g)
|
self.control_point.connected_objectives.append(g)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def generate_coastal_sites(self) -> None:
|
||||||
|
for i in range(self.faction.coastal_group_count):
|
||||||
|
self.generate_coastal_site()
|
||||||
|
|
||||||
|
def generate_coastal_site(self) -> None:
|
||||||
|
position = self.location_finder.location_for(LocationType.Coastal)
|
||||||
|
if position is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
g = CoastalSiteGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
position, self.control_point, position.heading)
|
||||||
|
group = generate_coastal_group(self.game, g, self.faction_name)
|
||||||
|
g.groups = []
|
||||||
|
if group is not None:
|
||||||
|
g.groups.append(group)
|
||||||
|
self.control_point.connected_objectives.append(g)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||||
def generate(self) -> bool:
|
def generate(self) -> bool:
|
||||||
|
|||||||
@ -309,6 +309,23 @@ class MissileSiteGroundObject(TheaterGroundObject):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CoastalSiteGroundObject(TheaterGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int, position: Point,
|
||||||
|
control_point: ControlPoint, heading) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="aa",
|
||||||
|
group_id=group_id,
|
||||||
|
position=position,
|
||||||
|
heading=heading,
|
||||||
|
control_point=control_point,
|
||||||
|
dcs_identifier="AA",
|
||||||
|
airbase_group=False,
|
||||||
|
sea_object=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDefenseGroundObject(TheaterGroundObject):
|
class BaseDefenseGroundObject(TheaterGroundObject):
|
||||||
"""Base type for all base defenses."""
|
"""Base type for all base defenses."""
|
||||||
|
|
||||||
|
|||||||
27
gen/coastal/coastal_group_generator.py
Normal file
27
gen/coastal/coastal_group_generator.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import logging
|
||||||
|
import random
|
||||||
|
from game import db
|
||||||
|
from gen.coastal.silkworm import SilkwormGenerator
|
||||||
|
|
||||||
|
COASTAL_MAP = {
|
||||||
|
"SilkwormGenerator": SilkwormGenerator,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_coastal_group(game, ground_object, faction_name: str):
|
||||||
|
"""
|
||||||
|
This generate a coastal defenses group
|
||||||
|
:return: Nothing, but put the group reference inside the ground object
|
||||||
|
"""
|
||||||
|
faction = db.FACTIONS[faction_name]
|
||||||
|
if len(faction.coastal_defenses) > 0:
|
||||||
|
generators = faction.coastal_defenses
|
||||||
|
if len(generators) > 0:
|
||||||
|
gen = random.choice(generators)
|
||||||
|
if gen in COASTAL_MAP.keys():
|
||||||
|
generator = COASTAL_MAP[gen](game, ground_object, faction)
|
||||||
|
generator.generate()
|
||||||
|
return generator.get_generated_group()
|
||||||
|
else:
|
||||||
|
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
|
||||||
|
return None
|
||||||
32
gen/coastal/silkworm.py
Normal file
32
gen/coastal/silkworm.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from dcs.vehicles import MissilesSS, Unarmed, AirDefence
|
||||||
|
|
||||||
|
from gen.sam.group_generator import GroupGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class SilkwormGenerator(GroupGenerator):
|
||||||
|
|
||||||
|
def __init__(self, game, ground_object, faction):
|
||||||
|
super(SilkwormGenerator, self).__init__(game, ground_object)
|
||||||
|
self.faction = faction
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
|
||||||
|
positions = self.get_circular_position(3, launcher_distance=120, coverage=180)
|
||||||
|
|
||||||
|
self.add_unit(MissilesSS.Silkworm_Radar, "SR#0", self.position.x, self.position.y, self.heading)
|
||||||
|
|
||||||
|
# Launchers
|
||||||
|
for i, p in enumerate(positions):
|
||||||
|
self.add_unit(MissilesSS.SS_N_2_Silkworm, "Missile#" + str(i), p[0], p[1], self.heading)
|
||||||
|
|
||||||
|
# Commander
|
||||||
|
self.add_unit(Unarmed.Transport_UAZ_469, "UAZ#0", self.position.x - 35, self.position.y - 20,
|
||||||
|
self.heading)
|
||||||
|
|
||||||
|
# Shorad
|
||||||
|
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "SHILKA#0", self.position.x - 55, self.position.y - 38,
|
||||||
|
self.heading)
|
||||||
|
|
||||||
|
# Shorad 2
|
||||||
|
self.add_unit(AirDefence.SAM_SA_9_Strela_1_9P31, "STRELA#0",
|
||||||
|
self.position.x + 200, self.position.y + 15, 90)
|
||||||
@ -39,6 +39,7 @@ def generate_ship_group(game, ground_object, faction_name: str):
|
|||||||
gen = random.choice(faction.navy_generators)
|
gen = random.choice(faction.navy_generators)
|
||||||
if gen in SHIP_MAP.keys():
|
if gen in SHIP_MAP.keys():
|
||||||
generator = SHIP_MAP[gen](game, ground_object, faction)
|
generator = SHIP_MAP[gen](game, ground_object, faction)
|
||||||
|
print(generator.position)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -12,7 +12,7 @@ MISSILES_MAP = {
|
|||||||
|
|
||||||
def generate_missile_group(game, ground_object, faction_name: str):
|
def generate_missile_group(game, ground_object, faction_name: str):
|
||||||
"""
|
"""
|
||||||
This generate a ship group
|
This generate a missiles group
|
||||||
:return: Nothing, but put the group reference inside the ground object
|
:return: Nothing, but put the group reference inside the ground object
|
||||||
"""
|
"""
|
||||||
faction = db.FACTIONS[faction_name]
|
faction = db.FACTIONS[faction_name]
|
||||||
|
|||||||
@ -134,6 +134,8 @@ def load_icons():
|
|||||||
ICONS["missile_blue"] = QPixmap("./resources/ui/ground_assets/missile_blue.png")
|
ICONS["missile_blue"] = QPixmap("./resources/ui/ground_assets/missile_blue.png")
|
||||||
ICONS["nothreat"] = QPixmap("./resources/ui/ground_assets/nothreat.png")
|
ICONS["nothreat"] = QPixmap("./resources/ui/ground_assets/nothreat.png")
|
||||||
ICONS["nothreat_blue"] = QPixmap("./resources/ui/ground_assets/nothreat_blue.png")
|
ICONS["nothreat_blue"] = QPixmap("./resources/ui/ground_assets/nothreat_blue.png")
|
||||||
|
ICONS["coastal"] = QPixmap("./resources/ui/ground_assets/coastal.png")
|
||||||
|
ICONS["coastal_blue"] = QPixmap("./resources/ui/ground_assets/coastal_blue.png")
|
||||||
|
|
||||||
ICONS["Generator"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/generator.png")
|
ICONS["Generator"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/generator.png")
|
||||||
ICONS["Missile"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/missile.png")
|
ICONS["Missile"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/missile.png")
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from game import Game
|
|||||||
from game.data.building_data import FORTIFICATION_BUILDINGS
|
from game.data.building_data import FORTIFICATION_BUILDINGS
|
||||||
from game.db import REWARDS
|
from game.db import REWARDS
|
||||||
from game.theater import ControlPoint, TheaterGroundObject
|
from game.theater import ControlPoint, TheaterGroundObject
|
||||||
from game.theater.theatergroundobject import MissileSiteGroundObject
|
from game.theater.theatergroundobject import MissileSiteGroundObject, CoastalSiteGroundObject
|
||||||
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
|
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
|
||||||
from .QMapObject import QMapObject
|
from .QMapObject import QMapObject
|
||||||
from ...displayoptions import DisplayOptions
|
from ...displayoptions import DisplayOptions
|
||||||
@ -77,6 +77,8 @@ class QMapGroundObject(QMapObject):
|
|||||||
cat = "ship"
|
cat = "ship"
|
||||||
if isinstance(self.ground_object, MissileSiteGroundObject):
|
if isinstance(self.ground_object, MissileSiteGroundObject):
|
||||||
cat = "missile"
|
cat = "missile"
|
||||||
|
if isinstance(self.ground_object, CoastalSiteGroundObject):
|
||||||
|
cat = "coastal"
|
||||||
|
|
||||||
rect = QRect(option.rect.x() + 2, option.rect.y(),
|
rect = QRect(option.rect.x() + 2, option.rect.y(),
|
||||||
option.rect.width() - 2, option.rect.height())
|
option.rect.width() - 2, option.rect.height())
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -57,6 +57,10 @@
|
|||||||
"BoxSpringGenerator",
|
"BoxSpringGenerator",
|
||||||
"TallRackGenerator"
|
"TallRackGenerator"
|
||||||
],
|
],
|
||||||
|
"coastal_defenses": [
|
||||||
|
"SilkwormGenerator"
|
||||||
|
],
|
||||||
|
"coastal_group_count": 2,
|
||||||
"aircraft_carrier": [
|
"aircraft_carrier": [
|
||||||
"CV_1143_5_Admiral_Kuznetsov"
|
"CV_1143_5_Admiral_Kuznetsov"
|
||||||
],
|
],
|
||||||
|
|||||||
@ -69,6 +69,10 @@
|
|||||||
"ScudGenerator"
|
"ScudGenerator"
|
||||||
],
|
],
|
||||||
"missiles_group_count": 1,
|
"missiles_group_count": 1,
|
||||||
|
"coastal_defenses": [
|
||||||
|
"SilkwormGenerator"
|
||||||
|
],
|
||||||
|
"coastal_group_count": 2,
|
||||||
"navy_generators": [
|
"navy_generators": [
|
||||||
"GrishaGroupGenerator",
|
"GrishaGroupGenerator",
|
||||||
"MolniyaGroupGenerator"
|
"MolniyaGroupGenerator"
|
||||||
|
|||||||
@ -79,6 +79,10 @@
|
|||||||
"ScudGenerator"
|
"ScudGenerator"
|
||||||
],
|
],
|
||||||
"missiles_group_count": 1,
|
"missiles_group_count": 1,
|
||||||
|
"coastal_defenses": [
|
||||||
|
"SilkwormGenerator"
|
||||||
|
],
|
||||||
|
"coastal_group_count": 3,
|
||||||
"navy_generators": [
|
"navy_generators": [
|
||||||
"GrishaGroupGenerator",
|
"GrishaGroupGenerator",
|
||||||
"MolniyaGroupGenerator"
|
"MolniyaGroupGenerator"
|
||||||
|
|||||||
BIN
resources/ui/ground_assets/coastal.png
Normal file
BIN
resources/ui/ground_assets/coastal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/ui/ground_assets/coastal_blue.png
Normal file
BIN
resources/ui/ground_assets/coastal_blue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
x
Reference in New Issue
Block a user