Remove random objective generation.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1076
This commit is contained in:
Dan Albert 2021-05-30 20:37:51 -07:00
parent d1c7146a47
commit 871e7f7a50
6 changed files with 91 additions and 593 deletions

View File

@ -1,7 +1,6 @@
from __future__ import annotations
import itertools
import logging
import math
from dataclasses import dataclass
from functools import cached_property
@ -40,9 +39,6 @@ from dcs.unitgroup import (
VehicleGroup,
)
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from .latlon import LatLon
from ..scenery_group import SceneryGroup
from pyproj import CRS, Transformer
from shapely import geometry, ops
@ -57,10 +53,12 @@ from .controlpoint import (
)
from .frontline import FrontLine
from .landmap import Landmap, load_landmap, poly_contains
from .latlon import LatLon
from .projections import TransverseMercator
from ..point_with_heading import PointWithHeading
from ..profiling import logged_duration
from ..utils import Distance, meters, nautical_miles
from ..scenery_group import SceneryGroup
from ..utils import Distance, meters
SIZE_TINY = 150
SIZE_SMALL = 600
@ -87,42 +85,39 @@ class MizCampaignLoader:
FOB_UNIT_TYPE = Unarmed.Truck_SKP_11_Mobile_ATC.id
FARP_HELIPAD = "SINGLE_HELIPAD"
EWR_UNIT_TYPE = AirDefence.EWR_55G6.id
SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR.id
GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_Grison.id
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
SHIP_UNIT_TYPE = DDG_Arleigh_Burke_IIa.id
MISSILE_SITE_UNIT_TYPE = MissilesSS.SSM_SS_1C_Scud_B.id
COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.AShM_SS_N_2_Silkworm.id
# Multiple options for the required SAMs so campaign designers can more
# accurately see the coverage of their IADS for the expected type.
REQUIRED_LONG_RANGE_SAM_UNIT_TYPES = {
# Multiple options for air defenses so campaign designers can more accurately see
# the coverage of their IADS for the expected type.
LONG_RANGE_SAM_UNIT_TYPES = {
AirDefence.SAM_Patriot_LN.id,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_C.id,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_D.id,
}
REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES = {
MEDIUM_RANGE_SAM_UNIT_TYPES = {
AirDefence.SAM_Hawk_LN_M192.id,
AirDefence.SAM_SA_2_S_75_Guideline_LN.id,
AirDefence.SAM_SA_3_S_125_Goa_LN.id,
}
REQUIRED_SHORT_RANGE_SAM_UNIT_TYPES = {
SHORT_RANGE_SAM_UNIT_TYPES = {
AirDefence.SAM_Avenger__Stinger.id,
AirDefence.SAM_Rapier_LN.id,
AirDefence.SAM_SA_19_Tunguska_Grison.id,
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL.id,
}
REQUIRED_AAA_UNIT_TYPES = {
AAA_UNIT_TYPES = {
AirDefence.AAA_8_8cm_Flak_18.id,
AirDefence.SPAAA_Vulcan_M163.id,
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish.id,
}
REQUIRED_EWR_UNIT_TYPE = AirDefence.EWR_1L13.id
EWR_UNIT_TYPE = AirDefence.EWR_1L13.id
ARMOR_GROUP_UNIT_TYPE = Armor.MBT_M1A2_Abrams.id
@ -130,9 +125,7 @@ class MizCampaignLoader:
AMMUNITION_DEPOT_UNIT_TYPE = Warehouse.Ammunition_depot.id
REQUIRED_STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id
BASE_DEFENSE_RADIUS = nautical_miles(2)
STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
self.theater = theater
@ -210,98 +203,56 @@ class MizCampaignLoader:
@property
def ships(self) -> Iterator[ShipGroup]:
for group in self.blue.ship_group:
if group.units[0].type == self.SHIP_UNIT_TYPE:
yield group
@property
def required_ships(self) -> Iterator[ShipGroup]:
for group in self.red.ship_group:
if group.units[0].type == self.SHIP_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
@property
def offshore_strike_targets(self) -> Iterator[StaticGroup]:
for group in self.blue.static_group:
if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE:
yield group
@property
def required_offshore_strike_targets(self) -> Iterator[StaticGroup]:
for group in self.red.static_group:
if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE:
yield group
@property
def missile_sites(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE:
yield group
@property
def required_missile_sites(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE:
yield group
@property
def coastal_defenses(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE:
yield group
@property
def required_coastal_defenses(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE:
yield group
@property
def required_long_range_sams(self) -> Iterator[VehicleGroup]:
def long_range_sams(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_LONG_RANGE_SAM_UNIT_TYPES:
if group.units[0].type in self.LONG_RANGE_SAM_UNIT_TYPES:
yield group
@property
def required_medium_range_sams(self) -> Iterator[VehicleGroup]:
def medium_range_sams(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES:
if group.units[0].type in self.MEDIUM_RANGE_SAM_UNIT_TYPES:
yield group
@property
def required_short_range_sams(self) -> Iterator[VehicleGroup]:
def short_range_sams(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_SHORT_RANGE_SAM_UNIT_TYPES:
if group.units[0].type in self.SHORT_RANGE_SAM_UNIT_TYPES:
yield group
@property
def required_aaa(self) -> Iterator[VehicleGroup]:
def aaa(self) -> Iterator[VehicleGroup]:
for group in itertools.chain(self.blue.vehicle_group, self.red.vehicle_group):
if group.units[0].type in self.REQUIRED_AAA_UNIT_TYPES:
if group.units[0].type in self.AAA_UNIT_TYPES:
yield group
@property
def required_ewrs(self) -> Iterator[VehicleGroup]:
def ewrs(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_EWR_UNIT_TYPE:
if group.units[0].type in self.EWR_UNIT_TYPE:
yield group
@property
@ -329,9 +280,9 @@ class MizCampaignLoader:
yield group
@property
def required_strike_targets(self) -> Iterator[StaticGroup]:
def strike_targets(self) -> Iterator[StaticGroup]:
for group in itertools.chain(self.blue.static_group, self.red.static_group):
if group.units[0].type in self.REQUIRED_STRIKE_TARGET_UNIT_TYPE:
if group.units[0].type in self.STRIKE_TARGET_UNIT_TYPE:
yield group
@property
@ -440,112 +391,57 @@ class MizCampaignLoader:
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(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
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(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
else:
closest.preset_locations.strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.ewrs:
closest, distance = self.objective_info(group)
if distance < self.BASE_DEFENSE_RADIUS:
closest.preset_locations.base_ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
else:
closest.preset_locations.ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.offshore_strike_targets:
closest, distance = self.objective_info(group)
closest.preset_locations.offshore_strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_offshore_strike_targets:
closest, distance = self.objective_info(group)
closest.preset_locations.required_offshore_strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.ships:
closest, distance = self.objective_info(group)
closest.preset_locations.ships.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_ships:
closest, distance = self.objective_info(group)
closest.preset_locations.required_ships.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.missile_sites:
closest, distance = self.objective_info(group)
closest.preset_locations.missile_sites.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_missile_sites:
closest, distance = self.objective_info(group)
closest.preset_locations.required_missile_sites.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.coastal_defenses:
closest, distance = self.objective_info(group)
closest.preset_locations.coastal_defenses.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_coastal_defenses:
for group in self.long_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.required_coastal_defenses.append(
closest.preset_locations.long_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_long_range_sams:
for group in self.medium_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.required_long_range_sams.append(
closest.preset_locations.medium_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_medium_range_sams:
for group in self.short_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.required_medium_range_sams.append(
closest.preset_locations.short_range_sams.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_short_range_sams:
for group in self.aaa:
closest, distance = self.objective_info(group)
closest.preset_locations.required_short_range_sams.append(
closest.preset_locations.aaa.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_aaa:
for group in self.ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.required_aaa.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.required_ewrs.append(
closest.preset_locations.ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
@ -573,9 +469,9 @@ class MizCampaignLoader:
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_strike_targets:
for group in self.strike_targets:
closest, distance = self.objective_info(group)
closest.preset_locations.required_strike_locations.append(
closest.preset_locations.strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import heapq
import itertools
import logging
import random
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass, field
@ -44,12 +43,8 @@ from gen.runways import RunwayAssigner, RunwayData
from .base import Base
from .missiontarget import MissionTarget
from .theatergroundobject import (
BaseDefenseGroundObject,
EwrGroundObject,
GenericCarrierGroundObject,
SamGroundObject,
TheaterGroundObject,
VehicleGroupGroundObject,
)
from ..db import PRICES
from ..utils import nautical_miles
@ -78,139 +73,55 @@ class ControlPointType(Enum):
OFF_MAP = 6
class LocationType(Enum):
BaseAirDefense = "base air defense"
Coastal = "coastal defense"
Ewr = "EWR"
BaseEwr = "Base EWR"
Garrison = "garrison"
MissileSite = "missile site"
OffshoreStrikeTarget = "offshore strike target"
Sam = "SAM"
Ship = "ship"
Shorad = "SHORAD"
StrikeTarget = "strike target"
@dataclass
class PresetLocations:
"""Defines the preset locations loaded from the campaign mission file."""
#: Locations used for spawning ground defenses for bases.
base_garrisons: List[PointWithHeading] = field(default_factory=list)
#: Locations used for spawning air defenses for bases. Used by SAMs, AAA,
#: and SHORADs.
base_air_defense: List[PointWithHeading] = field(default_factory=list)
#: Locations used by EWRs.
ewrs: List[PointWithHeading] = field(default_factory=list)
#: Locations used by Base EWRs.
base_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 that will be spawned unless the faction has
#: no navy or the player has disabled ship generation for the owning side.
ships: List[PointWithHeading] = field(default_factory=list)
#: Locations used by non-carrier ships that will be spawned unless the faction has
#: no navy or the player has disable ship generation for the original owning side.
required_ships: List[PointWithHeading] = field(default_factory=list)
#: Locations used by coastal defenses.
#: Locations used by coastal defenses that are generated if the faction is capable.
coastal_defenses: List[PointWithHeading] = field(default_factory=list)
#: Locations used by coastal defenses that are always generated if the faction is
#: capable.
required_coastal_defenses: List[PointWithHeading] = field(default_factory=list)
#: Locations used by ground based strike objectives.
strike_locations: List[PointWithHeading] = field(default_factory=list)
#: Locations used by ground based strike objectives that will always be spawned.
required_strike_locations: List[PointWithHeading] = field(default_factory=list)
#: Locations used by offshore strike objectives.
offshore_strike_locations: List[PointWithHeading] = field(default_factory=list)
#: Locations used by offshore strike objectives that will always be spawned.
required_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 that are generated if the
#: faction is capable.
missile_sites: List[PointWithHeading] = field(default_factory=list)
#: Locations used by missile sites like scuds and V-2s that are always generated if
#: the faction is capable.
required_missile_sites: List[PointWithHeading] = field(default_factory=list)
#: Locations of long range SAMs.
long_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of long range SAMs which should always be spawned.
required_long_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of medium range SAMs.
medium_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of medium range SAMs which should always be spawned.
required_medium_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of short range SAMs.
short_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of short range SAMs which should always be spawned.
required_short_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of AAA groups.
aaa: List[PointWithHeading] = field(default_factory=list)
#: Locations of AAA groups which should always be spawned.
required_aaa: List[PointWithHeading] = field(default_factory=list)
#: Locations of EWRs which should always be spawned.
required_ewrs: List[PointWithHeading] = field(default_factory=list)
#: Locations of EWRs.
ewrs: List[PointWithHeading] = field(default_factory=list)
#: Locations of map scenery to create zones for.
scenery: List[SceneryGroup] = field(default_factory=list)
#: Locations of factories for producing ground units. These will always be spawned.
#: Locations of factories for producing ground units.
factories: List[PointWithHeading] = field(default_factory=list)
#: Locations of ammo depots for controlling number of units on the front line at a control point.
#: Locations of ammo depots for controlling number of units on the front line at a
#: control point.
ammunition_depots: List[PointWithHeading] = field(default_factory=list)
#: Locations of stationary armor groups. These will always be spawned.
#: Locations of stationary armor groups.
armor_groups: List[PointWithHeading] = field(default_factory=list)
@staticmethod
def _random_from(points: List[PointWithHeading]) -> Optional[PointWithHeading]:
"""Finds, removes, and returns a random position from the given list."""
if not points:
return None
point = random.choice(points)
points.remove(point)
return point
def random_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
"""Returns a position suitable for the given location type.
The location, if found, will be claimed by the caller and not available
to subsequent calls.
"""
if location_type == LocationType.BaseAirDefense:
return self._random_from(self.base_air_defense)
if location_type == LocationType.Coastal:
return self._random_from(self.coastal_defenses)
if location_type == LocationType.Ewr:
return self._random_from(self.ewrs)
if location_type == LocationType.BaseEwr:
return self._random_from(self.base_ewrs)
if location_type == LocationType.Garrison:
return self._random_from(self.base_garrisons)
if location_type == LocationType.MissileSite:
return self._random_from(self.missile_sites)
if location_type == LocationType.OffshoreStrikeTarget:
return self._random_from(self.offshore_strike_locations)
if location_type == LocationType.Sam:
return self._random_from(self.strike_locations)
if location_type == LocationType.Ship:
return self._random_from(self.ships)
if location_type == LocationType.Shorad:
return self._random_from(self.base_garrisons)
if location_type == LocationType.StrikeTarget:
return self._random_from(self.strike_locations)
logging.error(f"Unknown location type: {location_type}")
return None
@dataclass(frozen=True)
class PendingOccupancy:
@ -338,7 +249,6 @@ class ControlPoint(MissionTarget, ABC):
self.full_name = name
self.at = at
self.connected_objectives: List[TheaterGroundObject] = []
self.base_defenses: List[BaseDefenseGroundObject] = []
self.preset_locations = PresetLocations()
self.helipads: List[PointWithHeading] = []
@ -367,7 +277,7 @@ class ControlPoint(MissionTarget, ABC):
@property
def ground_objects(self) -> List[TheaterGroundObject]:
return list(itertools.chain(self.connected_objectives, self.base_defenses))
return list(self.connected_objectives)
@property
@abstractmethod
@ -553,24 +463,6 @@ class ControlPoint(MissionTarget, ABC):
def is_friendly_to(self, control_point: ControlPoint) -> bool:
return control_point.is_friendly(self.captured)
# TODO: Should be Airbase specific.
def clear_base_defenses(self) -> None:
for base_defense in self.base_defenses:
p = PointWithHeading.from_point(base_defense.position, base_defense.heading)
if isinstance(base_defense, EwrGroundObject):
self.preset_locations.base_ewrs.append(p)
elif isinstance(base_defense, SamGroundObject):
self.preset_locations.base_air_defense.append(p)
elif isinstance(base_defense, VehicleGroupGroundObject):
self.preset_locations.base_garrisons.append(p)
else:
logging.error(
"Could not determine preset location type for "
f"{base_defense}. Assuming garrison type."
)
self.preset_locations.base_garrisons.append(p)
self.base_defenses = []
def capture_equipment(self, game: Game) -> None:
total = self.base.total_armor_value
self.base.armor.clear()
@ -668,11 +560,6 @@ class ControlPoint(MissionTarget, ABC):
self.base.set_strength_to_minimum()
self.clear_base_defenses()
from .start_generator import BaseDefenseGenerator
BaseDefenseGenerator(game, self).generate()
@abstractmethod
def can_operate(self, aircraft: Type[FlyingType]) -> bool:
...

View File

@ -1,12 +1,11 @@
from __future__ import annotations
from game.scenery_group import SceneryGroup
import logging
import pickle
import random
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional, Set
from typing import Any, Dict, Iterable, List, Set
from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike
@ -14,7 +13,8 @@ from dcs.vehicles import AirDefence
from game import Game, db
from game.factions.faction import Faction
from game.theater import Carrier, Lha, LocationType, PointWithHeading
from game.scenery_group import SceneryGroup
from game.theater import Carrier, Lha, PointWithHeading
from game.theater.theatergroundobject import (
BuildingGroundObject,
CarrierGroundObject,
@ -39,8 +39,8 @@ from gen.fleet.ship_group_generator import (
)
from gen.missiles.missiles_group_generator import generate_missile_group
from gen.sam.airdefensegroupgenerator import AirDefenseRange
from gen.sam.sam_group_generator import generate_anti_air_group
from gen.sam.ewr_group_generator import generate_ewr_group
from gen.sam.sam_group_generator import generate_anti_air_group
from . import (
ConflictTheater,
ControlPoint,
@ -145,24 +145,6 @@ class GameGenerator:
cp.captured = True
class LocationFinder:
def __init__(self, control_point: ControlPoint) -> None:
self.control_point = control_point
def location_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
position = self.control_point.preset_locations.random_for(location_type)
if position is not None:
logging.warning(
f"Campaign relies on random generation of %s at %s. Support for random "
"objectives will be removed soon.",
location_type.value,
self.control_point,
)
return position
return None
class ControlPointGroundObjectGenerator:
def __init__(
self,
@ -173,7 +155,6 @@ class ControlPointGroundObjectGenerator:
self.game = game
self.generator_settings = generator_settings
self.control_point = control_point
self.location_finder = LocationFinder(control_point)
@property
def faction_name(self) -> str:
@ -203,19 +184,9 @@ class ControlPointGroundObjectGenerator:
if not self.control_point.captured and skip_enemy_navy:
return
self.generate_required_ships()
for _ in range(self.faction.navy_group_count):
self.generate_ship()
def generate_required_ships(self) -> None:
for position in self.control_point.preset_locations.required_ships:
for position in self.control_point.preset_locations.ships:
self.generate_ship_at(position)
def generate_ship(self) -> None:
point = self.location_finder.location_for(LocationType.Ship)
if point is not None:
self.generate_ship_at(point)
def generate_ship_at(self, position: PointWithHeading) -> None:
group_id = self.game.next_group_id()
@ -289,159 +260,6 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
return True
class BaseDefenseGenerator:
def __init__(self, game: Game, control_point: ControlPoint) -> None:
self.game = game
self.control_point = control_point
self.location_finder = LocationFinder(control_point)
@property
def faction_name(self) -> str:
if self.control_point.captured:
return self.game.player_name
else:
return self.game.enemy_name
@property
def faction(self) -> Faction:
return db.FACTIONS[self.faction_name]
def generate(self) -> None:
self.generate_ewr()
self.generate_garrison()
self.generate_base_defenses()
def generate_ewr(self) -> None:
position = self.location_finder.location_for(LocationType.BaseEwr)
if position is None:
return
group_id = self.game.next_group_id()
g = EwrGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
True,
)
group = generate_ewr_group(self.game, g, self.faction)
if group is None:
logging.error(f"Could not generate EWR at {self.control_point}")
return
g.groups = [group]
self.control_point.base_defenses.append(g)
def generate_base_defenses(self) -> None:
# First group has a 1/2 chance of being a SAM, 1/6 chance of SHORAD,
# and a 1/6 chance of a garrison.
#
# Further groups have a 1/3 chance of being SHORAD and 2/3 chance of
# being a garrison.
for i in range(random.randint(2, 5)):
if i == 0 and random.randint(0, 1) == 0:
self.generate_sam()
elif random.randint(0, 2) == 1:
self.generate_shorad()
else:
self.generate_garrison()
def generate_garrison(self) -> None:
position = self.location_finder.location_for(LocationType.Garrison)
if position is None:
return
group_id = self.game.next_group_id()
g = VehicleGroupGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
group = generate_armor_group(self.faction_name, self.game, g)
if group is None:
logging.error(f"Could not generate garrison at {self.control_point}")
return
g.groups.append(group)
self.control_point.base_defenses.append(g)
def generate_sam(self) -> None:
position = self.location_finder.location_for(LocationType.BaseAirDefense)
if position is None:
return
group_id = self.game.next_group_id()
g = SamGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
groups = generate_anti_air_group(self.game, g, self.faction)
if not groups:
logging.error(f"Could not generate SAM at {self.control_point}")
return
g.groups = groups
self.control_point.base_defenses.append(g)
def generate_shorad(self) -> None:
position = self.location_finder.location_for(LocationType.BaseAirDefense)
if position is None:
return
group_id = self.game.next_group_id()
g = SamGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
groups = generate_anti_air_group(
self.game,
g,
self.faction,
ranges=[{AirDefenseRange.Short, AirDefenseRange.AAA}],
)
if not groups:
logging.error(f"Could not generate SHORAD group at {self.control_point}")
return
g.groups = groups
self.control_point.base_defenses.append(g)
class FobDefenseGenerator(BaseDefenseGenerator):
def generate(self) -> None:
self.generate_garrison()
self.generate_fob_defenses()
def generate_fob_defenses(self):
# First group has a 1/2 chance of being a SHORAD,
# and a 1/2 chance of a garrison.
#
# Further groups have a 1/3 chance of being SHORAD and 2/3 chance of
# being a garrison.
for i in range(random.randint(2, 5)):
if i == 0 and random.randint(0, 1) == 0:
self.generate_shorad()
elif i == 0 and random.randint(0, 1) == 0:
self.generate_garrison()
elif random.randint(0, 2) == 1:
self.generate_shorad()
else:
self.generate_garrison()
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
def __init__(
self,
@ -457,16 +275,14 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
if not super().generate():
return False
BaseDefenseGenerator(self.game, self.control_point).generate()
self.generate_ground_points()
return True
def generate_ground_points(self) -> None:
"""Generate ground objects and AA sites for the control point."""
self.generate_armor_groups()
skip_sams = self.generate_required_aa()
skip_ewrs = self.generate_required_ewr()
self.generate_aa()
self.generate_ewrs()
self.generate_scenery_sites()
self.generate_strike_targets()
self.generate_offshore_strike_targets()
@ -475,35 +291,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
if self.faction.missiles:
self.generate_missile_sites()
self.generate_required_missile_sites()
if self.faction.coastal_defenses:
self.generate_coastal_sites()
self.generate_required_coastal_sites()
if self.control_point.is_global:
return
# Always generate at least one AA point.
self.generate_aa_site()
# And between 2 and 7 other objectives.
amount = random.randrange(2, 7)
for i in range(amount):
# 1 in 4 additional objectives are AA.
if random.randint(0, 3) == 0:
if skip_sams > 0:
skip_sams -= 1
else:
self.generate_aa_site()
# 1 in 4 additional objectives are EWR.
elif random.randint(0, 3) == 0:
if skip_ewrs > 0:
skip_ewrs -= 1
else:
self.generate_ewr_site()
else:
self.generate_ground_point()
def generate_armor_groups(self) -> None:
for position in self.control_point.preset_locations.armor_groups:
@ -531,14 +321,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
g.groups = [group]
self.control_point.connected_objectives.append(g)
def generate_required_aa(self) -> int:
"""Generates the AA sites that are required by the campaign.
Returns:
The number of AA sites that were generated.
"""
def generate_aa(self) -> None:
presets = self.control_point.preset_locations
for position in presets.required_long_range_sams:
for position in presets.long_range_sams:
self.generate_aa_at(
position,
ranges=[
@ -548,7 +333,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
{AirDefenseRange.AAA},
],
)
for position in presets.required_medium_range_sams:
for position in presets.medium_range_sams:
self.generate_aa_at(
position,
ranges=[
@ -557,52 +342,21 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
{AirDefenseRange.AAA},
],
)
for position in presets.required_short_range_sams:
for position in presets.short_range_sams:
self.generate_aa_at(
position,
ranges=[{AirDefenseRange.Short}, {AirDefenseRange.AAA}],
)
for position in presets.required_aaa:
for position in presets.aaa:
self.generate_aa_at(
position,
ranges=[{AirDefenseRange.AAA}],
)
return (
len(presets.required_long_range_sams)
+ len(presets.required_medium_range_sams)
+ len(presets.required_short_range_sams)
+ len(presets.required_aaa)
)
def generate_required_ewr(self) -> int:
"""Generates the EWR sites that are required by the campaign.
Returns:
The number of EWR sites that were generated.
"""
def generate_ewrs(self) -> None:
presets = self.control_point.preset_locations
for position in presets.required_ewrs:
for position in presets.ewrs:
self.generate_ewr_at(position)
return len(presets.required_ewrs)
def generate_ground_point(self) -> None:
try:
category = random.choice(self.faction.building_set)
except IndexError:
logging.exception("Faction has no buildings defined")
return
if category == "oil":
location_type = LocationType.OffshoreStrikeTarget
else:
location_type = LocationType.StrikeTarget
# Pick from preset locations
point = self.location_finder.location_for(location_type)
if point is None:
return
self.generate_strike_target_at(category, point)
def generate_strike_target_at(self, category: str, position: Point) -> None:
@ -635,7 +389,6 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
self.generate_strike_target_at(category="ammo", position=position)
def generate_factories(self) -> None:
"""Generates the factories that are required by the campaign."""
for position in self.control_point.preset_locations.factories:
self.generate_factory_at(position)
@ -653,19 +406,6 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
self.control_point.connected_objectives.append(g)
def generate_aa_site(self) -> None:
position = self.location_finder.location_for(LocationType.Sam)
if position is None:
return
self.generate_aa_at(
position,
ranges=[
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
{AirDefenseRange.Long, AirDefenseRange.Medium},
{AirDefenseRange.Short},
],
)
def generate_aa_at(
self, position: Point, ranges: Iterable[Set[AirDefenseRange]]
) -> None:
@ -689,12 +429,6 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
g.groups = groups
self.control_point.connected_objectives.append(g)
def generate_ewr_site(self) -> None:
position = self.location_finder.location_for(LocationType.Ewr)
if position is None:
return
self.generate_ewr_at(position)
def generate_ewr_at(self, position: PointWithHeading) -> None:
group_id = self.game.next_group_id()
@ -750,18 +484,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
return
def generate_required_missile_sites(self) -> None:
for position in self.control_point.preset_locations.required_missile_sites:
self.generate_missile_site_at(position)
def generate_missile_sites(self) -> None:
for i in range(self.faction.missiles_group_count):
self.generate_missile_site()
def generate_missile_site(self) -> None:
position = self.location_finder.location_for(LocationType.MissileSite)
if position is not None:
return self.generate_missile_site_at(position)
for position in self.control_point.preset_locations.missile_sites:
self.generate_missile_site_at(position)
def generate_missile_site_at(self, position: PointWithHeading) -> None:
group_id = self.game.next_group_id()
@ -776,17 +501,8 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
self.control_point.connected_objectives.append(g)
return
def generate_required_coastal_sites(self) -> None:
for position in self.control_point.preset_locations.required_coastal_defenses:
self.generate_coastal_site_at(position)
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 not None:
for position in self.control_point.preset_locations.coastal_defenses:
self.generate_coastal_site_at(position)
def generate_coastal_site_at(self, position: PointWithHeading) -> None:
@ -807,46 +523,39 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
return
def generate_strike_targets(self) -> None:
"""Generates the strike targets that are required by the campaign."""
building_set = list(set(self.faction.building_set) - {"oil"})
if not building_set:
logging.error("Faction has no buildings defined")
return
for position in self.control_point.preset_locations.required_strike_locations:
for position in self.control_point.preset_locations.strike_locations:
category = random.choice(building_set)
self.generate_strike_target_at(category, position)
def generate_offshore_strike_targets(self) -> None:
"""Generates the offshore strike targets that are required by the campaign."""
if "oil" not in self.faction.building_set:
logging.error("Faction does not support offshore strike targets")
return
for (
position
) in self.control_point.preset_locations.required_offshore_strike_locations:
for position in self.control_point.preset_locations.offshore_strike_locations:
self.generate_strike_target_at("oil", position)
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
def generate(self) -> bool:
self.generate_fob()
FobDefenseGenerator(self.game, self.control_point).generate()
self.generate_armor_groups()
self.generate_factories()
self.generate_ammunition_depots()
self.generate_required_aa()
self.generate_required_ewr()
self.generate_aa()
self.generate_ewrs()
self.generate_scenery_sites()
self.generate_strike_targets()
self.generate_offshore_strike_targets()
if self.faction.missiles:
self.generate_missile_sites()
self.generate_required_missile_sites()
if self.faction.coastal_defenses:
self.generate_coastal_sites()
self.generate_required_coastal_sites()
return True

View File

@ -75,10 +75,16 @@ VERSION = _build_version_string()
#: * SPAAA_ZSU_23_4_Shilka_Gun_Dish,
#:
#: Version 5.0
#: * Ammunition Depots objective locations are now predetermined using the "Ammunition Depot"
#: Warehouse object, and through trigger zone based scenery objects.
#: * The number of alive Ammunition Depot objective buildings connected to a control point
#: directly influences how many ground units can be supported on the front line.
#: * The number of supported ground units at any control point is artificially capped at 50,
#: even if the number of alive Ammunition Depot objectives can support more.
CAMPAIGN_FORMAT_VERSION = (5, 0)
#: * Ammunition Depots objective locations are now predetermined using the "Ammunition
# Depot" Warehouse object, and through trigger zone based scenery objects.
#: * The number of alive Ammunition Depot objective buildings connected to a control
#: point directly influences how many ground units can be supported on the front
#: line.
#: * The number of supported ground units at any control point is artificially
#: capped at 50, even if the number of alive Ammunition Depot objectives can
#: support more.
#:
#: Version 6.0
#: * Random objective generation no is longer supported. Fixed objective locations were
#: added in 4.1.
CAMPAIGN_FORMAT_VERSION = (6, 0)

View File

@ -7,5 +7,5 @@
"description": "<p>You have managed to establish a foothold at Khasab. Continue pushing south.</p>",
"miz": "battle_of_abu_dhabi.miz",
"performance": 2,
"version": "5.0"
"version": "6.0"
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "USA 2005",
"recommended_enemy_faction": "Insurgents (Hard)",
"description": "<p>In this scenario, you start from Jordan, and have to fight your way through eastern Syria.</p>",
"version": "5.0",
"version": "6.0",
"miz": "inherent_resolve.miz",
"performance": 2
}