This commit is contained in:
Dan Albert
2021-02-12 19:58:30 -08:00
parent 053663bd76
commit a47bef1f13
222 changed files with 8434 additions and 4461 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,6 @@ from .radios import RadioFrequency, RadioRegistry
from .tacan import TacanBand, TacanChannel, TacanRegistry
TANKER_DISTANCE = 15000
TANKER_ALT = 4572
TANKER_HEADING_OFFSET = 45
@@ -35,6 +33,7 @@ AWACS_ALT = 13000
@dataclass
class AwacsInfo:
"""AWACS information for the kneeboard."""
dcsGroupName: str
callsign: str
freq: RadioFrequency
@@ -43,6 +42,7 @@ class AwacsInfo:
@dataclass
class TankerInfo:
"""Tanker information for the kneeboard."""
dcsGroupName: str
callsign: str
variant: str
@@ -57,10 +57,14 @@ class AirSupport:
class AirSupportConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry) -> None:
def __init__(
self,
mission: Mission,
conflict: Conflict,
game,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
) -> None:
self.mission = mission
self.conflict = conflict
self.game = game
@@ -81,22 +85,37 @@ class AirSupportConflictGenerator:
elif unit_type is KC135MPRS:
return (TANKER_ALT + 500, 596)
return (TANKER_ALT, 574)
def generate(self):
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
player_cp = (
self.conflict.from_cp
if self.conflict.from_cp.captured
else self.conflict.to_cp
)
fallback_tanker_number = 0
for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side)):
for i, tanker_unit_type in enumerate(
db.find_unittype(Refueling, self.conflict.attackers_side)
):
alt, airspeed = self._get_tanker_params(tanker_unit_type)
variant = db.unit_type_name(tanker_unit_type)
variant = db.unit_type_name(tanker_unit_type)
freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + TANKER_HEADING_OFFSET * i
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
tanker_heading = (
self.conflict.to_cp.position.heading_between_point(
self.conflict.from_cp.position
)
+ TANKER_HEADING_OFFSET * i
)
tanker_position = player_cp.position.point_from_heading(
tanker_heading, TANKER_DISTANCE
)
tanker_group = self.mission.refuel_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_tanker_name(self.mission.country(self.game.player_country), tanker_unit_type),
name=namegen.next_tanker_name(
self.mission.country(self.game.player_country), tanker_unit_type
),
airport=None,
plane_type=tanker_unit_type,
position=tanker_position,
@@ -127,14 +146,23 @@ class AirSupportConflictGenerator:
if tanker_unit_type != IL_78M:
# Override PyDCS tacan channel.
tanker_group.points[0].tasks.pop()
tanker_group.points[0].tasks.append(ActivateBeaconCommand(
tacan.number, tacan.band.value, tacan_callsign, True,
tanker_group.units[0].id, True))
tanker_group.points[0].tasks.append(
ActivateBeaconCommand(
tacan.number,
tacan.band.value,
tacan_callsign,
True,
tanker_group.units[0].id,
True,
)
)
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.tankers.append(TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan))
self.air_support.tankers.append(
TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan)
)
if not self.game.settings.disable_legacy_aewc:
possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side)
@@ -145,11 +173,15 @@ class AirSupportConflictGenerator:
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
name=namegen.next_awacs_name(
self.mission.country(self.game.player_country)
),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
position=self.conflict.position.random_point_within(
AWACS_DISTANCE, AWACS_DISTANCE
),
frequency=freq.mhz,
start_type=StartType.Warm,
)
@@ -158,7 +190,12 @@ class AirSupportConflictGenerator:
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.awacs.append(AwacsInfo(
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
self.air_support.awacs.append(
AwacsInfo(
str(awacs_flight.name),
callsign_for_support_unit(awacs_flight),
freq,
)
)
else:
logging.warning("No AWACS for faction")
logging.warning("No AWACS for faction")

View File

@@ -12,9 +12,17 @@ from dcs.country import Country
from dcs.mapping import Point
from dcs.planes import MQ_9_Reaper
from dcs.point import PointAction
from dcs.task import (EPLRS, AttackGroup, ControlledTask, FireAtPoint,
GoToWaypoint, Hold, OrbitAction, SetImmortalCommand,
SetInvisibleCommand)
from dcs.task import (
EPLRS,
AttackGroup,
ControlledTask,
FireAtPoint,
GoToWaypoint,
Hold,
OrbitAction,
SetImmortalCommand,
SetInvisibleCommand,
)
from dcs.triggers import Event, TriggerOnce
from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup
@@ -24,8 +32,11 @@ from game.unitmap import UnitMap
from game.utils import heading_sum, opposite_heading
from game.theater.controlpoint import ControlPoint
from gen.ground_forces.ai_ground_planner import (DISTANCE_FROM_FRONTLINE,
CombatGroup, CombatGroupRole)
from gen.ground_forces.ai_ground_planner import (
DISTANCE_FROM_FRONTLINE,
CombatGroup,
CombatGroupRole,
)
from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict
@@ -56,6 +67,7 @@ INFANTRY_GROUP_SIZE = 5
@dataclass(frozen=True)
class JtacInfo:
"""JTAC information."""
dcsGroupName: str
unit_name: str
callsign: str
@@ -65,16 +77,16 @@ class JtacInfo:
class GroundConflictGenerator:
def __init__(
self,
mission: Mission,
conflict: Conflict,
game: Game,
player_planned_combat_groups: List[CombatGroup],
enemy_planned_combat_groups: List[CombatGroup],
player_stance: CombatStance,
unit_map: UnitMap) -> None:
self,
mission: Mission,
conflict: Conflict,
game: Game,
player_planned_combat_groups: List[CombatGroup],
enemy_planned_combat_groups: List[CombatGroup],
player_stance: CombatStance,
unit_map: UnitMap,
) -> None:
self.mission = mission
self.conflict = conflict
self.enemy_planned_combat_groups = enemy_planned_combat_groups
@@ -87,14 +99,16 @@ class GroundConflictGenerator:
def _enemy_stance(self):
"""Picks the enemy stance according to the number of planned groups on the frontline for each side"""
if len(self.enemy_planned_combat_groups) > len(self.player_planned_combat_groups):
if len(self.enemy_planned_combat_groups) > len(
self.player_planned_combat_groups
):
return random.choice(
[
CombatStance.AGGRESSIVE,
CombatStance.AGGRESSIVE,
CombatStance.AGGRESSIVE,
CombatStance.ELIMINATION,
CombatStance.BREAKTHROUGH
CombatStance.BREAKTHROUGH,
]
)
else:
@@ -104,31 +118,37 @@ class GroundConflictGenerator:
CombatStance.DEFENSIVE,
CombatStance.DEFENSIVE,
CombatStance.AMBUSH,
CombatStance.AGGRESSIVE
CombatStance.AGGRESSIVE,
]
)
@staticmethod
def _group_point(point: Point, base_distance) -> Point:
distance = random.randint(
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
)
return point.random_point_within(distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR)
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
)
return point.random_point_within(
distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR
)
def generate(self):
position = Conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater)
position = Conflict.frontline_position(
self.conflict.from_cp, self.conflict.to_cp, self.game.theater
)
frontline_vector = Conflict.frontline_vector(
self.conflict.from_cp,
self.conflict.to_cp,
self.game.theater
)
self.conflict.from_cp, self.conflict.to_cp, self.game.theater
)
# Create player groups at random position
player_groups = self._generate_groups(self.player_planned_combat_groups, frontline_vector, True)
player_groups = self._generate_groups(
self.player_planned_combat_groups, frontline_vector, True
)
# Create enemy groups at random position
enemy_groups = self._generate_groups(self.enemy_planned_combat_groups, frontline_vector, False)
enemy_groups = self._generate_groups(
self.enemy_planned_combat_groups, frontline_vector, False
)
# Plan combat actions for groups
self.plan_action_for_groups(
@@ -137,7 +157,7 @@ class GroundConflictGenerator:
enemy_groups,
self.conflict.heading + 90,
self.conflict.from_cp,
self.conflict.to_cp
self.conflict.to_cp,
)
self.plan_action_for_groups(
self.enemy_stance,
@@ -145,7 +165,7 @@ class GroundConflictGenerator:
player_groups,
self.conflict.heading - 90,
self.conflict.to_cp,
self.conflict.from_cp
self.conflict.from_cp,
)
# Add JTAC
@@ -157,34 +177,38 @@ class GroundConflictGenerator:
if self.game.player_faction.jtac_unit is not None:
utype = self.game.player_faction.jtac_unit
jtac = self.mission.flight_group(country=self.mission.country(self.game.player_country),
name=n,
aircraft_type=utype,
position=position[0],
airport=None,
altitude=5000)
jtac = self.mission.flight_group(
country=self.mission.country(self.game.player_country),
name=n,
aircraft_type=utype,
position=position[0],
airport=None,
altitude=5000,
)
jtac.points[0].tasks.append(SetInvisibleCommand(True))
jtac.points[0].tasks.append(SetImmortalCommand(True))
jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle))
frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
jtac.points[0].tasks.append(
OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle)
)
frontline = (
f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
)
# Note: Will need to change if we ever add ground based JTAC.
callsign = callsign_for_support_unit(jtac)
self.jtacs.append(JtacInfo(str(jtac.name), n, callsign, frontline, str(code)))
self.jtacs.append(
JtacInfo(str(jtac.name), n, callsign, frontline, str(code))
)
def gen_infantry_group_for_group(
self,
group: VehicleGroup,
is_player: bool,
side: Country,
forward_heading: int
self, group: VehicleGroup, is_player: bool, side: Country, forward_heading: int
) -> None:
infantry_position = self.conflict.find_ground_position(
group.points[0].position.random_point_within(250, 50),
500,
forward_heading,
self.conflict.theater
)
self.conflict.theater,
)
if not infantry_position:
logging.warning("Could not find infantry position")
return
@@ -208,44 +232,50 @@ class GroundConflictGenerator:
u = random.choice(manpads)
self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, cp.id, u), u,
namegen.next_infantry_name(side, cp.id, u),
u,
position=infantry_position,
group_size=1,
heading=forward_heading,
move_formation=PointAction.OffRoad)
move_formation=PointAction.OffRoad,
)
return
possible_infantry_units = db.find_infantry(faction, allow_manpad=self.game.settings.manpads)
possible_infantry_units = db.find_infantry(
faction, allow_manpad=self.game.settings.manpads
)
if len(possible_infantry_units) == 0:
return
u = random.choice(possible_infantry_units)
self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, cp.id, u), u,
position=infantry_position,
group_size=1,
heading=forward_heading,
move_formation=PointAction.OffRoad)
side,
namegen.next_infantry_name(side, cp.id, u),
u,
position=infantry_position,
group_size=1,
heading=forward_heading,
move_formation=PointAction.OffRoad,
)
for i in range(INFANTRY_GROUP_SIZE):
u = random.choice(possible_infantry_units)
position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, cp.id, u), u,
namegen.next_infantry_name(side, cp.id, u),
u,
position=position,
group_size=1,
heading=forward_heading,
move_formation=PointAction.OffRoad)
move_formation=PointAction.OffRoad,
)
def _set_reform_waypoint(
self,
dcs_group: VehicleGroup,
forward_heading: int
self, dcs_group: VehicleGroup, forward_heading: int
) -> None:
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
rather than spin
rather than spin
"""
reform_point = dcs_group.position.point_from_heading(forward_heading, 50)
dcs_group.add_waypoint(reform_point)
@@ -256,7 +286,7 @@ class GroundConflictGenerator:
gen_group: CombatGroup,
dcs_group: VehicleGroup,
forward_heading: int,
target: Point
target: Point,
) -> bool:
"""
Handles adding the DCS tasks for artillery groups for all combat stances.
@@ -269,7 +299,9 @@ class GroundConflictGenerator:
dcs_group.add_trigger_action(hold_task)
# Artillery strike random start
artillery_trigger = TriggerOnce(Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id))
artillery_trigger = TriggerOnce(
Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id)
)
artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60))
# TODO: Update to fire at group instead of point
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100)
@@ -283,12 +315,19 @@ class GroundConflictGenerator:
# Hold position
dcs_group.points[1].tasks.append(Hold())
retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3))
dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad)
retreat = self.find_retreat_point(
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3)
)
dcs_group.add_waypoint(
dcs_group.position.point_from_heading(forward_heading, 1),
PointAction.OffRoad,
)
dcs_group.points[2].tasks.append(Hold())
dcs_group.add_waypoint(retreat, PointAction.OffRoad)
artillery_fallback = TriggerOnce(Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id))
artillery_fallback = TriggerOnce(
Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id)
)
for i, u in enumerate(dcs_group.units):
artillery_fallback.add_condition(UnitDamaged(u.id))
if i < len(dcs_group.units) - 1:
@@ -302,7 +341,9 @@ class GroundConflictGenerator:
retreat_task.number = 4
dcs_group.add_trigger_action(retreat_task)
artillery_fallback.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks)))
artillery_fallback.add_action(
AITaskPush(dcs_group.id, len(dcs_group.tasks))
)
self.mission.triggerrules.triggers.append(artillery_fallback)
for u in dcs_group.units:
@@ -330,12 +371,8 @@ class GroundConflictGenerator:
target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
if target is not None:
rand_offset = Point(
random.randint(
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
),
random.randint(
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
)
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
)
target_point = self.conflict.theater.nearest_land_pos(
target.points[0].position + rand_offset
@@ -345,8 +382,7 @@ class GroundConflictGenerator:
if (
to_cp.position.distance_to_point(dcs_group.points[0].position)
<=
AGGRESIVE_MOVE_DISTANCE
<= AGGRESIVE_MOVE_DISTANCE
):
attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
@@ -358,16 +394,16 @@ class GroundConflictGenerator:
if offset_heading < 0:
offset_heading = 358
attack_point = self.find_offensive_point(
dcs_group,
offset_heading,
AGGRESIVE_MOVE_DISTANCE
dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE
)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
elif stance == CombatStance.BREAKTHROUGH:
# In breakthrough mode, the units will move forward
# If the enemy base is close enough, the units will attack the base
if to_cp.position.distance_to_point(
dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE:
if (
to_cp.position.distance_to_point(dcs_group.points[0].position)
<= BREAKTHROUGH_OFFENSIVE_DISTANCE
):
attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
@@ -377,27 +413,27 @@ class GroundConflictGenerator:
offset_heading = forward_heading - 1
if offset_heading < 0:
offset_heading = 359
attack_point = self.find_offensive_point(dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE)
attack_point = self.find_offensive_point(
dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE
)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
elif stance == CombatStance.ELIMINATION:
# In elimination mode, the units focus on destroying as much enemy groups as possible
targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3)
for i, target in enumerate(targets, start=1):
rand_offset = Point(
random.randint(
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
),
random.randint(
-RANDOM_OFFSET_ATTACK,
RANDOM_OFFSET_ATTACK
)
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
)
target_point = self.conflict.theater.nearest_land_pos(
target.points[0].position+rand_offset
target.points[0].position + rand_offset
)
dcs_group.add_waypoint(target_point, PointAction.OffRoad)
dcs_group.points[i + 1].tasks.append(AttackGroup(target.id))
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
if (
to_cp.position.distance_to_point(dcs_group.points[0].position)
<= AGGRESIVE_MOVE_DISTANCE
):
attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
@@ -420,12 +456,23 @@ class GroundConflictGenerator:
Returns True if tasking was added, returns False if the stance was not a combat stance.
"""
self._set_reform_waypoint(dcs_group, forward_heading)
if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]:
if stance in [
CombatStance.AGGRESSIVE,
CombatStance.BREAKTHROUGH,
CombatStance.ELIMINATION,
]:
# APC & ATGM will never move too much forward, but will follow along any offensive
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
attack_point = self.conflict.theater.nearest_land_pos(to_cp.position.random_point_within(500, 0))
if (
to_cp.position.distance_to_point(dcs_group.points[0].position)
<= AGGRESIVE_MOVE_DISTANCE
):
attack_point = self.conflict.theater.nearest_land_pos(
to_cp.position.random_point_within(500, 0)
)
else:
attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE)
attack_point = self.find_offensive_point(
dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE
)
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
if stance != CombatStance.RETREAT:
@@ -434,29 +481,36 @@ class GroundConflictGenerator:
return False
def plan_action_for_groups(
self, stance: CombatStance,
self,
stance: CombatStance,
ally_groups: List[Tuple[VehicleGroup, CombatGroup]],
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
forward_heading: int,
from_cp: ControlPoint,
to_cp: ControlPoint
to_cp: ControlPoint,
) -> None:
if not self.game.settings.perf_moving_units:
return
for dcs_group, group in ally_groups:
if hasattr(group.units[0], 'eplrs') and group.units[0].eplrs:
if hasattr(group.units[0], "eplrs") and group.units[0].eplrs:
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
if group.role == CombatGroupRole.ARTILLERY:
if self.game.settings.perf_artillery:
target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups)
target = self.get_artillery_target_in_range(
dcs_group, group, enemy_groups
)
if target is not None:
self._plan_artillery_action(stance, group, dcs_group, forward_heading, target)
self._plan_artillery_action(
stance, group, dcs_group, forward_heading, target
)
elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]:
self._plan_tank_ifv_action(stance, enemy_groups, dcs_group, forward_heading, to_cp)
self._plan_tank_ifv_action(
stance, enemy_groups, dcs_group, forward_heading, to_cp
)
elif group.role in [CombatGroupRole.APC, CombatGroupRole.ATGM]:
self._plan_apc_atgm_action(stance, dcs_group, forward_heading, to_cp)
@@ -464,11 +518,16 @@ class GroundConflictGenerator:
if stance == CombatStance.RETREAT:
# In retreat mode, the units will fall back
# If the ally base is close enough, the units will even regroup there
if from_cp.position.distance_to_point(dcs_group.points[0].position) <= RETREAT_DISTANCE:
if (
from_cp.position.distance_to_point(dcs_group.points[0].position)
<= RETREAT_DISTANCE
):
retreat_point = from_cp.position.random_point_within(500, 250)
else:
retreat_point = self.find_retreat_point(dcs_group, forward_heading)
reposition_point = retreat_point.point_from_heading(forward_heading, 10) # Another point to make the unit face the enemy
reposition_point = retreat_point.point_from_heading(
forward_heading, 10
) # Another point to make the unit face the enemy
dcs_group.add_waypoint(retreat_point, PointAction.OffRoad)
dcs_group.add_waypoint(reposition_point, PointAction.OffRoad)
@@ -490,8 +549,10 @@ class GroundConflictGenerator:
# We add a new retreat waypoint
dcs_group.add_waypoint(
self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)),
PointAction.OffRoad
self.find_retreat_point(
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)
),
PointAction.OffRoad,
)
# Fallback task
@@ -515,7 +576,7 @@ class GroundConflictGenerator:
self,
dcs_group: VehicleGroup,
frontline_heading: int,
distance: int = RETREAT_DISTANCE
distance: int = RETREAT_DISTANCE,
) -> Point:
"""
Find a point to retreat to
@@ -523,17 +584,15 @@ class GroundConflictGenerator:
:param frontline_heading: Heading of the frontline
:return: dcs.mapping.Point object with the desired position
"""
desired_point = dcs_group.points[0].position.point_from_heading(heading_sum(frontline_heading, +180), distance)
desired_point = dcs_group.points[0].position.point_from_heading(
heading_sum(frontline_heading, +180), distance
)
if self.conflict.theater.is_on_land(desired_point):
return desired_point
return self.conflict.theater.nearest_land_pos(desired_point)
def find_offensive_point(
self,
dcs_group: VehicleGroup,
frontline_heading: int,
distance: int
self, dcs_group: VehicleGroup, frontline_heading: int, distance: int
) -> Point:
"""
Find a point to attack
@@ -542,7 +601,9 @@ class GroundConflictGenerator:
:param distance: Distance of the offensive (how far unit should move)
:return: dcs.mapping.Point object with the desired position
"""
desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance)
desired_point = dcs_group.points[0].position.point_from_heading(
frontline_heading, distance
)
if self.conflict.theater.is_on_land(desired_point):
return desired_point
return self.conflict.theater.nearest_land_pos(desired_point)
@@ -551,7 +612,7 @@ class GroundConflictGenerator:
def find_n_nearest_enemy_groups(
player_group: VehicleGroup,
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
n: int
n: int,
) -> List[VehicleGroup]:
"""
Return the nearest enemy group for the player group
@@ -562,7 +623,9 @@ class GroundConflictGenerator:
targets = [] # type: List[Optional[VehicleGroup]]
sorted_list = sorted(
enemy_groups,
key=lambda group: player_group.points[0].position.distance_to_point(group[0].points[0].position)
key=lambda group: player_group.points[0].position.distance_to_point(
group[0].points[0].position
),
)
for i in range(n):
# TODO: Is this supposed to return no groups if enemy_groups is less than n?
@@ -574,8 +637,7 @@ class GroundConflictGenerator:
@staticmethod
def find_nearest_enemy_group(
player_group: VehicleGroup,
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
player_group: VehicleGroup, enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
) -> Optional[VehicleGroup]:
"""
Search the enemy groups for a potential target suitable to armored assault
@@ -585,7 +647,9 @@ class GroundConflictGenerator:
min_distance = 99999999
target = None
for dcs_group, _ in enemy_groups:
dist = player_group.points[0].position.distance_to_point(dcs_group.points[0].position)
dist = player_group.points[0].position.distance_to_point(
dcs_group.points[0].position
)
if dist < min_distance:
min_distance = dist
target = dcs_group
@@ -595,7 +659,7 @@ class GroundConflictGenerator:
def get_artillery_target_in_range(
dcs_group: VehicleGroup,
group: CombatGroup,
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
) -> Optional[Point]:
"""
Search the enemy groups for a potential target suitable to an artillery unit
@@ -606,7 +670,9 @@ class GroundConflictGenerator:
return None
for _ in range(10):
potential_target = random.choice(enemy_groups)[0]
distance_to_target = dcs_group.points[0].position.distance_to_point(potential_target.points[0].position)
distance_to_target = dcs_group.points[0].position.distance_to_point(
potential_target.points[0].position
)
if distance_to_target < rng:
return potential_target.points[0].position
return None
@@ -620,12 +686,12 @@ class GroundConflictGenerator:
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
rg = random.randint(
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1],
)
elif rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
rg = random.randint(
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][0],
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1]
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1],
)
return rg
@@ -635,42 +701,46 @@ class GroundConflictGenerator:
combat_width: int,
distance_from_frontline: int,
heading: int,
spawn_heading: int
spawn_heading: int,
):
shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width))
desired_point = shifted.point_from_heading(
spawn_heading,
distance_from_frontline
shifted = conflict_position.point_from_heading(
heading, random.randint(0, combat_width)
)
desired_point = shifted.point_from_heading(
spawn_heading, distance_from_frontline
)
return Conflict.find_ground_position(
desired_point, combat_width, heading, self.conflict.theater
)
return Conflict.find_ground_position(desired_point, combat_width, heading, self.conflict.theater)
def _generate_groups(
self,
groups: List[CombatGroup],
frontline_vector: Tuple[Point, int, int],
is_player: bool
is_player: bool,
) -> List[Tuple[VehicleGroup, CombatGroup]]:
"""Finds valid positions for planned groups and generates a pydcs group for them"""
positioned_groups = []
position, heading, combat_width = frontline_vector
spawn_heading = int(heading_sum(heading, -90)) if is_player else int(heading_sum(heading, 90))
spawn_heading = (
int(heading_sum(heading, -90))
if is_player
else int(heading_sum(heading, 90))
)
country = self.game.player_country if is_player else self.game.enemy_country
for group in groups:
if group.role == CombatGroupRole.ARTILLERY:
distance_from_frontline = self.get_artilery_group_distance_from_frontline(group)
distance_from_frontline = (
self.get_artilery_group_distance_from_frontline(group)
)
else:
distance_from_frontline = random.randint(
DISTANCE_FROM_FRONTLINE[group.role][0],
DISTANCE_FROM_FRONTLINE[group.role][1]
DISTANCE_FROM_FRONTLINE[group.role][0],
DISTANCE_FROM_FRONTLINE[group.role][1],
)
final_position = self.get_valid_position_for_group(
position,
combat_width,
distance_from_frontline,
heading,
spawn_heading
position, combat_width, distance_from_frontline, heading, spawn_heading
)
if final_position is not None:
@@ -693,7 +763,7 @@ class GroundConflictGenerator:
g,
is_player,
self.mission.country(country),
opposite_heading(spawn_heading)
opposite_heading(spawn_heading),
)
else:
logging.warning(f"Unable to get valid position for {group}")
@@ -718,12 +788,14 @@ class GroundConflictGenerator:
logging.info("armorgen: {} for {}".format(unit, side.id))
group = self.mission.vehicle_group(
side,
namegen.next_unit_name(side, cp.id, unit), unit,
position=at,
group_size=count,
heading=heading,
move_formation=move_formation)
side,
namegen.next_unit_name(side, cp.id, unit),
unit,
position=at,
group_size=count,
heading=heading,
move_formation=move_formation,
)
self.unit_map.add_front_line_units(group, cp)

View File

@@ -96,7 +96,8 @@ class Package:
if tot is None:
logging.error(
f"{flight} requested escort at {waypoint} but that "
"waypoint has no TOT. It may not be escorted.")
"waypoint has no TOT. It may not be escorted."
)
continue
times.append(tot)
if times:
@@ -117,7 +118,8 @@ class Package:
logging.error(
f"{flight} dismissed escort at {waypoint} but that "
"waypoint has no TOT or departure time. It may not be "
"escorted.")
"escorted."
)
continue
times.append(tot)
if times:

View File

@@ -23,9 +23,11 @@ from .runways import RunwayData
if TYPE_CHECKING:
from game import Game
@dataclass
class CommInfo:
"""Communications information for the kneeboard."""
name: str
freq: RadioFrequency
@@ -37,10 +39,13 @@ class FrontLineInfo:
self.enemy_base: ControlPoint = front_line.control_point_b
self.player_zero: bool = self.player_base.base.total_armor == 0
self.enemy_zero: bool = self.enemy_base.base.total_armor == 0
self.advantage: bool = self.player_base.base.total_armor > self.enemy_base.base.total_armor
self.advantage: bool = (
self.player_base.base.total_armor > self.enemy_base.base.total_armor
)
self.stance: CombatStance = self.player_base.stances[self.enemy_base.id]
self.combat_stances = CombatStance
class MissionInfoGenerator:
"""Base type for generators of mission information for the player.
@@ -131,7 +136,6 @@ def format_waypoint_time(waypoint: FlightWaypoint, depart_prefix: str) -> str:
class BriefingGenerator(MissionInfoGenerator):
def __init__(self, mission: Mission, game: Game):
super().__init__(mission, game)
self.allied_flights_by_departure: Dict[str, List[FlightData]] = {}
@@ -141,36 +145,36 @@ class BriefingGenerator(MissionInfoGenerator):
disabled_extensions=("",),
default_for_string=True,
default=True,
),
),
trim_blocks=True,
lstrip_blocks=True,
)
)
env.filters["waypoint_timing"] = format_waypoint_time
self.template = env.get_template("briefingtemplate_EN.j2")
def generate(self) -> None:
"""Generate the mission briefing
"""
"""Generate the mission briefing"""
self._generate_frontline_info()
self.generate_allied_flights_by_departure()
self.mission.set_description_text(self.template.render(vars(self)))
self.mission.add_picture_blue(os.path.abspath(
"./resources/ui/splash_screen.png"))
self.mission.add_picture_blue(
os.path.abspath("./resources/ui/splash_screen.png")
)
def _generate_frontline_info(self) -> None:
"""Build FrontLineInfo objects from FrontLine type and append to briefing.
"""
"""Build FrontLineInfo objects from FrontLine type and append to briefing."""
for front_line in self.game.theater.conflicts(from_player=True):
self.add_frontline(FrontLineInfo(front_line))
# TODO: This should determine if runway is friendly through a method more robust than the existing string match
def generate_allied_flights_by_departure(self) -> None:
"""Create iterable to display allied flights grouped by departure airfield.
"""
"""Create iterable to display allied flights grouped by departure airfield."""
for flight in self.flights:
if not flight.client_units and flight.friendly:
name = flight.departure.airfield_name
if name in self.allied_flights_by_departure: # where else can we get this?
if (
name in self.allied_flights_by_departure
): # where else can we get this?
self.allied_flights_by_departure[name].append(flight)
else:
self.allied_flights_by_departure[name] = [flight]

View File

@@ -23,5 +23,9 @@ def generate_coastal_group(game, ground_object, faction_name: str):
generator.generate()
return generator.get_generated_group()
else:
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
return None
logging.info(
"Unable to generate missile group, generator : "
+ str(gen)
+ "does not exists"
)
return None

View File

@@ -4,7 +4,6 @@ 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
@@ -13,20 +12,47 @@ class SilkwormGenerator(GroupGenerator):
positions = self.get_circular_position(5, launcher_distance=120, coverage=180)
self.add_unit(MissilesSS.Silkworm_Radar, "SR#0", self.position.x, self.position.y, self.heading)
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)
self.add_unit(
MissilesSS.SS_N_2_Silkworm,
"Missile#" + str(i),
p[0],
p[1],
self.heading,
)
# Commander
self.add_unit(Unarmed.Transport_KAMAZ_43101, "KAMAZ#0", self.position.x - 35, self.position.y - 20,
self.heading)
self.add_unit(
Unarmed.Transport_KAMAZ_43101,
"KAMAZ#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)
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)
self.add_unit(
AirDefence.SAM_SA_9_Strela_1_9P31,
"STRELA#0",
self.position.x + 200,
self.position.y + 15,
90,
)

View File

@@ -12,19 +12,21 @@ from game.utils import heading_sum, opposite_heading
FRONTLINE_LENGTH = 80000
class Conflict:
def __init__(self,
theater: ConflictTheater,
from_cp: ControlPoint,
to_cp: ControlPoint,
attackers_side: str,
defenders_side: str,
attackers_country: Country,
defenders_country: Country,
position: Point,
heading: Optional[int] = None,
size: Optional[int] = None
):
def __init__(
self,
theater: ConflictTheater,
from_cp: ControlPoint,
to_cp: ControlPoint,
attackers_side: str,
defenders_side: str,
attackers_country: Country,
defenders_country: Country,
position: Point,
heading: Optional[int] = None,
size: Optional[int] = None,
):
self.attackers_side = attackers_side
self.defenders_side = defenders_side
@@ -43,27 +45,49 @@ class Conflict:
return from_cp.has_frontline and to_cp.has_frontline
@classmethod
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int]:
def frontline_position(
cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater
) -> Tuple[Point, int]:
frontline = FrontLine(from_cp, to_cp, theater)
attack_heading = frontline.attack_heading
position = cls.find_ground_position(frontline.position, FRONTLINE_LENGTH, heading_sum(attack_heading, 90), theater)
position = cls.find_ground_position(
frontline.position,
FRONTLINE_LENGTH,
heading_sum(attack_heading, 90),
theater,
)
return position, opposite_heading(attack_heading)
@classmethod
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
def frontline_vector(
cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater
) -> Tuple[Point, int, int]:
"""
Returns a vector for a valid frontline location avoiding exclusion zones.
"""
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
left_heading = heading_sum(heading, -90)
right_heading = heading_sum(heading, 90)
left_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater)
right_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
left_position = cls.extend_ground_position(
center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater
)
right_position = cls.extend_ground_position(
center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater
)
distance = int(left_position.distance_to_point(right_position))
return left_position, right_heading, distance
@classmethod
def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
def frontline_cas_conflict(
cls,
attacker_name: str,
defender_name: str,
attacker: Country,
defender: Country,
from_cp: ControlPoint,
to_cp: ControlPoint,
theater: ConflictTheater,
):
assert cls.has_frontline_between(from_cp, to_cp)
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
conflict = cls(
@@ -76,12 +100,14 @@ class Conflict:
defenders_side=defender_name,
attackers_country=attacker,
defenders_country=defender,
size=distance
size=distance,
)
return conflict
@classmethod
def extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
def extend_ground_position(
cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater
) -> Point:
"""Finds the first intersection with an exclusion zone in one heading from an initial point up to max_distance"""
extended = initial.point_from_heading(heading, max_distance)
if theater.landmap is None:
@@ -92,8 +118,7 @@ class Conflict:
p1 = ShapelyPoint(extended.x, extended.y)
line = LineString([p0, p1])
intersection = line.intersection(
theater.landmap.inclusion_zone_only.boundary)
intersection = line.intersection(theater.landmap.inclusion_zone_only.boundary)
if intersection.is_empty:
# Max extent does not intersect with the boundary of the inclusion
# zone, so the full front line is usable. This does assume that the
@@ -104,7 +129,14 @@ class Conflict:
return initial.point_from_heading(heading, p0.distance(intersection))
@classmethod
def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater, coerce=True) -> Optional[Point]:
def find_ground_position(
cls,
initial: Point,
max_distance: int,
heading: int,
theater: ConflictTheater,
coerce=True,
) -> Optional[Point]:
"""
Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance.
`coerce=True` will return the closest land position to `initial` regardless of heading or distance
@@ -123,4 +155,3 @@ class Conflict:
return pos
logging.error("Didn't find ground position ({})!".format(initial))
return None

View File

@@ -3,15 +3,20 @@ import random
from dcs.vehicles import Armor
from game import db
from gen.defenses.armored_group_generator import ArmoredGroupGenerator, FixedSizeArmorGroupGenerator
from gen.defenses.armored_group_generator import (
ArmoredGroupGenerator,
FixedSizeArmorGroupGenerator,
)
def generate_armor_group(faction:str, game, ground_object):
def generate_armor_group(faction: str, game, ground_object):
"""
This generate a group of ground units
:return: Generated group
"""
possible_unit = [u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()]
possible_unit = [
u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()
]
if len(possible_unit) > 0:
unit_type = random.choice(possible_unit)
return generate_armor_group_of_type(game, ground_object, unit_type)
@@ -36,4 +41,3 @@ def generate_armor_group_of_type_and_size(game, ground_object, unit_type, size:
generator = FixedSizeArmorGroupGenerator(game, ground_object, unit_type, size)
generator.generate()
return generator.get_generated_group()

View File

@@ -4,7 +4,6 @@ from gen.sam.group_generator import GroupGenerator
class ArmoredGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, unit_type):
super(ArmoredGroupGenerator, self).__init__(game, ground_object)
self.unit_type = unit_type
@@ -20,13 +19,16 @@ class ArmoredGroupGenerator(GroupGenerator):
for i in range(grid_x):
for j in range(grid_y):
index = index + 1
self.add_unit(self.unit_type, "Armor#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j, self.heading)
self.add_unit(
self.unit_type,
"Armor#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
self.heading,
)
class FixedSizeArmorGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, unit_type, size):
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
self.unit_type = unit_type
@@ -38,7 +40,10 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
index = 0
for i in range(self.size):
index = index + 1
self.add_unit(self.unit_type, "Armor#" + str(index),
self.position.x + spacing * i,
self.position.y, self.heading)
self.add_unit(
self.unit_type,
"Armor#" + str(index),
self.position.x + spacing * i,
self.position.y,
self.heading,
)

View File

@@ -2,54 +2,122 @@ import random
from gen.sam.group_generator import ShipGroupGenerator
from dcs.ships import (
USS_Arleigh_Burke_IIa,
Ticonderoga_class
)
from dcs.ships import USS_Arleigh_Burke_IIa, Ticonderoga_class
class CarrierGroupGenerator(ShipGroupGenerator):
def generate(self):
#Carrier Strike Group 8
# Carrier Strike Group 8
if self.faction.carrier_names[0] == "Carrier Strike Group 8":
carrier_type = random.choice(self.faction.aircraft_carrier)
self.add_unit(carrier_type, "CVN-75 Harry S. Truman", self.position.x, self.position.y, self.heading)
self.add_unit(
carrier_type,
"CVN-75 Harry S. Truman",
self.position.x,
self.position.y,
self.heading,
)
# Add Arleigh Burke escort
self.add_unit(USS_Arleigh_Burke_IIa, "USS Ramage", self.position.x + 6482, self.position.y + 6667, self.heading)
self.add_unit(
USS_Arleigh_Burke_IIa,
"USS Ramage",
self.position.x + 6482,
self.position.y + 6667,
self.heading,
)
self.add_unit(USS_Arleigh_Burke_IIa, "USS Mitscher", self.position.x - 7963, self.position.y + 7037, self.heading)
self.add_unit(
USS_Arleigh_Burke_IIa,
"USS Mitscher",
self.position.x - 7963,
self.position.y + 7037,
self.heading,
)
self.add_unit(USS_Arleigh_Burke_IIa, "USS Forrest Sherman", self.position.x - 7408, self.position.y - 7408, self.heading)
self.add_unit(
USS_Arleigh_Burke_IIa,
"USS Forrest Sherman",
self.position.x - 7408,
self.position.y - 7408,
self.heading,
)
self.add_unit(USS_Arleigh_Burke_IIa, "USS Lassen", self.position.x + 8704, self.position.y - 6296, self.heading)
self.add_unit(
USS_Arleigh_Burke_IIa,
"USS Lassen",
self.position.x + 8704,
self.position.y - 6296,
self.heading,
)
# Add Ticonderoga escort
if self.heading >= 180:
self.add_unit(Ticonderoga_class, "USS Hué City", self.position.x + 2222, self.position.y - 3333, self.heading)
self.add_unit(
Ticonderoga_class,
"USS Hué City",
self.position.x + 2222,
self.position.y - 3333,
self.heading,
)
else:
self.add_unit(Ticonderoga_class, "USS Hué City", self.position.x - 3333, self.position.y + 2222, self.heading)
self.add_unit(
Ticonderoga_class,
"USS Hué City",
self.position.x - 3333,
self.position.y + 2222,
self.heading,
)
self.get_generated_group().points[0].speed = 20
##################################################################################################
##################################################################################################
# Add carrier for normal generation
else:
if len(self.faction.aircraft_carrier) > 0:
carrier_type = random.choice(self.faction.aircraft_carrier)
self.add_unit(carrier_type, "Carrier", self.position.x, self.position.y, self.heading)
self.add_unit(
carrier_type,
"Carrier",
self.position.x,
self.position.y,
self.heading,
)
else:
return
# Add destroyers escort
if len(self.faction.destroyers) > 0:
dd_type = random.choice(self.faction.destroyers)
self.add_unit(dd_type, "DD1", self.position.x + 2500, self.position.y + 4500, self.heading)
self.add_unit(dd_type, "DD2", self.position.x + 2500, self.position.y - 4500, self.heading)
self.add_unit(
dd_type,
"DD1",
self.position.x + 2500,
self.position.y + 4500,
self.heading,
)
self.add_unit(
dd_type,
"DD2",
self.position.x + 2500,
self.position.y - 4500,
self.heading,
)
self.add_unit(dd_type, "DD3", self.position.x + 4500, self.position.y + 8500, self.heading)
self.add_unit(dd_type, "DD4", self.position.x + 4500, self.position.y - 8500, self.heading)
self.add_unit(
dd_type,
"DD3",
self.position.x + 4500,
self.position.y + 8500,
self.heading,
)
self.add_unit(
dd_type,
"DD4",
self.position.x + 4500,
self.position.y - 8500,
self.heading,
)
self.get_generated_group().points[0].speed = 20
self.get_generated_group().points[0].speed = 20

View File

@@ -20,7 +20,6 @@ if TYPE_CHECKING:
class ChineseNavyGroupGenerator(ShipGroupGenerator):
def generate(self):
include_frigate = random.choice([True, True, False])
@@ -30,17 +29,45 @@ class ChineseNavyGroupGenerator(ShipGroupGenerator):
include_frigate = True
if include_frigate:
self.add_unit(Type_054A_Frigate, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
self.add_unit(Type_054A_Frigate, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)
self.add_unit(
Type_054A_Frigate,
"FF1",
self.position.x + 1200,
self.position.y + 900,
self.heading,
)
self.add_unit(
Type_054A_Frigate,
"FF2",
self.position.x + 1200,
self.position.y - 900,
self.heading,
)
if include_dd:
dd_type = random.choice([Type_052C_Destroyer, Type_052B_Destroyer])
self.add_unit(dd_type, "DD1", self.position.x + 2400, self.position.y + 900, self.heading)
self.add_unit(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
self.add_unit(
dd_type,
"DD1",
self.position.x + 2400,
self.position.y + 900,
self.heading,
)
self.add_unit(
dd_type,
"DD2",
self.position.x + 2400,
self.position.y - 900,
self.heading,
)
self.get_generated_group().points[0].speed = 20
class Type54GroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(Type54GroupGenerator, self).__init__(game, ground_object, faction, Type_054A_Frigate)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(Type54GroupGenerator, self).__init__(
game, ground_object, faction, Type_054A_Frigate
)

View File

@@ -13,22 +13,47 @@ if TYPE_CHECKING:
class DDGroupGenerator(ShipGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction, ddtype: ShipType):
def __init__(
self,
game: Game,
ground_object: TheaterGroundObject,
faction: Faction,
ddtype: ShipType,
):
super(DDGroupGenerator, self).__init__(game, ground_object, faction)
self.ddtype = ddtype
def generate(self):
self.add_unit(self.ddtype, "DD1", self.position.x + 500, self.position.y + 900, self.heading)
self.add_unit(self.ddtype, "DD2", self.position.x + 500, self.position.y - 900, self.heading)
self.add_unit(
self.ddtype,
"DD1",
self.position.x + 500,
self.position.y + 900,
self.heading,
)
self.add_unit(
self.ddtype,
"DD2",
self.position.x + 500,
self.position.y - 900,
self.heading,
)
self.get_generated_group().points[0].speed = 20
class OliverHazardPerryGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(OliverHazardPerryGroupGenerator, self).__init__(game, ground_object, faction, Oliver_Hazzard_Perry_class)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(OliverHazardPerryGroupGenerator, self).__init__(
game, ground_object, faction, Oliver_Hazzard_Perry_class
)
class ArleighBurkeGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(ArleighBurkeGroupGenerator, self).__init__(game, ground_object, faction, USS_Arleigh_Burke_IIa)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(ArleighBurkeGroupGenerator, self).__init__(
game, ground_object, faction, USS_Arleigh_Burke_IIa
)

View File

@@ -4,18 +4,31 @@ from gen.sam.group_generator import ShipGroupGenerator
class LHAGroupGenerator(ShipGroupGenerator):
def generate(self):
# Add carrier
if len(self.faction.helicopter_carrier) > 0:
carrier_type = random.choice(self.faction.helicopter_carrier)
self.add_unit(carrier_type, "LHA", self.position.x, self.position.y, self.heading)
self.add_unit(
carrier_type, "LHA", self.position.x, self.position.y, self.heading
)
# Add destroyers escort
if len(self.faction.destroyers) > 0:
dd_type = random.choice(self.faction.destroyers)
self.add_unit(dd_type, "DD1", self.position.x + 1250, self.position.y + 1450, self.heading)
self.add_unit(dd_type, "DD2", self.position.x + 1250, self.position.y - 1450, self.heading)
self.add_unit(
dd_type,
"DD1",
self.position.x + 1250,
self.position.y + 1450,
self.heading,
)
self.add_unit(
dd_type,
"DD2",
self.position.x + 1250,
self.position.y - 1450,
self.heading,
)
self.get_generated_group().points[0].speed = 20

View File

@@ -9,7 +9,7 @@ from dcs.ships import (
FF_1135M_Rezky,
CG_1164_Moskva,
SSK_877,
SSK_641B
SSK_641B,
)
from gen.fleet.dd_group import DDGroupGenerator
@@ -23,7 +23,6 @@ if TYPE_CHECKING:
class RussianNavyGroupGenerator(ShipGroupGenerator):
def generate(self):
include_frigate = random.choice([True, True, False])
@@ -39,37 +38,79 @@ class RussianNavyGroupGenerator(ShipGroupGenerator):
if include_frigate:
frigate_type = random.choice([FFL_1124_4_Grisha, FSG_1241_1MP_Molniya])
self.add_unit(frigate_type, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
self.add_unit(frigate_type, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)
self.add_unit(
frigate_type,
"FF1",
self.position.x + 1200,
self.position.y + 900,
self.heading,
)
self.add_unit(
frigate_type,
"FF2",
self.position.x + 1200,
self.position.y - 900,
self.heading,
)
if include_dd:
dd_type = random.choice([FFG_11540_Neustrashimy, FF_1135M_Rezky])
self.add_unit(dd_type, "DD1", self.position.x + 2400, self.position.y + 900, self.heading)
self.add_unit(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
self.add_unit(
dd_type,
"DD1",
self.position.x + 2400,
self.position.y + 900,
self.heading,
)
self.add_unit(
dd_type,
"DD2",
self.position.x + 2400,
self.position.y - 900,
self.heading,
)
if include_cc:
# Only include the Moskva for now, the Pyotry Velikiy is an unkillable monster.
# See https://github.com/Khopa/dcs_liberation/issues/567
self.add_unit(CG_1164_Moskva, "CC1", self.position.x, self.position.y, self.heading)
self.add_unit(
CG_1164_Moskva, "CC1", self.position.x, self.position.y, self.heading
)
self.get_generated_group().points[0].speed = 20
class GrishaGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(GrishaGroupGenerator, self).__init__(game, ground_object, faction, FFL_1124_4_Grisha)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(GrishaGroupGenerator, self).__init__(
game, ground_object, faction, FFL_1124_4_Grisha
)
class MolniyaGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(MolniyaGroupGenerator, self).__init__(game, ground_object, faction, FSG_1241_1MP_Molniya)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(MolniyaGroupGenerator, self).__init__(
game, ground_object, faction, FSG_1241_1MP_Molniya
)
class KiloSubGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(KiloSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_877)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(KiloSubGroupGenerator, self).__init__(
game, ground_object, faction, SSK_877
)
class TangoSubGroupGenerator(DDGroupGenerator):
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
super(TangoSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_641B)
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
super(TangoSubGroupGenerator, self).__init__(
game, ground_object, faction, SSK_641B
)

View File

@@ -6,10 +6,15 @@ from gen.sam.group_generator import ShipGroupGenerator
class SchnellbootGroupGenerator(ShipGroupGenerator):
def generate(self):
for i in range(random.randint(2, 4)):
self.add_unit(Schnellboot_type_S130, "Schnellboot" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
self.add_unit(
Schnellboot_type_S130,
"Schnellboot" + str(i),
self.position.x + i * random.randint(100, 250),
self.position.y + (random.randint(100, 200) - 100),
self.heading,
)
self.get_generated_group().points[0].speed = 20

View File

@@ -4,10 +4,18 @@ import random
from game import db
from gen.fleet.carrier_group import CarrierGroupGenerator
from gen.fleet.cn_dd_group import ChineseNavyGroupGenerator, Type54GroupGenerator
from gen.fleet.dd_group import ArleighBurkeGroupGenerator, OliverHazardPerryGroupGenerator
from gen.fleet.dd_group import (
ArleighBurkeGroupGenerator,
OliverHazardPerryGroupGenerator,
)
from gen.fleet.lha_group import LHAGroupGenerator
from gen.fleet.ru_dd_group import RussianNavyGroupGenerator, GrishaGroupGenerator, MolniyaGroupGenerator, \
KiloSubGroupGenerator, TangoSubGroupGenerator
from gen.fleet.ru_dd_group import (
RussianNavyGroupGenerator,
GrishaGroupGenerator,
MolniyaGroupGenerator,
KiloSubGroupGenerator,
TangoSubGroupGenerator,
)
from gen.fleet.schnellboot import SchnellbootGroupGenerator
from gen.fleet.uboat import UBoatGroupGenerator
from gen.fleet.ww2lst import WW2LSTGroupGenerator
@@ -25,7 +33,7 @@ SHIP_MAP = {
"MolniyaGroupGenerator": MolniyaGroupGenerator,
"KiloSubGroupGenerator": KiloSubGroupGenerator,
"TangoSubGroupGenerator": TangoSubGroupGenerator,
"Type54GroupGenerator": Type54GroupGenerator
"Type54GroupGenerator": Type54GroupGenerator,
}
@@ -43,7 +51,11 @@ def generate_ship_group(game, ground_object, faction_name: str):
generator.generate()
return generator.get_generated_group()
else:
logging.info("Unable to generate ship group, generator : " + str(gen) + "does not exists")
logging.info(
"Unable to generate ship group, generator : "
+ str(gen)
+ "does not exists"
)
return None

View File

@@ -6,10 +6,15 @@ from gen.sam.group_generator import ShipGroupGenerator
class UBoatGroupGenerator(ShipGroupGenerator):
def generate(self):
for i in range(random.randint(1, 4)):
self.add_unit(Uboat_VIIC_U_flak, "Uboat" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
self.add_unit(
Uboat_VIIC_U_flak,
"Uboat" + str(i),
self.position.x + i * random.randint(100, 250),
self.position.y + (random.randint(100, 200) - 100),
self.heading,
)
self.get_generated_group().points[0].speed = 20
self.get_generated_group().points[0].speed = 20

View File

@@ -6,13 +6,24 @@ from gen.sam.group_generator import ShipGroupGenerator
class WW2LSTGroupGenerator(ShipGroupGenerator):
def generate(self):
# Add LS Samuel Chase
self.add_unit(LS_Samuel_Chase, "SamuelChase", self.position.x, self.position.y, self.heading)
self.add_unit(
LS_Samuel_Chase,
"SamuelChase",
self.position.x,
self.position.y,
self.heading,
)
for i in range(1, random.randint(3, 4)):
self.add_unit(LST_Mk_II, "LST" + str(i), self.position.x + i * random.randint(800, 1200), self.position.y, self.heading)
self.add_unit(
LST_Mk_II,
"LST" + str(i),
self.position.x + i * random.randint(800, 1200),
self.position.y,
self.heading,
)
self.get_generated_group().points[0].speed = 20
self.get_generated_group().points[0].speed = 20

View File

@@ -109,22 +109,25 @@ class ProposedMission:
flights: List[ProposedFlight]
def __str__(self) -> str:
flights = ', '.join([str(f) for f in self.flights])
flights = ", ".join([str(f) for f in self.flights])
return f"{self.location.name}: {flights}"
class AircraftAllocator:
"""Finds suitable aircraft for proposed missions."""
def __init__(self, closest_airfields: ClosestAirfields,
global_inventory: GlobalAircraftInventory,
is_player: bool) -> None:
def __init__(
self,
closest_airfields: ClosestAirfields,
global_inventory: GlobalAircraftInventory,
is_player: bool,
) -> None:
self.closest_airfields = closest_airfields
self.global_inventory = global_inventory
self.is_player = is_player
def find_aircraft_for_flight(
self, flight: ProposedFlight
self, flight: ProposedFlight
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
"""Finds aircraft suitable for the given mission.
@@ -144,12 +147,12 @@ class AircraftAllocator:
on subsequent calls. If the found aircraft are not used, the caller is
responsible for returning them to the inventory.
"""
return self.find_aircraft_of_type(
flight, aircraft_for_task(flight.task)
)
return self.find_aircraft_of_type(flight, aircraft_for_task(flight.task))
def find_aircraft_of_type(
self, flight: ProposedFlight, types: List[Type[FlyingType]],
self,
flight: ProposedFlight,
types: List[Type[FlyingType]],
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
airfields_in_range = self.closest_airfields.airfields_within(
flight.max_distance
@@ -171,18 +174,22 @@ class AircraftAllocator:
class PackageBuilder:
"""Builds a Package for the flights it receives."""
def __init__(self, location: MissionTarget,
closest_airfields: ClosestAirfields,
global_inventory: GlobalAircraftInventory,
is_player: bool,
package_country: str,
start_type: str) -> None:
def __init__(
self,
location: MissionTarget,
closest_airfields: ClosestAirfields,
global_inventory: GlobalAircraftInventory,
is_player: bool,
package_country: str,
start_type: str,
) -> None:
self.closest_airfields = closest_airfields
self.is_player = is_player
self.package_country = package_country
self.package = Package(location)
self.allocator = AircraftAllocator(closest_airfields, global_inventory,
is_player)
self.allocator = AircraftAllocator(
closest_airfields, global_inventory, is_player
)
self.global_inventory = global_inventory
self.start_type = start_type
@@ -203,14 +210,23 @@ class PackageBuilder:
else:
start_type = self.start_type
flight = Flight(self.package, self.package_country, aircraft, plan.num_aircraft, plan.task,
start_type, departure=airfield, arrival=airfield,
divert=self.find_divert_field(aircraft, airfield))
flight = Flight(
self.package,
self.package_country,
aircraft,
plan.num_aircraft,
plan.task,
start_type,
departure=airfield,
arrival=airfield,
divert=self.find_divert_field(aircraft, airfield),
)
self.package.add_flight(flight)
return True
def find_divert_field(self, aircraft: Type[FlyingType],
arrival: ControlPoint) -> Optional[ControlPoint]:
def find_divert_field(
self, aircraft: Type[FlyingType], arrival: ControlPoint
) -> Optional[ControlPoint]:
divert_limit = nautical_miles(150)
for airfield in self.closest_airfields.airfields_within(divert_limit):
if airfield.captured != self.is_player:
@@ -323,8 +339,8 @@ class ObjectiveFinder:
return self._targets_by_range(self.enemy_ships())
def _targets_by_range(
self,
targets: Iterable[MissionTarget]) -> Iterator[MissionTarget]:
self, targets: Iterable[MissionTarget]
) -> Iterator[MissionTarget]:
target_ranges: List[Tuple[MissionTarget, int]] = []
for target in targets:
ranges: List[int] = []
@@ -430,8 +446,9 @@ class ObjectiveFinder:
def friendly_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over all friendly control points."""
return (c for c in self.game.theater.controlpoints if
c.is_friendly(self.is_player))
return (
c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player)
)
def farthest_friendly_control_point(self) -> ControlPoint:
"""
@@ -451,8 +468,11 @@ class ObjectiveFinder:
def enemy_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over all enemy control points."""
return (c for c in self.game.theater.controlpoints if
not c.is_friendly(self.is_player))
return (
c
for c in self.game.theater.controlpoints
if not c.is_friendly(self.is_player)
)
def all_possible_targets(self) -> Iterator[MissionTarget]:
"""Iterates over all possible mission targets in the theater.
@@ -524,33 +544,46 @@ class CoalitionMissionPlanner:
eliminated this turn.
"""
#Find farthest, friendly CP for AEWC
# Find farthest, friendly CP for AEWC
cp = self.objective_finder.farthest_friendly_control_point()
yield ProposedMission(cp, [
ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)
])
yield ProposedMission(
cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)]
)
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
for cp in self.objective_finder.vulnerable_control_points():
# Plan three rounds of CAP to give ~90 minutes coverage. Spacing
# these out appropriately is done in stagger_missions.
yield ProposedMission(cp, [
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
])
yield ProposedMission(cp, [
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
])
yield ProposedMission(cp, [
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
])
yield ProposedMission(
cp,
[
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
],
)
yield ProposedMission(
cp,
[
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
],
)
yield ProposedMission(
cp,
[
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
],
)
# Find front lines, plan CAS.
for front_line in self.objective_finder.front_lines():
yield ProposedMission(front_line, [
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE,
EscortType.AirToAir),
])
yield ProposedMission(
front_line,
[
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
ProposedFlight(
FlightType.TARCAP, 2, self.MAX_CAP_RANGE, EscortType.AirToAir
),
],
)
def propose_missions(self) -> Iterator[ProposedMission]:
"""Identifies and iterates over potential mission in priority order."""
@@ -561,30 +594,46 @@ class CoalitionMissionPlanner:
# Find enemy SAM sites with ranges that extend to within 50 nmi of
# friendly CPs, front, lines, or objects, plan DEAD.
for sam in self.objective_finder.threatening_sams():
yield ProposedMission(sam, [
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
# TODO: Max escort range.
ProposedFlight(FlightType.ESCORT, 2, self.MAX_SEAD_RANGE,
EscortType.AirToAir),
])
yield ProposedMission(
sam,
[
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
# TODO: Max escort range.
ProposedFlight(
FlightType.ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.AirToAir
),
],
)
for group in self.objective_finder.threatening_ships():
yield ProposedMission(group, [
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
# TODO: Max escort range.
ProposedFlight(FlightType.ESCORT, 2, self.MAX_ANTISHIP_RANGE,
EscortType.AirToAir),
])
yield ProposedMission(
group,
[
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
# TODO: Max escort range.
ProposedFlight(
FlightType.ESCORT,
2,
self.MAX_ANTISHIP_RANGE,
EscortType.AirToAir,
),
],
)
for group in self.objective_finder.threatening_vehicle_groups():
yield ProposedMission(group, [
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
# TODO: Max escort range.
ProposedFlight(FlightType.ESCORT, 2, self.MAX_BAI_RANGE,
EscortType.AirToAir),
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
EscortType.Sead),
])
yield ProposedMission(
group,
[
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
# TODO: Max escort range.
ProposedFlight(
FlightType.ESCORT, 2, self.MAX_BAI_RANGE, EscortType.AirToAir
),
ProposedFlight(
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
),
],
)
for target in self.objective_finder.oca_targets(min_aircraft=20):
flights = [
@@ -593,27 +642,37 @@ class CoalitionMissionPlanner:
if self.game.settings.default_start_type == "Cold":
# Only schedule if the default start type is Cold. If the player
# has set anything else there are no targets to hit.
flights.append(ProposedFlight(FlightType.OCA_AIRCRAFT, 2,
self.MAX_OCA_RANGE))
flights.extend([
# TODO: Max escort range.
ProposedFlight(FlightType.ESCORT, 2, self.MAX_OCA_RANGE,
EscortType.AirToAir),
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
EscortType.Sead),
])
flights.append(
ProposedFlight(FlightType.OCA_AIRCRAFT, 2, self.MAX_OCA_RANGE)
)
flights.extend(
[
# TODO: Max escort range.
ProposedFlight(
FlightType.ESCORT, 2, self.MAX_OCA_RANGE, EscortType.AirToAir
),
ProposedFlight(
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
),
]
)
yield ProposedMission(target, flights)
# Plan strike missions.
for target in self.objective_finder.strike_targets():
yield ProposedMission(target, [
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
# TODO: Max escort range.
ProposedFlight(FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE,
EscortType.AirToAir),
ProposedFlight(FlightType.SEAD, 2, self.MAX_STRIKE_RANGE,
EscortType.Sead),
])
yield ProposedMission(
target,
[
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
# TODO: Max escort range.
ProposedFlight(
FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE, EscortType.AirToAir
),
ProposedFlight(
FlightType.SEAD, 2, self.MAX_STRIKE_RANGE, EscortType.Sead
),
],
)
def plan_missions(self) -> None:
"""Identifies and plans mission for the turn."""
@@ -628,19 +687,23 @@ class CoalitionMissionPlanner:
for cp in self.objective_finder.friendly_control_points():
inventory = self.game.aircraft_inventory.for_control_point(cp)
for aircraft, available in inventory.all_aircraft:
self.message("Unused aircraft",
f"{available} {aircraft.id} from {cp}")
self.message("Unused aircraft", f"{available} {aircraft.id} from {cp}")
def plan_flight(self, mission: ProposedMission, flight: ProposedFlight,
builder: PackageBuilder, missing_types: Set[FlightType],
for_reserves: bool) -> None:
def plan_flight(
self,
mission: ProposedMission,
flight: ProposedFlight,
builder: PackageBuilder,
missing_types: Set[FlightType],
for_reserves: bool,
) -> None:
if not builder.plan_flight(flight):
missing_types.add(flight.task)
purchase_order = AircraftProcurementRequest(
near=mission.location,
range=flight.max_distance,
task_capability=flight.task,
number=flight.num_aircraft
number=flight.num_aircraft,
)
if for_reserves:
# Reserves are planned for critical missions, so prioritize
@@ -650,26 +713,28 @@ class CoalitionMissionPlanner:
self.procurement_requests.append(purchase_order)
def scrub_mission_missing_aircraft(
self, mission: ProposedMission, builder: PackageBuilder,
missing_types: Set[FlightType],
not_attempted: Iterable[ProposedFlight],
reserves: bool) -> None:
self,
mission: ProposedMission,
builder: PackageBuilder,
missing_types: Set[FlightType],
not_attempted: Iterable[ProposedFlight],
reserves: bool,
) -> None:
# Try to plan the rest of the mission just so we can count the missing
# types to buy.
for flight in not_attempted:
self.plan_flight(mission, flight, builder, missing_types, reserves)
missing_types_str = ", ".join(
sorted([t.name for t in missing_types]))
missing_types_str = ", ".join(sorted([t.name for t in missing_types]))
builder.release_planned_aircraft()
desc = "reserve aircraft" if reserves else "aircraft"
self.message(
"Insufficient aircraft",
f"Not enough {desc} in range for {mission.location.name} "
f"capable of: {missing_types_str}")
f"capable of: {missing_types_str}",
)
def check_needed_escorts(
self, builder: PackageBuilder) -> Dict[EscortType, bool]:
def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]:
threats = defaultdict(bool)
for flight in builder.package.flights:
if self.threat_zones.threatened_by_aircraft(flight):
@@ -678,8 +743,7 @@ class CoalitionMissionPlanner:
threats[EscortType.Sead] = True
return threats
def plan_mission(self, mission: ProposedMission,
reserves: bool = False) -> None:
def plan_mission(self, mission: ProposedMission, reserves: bool = False) -> None:
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
if self.is_player:
@@ -693,7 +757,7 @@ class CoalitionMissionPlanner:
self.game.aircraft_inventory,
self.is_player,
package_country,
self.game.settings.default_start_type
self.game.settings.default_start_type,
)
# Attempt to plan all the main elements of the mission first. Escorts
@@ -707,12 +771,12 @@ class CoalitionMissionPlanner:
# If the package does not need escorts they may be pruned.
escorts.append(proposed_flight)
continue
self.plan_flight(mission, proposed_flight, builder, missing_types,
reserves)
self.plan_flight(mission, proposed_flight, builder, missing_types, reserves)
if missing_types:
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
escorts, reserves)
self.scrub_mission_missing_aircraft(
mission, builder, missing_types, escorts, reserves
)
return
# Create flight plans for the main flights of the package so we can
@@ -721,8 +785,9 @@ class CoalitionMissionPlanner:
# flights that will rendezvous with their package will be affected by
# the other flights in the package. Escorts will not be able to
# contribute to this.
flight_plan_builder = FlightPlanBuilder(self.game, builder.package,
self.is_player)
flight_plan_builder = FlightPlanBuilder(
self.game, builder.package, self.is_player
)
for flight in builder.package.flights:
flight_plan_builder.populate_flight_plan(flight)
@@ -732,14 +797,14 @@ class CoalitionMissionPlanner:
# impossible.
assert escort.escort_type is not None
if needed_escorts[escort.escort_type]:
self.plan_flight(mission, escort, builder, missing_types,
reserves)
self.plan_flight(mission, escort, builder, missing_types, reserves)
# Check again for unavailable aircraft. If the escort was required and
# none were found, scrub the mission.
if missing_types:
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
escorts, reserves)
self.scrub_mission_missing_aircraft(
mission, builder, missing_types, escorts, reserves
)
return
if reserves:
@@ -756,8 +821,9 @@ class CoalitionMissionPlanner:
self.ato.add_package(package)
def stagger_missions(self) -> None:
def start_time_generator(count: int, earliest: int, latest: int,
margin: int) -> Iterator[timedelta]:
def start_time_generator(
count: int, earliest: int, latest: int, margin: int
) -> Iterator[timedelta]:
interval = (latest - earliest) // count
for time in range(earliest, latest, interval):
error = random.randint(-margin, margin)
@@ -768,17 +834,13 @@ class CoalitionMissionPlanner:
FlightType.TARCAP,
}
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(
timedelta
)
non_dca_packages = [p for p in self.ato.packages if
p.primary_task not in dca_types]
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(timedelta)
non_dca_packages = [
p for p in self.ato.packages if p.primary_task not in dca_types
]
start_time = start_time_generator(
count=len(non_dca_packages),
earliest=5,
latest=90,
margin=5
count=len(non_dca_packages), earliest=5, latest=90, margin=5
)
for package in self.ato.packages:
tot = TotEstimator(package).earliest_tot()
@@ -795,8 +857,7 @@ class CoalitionMissionPlanner:
departure_time = package.mission_departure_time
# Should be impossible for CAPs
if departure_time is None:
logging.error(
f"Could not determine mission end time for {package}")
logging.error(f"Could not determine mission end time for {package}")
continue
previous_cap_end_time[package.target] = departure_time
else:
@@ -815,8 +876,6 @@ class CoalitionMissionPlanner:
message to the info panel.
"""
if self.is_player:
self.game.informations.append(
Information(title, text, self.game.turn)
)
self.game.informations.append(Information(title, text, self.game.turn))
else:
logging.info(f"{title}: {text}")

View File

@@ -370,11 +370,7 @@ TRANSPORT_CAPABLE = [
UH_1H,
]
DRONES = [
MQ_9_Reaper,
RQ_1A_Predator,
WingLoong_I
]
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
AEWC_CAPABLE = [
E_3A,

View File

@@ -12,8 +12,9 @@ if TYPE_CHECKING:
class ClosestAirfields:
"""Precalculates which control points are closes to the given target."""
def __init__(self, target: MissionTarget,
all_control_points: List[ControlPoint]) -> None:
def __init__(
self, target: MissionTarget, all_control_points: List[ControlPoint]
) -> None:
self.target = target
# This cache is configured once on load, so it's important that it is
# complete and deterministic to avoid different behaviors across loads.
@@ -52,9 +53,7 @@ class ObjectiveDistanceCache:
@classmethod
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
if cls.theater is None:
raise RuntimeError(
"Call ObjectiveDistanceCache.set_theater before using"
)
raise RuntimeError("Call ObjectiveDistanceCache.set_theater before using")
if location.name not in cls.closest_airfields:
cls.closest_airfields[location.name] = ClosestAirfields(

View File

@@ -28,6 +28,7 @@ class FlightType(Enum):
each flight and thus a part of the ATO, so changing these values will break
save compat.
"""
TARCAP = "TARCAP"
BARCAP = "BARCAP"
CAS = "CAS"
@@ -48,22 +49,22 @@ class FlightType(Enum):
class FlightWaypointType(Enum):
TAKEOFF = 0 # Take off point
ASCEND_POINT = 1 # Ascension point after take off
PATROL = 2 # Patrol point
PATROL_TRACK = 3 # Patrol race track
NAV = 4 # Nav point
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
CAS = 8 # Should do CAS there
EGRESS = 9 # Should stop attack
DESCENT_POINT = 10 # Should start descending to pattern alt
LANDING_POINT = 11 # Should land there
TARGET_POINT = 12 # A target building or static object, position
TARGET_GROUP_LOC = 13 # A target group approximate location
TARGET_SHIP = 14 # A target ship known location
CUSTOM = 15 # User waypoint (no specific behaviour)
TAKEOFF = 0 # Take off point
ASCEND_POINT = 1 # Ascension point after take off
PATROL = 2 # Patrol point
PATROL_TRACK = 3 # Patrol race track
NAV = 4 # Nav point
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
CAS = 8 # Should do CAS there
EGRESS = 9 # Should stop attack
DESCENT_POINT = 10 # Should start descending to pattern alt
LANDING_POINT = 11 # Should land there
TARGET_POINT = 12 # A target building or static object, position
TARGET_GROUP_LOC = 13 # A target group approximate location
TARGET_SHIP = 14 # A target ship known location
CUSTOM = 15 # User waypoint (no specific behaviour)
JOIN = 16
SPLIT = 17
LOITER = 18
@@ -77,9 +78,13 @@ class FlightWaypointType(Enum):
class FlightWaypoint:
def __init__(self, waypoint_type: FlightWaypointType, x: float, y: float,
alt: Distance = meters(0)) -> None:
def __init__(
self,
waypoint_type: FlightWaypointType,
x: float,
y: float,
alt: Distance = meters(0),
) -> None:
"""Creates a flight waypoint.
Args:
@@ -117,10 +122,13 @@ class FlightWaypoint:
return Point(self.x, self.y)
@classmethod
def from_pydcs(cls, point: MovingPoint,
from_cp: ControlPoint) -> "FlightWaypoint":
waypoint = FlightWaypoint(FlightWaypointType.NAV, point.position.x,
point.position.y, meters(point.alt))
def from_pydcs(cls, point: MovingPoint, from_cp: ControlPoint) -> "FlightWaypoint":
waypoint = FlightWaypoint(
FlightWaypointType.NAV,
point.position.x,
point.position.y,
meters(point.alt),
)
waypoint.alt_type = point.alt_type
# Other actions exist... but none of them *should* be the first
# waypoint for a flight.
@@ -144,12 +152,19 @@ class FlightWaypoint:
class Flight:
def __init__(self, package: Package, country: str, unit_type: Type[FlyingType],
count: int, flight_type: FlightType, start_type: str,
departure: ControlPoint, arrival: ControlPoint,
divert: Optional[ControlPoint],
custom_name: Optional[str] = None) -> None:
def __init__(
self,
package: Package,
country: str,
unit_type: Type[FlyingType],
count: int,
flight_type: FlightType,
start_type: str,
departure: ControlPoint,
arrival: ControlPoint,
divert: Optional[ControlPoint],
custom_name: Optional[str] = None,
) -> None:
self.package = package
self.country = country
self.unit_type = unit_type
@@ -170,10 +185,9 @@ class Flight:
# FlightPlanBuilder, but an empty flight plan the flight begins with an
# empty flight plan.
from gen.flights.flightplan import CustomFlightPlan
self.flight_plan: FlightPlan = CustomFlightPlan(
package=package,
flight=self,
custom_waypoints=[]
package=package, flight=self, custom_waypoints=[]
)
@property
@@ -191,7 +205,7 @@ class Flight:
return f"[{self.flight_type}] {self.count} x {name}"
def __str__(self):
name = db.unit_get_expanded_info(self.country, self.unit_type, 'name')
name = db.unit_get_expanded_info(self.country, self.unit_type, "name")
if self.custom_name:
return f"{self.custom_name} {self.count} x {name}"
return f"[{self.flight_type}] {self.count} x {name}"

View File

@@ -15,12 +15,7 @@ from datetime import timedelta
from functools import cached_property
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
from dcs.planes import (
E_3A,
E_2C,
A_50,
KJ_2000
)
from dcs.planes import E_3A, E_2C, A_50, KJ_2000
from dcs.mapping import Point
from dcs.unit import Unit
@@ -82,7 +77,7 @@ class FlightPlan:
raise NotImplementedError
def edges(
self, until: Optional[FlightWaypoint] = None
self, until: Optional[FlightWaypoint] = None
) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
"""A list of all paths between waypoints, in order."""
waypoints = self.waypoints
@@ -93,8 +88,9 @@ class FlightPlan:
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
def best_speed_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> Speed:
def best_speed_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint
) -> Speed:
"""Desired ground speed between points a and b."""
factor = 1.0
if b.waypoint_type == FlightWaypointType.ASCEND_POINT:
@@ -115,8 +111,7 @@ class FlightPlan:
# near 2000 ft MSL.
return GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor
def speed_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> Speed:
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
return self.best_speed_between_waypoints(a, b)
@property
@@ -131,8 +126,7 @@ class FlightPlan:
@cached_property
def bingo_fuel(self) -> int:
"""Bingo fuel value for the FlightPlan
"""
"""Bingo fuel value for the FlightPlan"""
distance_to_arrival = self.max_distance_from(self.flight.arrival)
bingo = 1000.0 # Minimum Emergency Fuel
@@ -149,8 +143,7 @@ class FlightPlan:
@cached_property
def joker_fuel(self) -> int:
"""Joker fuel value for the FlightPlan
"""
"""Joker fuel value for the FlightPlan"""
return self.bingo_fuel + 1000
def max_distance_from(self, cp: ControlPoint) -> Distance:
@@ -159,8 +152,9 @@ class FlightPlan:
"""
if not self.waypoints:
return meters(0)
return max([meters(cp.position.distance_to_point(w.position)) for w in
self.waypoints])
return max(
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
)
@property
def tot_offset(self) -> timedelta:
@@ -171,30 +165,30 @@ class FlightPlan:
"""
return timedelta()
def _travel_time_to_waypoint(
self, destination: FlightWaypoint) -> timedelta:
def _travel_time_to_waypoint(self, destination: FlightWaypoint) -> timedelta:
total = timedelta()
if destination not in self.waypoints:
raise PlanningError(
f"Did not find destination waypoint {destination} in "
f"waypoints for {self.flight}")
f"waypoints for {self.flight}"
)
for previous_waypoint, waypoint in self.edges(until=destination):
total += self.travel_time_between_waypoints(previous_waypoint,
waypoint)
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
return total
def travel_time_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> timedelta:
return TravelTime.between_points(a.position, b.position,
self.speed_between_waypoints(a, b))
def travel_time_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint
) -> timedelta:
return TravelTime.between_points(
a.position, b.position, self.speed_between_waypoints(a, b)
)
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
raise NotImplementedError
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
raise NotImplementedError
def request_escort_at(self) -> Optional[FlightWaypoint]:
@@ -219,8 +213,7 @@ class FlightPlan:
if takeoff_time is None:
return None
start_time = (takeoff_time - self.estimate_startup() -
self.estimate_ground_ops())
start_time = takeoff_time - self.estimate_startup() - self.estimate_ground_ops()
# In case FP math has given us some barely below zero time, round to
# zero.
@@ -276,14 +269,14 @@ class LoiterFlightPlan(FlightPlan):
def push_time(self) -> timedelta:
raise NotImplementedError
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.hold:
return self.push_time
return None
def travel_time_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> timedelta:
def travel_time_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint
) -> timedelta:
travel_time = super().travel_time_between_waypoints(a, b)
if a != self.hold:
return travel_time
@@ -328,12 +321,12 @@ class FormationFlightPlan(LoiterFlightPlan):
speeds = []
for previous_waypoint, waypoint in self.edges():
if waypoint in self.package_speed_waypoints:
speeds.append(self.best_speed_between_waypoints(
previous_waypoint, waypoint))
speeds.append(
self.best_speed_between_waypoints(previous_waypoint, waypoint)
)
return min(speeds)
def speed_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> Speed:
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
if b in self.package_speed_waypoints:
# Should be impossible, as any package with at least one
# FormationFlightPlan flight needs a formation speed.
@@ -366,7 +359,7 @@ class FormationFlightPlan(LoiterFlightPlan):
return self.join_time - TravelTime.between_points(
self.hold.position,
self.join.position,
GroundSpeed.for_flight(self.flight, self.hold.alt)
GroundSpeed.for_flight(self.flight, self.hold.alt),
)
@property
@@ -406,8 +399,7 @@ class PatrollingFlightPlan(FlightPlan):
return self.patrol_start_time
return None
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.patrol_end:
return self.patrol_end_time
return None
@@ -497,8 +489,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
def tot_offset(self) -> timedelta:
return -self.lead_time
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.patrol_end:
return self.patrol_end_time
return super().depart_time_for_waypoint(waypoint)
@@ -549,13 +540,12 @@ class StrikeFlightPlan(FormationFlightPlan):
@property
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
return {
self.ingress,
self.egress,
self.split,
} | set(self.targets)
self.ingress,
self.egress,
self.split,
} | set(self.targets)
def speed_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> Speed:
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
# FlightWaypoint is only comparable by identity, so adding
# target_area_waypoint to package_speed_waypoints is useless.
if b.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
@@ -571,10 +561,12 @@ class StrikeFlightPlan(FormationFlightPlan):
@property
def target_area_waypoint(self) -> FlightWaypoint:
return FlightWaypoint(FlightWaypointType.TARGET_GROUP_LOC,
self.package.target.position.x,
self.package.target.position.y,
meters(0))
return FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
self.package.target.position.x,
self.package.target.position.y,
meters(0),
)
@property
def travel_time_to_target(self) -> timedelta:
@@ -588,14 +580,15 @@ class StrikeFlightPlan(FormationFlightPlan):
# package we need to use the travel time to the same position as
# the others.
total += self.travel_time_between_waypoints(
previous_waypoint, self.target_area_waypoint)
previous_waypoint, self.target_area_waypoint
)
break
total += self.travel_time_between_waypoints(previous_waypoint,
waypoint)
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
else:
raise PlanningError(
f"Did not find destination waypoint {destination} in "
f"waypoints for {self.flight}")
f"waypoints for {self.flight}"
)
return total
@property
@@ -604,28 +597,28 @@ class StrikeFlightPlan(FormationFlightPlan):
@property
def join_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints(
self.join, self.ingress)
travel_time = self.travel_time_between_waypoints(self.join, self.ingress)
return self.ingress_time - travel_time
@property
def split_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints(
self.egress, self.split)
travel_time = self.travel_time_between_waypoints(self.egress, self.split)
return self.egress_time + travel_time
@property
def ingress_time(self) -> timedelta:
tot = self.package.time_over_target
travel_time = self.travel_time_between_waypoints(
self.ingress, self.target_area_waypoint)
self.ingress, self.target_area_waypoint
)
return tot - travel_time
@property
def egress_time(self) -> timedelta:
tot = self.package.time_over_target
travel_time = self.travel_time_between_waypoints(
self.target_area_waypoint, self.egress)
self.target_area_waypoint, self.egress
)
return tot + travel_time
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
@@ -671,7 +664,8 @@ class SweepFlightPlan(LoiterFlightPlan):
@property
def sweep_start_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints(
self.sweep_start, self.sweep_end)
self.sweep_start, self.sweep_end
)
return self.sweep_end_time - travel_time
@property
@@ -685,8 +679,7 @@ class SweepFlightPlan(LoiterFlightPlan):
return self.sweep_end_time
return None
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
if waypoint == self.hold:
return self.push_time
return None
@@ -696,7 +689,7 @@ class SweepFlightPlan(LoiterFlightPlan):
return self.sweep_end_time - TravelTime.between_points(
self.hold.position,
self.sweep_end.position,
GroundSpeed.for_flight(self.flight, self.hold.alt)
GroundSpeed.for_flight(self.flight, self.hold.alt),
)
def mission_departure_time(self) -> timedelta:
@@ -763,8 +756,7 @@ class CustomFlightPlan(FlightPlan):
return self.package.time_over_target
return None
def depart_time_for_waypoint(
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
return None
@property
@@ -794,9 +786,11 @@ class FlightPlanBuilder:
self.threat_zones = self.game.threat_zone_for(not self.is_player)
def populate_flight_plan(
self, flight: Flight,
# TODO: Custom targets should be an attribute of the flight.
custom_targets: Optional[List[Unit]] = None) -> None:
self,
flight: Flight,
# TODO: Custom targets should be an attribute of the flight.
custom_targets: Optional[List[Unit]] = None,
) -> None:
"""Creates a default flight plan for the given mission."""
if flight not in self.package.flights:
raise RuntimeError("Flight must be a part of the package")
@@ -805,8 +799,8 @@ class FlightPlanBuilder:
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
def generate_flight_plan(
self, flight: Flight,
custom_targets: Optional[List[Unit]]) -> FlightPlan:
self, flight: Flight, custom_targets: Optional[List[Unit]]
) -> FlightPlan:
# TODO: Flesh out mission types.
task = flight.flight_type
if task == FlightType.ANTISHIP:
@@ -835,8 +829,7 @@ class FlightPlanBuilder:
return self.generate_tarcap(flight)
elif task == FlightType.AEWC:
return self.generate_aewc(flight)
raise PlanningError(
f"{task} flight plan generation not implemented")
raise PlanningError(f"{task} flight plan generation not implemented")
def regenerate_package_waypoints(self) -> None:
# The simple case is where the target is greater than the ingress
@@ -873,6 +866,7 @@ class FlightPlanBuilder:
# | | | |
# +--------------+ +---------------+
from gen.ato import PackageWaypoints
target = self.package.target.position
join_point = self.preferred_join_point()
@@ -906,10 +900,9 @@ class FlightPlanBuilder:
def legacy_package_waypoints_impl(self) -> None:
from gen.ato import PackageWaypoints
ingress_point = self._ingress_point(
self._target_heading_to_package_airfield())
egress_point = self._egress_point(
self._target_heading_to_package_airfield())
ingress_point = self._ingress_point(self._target_heading_to_package_airfield())
egress_point = self._egress_point(self._target_heading_to_package_airfield())
join_point = self._rendezvous_point(ingress_point)
split_point = self._rendezvous_point(egress_point)
self.package.waypoints = PackageWaypoints(
@@ -921,7 +914,8 @@ class FlightPlanBuilder:
def preferred_join_point(self) -> Optional[Point]:
path = self.game.navmesh_for(self.is_player).shortest_path(
self.package_airfield().position, self.package.target.position)
self.package_airfield().position, self.package.target.position
)
for point in reversed(path):
if not self.threat_zones.threatened(point):
return point
@@ -961,16 +955,16 @@ class FlightPlanBuilder:
targets.append(StrikeTarget(building.category, building))
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_STRIKE,
targets)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_STRIKE, targets
)
def generate_aewc(self, flight: Flight) -> AwacsFlightPlan:
"""Generate a AWACS flight at a given location.
Args:
flight: The flight to generate the flight plan for.
"""
Args:
flight: The flight to generate the flight plan for.
"""
location = self.package.target
start = self.aewc_orbit(location)
@@ -994,10 +988,12 @@ class FlightPlanBuilder:
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path(flight.departure.position, start.position,
patrol_alt),
nav_from=builder.nav_path(start.position, flight.arrival.position,
patrol_alt),
nav_to=builder.nav_path(
flight.departure.position, start.position, patrol_alt
),
nav_from=builder.nav_path(
start.position, flight.arrival.position, patrol_alt
),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert),
hold=start,
@@ -1017,11 +1013,11 @@ class FlightPlanBuilder:
targets: List[StrikeTarget] = []
for group in location.groups:
targets.append(
StrikeTarget(f"{group.name} at {location.name}", group))
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_BAI, targets)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_BAI, targets
)
def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan:
"""Generates an anti-ship flight plan.
@@ -1043,11 +1039,11 @@ class FlightPlanBuilder:
targets: List[StrikeTarget] = []
for group in location.groups:
targets.append(
StrikeTarget(f"{group.name} at {location.name}", group))
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_BAI, targets)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_BAI, targets
)
def generate_barcap(self, flight: Flight) -> BarCapFlightPlan:
"""Generate a BARCAP flight at a given location.
@@ -1061,10 +1057,12 @@ class FlightPlanBuilder:
raise InvalidObjectiveLocation(flight.flight_type, location)
start, end = self.racetrack_for_objective(location, barcap=True)
patrol_alt = meters(random.randint(
int(self.doctrine.min_patrol_altitude.meters),
int(self.doctrine.max_patrol_altitude.meters)
))
patrol_alt = meters(
random.randint(
int(self.doctrine.min_patrol_altitude.meters),
int(self.doctrine.max_patrol_altitude.meters),
)
)
builder = WaypointBuilder(flight, self.game, self.is_player)
start, end = builder.race_track(start, end, patrol_alt)
@@ -1075,14 +1073,16 @@ class FlightPlanBuilder:
patrol_duration=self.doctrine.cap_duration,
engagement_distance=self.doctrine.cap_engagement_range,
takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path(flight.departure.position, start.position,
patrol_alt),
nav_from=builder.nav_path(end.position, flight.arrival.position,
patrol_alt),
nav_to=builder.nav_path(
flight.departure.position, start.position, patrol_alt
),
nav_from=builder.nav_path(
end.position, flight.arrival.position, patrol_alt
),
patrol_start=start,
patrol_end=end,
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
@@ -1095,12 +1095,10 @@ class FlightPlanBuilder:
target = self.package.target.position
heading = self.package.waypoints.join.heading_between_point(target)
start = target.point_from_heading(heading,
-self.doctrine.sweep_distance.meters)
start = target.point_from_heading(heading, -self.doctrine.sweep_distance.meters)
builder = WaypointBuilder(flight, self.game, self.is_player)
start, end = builder.sweep(start, target,
self.doctrine.ingress_altitude)
start, end = builder.sweep(start, target, self.doctrine.ingress_altitude)
hold = builder.hold(self._hold_point(flight))
@@ -1111,18 +1109,21 @@ class FlightPlanBuilder:
takeoff=builder.takeoff(flight.departure),
hold=hold,
hold_duration=timedelta(minutes=5),
nav_to=builder.nav_path(hold.position, start.position,
self.doctrine.ingress_altitude),
nav_from=builder.nav_path(end.position, flight.arrival.position,
self.doctrine.ingress_altitude),
nav_to=builder.nav_path(
hold.position, start.position, self.doctrine.ingress_altitude
),
nav_from=builder.nav_path(
end.position, flight.arrival.position, self.doctrine.ingress_altitude
),
sweep_start=start,
sweep_end=end,
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
def racetrack_for_objective(self, location: MissionTarget,
barcap: bool) -> Tuple[Point, Point]:
def racetrack_for_objective(
self, location: MissionTarget, barcap: bool
) -> Tuple[Point, Point]:
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
for airfield in closest_cache.operational_airfields:
# If the mission is a BARCAP of an enemy airfield, find the *next*
@@ -1135,20 +1136,21 @@ class FlightPlanBuilder:
else:
raise PlanningError("Could not find any enemy airfields")
heading = location.position.heading_between_point(
closest_airfield.position
)
heading = location.position.heading_between_point(closest_airfield.position)
position = ShapelyPoint(self.package.target.position.x,
self.package.target.position.y)
position = ShapelyPoint(
self.package.target.position.x, self.package.target.position.y
)
if barcap:
# BARCAPs should remain far enough back from the enemy that their
# commit range does not enter the enemy's threat zone. Include a 5nm
# buffer.
distance_to_no_fly = meters(
position.distance(self.threat_zones.all)
) - self.doctrine.cap_engagement_range - nautical_miles(5)
distance_to_no_fly = (
meters(position.distance(self.threat_zones.all))
- self.doctrine.cap_engagement_range
- nautical_miles(5)
)
else:
# Other race tracks (TARCAPs, currently) just try to keep some
# distance from the nearest enemy airbase, but since they are by
@@ -1158,22 +1160,24 @@ class FlightPlanBuilder:
distance_to_airfield = meters(
closest_airfield.position.distance_to_point(
self.package.target.position
))
)
)
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
distance_to_no_fly)
max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
distance_to_no_fly)
min_cap_distance = min(
self.doctrine.cap_min_distance_from_cp, distance_to_no_fly
)
max_cap_distance = min(
self.doctrine.cap_max_distance_from_cp, distance_to_no_fly
)
end = location.position.point_from_heading(
heading,
random.randint(int(min_cap_distance.meters),
int(max_cap_distance.meters))
random.randint(int(min_cap_distance.meters), int(max_cap_distance.meters)),
)
diameter = random.randint(
int(self.doctrine.cap_min_track_length.meters),
int(self.doctrine.cap_max_track_length.meters)
int(self.doctrine.cap_max_track_length.meters),
)
start = end.point_from_heading(heading - 180, diameter)
return start, end
@@ -1185,13 +1189,11 @@ class FlightPlanBuilder:
# Place this either over the target or as close as possible outside the
# threat zone: https://github.com/Khopa/dcs_liberation/issues/842.
heading = location.position.heading_between_point(closest_airfield.position)
return location.position.point_from_heading(
heading,
5000
)
return location.position.point_from_heading(heading, 5000)
def racetrack_for_frontline(self, origin: Point,
front_line: FrontLine) -> Tuple[Point, Point]:
def racetrack_for_frontline(
self, origin: Point, front_line: FrontLine
) -> Tuple[Point, Point]:
ally_cp, enemy_cp = front_line.control_points
# Find targets waypoints
@@ -1200,8 +1202,10 @@ class FlightPlanBuilder:
)
center = ingress.point_from_heading(heading, distance / 2)
orbit_center = center.point_from_heading(
heading - 90, random.randint(int(nautical_miles(6).meters),
int(nautical_miles(15).meters))
heading - 90,
random.randint(
int(nautical_miles(6).meters), int(nautical_miles(15).meters)
),
)
combat_width = distance / 2
@@ -1227,18 +1231,21 @@ class FlightPlanBuilder:
location = self.package.target
patrol_alt = meters(
random.randint(int(self.doctrine.min_patrol_altitude.meters),
int(self.doctrine.max_patrol_altitude.meters)))
random.randint(
int(self.doctrine.min_patrol_altitude.meters),
int(self.doctrine.max_patrol_altitude.meters),
)
)
# Create points
builder = WaypointBuilder(flight, self.game, self.is_player)
if isinstance(location, FrontLine):
orbit0p, orbit1p = self.racetrack_for_frontline(
flight.departure.position, location)
flight.departure.position, location
)
else:
orbit0p, orbit1p = self.racetrack_for_objective(location,
barcap=False)
orbit0p, orbit1p = self.racetrack_for_objective(location, barcap=False)
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
return TarCapFlightPlan(
@@ -1252,18 +1259,17 @@ class FlightPlanBuilder:
patrol_duration=self.doctrine.cap_duration,
engagement_distance=self.doctrine.cap_engagement_range,
takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path(flight.departure.position, orbit0p,
patrol_alt),
nav_from=builder.nav_path(orbit1p, flight.arrival.position,
patrol_alt),
nav_to=builder.nav_path(flight.departure.position, orbit0p, patrol_alt),
nav_from=builder.nav_path(orbit1p, flight.arrival.position, patrol_alt),
patrol_start=start,
patrol_end=end,
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
def generate_dead(self, flight: Flight,
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
def generate_dead(
self, flight: Flight, custom_targets: Optional[List[Unit]]
) -> StrikeFlightPlan:
"""Generate a DEAD flight at a given location.
Args:
@@ -1276,7 +1282,8 @@ class FlightPlanBuilder:
is_sam = isinstance(location, SamGroundObject)
if not is_ewr and not is_sam:
logging.exception(
f"Invalid Objective Location for DEAD flight {flight=} at {location=}")
f"Invalid Objective Location for DEAD flight {flight=} at {location=}"
)
raise InvalidObjectiveLocation(flight.flight_type, location)
# TODO: Unify these.
@@ -1288,8 +1295,9 @@ class FlightPlanBuilder:
for target in custom_targets:
targets.append(StrikeTarget(location.name, target))
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_DEAD, targets)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_DEAD, targets
)
def generate_oca_strike(self, flight: Flight) -> StrikeFlightPlan:
"""Generate an OCA Strike flight plan at a given location.
@@ -1302,11 +1310,13 @@ class FlightPlanBuilder:
if not isinstance(location, Airfield):
logging.exception(
f"Invalid Objective Location for OCA Strike flight "
f"{flight=} at {location=}.")
f"{flight=} at {location=}."
)
raise InvalidObjectiveLocation(flight.flight_type, location)
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_OCA_AIRCRAFT)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_OCA_AIRCRAFT
)
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
"""Generate a runway attack flight plan at a given location.
@@ -1319,14 +1329,17 @@ class FlightPlanBuilder:
if not isinstance(location, Airfield):
logging.exception(
f"Invalid Objective Location for runway bombing flight "
f"{flight=} at {location=}.")
f"{flight=} at {location=}."
)
raise InvalidObjectiveLocation(flight.flight_type, location)
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_OCA_RUNWAY)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_OCA_RUNWAY
)
def generate_sead(self, flight: Flight,
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
def generate_sead(
self, flight: Flight, custom_targets: Optional[List[Unit]]
) -> StrikeFlightPlan:
"""Generate a SEAD flight at a given location.
Args:
@@ -1344,16 +1357,19 @@ class FlightPlanBuilder:
for target in custom_targets:
targets.append(StrikeTarget(location.name, target))
return self.strike_flightplan(flight, location,
FlightWaypointType.INGRESS_SEAD, targets)
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_SEAD, targets
)
def generate_escort(self, flight: Flight) -> StrikeFlightPlan:
assert self.package.waypoints is not None
builder = WaypointBuilder(flight, self.game, self.is_player)
ingress, target, egress = builder.escort(
self.package.waypoints.ingress, self.package.target,
self.package.waypoints.egress)
self.package.waypoints.ingress,
self.package.target,
self.package.waypoints.egress,
)
hold = builder.hold(self._hold_point(flight))
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
@@ -1364,17 +1380,19 @@ class FlightPlanBuilder:
takeoff=builder.takeoff(flight.departure),
hold=hold,
hold_duration=timedelta(minutes=5),
nav_to=builder.nav_path(hold.position, join.position,
self.doctrine.ingress_altitude),
nav_to=builder.nav_path(
hold.position, join.position, self.doctrine.ingress_altitude
),
join=join,
ingress=ingress,
targets=[target],
egress=egress,
split=split,
nav_from=builder.nav_path(split.position, flight.arrival.position,
self.doctrine.ingress_altitude),
nav_from=builder.nav_path(
split.position, flight.arrival.position, self.doctrine.ingress_altitude
),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
def generate_cas(self, flight: Flight) -> CasFlightPlan:
@@ -1389,8 +1407,7 @@ class FlightPlanBuilder:
raise InvalidObjectiveLocation(flight.flight_type, location)
ingress, heading, distance = Conflict.frontline_vector(
location.control_points[0], location.control_points[1],
self.game.theater
location.control_points[0], location.control_points[1], self.game.theater
)
center = ingress.point_from_heading(heading, distance / 2)
egress = ingress.point_from_heading(heading, distance)
@@ -1407,22 +1424,26 @@ class FlightPlanBuilder:
flight=flight,
patrol_duration=self.doctrine.cas_duration,
takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path(flight.departure.position, ingress,
self.doctrine.ingress_altitude),
nav_from=builder.nav_path(egress, flight.arrival.position,
self.doctrine.ingress_altitude),
patrol_start=builder.ingress(FlightWaypointType.INGRESS_CAS,
ingress, location),
nav_to=builder.nav_path(
flight.departure.position, ingress, self.doctrine.ingress_altitude
),
nav_from=builder.nav_path(
egress, flight.arrival.position, self.doctrine.ingress_altitude
),
patrol_start=builder.ingress(
FlightWaypointType.INGRESS_CAS, ingress, location
),
engagement_distance=meters(FRONTLINE_LENGTH) / 2,
target=builder.cas(center),
patrol_end=builder.egress(egress, location),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
@staticmethod
def target_waypoint(flight: Flight, builder: WaypointBuilder,
target: StrikeTarget) -> FlightWaypoint:
def target_waypoint(
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
) -> FlightWaypoint:
if flight.flight_type in {FlightType.ANTISHIP, FlightType.BAI}:
return builder.bai_group(target)
elif flight.flight_type == FlightType.DEAD:
@@ -1433,8 +1454,9 @@ class FlightPlanBuilder:
return builder.strike_point(target)
@staticmethod
def target_area_waypoint(flight: Flight, location: MissionTarget,
builder: WaypointBuilder) -> FlightWaypoint:
def target_area_waypoint(
flight: Flight, location: MissionTarget, builder: WaypointBuilder
) -> FlightWaypoint:
if flight.flight_type == FlightType.DEAD:
return builder.dead_area(location)
elif flight.flight_type == FlightType.SEAD:
@@ -1455,12 +1477,14 @@ class FlightPlanBuilder:
# If the origin airfield is closer to the target than the join
# point, plan the hold point such that it retreats from the origin
# airfield.
return join.point_from_heading(target.heading_between_point(origin),
self.doctrine.push_distance.meters)
return join.point_from_heading(
target.heading_between_point(origin), self.doctrine.push_distance.meters
)
heading_to_join = origin.heading_between_point(join)
hold_point = origin.point_from_heading(
heading_to_join, self.doctrine.push_distance.meters)
heading_to_join, self.doctrine.push_distance.meters
)
hold_distance = meters(hold_point.distance_to_point(join))
if hold_distance >= self.doctrine.push_distance:
# Hold point is between the origin airfield and the join point and
@@ -1474,26 +1498,27 @@ class FlightPlanBuilder:
# properly.
origin_to_join = origin.distance_to_point(join)
cos_theta = (
(self.doctrine.hold_distance.meters ** 2 +
origin_to_join ** 2 -
self.doctrine.join_distance.meters ** 2) /
(2 * self.doctrine.hold_distance.meters * origin_to_join)
)
self.doctrine.hold_distance.meters ** 2
+ origin_to_join ** 2
- self.doctrine.join_distance.meters ** 2
) / (2 * self.doctrine.hold_distance.meters * origin_to_join)
try:
theta = math.acos(cos_theta)
except ValueError:
# No solution that maintains hold and join distances. Extend the
# hold point away from the target.
return origin.point_from_heading(
target.heading_between_point(origin),
self.doctrine.hold_distance.meters)
target.heading_between_point(origin), self.doctrine.hold_distance.meters
)
return origin.point_from_heading(heading_to_join - theta,
self.doctrine.hold_distance.meters)
return origin.point_from_heading(
heading_to_join - theta, self.doctrine.hold_distance.meters
)
# TODO: Make a model for the waypoint builder and use that in the UI.
def generate_rtb_waypoint(self, flight: Flight,
arrival: ControlPoint) -> FlightWaypoint:
def generate_rtb_waypoint(
self, flight: Flight, arrival: ControlPoint
) -> FlightWaypoint:
"""Generate RTB landing point.
Args:
@@ -1504,20 +1529,23 @@ class FlightPlanBuilder:
return builder.land(arrival)
def strike_flightplan(
self, flight: Flight, location: MissionTarget,
ingress_type: FlightWaypointType,
targets: Optional[List[StrikeTarget]] = None) -> StrikeFlightPlan:
self,
flight: Flight,
location: MissionTarget,
ingress_type: FlightWaypointType,
targets: Optional[List[StrikeTarget]] = None,
) -> StrikeFlightPlan:
assert self.package.waypoints is not None
builder = WaypointBuilder(flight, self.game, self.is_player, targets)
target_waypoints: List[FlightWaypoint] = []
if targets is not None:
for target in targets:
target_waypoints.append(
self.target_waypoint(flight, builder, target))
target_waypoints.append(self.target_waypoint(flight, builder, target))
else:
target_waypoints.append(
self.target_area_waypoint(flight, location, builder))
self.target_area_waypoint(flight, location, builder)
)
hold = builder.hold(self._hold_point(flight))
join = builder.join(self.package.waypoints.join)
@@ -1529,32 +1557,38 @@ class FlightPlanBuilder:
takeoff=builder.takeoff(flight.departure),
hold=hold,
hold_duration=timedelta(minutes=5),
nav_to=builder.nav_path(hold.position, join.position,
self.doctrine.ingress_altitude),
nav_to=builder.nav_path(
hold.position, join.position, self.doctrine.ingress_altitude
),
join=join,
ingress=builder.ingress(ingress_type,
self.package.waypoints.ingress, location),
ingress=builder.ingress(
ingress_type, self.package.waypoints.ingress, location
),
targets=target_waypoints,
egress=builder.egress(self.package.waypoints.egress, location),
split=split,
nav_from=builder.nav_path(split.position, flight.arrival.position,
self.doctrine.ingress_altitude),
nav_from=builder.nav_path(
split.position, flight.arrival.position, self.doctrine.ingress_altitude
),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert)
divert=builder.divert(flight.divert),
)
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
"""Creates a rendezvous point that retreats from the origin airfield."""
return attack_transition.point_from_heading(
self.package.target.position.heading_between_point(
self.package_airfield().position),
self.doctrine.join_distance.meters)
self.package_airfield().position
),
self.doctrine.join_distance.meters,
)
def _advancing_rendezvous_point(self, attack_transition: Point) -> Point:
"""Creates a rendezvous point that advances toward the target."""
heading = self._heading_to_package_airfield(attack_transition)
return attack_transition.point_from_heading(
heading, -self.doctrine.join_distance.meters)
heading, -self.doctrine.join_distance.meters
)
def _rendezvous_should_retreat(self, attack_transition: Point) -> bool:
transition_target_distance = attack_transition.distance_to_point(
@@ -1609,13 +1643,9 @@ class FlightPlanBuilder:
# The package airfield is either the flight's airfield (when there is no
# package) or the closest airfield to the objective that is the
# departure airfield for some flight in the package.
cache = ObjectiveDistanceCache.get_closest_airfields(
self.package.target
)
cache = ObjectiveDistanceCache.get_closest_airfields(self.package.target)
for airfield in cache.operational_airfields:
for flight in self.package.flights:
if flight.departure == airfield:
return airfield
raise RuntimeError(
"Could not find any airfield assigned to this package"
)
raise RuntimeError("Could not find any airfield assigned to this package")

View File

@@ -23,7 +23,6 @@ if TYPE_CHECKING:
class GroundSpeed:
@classmethod
def for_flight(cls, flight: Flight, altitude: Distance) -> Speed:
if not issubclass(flight.unit_type, FlyingType):
@@ -55,13 +54,11 @@ class TravelTime:
def between_points(a: Point, b: Point, speed: Speed) -> timedelta:
error_factor = 1.1
distance = meters(a.distance_to_point(b))
return timedelta(
hours=distance.nautical_miles / speed.knots * error_factor)
return timedelta(hours=distance.nautical_miles / speed.knots * error_factor)
# TODO: Most if not all of this should move into FlightPlan.
class TotEstimator:
def __init__(self, package: Package) -> None:
self.package = package
@@ -75,9 +72,9 @@ class TotEstimator:
return startup_time
def earliest_tot(self) -> timedelta:
earliest_tot = max((
self.earliest_tot_for_flight(f) for f in self.package.flights
))
earliest_tot = max(
(self.earliest_tot_for_flight(f) for f in self.package.flights)
)
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
# and they're not interesting from a mission planning perspective so we

View File

@@ -36,8 +36,13 @@ class StrikeTarget:
class WaypointBuilder:
def __init__(self, flight: Flight, game: Game, player: bool,
targets: Optional[List[StrikeTarget]] = None) -> None:
def __init__(
self,
flight: Flight,
game: Game,
player: bool,
targets: Optional[List[StrikeTarget]] = None,
) -> None:
self.flight = flight
self.conditions = game.conditions
self.doctrine = game.faction_for(player).doctrine
@@ -65,9 +70,7 @@ class WaypointBuilder:
FlightWaypointType.NAV,
position.x,
position.y,
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
)
waypoint.name = "NAV"
waypoint.alt_type = "BARO"
@@ -75,10 +78,7 @@ class WaypointBuilder:
waypoint.pretty_name = "Enter theater"
else:
waypoint = FlightWaypoint(
FlightWaypointType.TAKEOFF,
position.x,
position.y,
meters(0)
FlightWaypointType.TAKEOFF, position.x, position.y, meters(0)
)
waypoint.name = "TAKEOFF"
waypoint.alt_type = "RADIO"
@@ -98,9 +98,7 @@ class WaypointBuilder:
FlightWaypointType.NAV,
position.x,
position.y,
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
)
waypoint.name = "NAV"
waypoint.alt_type = "BARO"
@@ -108,10 +106,7 @@ class WaypointBuilder:
waypoint.pretty_name = "Exit theater"
else:
waypoint = FlightWaypoint(
FlightWaypointType.LANDING_POINT,
position.x,
position.y,
meters(0)
FlightWaypointType.LANDING_POINT, position.x, position.y, meters(0)
)
waypoint.name = "LANDING"
waypoint.alt_type = "RADIO"
@@ -119,8 +114,7 @@ class WaypointBuilder:
waypoint.pretty_name = "Land"
return waypoint
def divert(self,
divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
def divert(self, divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
"""Create divert waypoint for the given arrival airfield or carrier.
Args:
@@ -141,10 +135,7 @@ class WaypointBuilder:
altitude_type = "RADIO"
waypoint = FlightWaypoint(
FlightWaypointType.DIVERT,
position.x,
position.y,
altitude
FlightWaypointType.DIVERT, position.x, position.y, altitude
)
waypoint.alt_type = altitude_type
waypoint.name = "DIVERT"
@@ -158,9 +149,7 @@ class WaypointBuilder:
FlightWaypointType.LOITER,
position.x,
position.y,
meters(
500
) if self.is_helo else self.doctrine.rendezvous_altitude
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
)
waypoint.pretty_name = "Hold"
waypoint.description = "Wait until push time"
@@ -172,9 +161,7 @@ class WaypointBuilder:
FlightWaypointType.JOIN,
position.x,
position.y,
meters(
80
) if self.is_helo else self.doctrine.ingress_altitude
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
)
if self.is_helo:
waypoint.alt_type = "RADIO"
@@ -188,9 +175,7 @@ class WaypointBuilder:
FlightWaypointType.SPLIT,
position.x,
position.y,
meters(
80
) if self.is_helo else self.doctrine.ingress_altitude
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
)
if self.is_helo:
waypoint.alt_type = "RADIO"
@@ -199,15 +184,17 @@ class WaypointBuilder:
waypoint.name = "SPLIT"
return waypoint
def ingress(self, ingress_type: FlightWaypointType, position: Point,
objective: MissionTarget) -> FlightWaypoint:
def ingress(
self,
ingress_type: FlightWaypointType,
position: Point,
objective: MissionTarget,
) -> FlightWaypoint:
waypoint = FlightWaypoint(
ingress_type,
position.x,
position.y,
meters(
50
) if self.is_helo else self.doctrine.ingress_altitude
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
)
if self.is_helo:
waypoint.alt_type = "RADIO"
@@ -223,9 +210,7 @@ class WaypointBuilder:
FlightWaypointType.EGRESS,
position.x,
position.y,
meters(
50
) if self.is_helo else self.doctrine.ingress_altitude
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
)
if self.is_helo:
waypoint.alt_type = "RADIO"
@@ -252,7 +237,7 @@ class WaypointBuilder:
FlightWaypointType.TARGET_POINT,
target.target.position.x,
target.target.position.y,
meters(0)
meters(0),
)
waypoint.description = description
waypoint.pretty_name = description
@@ -277,13 +262,14 @@ class WaypointBuilder:
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
@staticmethod
def _target_area(name: str, location: MissionTarget,
flyover: bool = False) -> FlightWaypoint:
def _target_area(
name: str, location: MissionTarget, flyover: bool = False
) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
location.position.x,
location.position.y,
meters(0)
meters(0),
)
waypoint.description = name
waypoint.pretty_name = name
@@ -308,7 +294,7 @@ class WaypointBuilder:
FlightWaypointType.CAS,
position.x,
position.y,
meters(50) if self.is_helo else meters(1000)
meters(50) if self.is_helo else meters(1000),
)
waypoint.alt_type = "RADIO"
waypoint.description = "Provide CAS"
@@ -325,10 +311,7 @@ class WaypointBuilder:
altitude: Altitude of the racetrack.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL_TRACK,
position.x,
position.y,
altitude
FlightWaypointType.PATROL_TRACK, position.x, position.y, altitude
)
waypoint.name = "RACETRACK START"
waypoint.description = "Orbit between this point and the next point"
@@ -344,18 +327,16 @@ class WaypointBuilder:
altitude: Altitude of the racetrack.
"""
waypoint = FlightWaypoint(
FlightWaypointType.PATROL,
position.x,
position.y,
altitude
FlightWaypointType.PATROL, position.x, position.y, altitude
)
waypoint.name = "RACETRACK END"
waypoint.description = "Orbit between this point and the previous point"
waypoint.pretty_name = "Race-track end"
return waypoint
def race_track(self, start: Point, end: Point,
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
def race_track(
self, start: Point, end: Point, altitude: Distance
) -> Tuple[FlightWaypoint, FlightWaypoint]:
"""Creates two waypoint for a racetrack orbit.
Args:
@@ -363,8 +344,10 @@ class WaypointBuilder:
end: The ending racetrack waypoint.
altitude: The racetrack altitude.
"""
return (self.race_track_start(start, altitude),
self.race_track_end(end, altitude))
return (
self.race_track_start(start, altitude),
self.race_track_end(end, altitude),
)
@staticmethod
def orbit(start: Point, altitude: Distance) -> FlightWaypoint:
@@ -375,12 +358,7 @@ class WaypointBuilder:
altitude: Altitude of the racetrack.
"""
waypoint = FlightWaypoint(
FlightWaypointType.LOITER,
start.x,
start.y,
altitude
)
waypoint = FlightWaypoint(FlightWaypointType.LOITER, start.x, start.y, altitude)
waypoint.name = "ORBIT"
waypoint.description = "Anchor and hold at this point"
waypoint.pretty_name = "Orbit"
@@ -395,10 +373,7 @@ class WaypointBuilder:
altitude: Altitude of the sweep in meters.
"""
waypoint = FlightWaypoint(
FlightWaypointType.INGRESS_SWEEP,
position.x,
position.y,
altitude
FlightWaypointType.INGRESS_SWEEP, position.x, position.y, altitude
)
waypoint.name = "SWEEP START"
waypoint.description = "Proceed to the target and engage enemy aircraft"
@@ -414,18 +389,16 @@ class WaypointBuilder:
altitude: Altitude of the sweep in meters.
"""
waypoint = FlightWaypoint(
FlightWaypointType.EGRESS,
position.x,
position.y,
altitude
FlightWaypointType.EGRESS, position.x, position.y, altitude
)
waypoint.name = "SWEEP END"
waypoint.description = "End of sweep"
waypoint.pretty_name = "Sweep end"
return waypoint
def sweep(self, start: Point, end: Point,
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
def sweep(
self, start: Point, end: Point, altitude: Distance
) -> Tuple[FlightWaypoint, FlightWaypoint]:
"""Creates two waypoint for a racetrack orbit.
Args:
@@ -433,11 +406,11 @@ class WaypointBuilder:
end: The end of the sweep.
altitude: The sweep altitude.
"""
return (self.sweep_start(start, altitude),
self.sweep_end(end, altitude))
return (self.sweep_start(start, altitude), self.sweep_end(end, altitude))
def escort(self, ingress: Point, target: MissionTarget, egress: Point) -> \
Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
def escort(
self, ingress: Point, target: MissionTarget, egress: Point
) -> Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
"""Creates the waypoints needed to escort the package.
Args:
@@ -451,16 +424,13 @@ class WaypointBuilder:
# description in gen.aircraft.JoinPointBuilder), so instead we give
# the escort flights a flight plan including the ingress point, target
# area, and egress point.
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress,
target)
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
target.position.x,
target.position.y,
meters(
50
) if self.is_helo else self.doctrine.ingress_altitude
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
)
if self.is_helo:
waypoint.alt_type = "RADIO"
@@ -480,18 +450,14 @@ class WaypointBuilder:
altitude: Altitude of the waypoint.
"""
waypoint = FlightWaypoint(
FlightWaypointType.NAV,
position.x,
position.y,
altitude
FlightWaypointType.NAV, position.x, position.y, altitude
)
waypoint.name = "NAV"
waypoint.description = "NAV"
waypoint.pretty_name = "Nav"
return waypoint
def nav_path(self, a: Point, b: Point,
altitude: Distance) -> List[FlightWaypoint]:
def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]:
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
return [self.nav(self.perturb(p), altitude) for p in path]
@@ -518,10 +484,8 @@ class WaypointBuilder:
previous = current
current = nxt
def nav_point_prunable(self, previous: Point, current: Point,
nxt: Point) -> bool:
previous_threatened = self.threat_zones.path_threatened(previous,
current)
def nav_point_prunable(self, previous: Point, current: Point, nxt: Point) -> bool:
previous_threatened = self.threat_zones.path_threatened(previous, current)
next_threatened = self.threat_zones.path_threatened(current, nxt)
pruned_threatened = self.threat_zones.path_threatened(previous, nxt)
previous_distance = meters(previous.distance_to_point(current))

View File

@@ -15,11 +15,15 @@ class ForcedOptionsGenerator:
self.game = game
def _set_options_view(self) -> None:
self.mission.forced_options.options_view = self.game.settings.map_coalition_visibility
self.mission.forced_options.options_view = (
self.game.settings.map_coalition_visibility
)
def _set_external_views(self) -> None:
if not self.game.settings.external_views_allowed:
self.mission.forced_options.external_views = self.game.settings.external_views_allowed
self.mission.forced_options.external_views = (
self.game.settings.external_views_allowed
)
def _set_labels(self) -> None:
# TODO: Fix settings to use the real type.

View File

@@ -39,12 +39,11 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
CombatStance.RETREAT: [2, 4, 6, 8],
CombatStance.BREAKTHROUGH: [4, 6, 6, 8],
CombatStance.ELIMINATION: [2, 4, 4, 4, 6],
CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4]
CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4],
}
class CombatGroup:
def __init__(self, role: CombatGroupRole):
self.units: List[VehicleType] = []
self.role = role
@@ -60,11 +59,12 @@ class CombatGroup:
class GroundPlanner:
def __init__(self, cp:ControlPoint, game):
def __init__(self, cp: ControlPoint, game):
self.cp = cp
self.game = game
self.connected_enemy_cp = [cp for cp in self.cp.connected_points if cp.captured != self.cp.captured]
self.connected_enemy_cp = [
cp for cp in self.cp.connected_points if cp.captured != self.cp.captured
]
self.tank_groups: List[CombatGroup] = []
self.apc_group: List[CombatGroup] = []
self.ifv_group: List[CombatGroup] = []
@@ -80,7 +80,7 @@ class GroundPlanner:
def plan_groundwar(self):
if hasattr(self.cp, 'stance'):
if hasattr(self.cp, "stance"):
group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[self.cp.stance]
else:
self.cp.stance = CombatStance.DEFENSIVE
@@ -152,12 +152,3 @@ class GroundPlanner:
print("For : #" + str(key))
for group in self.units_per_cp[key]:
print(str(group))

View File

@@ -16,7 +16,6 @@ TYPE_TANKS = [
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Armor.ZTZ_96B,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
@@ -29,36 +28,30 @@ TYPE_TANKS = [
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Armor.M30_Cargo_Carrier,
Armor.TD_Jagdpanzer_IV,
Armor.TD_Jagdpanther_G1,
Armor.TD_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
@@ -73,17 +66,14 @@ TYPE_IFV = [
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
Armor.Daimler_Armoured_Car,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13
frenchpack.VAB_T20_13,
]
TYPE_APC = [
@@ -101,16 +91,13 @@ TYPE_APC = [
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
@@ -125,10 +112,9 @@ TYPE_ARTILLERY = [
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.MLRS_FDDM,
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
Artillery.M12_GMC,
]
TYPE_LOGI = [
@@ -147,11 +133,9 @@ TYPE_LOGI = [
Unarmed.Willys_MB,
Unarmed.Land_Rover_109_S3,
Unarmed.Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
@@ -179,7 +163,6 @@ TYPE_SHORAD = [
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SPAAA_Gepard,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
@@ -187,7 +170,6 @@ TYPE_SHORAD = [
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
@@ -195,5 +177,4 @@ TYPE_SHORAD = [
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_M1_37mm,
AirDefence.AA_gun_QF_3_7,
]

View File

@@ -2,10 +2,11 @@ from enum import Enum
class CombatStance(Enum):
DEFENSIVE = 0 # Unit will adopt defensive stance with medium group of units
AGGRESSIVE = 1 # Unit will attempt to make progress with medium sized group of units
RETREAT = 2 # Unit will retreat
BREAKTHROUGH = 3 # Unit will attempt a breakthrough, rushing forward very aggresively with big group of armored units, and even less armored units will move aggresively
DEFENSIVE = 0 # Unit will adopt defensive stance with medium group of units
AGGRESSIVE = (
1 # Unit will attempt to make progress with medium sized group of units
)
RETREAT = 2 # Unit will retreat
BREAKTHROUGH = 3 # Unit will attempt a breakthrough, rushing forward very aggresively with big group of armored units, and even less armored units will move aggresively
ELIMINATION = 4 # Unit will progress aggresively toward anemy units, attempting to eliminate the ennemy force
AMBUSH = 5 # Units will adopt a defensive stance a bit different from 'DEFENSIVE', ATGM & INFANTRY with RPG will be located on frontline with the armored units. (The groups of units will be smaller)
AMBUSH = 5 # Units will adopt a defensive stance a bit different from 'DEFENSIVE', ATGM & INFANTRY with RPG will be located on frontline with the armored units. (The groups of units will be smaller)

View File

@@ -18,7 +18,8 @@ from dcs.task import (
ActivateBeaconCommand,
ActivateICLSCommand,
EPLRS,
OptAlarmState, FireAtPoint,
OptAlarmState,
FireAtPoint,
)
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
@@ -30,9 +31,12 @@ from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
from game.db import unit_type_from_name
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
BuildingGroundObject, CarrierGroundObject,
BuildingGroundObject,
CarrierGroundObject,
GenericCarrierGroundObject,
LhaGroundObject, ShipGroundObject, MissileSiteGroundObject,
LhaGroundObject,
ShipGroundObject,
MissileSiteGroundObject,
)
from game.unitmap import UnitMap
from game.utils import knots, mps
@@ -53,8 +57,15 @@ class GenericGroundObjectGenerator:
Currently used only for SAM
"""
def __init__(self, ground_object: TheaterGroundObject, country: Country,
game: Game, mission: Mission, unit_map: UnitMap) -> None:
def __init__(
self,
ground_object: TheaterGroundObject,
country: Country,
game: Game,
mission: Mission,
unit_map: UnitMap,
) -> None:
self.ground_object = ground_object
self.country = country
self.game = game
@@ -72,18 +83,22 @@ class GenericGroundObjectGenerator:
unit_type = unit_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(
f"Unrecognized unit type: {group.units[0].type}")
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
vg = self.m.vehicle_group(self.country, group.name, unit_type,
position=group.position,
heading=group.units[0].heading)
vg = self.m.vehicle_group(
self.country,
group.name,
unit_type,
position=group.position,
heading=group.units[0].heading,
)
vg.units[0].name = self.m.string(group.units[0].name)
vg.units[0].player_can_drive = True
for i, u in enumerate(group.units):
if i > 0:
vehicle = Vehicle(self.m.next_unit_id(),
self.m.string(u.name), u.type)
vehicle = Vehicle(
self.m.next_unit_id(), self.m.string(u.name), u.type
)
vehicle.position.x = u.position.x
vehicle.position.y = u.position.y
vehicle.heading = u.heading
@@ -96,7 +111,7 @@ class GenericGroundObjectGenerator:
@staticmethod
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None:
if hasattr(unit_type, 'eplrs'):
if hasattr(unit_type, "eplrs"):
if unit_type.eplrs:
group.points[0].tasks.append(EPLRS(group.id))
@@ -106,14 +121,13 @@ class GenericGroundObjectGenerator:
else:
group.points[0].tasks.append(OptAlarmState(1))
def _register_unit_group(self, persistence_group: Group,
miz_group: Group) -> None:
self.unit_map.add_ground_object_units(self.ground_object,
persistence_group, miz_group)
def _register_unit_group(self, persistence_group: Group, miz_group: Group) -> None:
self.unit_map.add_ground_object_units(
self.ground_object, persistence_group, miz_group
)
class MissileSiteGenerator(GenericGroundObjectGenerator):
def generate(self) -> None:
super(MissileSiteGenerator, self).generate()
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
@@ -125,13 +139,19 @@ class MissileSiteGenerator(GenericGroundObjectGenerator):
targets = self.possible_missile_targets(vg)
if targets:
target = random.choice(targets)
real_target = target.point_from_heading(random.randint(0, 360), random.randint(0, 2500))
real_target = target.point_from_heading(
random.randint(0, 360), random.randint(0, 2500)
)
vg.points[0].add_task(FireAtPoint(real_target))
logging.info("Set up fire task for missile group.")
else:
logging.info("Couldn't setup missile site to fire, no valid target in range.")
logging.info(
"Couldn't setup missile site to fire, no valid target in range."
)
else:
logging.info("Couldn't setup missile site to fire, group was not generated.")
logging.info(
"Couldn't setup missile site to fire, group was not generated."
)
def possible_missile_targets(self, vg: Group) -> List[Point]:
"""
@@ -190,7 +210,8 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator):
break
else:
logging.error(
f"{self.ground_object.dcs_identifier} not found in static maps")
f"{self.ground_object.dcs_identifier} not found in static maps"
)
def generate_vehicle_group(self, unit_type: UnitType) -> None:
if not self.ground_object.is_dead:
@@ -228,11 +249,20 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
Used by both CV(N) groups and LHA groups.
"""
def __init__(self, ground_object: GenericCarrierGroundObject,
control_point: ControlPoint, country: Country, game: Game,
mission: Mission, radio_registry: RadioRegistry,
tacan_registry: TacanRegistry, icls_alloc: Iterator[int],
runways: Dict[str, RunwayData], unit_map: UnitMap) -> None:
def __init__(
self,
ground_object: GenericCarrierGroundObject,
control_point: ControlPoint,
country: Country,
game: Game,
mission: Mission,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
icls_alloc: Iterator[int],
runways: Dict[str, RunwayData],
unit_map: UnitMap,
) -> None:
super().__init__(ground_object, country, game, mission, unit_map)
self.ground_object = ground_object
self.control_point = control_point
@@ -245,8 +275,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
# TODO: Require single group?
for group in self.ground_object.groups:
if not group.units:
logging.warning(
f"Found empty carrier group in {self.control_point}")
logging.warning(f"Found empty carrier group in {self.control_point}")
continue
atc = self.radio_registry.alloc_uhf()
@@ -270,25 +299,29 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
def get_carrier_type(self, group: Group) -> Type[UnitType]:
unit_type = unit_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(
f"Unrecognized carrier name: {group.units[0].type}")
raise RuntimeError(f"Unrecognized carrier name: {group.units[0].type}")
return unit_type
def configure_carrier(self, group: Group,
atc_channel: RadioFrequency) -> ShipGroup:
def configure_carrier(self, group: Group, atc_channel: RadioFrequency) -> ShipGroup:
unit_type = self.get_carrier_type(group)
ship_group = self.m.ship_group(self.country, group.name, unit_type,
position=group.position,
heading=group.units[0].heading)
ship_group = self.m.ship_group(
self.country,
group.name,
unit_type,
position=group.position,
heading=group.units[0].heading,
)
ship_group.set_frequency(atc_channel.hertz)
ship_group.units[0].name = self.m.string(group.units[0].name)
return ship_group
def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship:
ship = Ship(self.m.next_unit_id(),
self.m.string(unit.name),
unit_type_from_name(unit.type))
ship = Ship(
self.m.next_unit_id(),
self.m.string(unit.name),
unit_type_from_name(unit.type),
)
ship.position.x = unit.position.x
ship.position.y = unit.position.y
ship.heading = unit.heading
@@ -303,7 +336,8 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
carrier_speed = knots(25) - mps(wind.speed)
for attempt in range(5):
point = group.points[0].position.point_from_heading(
brc, 100000 - attempt * 20000)
brc, 100000 - attempt * 20000
)
if self.game.theater.is_in_sea(point):
group.points[0].speed = carrier_speed.meters_per_second
group.add_waypoint(point, carrier_speed.kph)
@@ -314,21 +348,30 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
raise NotImplementedError
@staticmethod
def activate_beacons(group: ShipGroup, tacan: TacanChannel,
callsign: str, icls: int) -> None:
group.points[0].tasks.append(ActivateBeaconCommand(
channel=tacan.number,
modechannel=tacan.band.value,
callsign=callsign,
unit_id=group.units[0].id,
aa=False
))
group.points[0].tasks.append(ActivateICLSCommand(
icls, unit_id=group.units[0].id
))
def activate_beacons(
group: ShipGroup, tacan: TacanChannel, callsign: str, icls: int
) -> None:
group.points[0].tasks.append(
ActivateBeaconCommand(
channel=tacan.number,
modechannel=tacan.band.value,
callsign=callsign,
unit_id=group.units[0].id,
aa=False,
)
)
group.points[0].tasks.append(
ActivateICLSCommand(icls, unit_id=group.units[0].id)
)
def add_runway_data(self, brc: int, atc: RadioFrequency,
tacan: TacanChannel, callsign: str, icls: int) -> None:
def add_runway_data(
self,
brc: int,
atc: RadioFrequency,
tacan: TacanChannel,
callsign: str,
icls: int,
) -> None:
# TODO: Make unit name usable.
# This relies on one control point mapping exactly
# to one LHA, carrier, or other usable "runway".
@@ -354,8 +397,7 @@ class CarrierGenerator(GenericCarrierGenerator):
def get_carrier_type(self, group: Group) -> UnitType:
unit_type = super().get_carrier_type(group)
if self.game.settings.supercarrier:
unit_type = db.upgrade_to_supercarrier(unit_type,
self.control_point.name)
unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name)
return unit_type
def tacan_callsign(self) -> str:
@@ -363,18 +405,20 @@ class CarrierGenerator(GenericCarrierGenerator):
if self.control_point.name == "Carrier Strike Group 8":
return "TRU"
else:
return random.choice([
"STE",
"CVN",
"CVH",
"CCV",
"ACC",
"ARC",
"GER",
"ABR",
"LIN",
"TRU",
])
return random.choice(
[
"STE",
"CVN",
"CVH",
"CCV",
"ACC",
"ARC",
"GER",
"ABR",
"LIN",
"TRU",
]
)
class LhaGenerator(GenericCarrierGenerator):
@@ -382,14 +426,16 @@ class LhaGenerator(GenericCarrierGenerator):
def tacan_callsign(self) -> str:
# TODO: Assign these properly.
return random.choice([
"LHD",
"LHA",
"LHB",
"LHC",
"LHD",
"LDS",
])
return random.choice(
[
"LHD",
"LHA",
"LHB",
"LHC",
"LHD",
"LDS",
]
)
class ShipObjectGenerator(GenericGroundObjectGenerator):
@@ -406,22 +452,23 @@ class ShipObjectGenerator(GenericGroundObjectGenerator):
unit_type = unit_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(
f"Unrecognized unit type: {group.units[0].type}")
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
self.generate_group(group, unit_type)
def generate_group(self, group_def: Group,
first_unit_type: Type[UnitType]) -> None:
group = self.m.ship_group(self.country, group_def.name, first_unit_type,
position=group_def.position,
heading=group_def.units[0].heading)
def generate_group(self, group_def: Group, first_unit_type: Type[UnitType]) -> None:
group = self.m.ship_group(
self.country,
group_def.name,
first_unit_type,
position=group_def.position,
heading=group_def.units[0].heading,
)
group.units[0].name = self.m.string(group_def.units[0].name)
# TODO: Skipping the first unit looks like copy pasta from the carrier.
for unit in group_def.units[1:]:
unit_type = unit_type_from_name(unit.type)
ship = Ship(self.m.next_unit_id(),
self.m.string(unit.name), unit_type)
ship = Ship(self.m.next_unit_id(), self.m.string(unit.name), unit_type)
ship.position.x = unit.position.x
ship.position.y = unit.position.y
ship.heading = unit.heading
@@ -439,9 +486,14 @@ class GroundObjectsGenerator:
the appropriate generators.
"""
def __init__(self, mission: Mission, game: Game,
radio_registry: RadioRegistry, tacan_registry: TacanRegistry,
unit_map: UnitMap) -> None:
def __init__(
self,
mission: Mission,
game: Game,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
unit_map: UnitMap,
) -> None:
self.m = mission
self.game = game
self.radio_registry = radio_registry
@@ -461,28 +513,44 @@ class GroundObjectsGenerator:
for ground_object in cp.ground_objects:
if isinstance(ground_object, BuildingGroundObject):
generator = BuildingSiteGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
ground_object, country, self.game, self.m, self.unit_map
)
elif isinstance(ground_object, CarrierGroundObject):
generator = CarrierGenerator(
ground_object, cp, country, self.game, self.m,
self.radio_registry, self.tacan_registry,
self.icls_alloc, self.runways, self.unit_map)
ground_object,
cp,
country,
self.game,
self.m,
self.radio_registry,
self.tacan_registry,
self.icls_alloc,
self.runways,
self.unit_map,
)
elif isinstance(ground_object, LhaGroundObject):
generator = CarrierGenerator(
ground_object, cp, country, self.game, self.m,
self.radio_registry, self.tacan_registry,
self.icls_alloc, self.runways, self.unit_map)
ground_object,
cp,
country,
self.game,
self.m,
self.radio_registry,
self.tacan_registry,
self.icls_alloc,
self.runways,
self.unit_map,
)
elif isinstance(ground_object, ShipGroundObject):
generator = ShipObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
ground_object, country, self.game, self.m, self.unit_map
)
elif isinstance(ground_object, MissileSiteGroundObject):
generator = MissileSiteGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
ground_object, country, self.game, self.m, self.unit_map
)
else:
generator = GenericGroundObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
ground_object, country, self.game, self.m, self.unit_map
)
generator.generate()

View File

@@ -49,7 +49,7 @@ class KneeboardPageWriter:
"""Creates kneeboard images."""
def __init__(self, page_margin: int = 24, line_spacing: int = 12) -> None:
self.image = Image.new('RGB', (768, 1024), (0xff, 0xff, 0xff))
self.image = Image.new("RGB", (768, 1024), (0xFF, 0xFF, 0xFF))
# These font sizes create a relatively full page for current sorties. If
# we start generating more complicated flight plans, or start including
# more information in the comm ladder (the latter of which we should
@@ -58,8 +58,7 @@ class KneeboardPageWriter:
self.title_font = ImageFont.truetype("arial.ttf", 32)
self.heading_font = ImageFont.truetype("arial.ttf", 24)
self.content_font = ImageFont.truetype("arial.ttf", 20)
self.table_font = ImageFont.truetype(
"resources/fonts/Inconsolata.otf", 20)
self.table_font = ImageFont.truetype("resources/fonts/Inconsolata.otf", 20)
self.draw = ImageDraw.Draw(self.image)
self.x = page_margin
self.y = page_margin
@@ -69,8 +68,9 @@ class KneeboardPageWriter:
def position(self) -> Tuple[int, int]:
return self.x, self.y
def text(self, text: str, font=None,
fill: Tuple[int, int, int] = (0, 0, 0)) -> None:
def text(
self, text: str, font=None, fill: Tuple[int, int, int] = (0, 0, 0)
) -> None:
if font is None:
font = self.content_font
@@ -84,8 +84,9 @@ class KneeboardPageWriter:
def heading(self, text: str) -> None:
self.text(text, font=self.heading_font)
def table(self, cells: List[List[str]],
headers: Optional[List[str]] = None) -> None:
def table(
self, cells: List[List[str]], headers: Optional[List[str]] = None
) -> None:
if headers is None:
headers = []
table = tabulate(cells, headers=headers, numalign="right")
@@ -157,29 +158,34 @@ class FlightPlanBuilder:
first_waypoint_num = self.target_points[0].number
last_waypoint_num = self.target_points[-1].number
self.rows.append([
f"{first_waypoint_num}-{last_waypoint_num}",
"Target points",
"0",
self._waypoint_distance(self.target_points[0].waypoint),
self._ground_speed(self.target_points[0].waypoint),
self._format_time(self.target_points[0].waypoint.tot),
self._format_time(self.target_points[0].waypoint.departure_time),
])
self.rows.append(
[
f"{first_waypoint_num}-{last_waypoint_num}",
"Target points",
"0",
self._waypoint_distance(self.target_points[0].waypoint),
self._ground_speed(self.target_points[0].waypoint),
self._format_time(self.target_points[0].waypoint.tot),
self._format_time(self.target_points[0].waypoint.departure_time),
]
)
self.last_waypoint = self.target_points[-1].waypoint
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
self.rows.append([
str(waypoint.number),
KneeboardPageWriter.wrap_line(
waypoint.waypoint.pretty_name,
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN),
str(int(waypoint.waypoint.alt.feet)),
self._waypoint_distance(waypoint.waypoint),
self._ground_speed(waypoint.waypoint),
self._format_time(waypoint.waypoint.tot),
self._format_time(waypoint.waypoint.departure_time),
])
self.rows.append(
[
str(waypoint.number),
KneeboardPageWriter.wrap_line(
waypoint.waypoint.pretty_name,
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN,
),
str(int(waypoint.waypoint.alt.feet)),
self._waypoint_distance(waypoint.waypoint),
self._ground_speed(waypoint.waypoint),
self._format_time(waypoint.waypoint.tot),
self._format_time(waypoint.waypoint.departure_time),
]
)
def _format_time(self, time: Optional[datetime.timedelta]) -> str:
if time is None:
@@ -191,9 +197,9 @@ class FlightPlanBuilder:
if self.last_waypoint is None:
return "-"
distance = meters(self.last_waypoint.position.distance_to_point(
waypoint.position
))
distance = meters(
self.last_waypoint.position.distance_to_point(waypoint.position)
)
return f"{distance.nautical_miles:.1f} NM"
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
@@ -210,9 +216,9 @@ class FlightPlanBuilder:
else:
return "-"
distance = meters(self.last_waypoint.position.distance_to_point(
waypoint.position
))
distance = meters(
self.last_waypoint.position.distance_to_point(waypoint.position)
)
duration = (waypoint.tot - last_time).total_seconds() / 3600
return f"{int(distance.nautical_miles / duration)} kt"
@@ -222,9 +228,16 @@ class FlightPlanBuilder:
class BriefingPage(KneeboardPage):
"""A kneeboard page containing briefing information."""
def __init__(self, flight: FlightData, comms: List[CommInfo],
awacs: List[AwacsInfo], tankers: List[TankerInfo],
jtacs: List[JtacInfo], start_time: datetime.datetime) -> None:
def __init__(
self,
flight: FlightData,
comms: List[CommInfo],
awacs: List[AwacsInfo],
tankers: List[TankerInfo],
jtacs: List[JtacInfo],
start_time: datetime.datetime,
) -> None:
self.flight = flight
self.comms = list(comms)
self.awacs = awacs
@@ -239,49 +252,59 @@ class BriefingPage(KneeboardPage):
# TODO: Handle carriers.
writer.heading("Airfield Info")
writer.table([
self.airfield_info_row("Departure", self.flight.departure),
self.airfield_info_row("Arrival", self.flight.arrival),
self.airfield_info_row("Divert", self.flight.divert),
], headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"])
writer.table(
[
self.airfield_info_row("Departure", self.flight.departure),
self.airfield_info_row("Arrival", self.flight.arrival),
self.airfield_info_row("Divert", self.flight.divert),
],
headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"],
)
writer.heading("Flight Plan")
flight_plan_builder = FlightPlanBuilder(self.start_time)
for num, waypoint in enumerate(self.flight.waypoints):
flight_plan_builder.add_waypoint(num, waypoint)
writer.table(flight_plan_builder.build(), headers=[
"#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"
])
writer.table(
flight_plan_builder.build(),
headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"],
)
flight_plan_builder
writer.table([
["{}lbs".format(self.flight.bingo_fuel), "{}lbs".format(self.flight.joker_fuel)]
], ['Bingo', 'Joker'])
writer.table(
[
[
"{}lbs".format(self.flight.bingo_fuel),
"{}lbs".format(self.flight.joker_fuel),
]
],
["Bingo", "Joker"],
)
# Package Section
writer.heading("Comm ladder")
comm_ladder = []
for comm in self.comms:
comm_ladder.append([comm.name, '', '', '', self.format_frequency(comm.freq)])
comm_ladder.append(
[comm.name, "", "", "", self.format_frequency(comm.freq)]
)
for a in self.awacs:
comm_ladder.append([
a.callsign,
'AWACS',
'',
'',
self.format_frequency(a.freq)
])
comm_ladder.append(
[a.callsign, "AWACS", "", "", self.format_frequency(a.freq)]
)
for tanker in self.tankers:
comm_ladder.append([
tanker.callsign,
"Tanker",
tanker.variant,
str(tanker.tacan),
self.format_frequency(tanker.freq),
])
writer.table(comm_ladder, headers=["Callsign","Task", "Type", "TACAN", "FREQ"])
comm_ladder.append(
[
tanker.callsign,
"Tanker",
tanker.variant,
str(tanker.tacan),
self.format_frequency(tanker.freq),
]
)
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "TACAN", "FREQ"])
writer.heading("JTAC")
jtacs = []
@@ -291,8 +314,9 @@ class BriefingPage(KneeboardPage):
writer.write(path)
def airfield_info_row(self, row_title: str,
runway: Optional[RunwayData]) -> List[str]:
def airfield_info_row(
self, row_title: str, runway: Optional[RunwayData]
) -> List[str]:
"""Creates a table row for a given airfield.
Args:
@@ -372,7 +396,8 @@ class KneeboardGenerator(MissionInfoGenerator):
if not flight.client_units:
continue
all_flights[flight.aircraft_type].extend(
self.generate_flight_kneeboard(flight))
self.generate_flight_kneeboard(flight)
)
return all_flights
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
@@ -384,6 +409,6 @@ class KneeboardGenerator(MissionInfoGenerator):
self.awacs,
self.tankers,
self.jtacs,
self.mission.start_time
self.mission.start_time,
),
]

View File

@@ -9,9 +9,10 @@ from gen.locations.preset_locations import PresetLocation
class MizDataLocationFinder:
@staticmethod
def compute_possible_locations(terrain_name: str, cp_name: str) -> PresetControlPointLocations:
def compute_possible_locations(
terrain_name: str, cp_name: str
) -> PresetControlPointLocations:
"""
Extract the list of preset locations from miz data
:param terrain_name: Terrain/Map name
@@ -32,28 +33,54 @@ class MizDataLocationFinder:
for vehicle_group in m.country("USA").vehicle_group:
if len(vehicle_group.units) > 0:
ashore_locations.append(PresetLocation(vehicle_group.position,
vehicle_group.units[0].heading,
vehicle_group.name))
ashore_locations.append(
PresetLocation(
vehicle_group.position,
vehicle_group.units[0].heading,
vehicle_group.name,
)
)
for ship_group in m.country("USA").ship_group:
if len(ship_group.units) > 0 and ship_group.units[0].type == ships.Oliver_Hazzard_Perry_class.id:
offshore_locations.append(PresetLocation(ship_group.position,
ship_group.units[0].heading,
ship_group.name))
if (
len(ship_group.units) > 0
and ship_group.units[0].type == ships.Oliver_Hazzard_Perry_class.id
):
offshore_locations.append(
PresetLocation(
ship_group.position,
ship_group.units[0].heading,
ship_group.name,
)
)
for static_group in m.country("USA").static_group:
if len(static_group.units) > 0:
powerplants_locations.append(PresetLocation(static_group.position,
static_group.units[0].heading,
static_group.name))
powerplants_locations.append(
PresetLocation(
static_group.position,
static_group.units[0].heading,
static_group.name,
)
)
if m.country("Iran") is not None:
for vehicle_group in m.country("Iran").vehicle_group:
if len(vehicle_group.units) > 0 and vehicle_group.units[0].type == MissilesSS.SS_N_2_Silkworm.id:
antiship_locations.append(PresetLocation(vehicle_group.position,
vehicle_group.units[0].heading,
vehicle_group.name))
if (
len(vehicle_group.units) > 0
and vehicle_group.units[0].type == MissilesSS.SS_N_2_Silkworm.id
):
antiship_locations.append(
PresetLocation(
vehicle_group.position,
vehicle_group.units[0].heading,
vehicle_group.name,
)
)
return PresetControlPointLocations(ashore_locations, offshore_locations,
antiship_locations, powerplants_locations)
return PresetControlPointLocations(
ashore_locations,
offshore_locations,
antiship_locations,
powerplants_locations,
)

View File

@@ -6,10 +6,16 @@ from dcs import Point
@dataclass
class PresetLocation:
"""A preset location"""
position: Point
heading: int
id: str
def __str__(self):
return "-" * 10 + "X: {}\n Y: {}\nHdg: {}°\nId: {}".format(self.position.x, self.position.y, self.heading,
self.id) + "-" * 10
return (
"-" * 10
+ "X: {}\n Y: {}\nHdg: {}°\nId: {}".format(
self.position.x, self.position.y, self.heading, self.id
)
+ "-" * 10
)

View File

@@ -4,10 +4,7 @@ from game import db
from gen.missiles.scud_site import ScudGenerator
from gen.missiles.v1_group import V1GroupGenerator
MISSILES_MAP = {
"V1GroupGenerator": V1GroupGenerator,
"ScudGenerator": ScudGenerator
}
MISSILES_MAP = {"V1GroupGenerator": V1GroupGenerator, "ScudGenerator": ScudGenerator}
def generate_missile_group(game, ground_object, faction_name: str):
@@ -25,5 +22,9 @@ def generate_missile_group(game, ground_object, faction_name: str):
generator.generate()
return generator.get_generated_group()
else:
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
return None
logging.info(
"Unable to generate missile group, generator : "
+ str(gen)
+ "does not exists"
)
return None

View File

@@ -6,7 +6,6 @@ from gen.sam.group_generator import GroupGenerator
class ScudGenerator(GroupGenerator):
def __init__(self, game, ground_object, faction):
super(ScudGenerator, self).__init__(game, ground_object)
self.faction = faction
@@ -14,17 +13,50 @@ class ScudGenerator(GroupGenerator):
def generate(self):
# Scuds
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#0", self.position.x, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#1", self.position.x + 50, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#2", self.position.x + 100, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
"V1#0",
self.position.x,
self.position.y + random.randint(1, 8),
self.heading,
)
self.add_unit(
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
"V1#1",
self.position.x + 50,
self.position.y + random.randint(1, 8),
self.heading,
)
self.add_unit(
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
"V1#2",
self.position.x + 100,
self.position.y + random.randint(1, 8),
self.heading,
)
# Commander
self.add_unit(Unarmed.Transport_UAZ_469, "Kubel#0", self.position.x - 35, self.position.y - 20,
self.heading)
self.add_unit(
Unarmed.Transport_UAZ_469,
"Kubel#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)
self.add_unit(
AirDefence.SPAAA_ZSU_23_4_Shilka,
"SHILKA#0",
self.position.x - 55,
self.position.y - 38,
self.heading,
)
self.add_unit(AirDefence.SAM_SA_9_Strela_1_9P31, "STRELA#0",
self.position.x + 200, self.position.y + 15, 90)
self.add_unit(
AirDefence.SAM_SA_9_Strela_1_9P31,
"STRELA#0",
self.position.x + 200,
self.position.y + 15,
90,
)

View File

@@ -6,7 +6,6 @@ from gen.sam.group_generator import GroupGenerator
class V1GroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, faction):
super(V1GroupGenerator, self).__init__(game, ground_object)
self.faction = faction
@@ -14,19 +13,54 @@ class V1GroupGenerator(GroupGenerator):
def generate(self):
# Ramps
self.add_unit(MissilesSS.V_1_ramp, "V1#0", self.position.x, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(MissilesSS.V_1_ramp, "V1#1", self.position.x + 50, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(MissilesSS.V_1_ramp, "V1#2", self.position.x + 100, self.position.y + random.randint(1, 8), self.heading)
self.add_unit(
MissilesSS.V_1_ramp,
"V1#0",
self.position.x,
self.position.y + random.randint(1, 8),
self.heading,
)
self.add_unit(
MissilesSS.V_1_ramp,
"V1#1",
self.position.x + 50,
self.position.y + random.randint(1, 8),
self.heading,
)
self.add_unit(
MissilesSS.V_1_ramp,
"V1#2",
self.position.x + 100,
self.position.y + random.randint(1, 8),
self.heading,
)
# Commander
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#0", self.position.x - 35, self.position.y - 20,
self.heading)
self.add_unit(
Unarmed.Kübelwagen_82,
"Kubel#0",
self.position.x - 35,
self.position.y - 20,
self.heading,
)
# Self defense flak
flak_unit = random.choice([AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_38])
flak_unit = random.choice(
[AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_38]
)
self.add_unit(flak_unit, "FLAK#0", self.position.x - 55, self.position.y - 38,
self.heading)
self.add_unit(
flak_unit,
"FLAK#0",
self.position.x - 55,
self.position.y - 38,
self.heading,
)
self.add_unit(Unarmed.Blitz_3_6_6700A, "Blitz#0",
self.position.x + 200, self.position.y + 15, 90)
self.add_unit(
Unarmed.Blitz_3_6_6700A,
"Blitz#0",
self.position.x + 200,
self.position.y + 15,
90,
)

View File

@@ -9,39 +9,242 @@ from game import db
from gen.flights.flight import Flight
ALPHA_MILITARY = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot",
"Golf", "Hotel", "India", "Juliet", "Kilo", "Lima", "Mike",
"November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra",
"Tango", "Uniform", "Victor", "Whisky", "XRay", "Yankee",
"Zulu", "Zero"]
ALPHA_MILITARY = [
"Alpha",
"Bravo",
"Charlie",
"Delta",
"Echo",
"Foxtrot",
"Golf",
"Hotel",
"India",
"Juliet",
"Kilo",
"Lima",
"Mike",
"November",
"Oscar",
"Papa",
"Quebec",
"Romeo",
"Sierra",
"Tango",
"Uniform",
"Victor",
"Whisky",
"XRay",
"Yankee",
"Zulu",
"Zero",
]
ANIMALS = [
"SHARK", "TORTOISE", "BAT", "PANGOLIN", "AARDWOLF",
"MONKEY", "BUFFALO", "DOG", "BOBCAT", "LYNX", "PANTHER", "TIGER",
"LION", "OWL", "BUTTERFLY", "BISON", "DUCK", "COBRA", "MAMBA",
"DOLPHIN", "PHEASANT", "ARMADILLLO", "RACOON", "ZEBRA", "COW", "COYOTE", "FOX",
"LIGHTFOOT", "COTTONMOUTH", "TAURUS", "VIPER", "CASTOR", "GIRAFFE", "SNAKE",
"MONSTER", "ALBATROSS", "HAWK", "DOVE", "MOCKINGBIRD", "GECKO", "ORYX", "GORILLA",
"HARAMBE", "GOOSE", "MAVERICK", "HARE", "JACKAL", "LEOPARD", "CAT", "MUSK", "ORCA",
"OCELOT", "BEAR", "PANDA", "GULL", "PENGUIN", "PYTHON", "RAVEN", "DEER", "MOOSE",
"REINDEER", "SHEEP", "GAZELLE", "INSECT", "VULTURE", "WALLABY", "KANGAROO", "KOALA",
"KIWI", "WHALE", "FISH", "RHINO", "HIPPO", "RAT", "WOODPECKER", "WORM", "BABOON",
"YAK", "SCORPIO", "HORSE", "POODLE", "CENTIPEDE", "CHICKEN", "CHEETAH", "CHAMELEON",
"CATFISH", "CATERPILLAR", "CARACAL", "CAMEL", "CAIMAN", "BARRACUDA", "BANDICOOT",
"ALLIGATOR", "BONGO", "CORAL", "ELEPHANT", "ANTELOPE", "CRAB", "DACHSHUND", "DODO",
"FLAMINGO", "FERRET", "FALCON", "BULLDOG", "DONKEY", "IGUANA", "TAMARIN", "HARRIER",
"GRIZZLY", "GREYHOUND", "GRASSHOPPER", "JAGUAR", "LADYBUG", "KOMODO", "DRAGON", "LIZARD",
"LLAMA", "LOBSTER", "OCTOPUS", "MANATEE", "MAGPIE", "MACAW", "OSTRICH", "OYSTER",
"MOLE", "MULE", "MOTH", "MONGOOSE", "MOLLY", "MEERKAT", "MOUSE", "PEACOCK", "PIKE", "ROBIN",
"RAGDOLL", "PLATYPUS", "PELICAN", "PARROT", "PORCUPINE", "PIRANHA", "PUMA", "PUG", "TAPIR",
"TERMITE", "URCHIN", "SHRIMP", "TURKEY", "TOUCAN", "TETRA", "HUSKY", "STARFISH", "SWAN",
"FROG", "SQUIRREL", "WALRUS", "WARTHOG", "CORGI", "WEASEL", "WOMBAT", "WOLVERINE", "MAMMOTH",
"TOAD", "WOLF", "ZEBU", "SEAL", "SKATE", "JELLYFISH", "MOSQUITO", "LOCUST", "SLUG", "SNAIL",
"HEDGEHOG", "PIGLET", "FENNEC", "BADGER", "ALPACA", "DINGO", "COLT", "SKUNK", "BUNNY", "IMPALA",
"GUANACO", "CAPYBARA", "ELK", "MINK", "PRONGHORN", "CROW", "BUMBLEBEE", "FAWN", "OTTER", "WATERBUCK",
"JERBOA", "KITTEN", "ARGALI", "OX", "MARE", "FINCH", "BASILISK", "GOPHER", "HAMSTER", "CANARY", "WOODCHUCK",
"ANACONDA"
]
"SHARK",
"TORTOISE",
"BAT",
"PANGOLIN",
"AARDWOLF",
"MONKEY",
"BUFFALO",
"DOG",
"BOBCAT",
"LYNX",
"PANTHER",
"TIGER",
"LION",
"OWL",
"BUTTERFLY",
"BISON",
"DUCK",
"COBRA",
"MAMBA",
"DOLPHIN",
"PHEASANT",
"ARMADILLLO",
"RACOON",
"ZEBRA",
"COW",
"COYOTE",
"FOX",
"LIGHTFOOT",
"COTTONMOUTH",
"TAURUS",
"VIPER",
"CASTOR",
"GIRAFFE",
"SNAKE",
"MONSTER",
"ALBATROSS",
"HAWK",
"DOVE",
"MOCKINGBIRD",
"GECKO",
"ORYX",
"GORILLA",
"HARAMBE",
"GOOSE",
"MAVERICK",
"HARE",
"JACKAL",
"LEOPARD",
"CAT",
"MUSK",
"ORCA",
"OCELOT",
"BEAR",
"PANDA",
"GULL",
"PENGUIN",
"PYTHON",
"RAVEN",
"DEER",
"MOOSE",
"REINDEER",
"SHEEP",
"GAZELLE",
"INSECT",
"VULTURE",
"WALLABY",
"KANGAROO",
"KOALA",
"KIWI",
"WHALE",
"FISH",
"RHINO",
"HIPPO",
"RAT",
"WOODPECKER",
"WORM",
"BABOON",
"YAK",
"SCORPIO",
"HORSE",
"POODLE",
"CENTIPEDE",
"CHICKEN",
"CHEETAH",
"CHAMELEON",
"CATFISH",
"CATERPILLAR",
"CARACAL",
"CAMEL",
"CAIMAN",
"BARRACUDA",
"BANDICOOT",
"ALLIGATOR",
"BONGO",
"CORAL",
"ELEPHANT",
"ANTELOPE",
"CRAB",
"DACHSHUND",
"DODO",
"FLAMINGO",
"FERRET",
"FALCON",
"BULLDOG",
"DONKEY",
"IGUANA",
"TAMARIN",
"HARRIER",
"GRIZZLY",
"GREYHOUND",
"GRASSHOPPER",
"JAGUAR",
"LADYBUG",
"KOMODO",
"DRAGON",
"LIZARD",
"LLAMA",
"LOBSTER",
"OCTOPUS",
"MANATEE",
"MAGPIE",
"MACAW",
"OSTRICH",
"OYSTER",
"MOLE",
"MULE",
"MOTH",
"MONGOOSE",
"MOLLY",
"MEERKAT",
"MOUSE",
"PEACOCK",
"PIKE",
"ROBIN",
"RAGDOLL",
"PLATYPUS",
"PELICAN",
"PARROT",
"PORCUPINE",
"PIRANHA",
"PUMA",
"PUG",
"TAPIR",
"TERMITE",
"URCHIN",
"SHRIMP",
"TURKEY",
"TOUCAN",
"TETRA",
"HUSKY",
"STARFISH",
"SWAN",
"FROG",
"SQUIRREL",
"WALRUS",
"WARTHOG",
"CORGI",
"WEASEL",
"WOMBAT",
"WOLVERINE",
"MAMMOTH",
"TOAD",
"WOLF",
"ZEBU",
"SEAL",
"SKATE",
"JELLYFISH",
"MOSQUITO",
"LOCUST",
"SLUG",
"SNAIL",
"HEDGEHOG",
"PIGLET",
"FENNEC",
"BADGER",
"ALPACA",
"DINGO",
"COLT",
"SKUNK",
"BUNNY",
"IMPALA",
"GUANACO",
"CAPYBARA",
"ELK",
"MINK",
"PRONGHORN",
"CROW",
"BUMBLEBEE",
"FAWN",
"OTTER",
"WATERBUCK",
"JERBOA",
"KITTEN",
"ARGALI",
"OX",
"MARE",
"FINCH",
"BASILISK",
"GOPHER",
"HAMSTER",
"CANARY",
"WOODCHUCK",
"ANACONDA",
]
class NameGenerator:
number = 0
@@ -72,21 +275,36 @@ class NameGenerator:
name_str = flight.custom_name
else:
name_str = "{} {}".format(
flight.package.target.name, flight.flight_type)
flight.package.target.name, flight.flight_type
)
except AttributeError: # Here to maintain save compatibility with 2.3
name_str = "{} {}".format(
flight.package.target.name, flight.flight_type)
return "{}|{}|{}|{}|{}|".format(name_str, country.id, cls.aircraft_number, parent_base_id, db.unit_type_name(flight.unit_type))
name_str = "{} {}".format(flight.package.target.name, flight.flight_type)
return "{}|{}|{}|{}|{}|".format(
name_str,
country.id,
cls.aircraft_number,
parent_base_id,
db.unit_type_name(flight.unit_type),
)
@classmethod
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
cls.number += 1
return "unit|{}|{}|{}|{}|".format(country.id, cls.number, parent_base_id, db.unit_type_name(unit_type))
return "unit|{}|{}|{}|{}|".format(
country.id, cls.number, parent_base_id, db.unit_type_name(unit_type)
)
@classmethod
def next_infantry_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
def next_infantry_name(
cls, country: Country, parent_base_id: int, unit_type: UnitType
):
cls.infantry_number += 1
return "infantry|{}|{}|{}|{}|".format(country.id, cls.infantry_number, parent_base_id, db.unit_type_name(unit_type))
return "infantry|{}|{}|{}|{}|".format(
country.id,
cls.infantry_number,
parent_base_id,
db.unit_type_name(unit_type),
)
@staticmethod
def next_basedefense_name():
@@ -100,7 +318,9 @@ class NameGenerator:
@classmethod
def next_tanker_name(cls, country: Country, unit_type: UnitType):
cls.number += 1
return "tanker|{}|{}|0|{}".format(country.id, cls.number, db.unit_type_name(unit_type))
return "tanker|{}|{}|0|{}".format(
country.id, cls.number, db.unit_type_name(unit_type)
)
@classmethod
def next_carrier_name(cls, country: Country):
@@ -112,7 +332,11 @@ class NameGenerator:
if len(cls.ANIMALS) == 0:
for i in range(10):
new_name_generated = True
alpha_mil_name = random.choice(ALPHA_MILITARY).upper() + "#" + str(random.randint(0, 100))
alpha_mil_name = (
random.choice(ALPHA_MILITARY).upper()
+ "#"
+ str(random.randint(0, 100))
)
for existing_name in cls.existing_alphas:
if existing_name == alpha_mil_name:
new_name_generated = False

View File

@@ -68,9 +68,10 @@ class Radio:
def range(self) -> Iterator[RadioFrequency]:
"""Returns an iterator over the usable frequencies of this radio."""
return (RadioFrequency(x) for x in range(
self.minimum.hertz, self.maximum.hertz, self.step.hertz
))
return (
RadioFrequency(x)
for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz)
)
@property
def last_channel(self) -> RadioFrequency:
@@ -99,14 +100,12 @@ RADIOS: List[Radio] = [
Radio("SCR-522", MHz(100), MHz(156), step=MHz(1)),
Radio("A.R.I. 1063", MHz(100), MHz(156), step=MHz(1)),
Radio("BC-1206", kHz(200), kHz(400), step=kHz(10)),
# Note: The M2000C V/UHF can operate in both ranges, but has a gap between
# 150 MHz and 225 MHz. We can't allocate in that gap, and the current
# system doesn't model gaps, so just pretend it ends at 150 MHz for now. We
# can model gaps later if needed.
Radio("TRT ERA 7000 V/UHF", MHz(118), MHz(150), step=MHz(1)),
Radio("TRT ERA 7200 UHF", MHz(225), MHz(400), step=MHz(1)),
# Tomcat radios
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
Radio("AN/ARC-159", MHz(225), MHz(400), step=MHz(1)),
@@ -114,31 +113,23 @@ RADIOS: List[Radio] = [
# to 400 MHz range, but we can't model gaps with the current implementation.
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
Radio("AN/ARC-182", MHz(108), MHz(174), step=MHz(1)),
# Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps.
Radio("FR 22", MHz(225), MHz(400), step=kHz(50)),
# P-51 / P-47 Radio
# 4 preset channels (A/B/C/D)
Radio("SCR522", MHz(100), MHz(156), step=kHz(25)),
Radio("R&S M3AR VHF", MHz(120), MHz(174), step=MHz(1)),
Radio("R&S M3AR UHF", MHz(225), MHz(400), step=MHz(1)),
# MiG-15bis
Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)),
# MiG-19P
Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)),
# MiG-21bis
Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)),
# Ka-50
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)),
Radio("R-828", MHz(20), MHz(60), step=kHz(25)),
# UH-1H
Radio("AN/ARC-51BX", MHz(225), MHz(400), step=kHz(50)),
Radio("AN/ARC-131", MHz(30), MHz(76), step=kHz(50)),
@@ -218,7 +209,8 @@ class RadioRegistry:
# https://github.com/Khopa/dcs_liberation/issues/598
channel = radio.last_channel
logging.warning(
f"No more free channels for {radio.name}. Reusing {channel}.")
f"No more free channels for {radio.name}. Reusing {channel}."
)
return channel
def alloc_uhf(self) -> RadioFrequency:

View File

@@ -25,8 +25,9 @@ class RunwayData:
icls: Optional[int] = None
@classmethod
def for_airfield(cls, airport: Airport, runway_heading: int,
runway_name: str) -> RunwayData:
def for_airfield(
cls, airport: Airport, runway_heading: int, runway_name: str
) -> RunwayData:
"""Creates RunwayData for the given runway of an airfield.
Args:
@@ -56,7 +57,7 @@ class RunwayData:
atc=atc,
tacan=tacan,
tacan_callsign=tacan_callsign,
ils=ils
ils=ils,
)
@classmethod

View File

@@ -20,15 +20,19 @@ class BoforsGenerator(AirDefenseGroupGenerator):
grid_x = random.randint(2, 3)
grid_y = random.randint(2, 3)
spacing = random.randint(10,40)
spacing = random.randint(10, 40)
index = 0
for i in range(grid_x):
for j in range(grid_y):
index = index+1
self.add_unit(AirDefence.AAA_Bofors_40mm, "AAA#" + str(index),
self.position.x + spacing*i,
self.position.y + spacing*j, self.heading)
index = index + 1
self.add_unit(
AirDefence.AAA_Bofors_40mm,
"AAA#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -37,34 +37,64 @@ class FlakGenerator(AirDefenseGroupGenerator):
for i in range(grid_x):
for j in range(grid_y):
index = index+1
self.add_unit(unit_type, "AAA#" + str(index),
self.position.x + spacing*i + random.randint(1,5),
self.position.y + spacing*j + random.randint(1,5), self.heading)
index = index + 1
self.add_unit(
unit_type,
"AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5),
self.heading,
)
if(mixed):
if mixed:
unit_type = random.choice(GFLAK)
# Search lights
search_pos = self.get_circular_position(random.randint(2,3), 80)
search_pos = self.get_circular_position(random.randint(2, 3), 80)
for index, pos in enumerate(search_pos):
self.add_unit(AirDefence.Flak_Searchlight_37, "SearchLight#" + str(index), pos[0], pos[1], self.heading)
self.add_unit(
AirDefence.Flak_Searchlight_37,
"SearchLight#" + str(index),
pos[0],
pos[1],
self.heading,
)
# Support
self.add_unit(AirDefence.Maschinensatz_33, "MC33#", self.position.x-20, self.position.y-20, self.heading)
self.add_unit(AirDefence.AAA_Kdo_G_40, "KDO#", self.position.x - 25, self.position.y - 20,
self.heading)
self.add_unit(
AirDefence.Maschinensatz_33,
"MC33#",
self.position.x - 20,
self.position.y - 20,
self.heading,
)
self.add_unit(
AirDefence.AAA_Kdo_G_40,
"KDO#",
self.position.x - 25,
self.position.y - 20,
self.heading,
)
# Commander
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#", self.position.x - 35, self.position.y - 20,
self.heading)
self.add_unit(
Unarmed.Kübelwagen_82,
"Kubel#",
self.position.x - 35,
self.position.y - 20,
self.heading,
)
# Some Opel Blitz trucks
for i in range(int(max(1,grid_x/2))):
for j in range(int(max(1,grid_x/2))):
self.add_unit(Unarmed.Blitz_3_6_6700A, "BLITZ#" + str(index),
self.position.x + 125 + 15*i + random.randint(1,5),
self.position.y + 15*j + random.randint(1,5), 75)
for i in range(int(max(1, grid_x / 2))):
for j in range(int(max(1, grid_x / 2))):
self.add_unit(
Unarmed.Blitz_3_6_6700A,
"BLITZ#" + str(index),
self.position.x + 125 + 15 * i + random.randint(1, 5),
self.position.y + 15 * j + random.randint(1, 5),
75,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -24,12 +24,22 @@ class Flak18Generator(AirDefenseGroupGenerator):
for i in range(3):
for j in range(2):
index = index + 1
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5), self.heading)
self.add_unit(
AirDefence.AAA_8_8cm_Flak_18,
"AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5),
self.heading,
)
# Add a commander truck
self.add_unit(Unarmed.Blitz_3_6_6700A, "Blitz#", self.position.x - 35, self.position.y - 20, self.heading)
self.add_unit(
Unarmed.Blitz_3_6_6700A,
"Blitz#",
self.position.x - 35,
self.position.y - 20,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,15 +19,25 @@ class KS19Generator(AirDefenseGroupGenerator):
spacing = random.randint(10, 40)
self.add_unit(highdigitsams.AAA_SON_9_Fire_Can, "TR", self.position.x - 20, self.position.y - 20, self.heading)
self.add_unit(
highdigitsams.AAA_SON_9_Fire_Can,
"TR",
self.position.x - 20,
self.position.y - 20,
self.heading,
)
index = 0
for i in range(3):
for j in range(3):
index = index + 1
self.add_unit(highdigitsams.AAA_100mm_KS_19, "AAA#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j, self.heading)
self.add_unit(
highdigitsams.AAA_100mm_KS_19,
"AAA#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -20,21 +20,63 @@ class AllyWW2FlakGenerator(AirDefenseGroupGenerator):
positions = self.get_circular_position(4, launcher_distance=30, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AA_gun_QF_3_7, "AA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AA_gun_QF_3_7,
"AA#" + str(i),
position[0],
position[1],
position[2],
)
positions = self.get_circular_position(8, launcher_distance=60, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_M1_37mm, "AA#" + str(4 + i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_M1_37mm,
"AA#" + str(4 + i),
position[0],
position[1],
position[2],
)
positions = self.get_circular_position(8, launcher_distance=90, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_M45_Quadmount, "AA#" + str(12 + i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_M45_Quadmount,
"AA#" + str(12 + i),
position[0],
position[1],
position[2],
)
# Add a commander truck
self.add_unit(Unarmed.Willys_MB, "CMD#1", self.position.x, self.position.y - 20, random.randint(0, 360))
self.add_unit(Armor.M30_Cargo_Carrier, "LOG#1", self.position.x, self.position.y + 20, random.randint(0, 360))
self.add_unit(Armor.M4_Tractor, "LOG#2", self.position.x + 20, self.position.y, random.randint(0, 360))
self.add_unit(Unarmed.Bedford_MWD, "LOG#3", self.position.x - 20, self.position.y, random.randint(0, 360))
self.add_unit(
Unarmed.Willys_MB,
"CMD#1",
self.position.x,
self.position.y - 20,
random.randint(0, 360),
)
self.add_unit(
Armor.M30_Cargo_Carrier,
"LOG#1",
self.position.x,
self.position.y + 20,
random.randint(0, 360),
)
self.add_unit(
Armor.M4_Tractor,
"LOG#2",
self.position.x + 20,
self.position.y,
random.randint(0, 360),
)
self.add_unit(
Unarmed.Bedford_MWD,
"LOG#3",
self.position.x - 20,
self.position.y,
random.randint(0, 360),
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -16,9 +16,17 @@ class ZSU57Generator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = 5
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=110, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_ZSU_57_2, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_ZSU_57_2,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -20,15 +20,19 @@ class ZU23InsurgentGenerator(AirDefenseGroupGenerator):
grid_x = random.randint(2, 3)
grid_y = random.randint(2, 3)
spacing = random.randint(10,40)
spacing = random.randint(10, 40)
index = 0
for i in range(grid_x):
for j in range(grid_y):
index = index+1
self.add_unit(AirDefence.AAA_ZU_23_Insurgent_Closed, "AAA#" + str(index),
self.position.x + spacing*i,
self.position.y + spacing*j, self.heading)
index = index + 1
self.add_unit(
AirDefence.AAA_ZU_23_Insurgent_Closed,
"AAA#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -28,8 +28,9 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
self.auxiliary_groups: List[VehicleGroup] = []
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
group = VehicleGroup(self.game.next_group_id(),
"|".join([self.go.group_name, name_suffix]))
group = VehicleGroup(
self.game.next_group_id(), "|".join([self.go.group_name, name_suffix])
)
self.auxiliary_groups.append(group)
return group
@@ -37,7 +38,8 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
raise RuntimeError(
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
"instead.")
"instead."
)
@property
def groups(self) -> Iterator[VehicleGroup]:

View File

@@ -29,18 +29,38 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
for i in range(3):
for j in range(2):
index = index + 1
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5), self.heading)
self.add_unit(
AirDefence.AAA_8_8cm_Flak_18,
"AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5),
self.heading,
)
# Short range guns
self.add_unit(AirDefence.AAA_Bofors_40mm, "SHO#1",
self.position.x - 40, self.position.y - 40, self.heading + 180),
self.add_unit(AirDefence.AAA_Bofors_40mm, "SHO#2",
self.position.x + spacing * 2 + 40, self.position.y + spacing + 40, self.heading),
self.add_unit(
AirDefence.AAA_Bofors_40mm,
"SHO#1",
self.position.x - 40,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_Bofors_40mm,
"SHO#2",
self.position.x + spacing * 2 + 40,
self.position.y + spacing + 40,
self.heading,
),
# Add a truck
self.add_unit(Unarmed.Transport_KAMAZ_43101, "Truck#", self.position.x - 60, self.position.y - 20, self.heading)
self.add_unit(
Unarmed.Transport_KAMAZ_43101,
"Truck#",
self.position.x - 60,
self.position.y - 20,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:
@@ -66,18 +86,38 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
for i in range(3):
for j in range(2):
index = index + 1
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5), self.heading)
self.add_unit(
AirDefence.AAA_8_8cm_Flak_18,
"AAA#" + str(index),
self.position.x + spacing * i + random.randint(1, 5),
self.position.y + spacing * j + random.randint(1, 5),
self.heading,
)
# Short range guns
self.add_unit(AirDefence.AAA_ZU_23_Closed, "SHO#1",
self.position.x - 40, self.position.y - 40, self.heading + 180),
self.add_unit(AirDefence.AAA_ZU_23_Closed, "SHO#2",
self.position.x + spacing * 2 + 40, self.position.y + spacing + 40, self.heading),
self.add_unit(
AirDefence.AAA_ZU_23_Closed,
"SHO#1",
self.position.x - 40,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_ZU_23_Closed,
"SHO#2",
self.position.x + spacing * 2 + 40,
self.position.y + spacing + 40,
self.heading,
),
# Add a P19 Radar for EWR
self.add_unit(AirDefence.SAM_SR_P_19, "SR#0", self.position.x - 60, self.position.y - 20, self.heading)
self.add_unit(
AirDefence.SAM_SR_P_19,
"SR#0",
self.position.x - 60,
self.position.y - 20,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -10,8 +10,9 @@ class EwrGenerator(GroupGenerator):
raise NotImplementedError
def generate(self) -> None:
self.add_unit(self.unit_type, "EWR", self.position.x, self.position.y,
self.heading)
self.add_unit(
self.unit_type, "EWR", self.position.x, self.position.y, self.heading
)
class BoxSpringGenerator(EwrGenerator):

View File

@@ -17,27 +17,93 @@ class FreyaGenerator(AirDefenseGroupGenerator):
def generate(self):
# TODO : would be better with the Concrete structure that is supposed to protect it
self.add_unit(AirDefence.EWR_FuMG_401_Freya_LZ, "EWR#1", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.EWR_FuMG_401_Freya_LZ,
"EWR#1",
self.position.x,
self.position.y,
self.heading,
)
positions = self.get_circular_position(4, launcher_distance=50, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_Flak_Vierling_38, "AA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_Flak_Vierling_38,
"AA#" + str(i),
position[0],
position[1],
position[2],
)
positions = self.get_circular_position(4, launcher_distance=100, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AA#" + str(4+i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_8_8cm_Flak_18,
"AA#" + str(4 + i),
position[0],
position[1],
position[2],
)
# Command/Logi
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#1", self.position.x - 20, self.position.y - 20, self.heading)
self.add_unit(Unarmed.Sd_Kfz_7, "Sdkfz#1", self.position.x + 20, self.position.y + 22, self.heading)
self.add_unit(Unarmed.Sd_Kfz_2, "Sdkfz#2", self.position.x - 22, self.position.y + 20, self.heading)
self.add_unit(
Unarmed.Kübelwagen_82,
"Kubel#1",
self.position.x - 20,
self.position.y - 20,
self.heading,
)
self.add_unit(
Unarmed.Sd_Kfz_7,
"Sdkfz#1",
self.position.x + 20,
self.position.y + 22,
self.heading,
)
self.add_unit(
Unarmed.Sd_Kfz_2,
"Sdkfz#2",
self.position.x - 22,
self.position.y + 20,
self.heading,
)
# Maschinensatz_33 and Kdo.g 40 Telemeter
self.add_unit(AirDefence.Maschinensatz_33, "Energy#1", self.position.x + 20, self.position.y - 20, self.heading)
self.add_unit(AirDefence.AAA_Kdo_G_40, "Telemeter#1", self.position.x + 20, self.position.y - 10, self.heading)
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#1", self.position.x + 20, self.position.y - 14, self.heading)
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#2", self.position.x + 20, self.position.y - 22, self.heading)
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#3", self.position.x + 20, self.position.y - 24, self.heading + 45)
self.add_unit(
AirDefence.Maschinensatz_33,
"Energy#1",
self.position.x + 20,
self.position.y - 20,
self.heading,
)
self.add_unit(
AirDefence.AAA_Kdo_G_40,
"Telemeter#1",
self.position.x + 20,
self.position.y - 10,
self.heading,
)
self.add_unit(
Infantry.Infantry_Mauser_98,
"Inf#1",
self.position.x + 20,
self.position.y - 14,
self.heading,
)
self.add_unit(
Infantry.Infantry_Mauser_98,
"Inf#2",
self.position.x + 20,
self.position.y - 22,
self.heading,
)
self.add_unit(
Infantry.Infantry_Mauser_98,
"Inf#3",
self.position.x + 20,
self.position.y - 24,
self.heading + 45,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -23,14 +23,12 @@ if TYPE_CHECKING:
# care about in the format we want if we just generate our own group description
# types rather than pydcs groups.
class GroupGenerator:
def __init__(self, game: Game, ground_object: TheaterGroundObject) -> None:
self.game = game
self.go = ground_object
self.position = ground_object.position
self.heading = random.randint(0, 359)
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(),
self.go.group_name)
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), self.go.group_name)
wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0)
wp.ETA_locked = True
@@ -40,16 +38,27 @@ class GroupGenerator:
def get_generated_group(self) -> unitgroup.VehicleGroup:
return self.vg
def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float,
pos_y: float, heading: int) -> Vehicle:
return self.add_unit_to_group(self.vg, unit_type, name,
Point(pos_x, pos_y), heading)
def add_unit(
self,
unit_type: Type[VehicleType],
name: str,
pos_x: float,
pos_y: float,
heading: int,
) -> Vehicle:
return self.add_unit_to_group(
self.vg, unit_type, name, Point(pos_x, pos_y), heading
)
def add_unit_to_group(self, group: unitgroup.VehicleGroup,
unit_type: Type[VehicleType], name: str,
position: Point, heading: int) -> Vehicle:
unit = Vehicle(self.game.next_unit_id(),
f"{group.name}|{name}", unit_type.id)
def add_unit_to_group(
self,
group: unitgroup.VehicleGroup,
unit_type: Type[VehicleType],
name: str,
position: Point,
heading: int,
) -> Vehicle:
unit = Vehicle(self.game.next_unit_id(), f"{group.name}|{name}", unit_type.id)
unit.position = position
unit.heading = heading
group.add_unit(unit)
@@ -82,31 +91,36 @@ class GroupGenerator:
current_offset = self.heading
current_offset -= outer_offset * (math.ceil(num_units / 2) - 1)
for x in range(1, num_units + 1):
positions.append((
self.position.x + launcher_distance * math.cos(math.radians(current_offset)),
self.position.y + launcher_distance * math.sin(math.radians(current_offset)),
current_offset,
))
positions.append(
(
self.position.x
+ launcher_distance * math.cos(math.radians(current_offset)),
self.position.y
+ launcher_distance * math.sin(math.radians(current_offset)),
current_offset,
)
)
current_offset += outer_offset
return positions
class ShipGroupGenerator(GroupGenerator):
"""Abstract class for other ship generator classes"""
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
def __init__(
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
):
self.game = game
self.go = ground_object
self.position = ground_object.position
self.heading = random.randint(0, 359)
self.faction = faction
self.vg = unitgroup.ShipGroup(self.game.next_group_id(),
self.go.group_name)
self.vg = unitgroup.ShipGroup(self.game.next_group_id(), self.go.group_name)
wp = self.vg.add_waypoint(self.position, 0)
wp.ETA_locked = True
def add_unit(self, unit_type, name, pos_x, pos_y, heading) -> Ship:
unit = Ship(self.game.next_unit_id(),
f"{self.go.group_name}|{name}", unit_type)
unit = Ship(self.game.next_unit_id(), f"{self.go.group_name}|{name}", unit_type)
unit.position.x = pos_x
unit.position.y = pos_y
unit.heading = heading

View File

@@ -19,10 +19,24 @@ class AvengerGenerator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(2, 3)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x,
self.position.y,
self.heading,
)
positions = self.get_circular_position(
num_launchers, launcher_distance=110, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_Avenger_M1097, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_Avenger_M1097,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,10 +19,24 @@ class ChaparralGenerator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(2, 4)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x,
self.position.y,
self.heading,
)
positions = self.get_circular_position(
num_launchers, launcher_distance=110, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_Chaparral_M48, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_Chaparral_M48,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,10 +17,28 @@ class GepardGenerator(AirDefenseGroupGenerator):
price = 50
def generate(self):
self.add_unit(AirDefence.SPAAA_Gepard, "SPAAA", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.SPAAA_Gepard,
"SPAAA",
self.position.x,
self.position.y,
self.heading,
)
if random.randint(0, 1) == 1:
self.add_unit(AirDefence.SPAAA_Gepard, "SPAAA2", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading)
self.add_unit(
AirDefence.SPAAA_Gepard,
"SPAAA2",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x + 80,
self.position.y,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -49,7 +49,12 @@ from gen.sam.sam_roland import RolandGenerator
from gen.sam.sam_sa10 import (
SA10Generator,
Tier2SA10Generator,
Tier3SA10Generator, SA10BGenerator, SA12Generator, SA20Generator, SA20BGenerator, SA23Generator,
Tier3SA10Generator,
SA10BGenerator,
SA12Generator,
SA20Generator,
SA20BGenerator,
SA23Generator,
)
from gen.sam.sam_sa11 import SA11Generator
from gen.sam.sam_sa13 import SA13Generator
@@ -103,7 +108,6 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
"FreyaGenerator": FreyaGenerator,
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
"ZSU57Generator": ZSU57Generator,
"KS19Generator": KS19Generator,
"SA10BGenerator": SA10BGenerator,
"SA12Generator": SA12Generator,
@@ -145,7 +149,7 @@ SAM_PRICES = {
AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 30,
AirDefence.SAM_SA_15_Tor_9A331: 40,
AirDefence.SAM_SA_19_Tunguska_2S6: 35,
AirDefence.HQ_7_Self_Propelled_LN: 35
AirDefence.HQ_7_Self_Propelled_LN: 35,
}
EWR_MAP = {
@@ -163,7 +167,8 @@ EWR_MAP = {
def get_faction_possible_sams_generator(
faction: Faction) -> List[Type[AirDefenseGroupGenerator]]:
faction: Faction,
) -> List[Type[AirDefenseGroupGenerator]]:
"""
Return the list of possible SAM generator for the given faction
:param faction: Faction name to search units for
@@ -180,8 +185,10 @@ def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGene
def _generate_anti_air_from(
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
ground_object: SamGroundObject) -> List[VehicleGroup]:
generators: Sequence[Type[AirDefenseGroupGenerator]],
game: Game,
ground_object: SamGroundObject,
) -> List[VehicleGroup]:
if not generators:
return []
sam_generator_class = random.choice(generators)
@@ -191,8 +198,10 @@ def _generate_anti_air_from(
def generate_anti_air_group(
game: Game, ground_object: SamGroundObject, faction: Faction,
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
game: Game,
ground_object: SamGroundObject,
faction: Faction,
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None,
) -> List[VehicleGroup]:
"""
This generate a SAM group
@@ -213,24 +222,25 @@ def generate_anti_air_group(
"""
generators = get_faction_possible_sams_generator(faction)
if ranges is None:
ranges = [{
AirDefenseRange.Long,
AirDefenseRange.Medium,
AirDefenseRange.Short,
}]
ranges = [
{
AirDefenseRange.Long,
AirDefenseRange.Medium,
AirDefenseRange.Short,
}
]
for range_options in ranges:
generators_for_range = [g for g in generators if
g.range() in range_options]
groups = _generate_anti_air_from(generators_for_range, game,
ground_object)
generators_for_range = [g for g in generators if g.range() in range_options]
groups = _generate_anti_air_from(generators_for_range, game, ground_object)
if groups:
return groups
return []
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,
faction: Faction) -> Optional[VehicleGroup]:
def generate_ewr_group(
game: Game, ground_object: TheaterGroundObject, faction: Faction
) -> Optional[VehicleGroup]:
"""Generates an early warning radar group.
:param game: The Game.

View File

@@ -18,20 +18,51 @@ class HawkGenerator(AirDefenseGroupGenerator):
price = 115
def generate(self):
self.add_unit(AirDefence.SAM_Hawk_SR_AN_MPQ_50, "SR", self.position.x + 20, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Hawk_PCP, "PCP", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_Hawk_SR_AN_MPQ_50,
"SR",
self.position.x + 20,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Hawk_PCP,
"PCP",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Hawk_TR_AN_MPQ_46,
"TR",
self.position.x + 40,
self.position.y,
self.heading,
)
# Triple A for close range defense
aa_group = self.add_auxiliary_group("AA")
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163, "AAA",
self.position + Point(20, 30), self.heading)
self.add_unit_to_group(
aa_group,
AirDefence.AAA_Vulcan_M163,
"AAA",
self.position + Point(20, 30),
self.heading,
)
num_launchers = random.randint(3, 6)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_Hawk_LN_M192, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_Hawk_LN_M192,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -18,23 +18,51 @@ class HQ7Generator(AirDefenseGroupGenerator):
price = 120
def generate(self):
self.add_unit(AirDefence.HQ_7_Self_Propelled_STR, "STR", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading)
self.add_unit(
AirDefence.HQ_7_Self_Propelled_STR,
"STR",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.HQ_7_Self_Propelled_LN,
"LN",
self.position.x + 20,
self.position.y,
self.heading,
)
# Triple A for close range defense
aa_group = self.add_auxiliary_group("AA")
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
"AAA1", self.position + Point(20, 30),
self.heading)
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
"AAA2", self.position - Point(20, 30),
self.heading)
self.add_unit_to_group(
aa_group,
AirDefence.AAA_ZU_23_on_Ural_375,
"AAA1",
self.position + Point(20, 30),
self.heading,
)
self.add_unit_to_group(
aa_group,
AirDefence.AAA_ZU_23_on_Ural_375,
"AAA2",
self.position - Point(20, 30),
self.heading,
)
num_launchers = random.randint(0, 3)
if num_launchers > 0:
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.HQ_7_Self_Propelled_LN,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,10 +19,24 @@ class LinebackerGenerator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(2, 4)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x,
self.position.y,
self.heading,
)
positions = self.get_circular_position(
num_launchers, launcher_distance=110, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_Linebacker_M6, "M6#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_Linebacker_M6,
"M6#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,24 +19,65 @@ class PatriotGenerator(AirDefenseGroupGenerator):
def generate(self):
# Command Post
self.add_unit(AirDefence.SAM_Patriot_STR_AN_MPQ_53, "STR", self.position.x + 30, self.position.y + 30, self.heading)
self.add_unit(AirDefence.SAM_Patriot_AMG_AN_MRC_137, "MRC", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Patriot_ECS_AN_MSQ_104, "MSQ", self.position.x + 30, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Patriot_ICC, "ICC", self.position.x + 60, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Patriot_EPP_III, "EPP", self.position.x, self.position.y + 30, self.heading)
self.add_unit(
AirDefence.SAM_Patriot_STR_AN_MPQ_53,
"STR",
self.position.x + 30,
self.position.y + 30,
self.heading,
)
self.add_unit(
AirDefence.SAM_Patriot_AMG_AN_MRC_137,
"MRC",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Patriot_ECS_AN_MSQ_104,
"MSQ",
self.position.x + 30,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Patriot_ICC,
"ICC",
self.position.x + 60,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Patriot_EPP_III,
"EPP",
self.position.x,
self.position.y + 30,
self.heading,
)
num_launchers = random.randint(3, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_Patriot_LN_M901,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
# Short range protection for high value site
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(3, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=200, coverage=360
)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
f"SPAAA#{i}", Point(x, y), heading)
self.add_unit_to_group(
aa_group, AirDefence.AAA_Vulcan_M163, f"SPAAA#{i}", Point(x, y), heading
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,14 +17,34 @@ class RapierGenerator(AirDefenseGroupGenerator):
price = 50
def generate(self):
self.add_unit(AirDefence.Rapier_FSA_Blindfire_Tracker, "BT", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.Rapier_FSA_Optical_Tracker, "OT", self.position.x + 20, self.position.y, self.heading)
self.add_unit(
AirDefence.Rapier_FSA_Blindfire_Tracker,
"BT",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.Rapier_FSA_Optical_Tracker,
"OT",
self.position.x + 20,
self.position.y,
self.heading,
)
num_launchers = random.randint(3, 6)
positions = self.get_circular_position(num_launchers, launcher_distance=80, coverage=240)
positions = self.get_circular_position(
num_launchers, launcher_distance=80, coverage=240
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.Rapier_FSA_Launcher, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.Rapier_FSA_Launcher,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -15,9 +15,27 @@ class RolandGenerator(AirDefenseGroupGenerator):
price = 40
def generate(self):
self.add_unit(AirDefence.SAM_Roland_EWR, "EWR", self.position.x + 40, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_Roland_ADS, "ADS", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_Roland_EWR,
"EWR",
self.position.x + 40,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_Roland_ADS,
"ADS",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x + 80,
self.position.y,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -33,28 +33,41 @@ class SA10Generator(AirDefenseGroupGenerator):
def generate(self):
# Search Radar
self.add_unit(self.sr1, "SR1", self.position.x, self.position.y + 40, self.heading)
self.add_unit(
self.sr1, "SR1", self.position.x, self.position.y + 40, self.heading
)
# Search radar for missiles (optionnal)
self.add_unit(self.sr2, "SR2", self.position.x - 40, self.position.y, self.heading)
self.add_unit(
self.sr2, "SR2", self.position.x - 40, self.position.y, self.heading
)
# Command Post
self.add_unit(self.cp, "CP", self.position.x, self.position.y, self.heading)
# 2 Tracking radars
self.add_unit(self.tr1, "TR1", self.position.x - 40, self.position.y - 40, self.heading)
self.add_unit(
self.tr1, "TR1", self.position.x - 40, self.position.y - 40, self.heading
)
self.add_unit(self.tr2, "TR2", self.position.x + 40, self.position.y - 40,
self.heading)
self.add_unit(
self.tr2, "TR2", self.position.x + 40, self.position.y - 40, self.heading
)
# 2 different launcher type (C & D)
num_launchers = random.randint(6, 8)
positions = self.get_circular_position(num_launchers, launcher_distance=100, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=100, coverage=360
)
for i, position in enumerate(positions):
if i % 2 == 0:
self.add_unit(self.ln1, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
self.ln1, "LN#" + str(i), position[0], position[1], position[2]
)
else:
self.add_unit(self.ln2, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
self.ln2, "LN#" + str(i), position[0], position[1], position[2]
)
self.generate_defensive_groups()
@@ -67,10 +80,16 @@ class SA10Generator(AirDefenseGroupGenerator):
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(6, 8)
positions = self.get_circular_position(
num_launchers, launcher_distance=210, coverage=360)
num_launchers, launcher_distance=210, coverage=360
)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.SPAAA_ZSU_23_4_Shilka,
f"AA#{i}", Point(x, y), heading)
self.add_unit_to_group(
aa_group,
AirDefence.SPAAA_ZSU_23_4_Shilka,
f"AA#{i}",
Point(x, y),
heading,
)
class Tier2SA10Generator(SA10Generator):
@@ -86,10 +105,16 @@ class Tier2SA10Generator(SA10Generator):
pd_group = self.add_auxiliary_group("PD")
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=360)
num_launchers, launcher_distance=140, coverage=360
)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
f"PD#{i}", Point(x, y), heading)
self.add_unit_to_group(
pd_group,
AirDefence.SAM_SA_15_Tor_9A331,
f"PD#{i}",
Point(x, y),
heading,
)
class Tier3SA10Generator(SA10Generator):
@@ -102,19 +127,31 @@ class Tier3SA10Generator(SA10Generator):
aa_group = self.add_auxiliary_group("AA")
num_launchers = random.randint(6, 8)
positions = self.get_circular_position(
num_launchers, launcher_distance=210, coverage=360)
num_launchers, launcher_distance=210, coverage=360
)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(aa_group, AirDefence.SAM_SA_19_Tunguska_2S6,
f"AA#{i}", Point(x, y), heading)
self.add_unit_to_group(
aa_group,
AirDefence.SAM_SA_19_Tunguska_2S6,
f"AA#{i}",
Point(x, y),
heading,
)
# SA-15 for both shorter range targets and point defense.
pd_group = self.add_auxiliary_group("PD")
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=360)
num_launchers, launcher_distance=140, coverage=360
)
for i, (x, y, heading) in enumerate(positions):
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
f"PD#{i}", Point(x, y), heading)
self.add_unit_to_group(
pd_group,
AirDefence.SAM_SA_15_Tor_9A331,
f"PD#{i}",
Point(x, y),
heading,
)
class SA10BGenerator(Tier3SA10Generator):
@@ -194,4 +231,4 @@ class SA23Generator(Tier3SA10Generator):
self.tr1 = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
self.tr2 = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
self.ln1 = highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN
self.ln2 = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN
self.ln2 = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN

View File

@@ -17,14 +17,34 @@ class SA11Generator(AirDefenseGroupGenerator):
price = 180
def generate(self):
self.add_unit(AirDefence.SAM_SA_11_Buk_SR_9S18M1, "SR", self.position.x+20, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_11_Buk_SR_9S18M1,
"SR",
self.position.x + 20,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
"CC",
self.position.x,
self.position.y,
self.heading,
)
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=140, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=140, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_11_Buk_LN_9A310M1, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,13 +17,33 @@ class SA13Generator(AirDefenseGroupGenerator):
price = 50
def generate(self):
self.add_unit(Unarmed.Transport_UAZ_469, "UAZ", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_KAMAZ_43101, "TRUCK", self.position.x+40, self.position.y, self.heading)
self.add_unit(
Unarmed.Transport_UAZ_469,
"UAZ",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_KAMAZ_43101,
"TRUCK",
self.position.x + 40,
self.position.y,
self.heading,
)
num_launchers = random.randint(2, 3)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_13_Strela_10M3_9A35M3, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -15,10 +15,28 @@ class SA15Generator(AirDefenseGroupGenerator):
price = 55
def generate(self):
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "ADS", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_UAZ_469, "EWR", self.position.x + 40, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_KAMAZ_43101, "TRUCK", self.position.x + 80, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_15_Tor_9A331,
"ADS",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_UAZ_469,
"EWR",
self.position.x + 40,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_KAMAZ_43101,
"TRUCK",
self.position.x + 80,
self.position.y,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:
return AirDefenseRange.Medium
return AirDefenseRange.Medium

View File

@@ -16,14 +16,31 @@ class SA17Generator(AirDefenseGroupGenerator):
price = 180
def generate(self):
self.add_unit(AirDefence.SAM_SA_11_Buk_SR_9S18M1, "SR", self.position.x + 20, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_11_Buk_SR_9S18M1,
"SR",
self.position.x + 20,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
"CC",
self.position.x,
self.position.y,
self.heading,
)
positions = self.get_circular_position(3, launcher_distance=140, coverage=180)
for i, position in enumerate(positions):
self.add_unit(highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2, "LN#" + str(i), position[0], position[1],
position[2])
self.add_unit(
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -20,11 +20,25 @@ class SA19Generator(AirDefenseGroupGenerator):
num_launchers = random.randint(1, 3)
if num_launchers == 1:
self.add_unit(AirDefence.SAM_SA_19_Tunguska_2S6, "LN#0", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_19_Tunguska_2S6,
"LN#0",
self.position.x,
self.position.y,
self.heading,
)
else:
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_19_Tunguska_2S6, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_19_Tunguska_2S6,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,14 +17,30 @@ class SA2Generator(AirDefenseGroupGenerator):
price = 74
def generate(self):
self.add_unit(AirDefence.SAM_SR_P_19, "SR", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song, "TR", self.position.x + 20, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SR_P_19, "SR", self.position.x, self.position.y, self.heading
)
self.add_unit(
AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song,
"TR",
self.position.x + 20,
self.position.y,
self.heading,
)
num_launchers = random.randint(3, 6)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_2_LN_SM_90, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_2_LN_SM_90,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,14 +17,30 @@ class SA3Generator(AirDefenseGroupGenerator):
price = 80
def generate(self):
self.add_unit(AirDefence.SAM_SR_P_19, "SR", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_SA_3_S_125_TR_SNR, "TR", self.position.x + 20, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SR_P_19, "SR", self.position.x, self.position.y, self.heading
)
self.add_unit(
AirDefence.SAM_SA_3_S_125_TR_SNR,
"TR",
self.position.x + 20,
self.position.y,
self.heading,
)
num_launchers = random.randint(3, 6)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_3_S_125_LN_5P73, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_3_S_125_LN_5P73,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,13 +17,27 @@ class SA6Generator(AirDefenseGroupGenerator):
price = 102
def generate(self):
self.add_unit(AirDefence.SAM_SA_6_Kub_STR_9S91, "STR", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_6_Kub_STR_9S91,
"STR",
self.position.x,
self.position.y,
self.heading,
)
num_launchers = random.randint(2, 4)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_6_Kub_LN_2P25, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_6_Kub_LN_2P25,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -15,8 +15,20 @@ class SA8Generator(AirDefenseGroupGenerator):
price = 55
def generate(self):
self.add_unit(AirDefence.SAM_SA_8_Osa_9A33, "OSA", self.position.x, self.position.y, self.heading)
self.add_unit(AirDefence.SAM_SA_8_Osa_LD_9T217, "LD", self.position.x + 20, self.position.y, self.heading)
self.add_unit(
AirDefence.SAM_SA_8_Osa_9A33,
"OSA",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
AirDefence.SAM_SA_8_Osa_LD_9T217,
"LD",
self.position.x + 20,
self.position.y,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,13 +17,33 @@ class SA9Generator(AirDefenseGroupGenerator):
price = 40
def generate(self):
self.add_unit(Unarmed.Transport_UAZ_469, "UAZ", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_KAMAZ_43101, "TRUCK", self.position.x+40, self.position.y, self.heading)
self.add_unit(
Unarmed.Transport_UAZ_469,
"UAZ",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_KAMAZ_43101,
"TRUCK",
self.position.x + 40,
self.position.y,
self.heading,
)
num_launchers = random.randint(2, 3)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SAM_SA_9_Strela_1_9P31, "LN#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SAM_SA_9_Strela_1_9P31,
"LN#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -17,12 +17,29 @@ class VulcanGenerator(AirDefenseGroupGenerator):
price = 25
def generate(self):
self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA", self.position.x, self.position.y, self.heading)
self.add_unit(
AirDefence.AAA_Vulcan_M163,
"SPAAA",
self.position.x,
self.position.y,
self.heading,
)
if random.randint(0, 1) == 1:
self.add_unit(AirDefence.AAA_Vulcan_M163, "SPAAA2", self.position.x, self.position.y, self.heading)
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading)
self.add_unit(
AirDefence.AAA_Vulcan_M163,
"SPAAA2",
self.position.x,
self.position.y,
self.heading,
)
self.add_unit(
Unarmed.Transport_M818,
"TRUCK",
self.position.x + 80,
self.position.y,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:
return AirDefenseRange.Short

View File

@@ -19,9 +19,17 @@ class ZSU23Generator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(4, 5)
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
positions = self.get_circular_position(
num_launchers, launcher_distance=120, coverage=180
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.SPAAA_ZSU_23_4_Shilka,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -20,15 +20,19 @@ class ZU23Generator(AirDefenseGroupGenerator):
grid_x = random.randint(2, 3)
grid_y = random.randint(2, 3)
spacing = random.randint(10,40)
spacing = random.randint(10, 40)
index = 0
for i in range(grid_x):
for j in range(grid_y):
index = index+1
self.add_unit(AirDefence.AAA_ZU_23_Closed, "AAA#" + str(index),
self.position.x + spacing*i,
self.position.y + spacing*j, self.heading)
index = index + 1
self.add_unit(
AirDefence.AAA_ZU_23_Closed,
"AAA#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
self.heading,
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,9 +19,17 @@ class ZU23UralGenerator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(2, 8)
positions = self.get_circular_position(num_launchers, launcher_distance=80, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=80, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_ZU_23_on_Ural_375, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_ZU_23_on_Ural_375,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:

View File

@@ -19,11 +19,18 @@ class ZU23UralInsurgentGenerator(AirDefenseGroupGenerator):
def generate(self):
num_launchers = random.randint(2, 8)
positions = self.get_circular_position(num_launchers, launcher_distance=80, coverage=360)
positions = self.get_circular_position(
num_launchers, launcher_distance=80, coverage=360
)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_ZU_23_Insurgent_on_Ural_375, "SPAA#" + str(i), position[0], position[1], position[2])
self.add_unit(
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
"SPAA#" + str(i),
position[0],
position[1],
position[2],
)
@classmethod
def range(cls) -> AirDefenseRange:
return AirDefenseRange.Short

View File

@@ -2,18 +2,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from dcs.action import (
MarkToAll,
SetFlag,
DoScript,
ClearFlag
)
from dcs.action import MarkToAll, SetFlag, DoScript, ClearFlag
from dcs.condition import (
TimeAfter,
AllOfCoalitionOutsideZone,
PartOfCoalitionInZone,
FlagIsFalse,
FlagIsTrue
FlagIsTrue,
)
from dcs.unitgroup import FlyingGroup
from dcs.mission import Mission
@@ -56,7 +51,7 @@ class Silence(Option):
class TriggersGenerator:
capture_zone_types = (Fob, )
capture_zone_types = (Fob,)
capture_zone_flag = 600
def __init__(self, mission: Mission, game: Game):
@@ -82,16 +77,18 @@ class TriggersGenerator:
airport.operating_level_air = 0
airport.operating_level_equipment = 0
airport.operating_level_fuel = 0
for airport in self.mission.terrain.airport_list():
if airport.id not in cp_ids:
airport.unlimited_fuel = True
airport.unlimited_munitions = True
airport.unlimited_aircrafts = True
for cp in self.game.theater.controlpoints:
if isinstance(cp, Airfield):
self.mission.terrain.airport_by_id(cp.at.id).set_coalition(cp.captured and player_coalition or enemy_coalition)
self.mission.terrain.airport_by_id(cp.at.id).set_coalition(
cp.captured and player_coalition or enemy_coalition
)
def _set_skill(self, player_coalition: str, enemy_coalition: str):
"""
@@ -99,17 +96,28 @@ class TriggersGenerator:
"""
for coalition_name, coalition in self.mission.coalition.items():
if coalition_name == player_coalition:
skill_level = self.game.settings.player_skill, self.game.settings.player_skill
skill_level = (
self.game.settings.player_skill,
self.game.settings.player_skill,
)
elif coalition_name == enemy_coalition:
skill_level = self.game.settings.enemy_skill, self.game.settings.enemy_vehicle_skill
skill_level = (
self.game.settings.enemy_skill,
self.game.settings.enemy_vehicle_skill,
)
else:
continue
for country in coalition.countries.values():
flying_groups = country.plane_group + country.helicopter_group # type: FlyingGroup
flying_groups = (
country.plane_group + country.helicopter_group
) # type: FlyingGroup
for flying_group in flying_groups:
for plane_unit in flying_group.units:
if plane_unit.skill != Skill.Client and plane_unit.skill != Skill.Player:
if (
plane_unit.skill != Skill.Client
and plane_unit.skill != Skill.Player
):
plane_unit.skill = Skill(skill_level[0])
for vehicle_group in country.vehicle_group:
@@ -127,7 +135,9 @@ class TriggersGenerator:
added = []
for ground_object in cp.ground_objects:
if ground_object.obj_name not in added:
zone = self.mission.triggers.add_triggerzone(ground_object.position, radius=10, hidden=True, name="MARK")
zone = self.mission.triggers.add_triggerzone(
ground_object.position, radius=10, hidden=True, name="MARK"
)
if cp.captured:
name = ground_object.obj_name + " [ALLY]"
else:
@@ -137,7 +147,9 @@ class TriggersGenerator:
added.append(ground_object.obj_name)
self.mission.triggerrules.triggers.append(mark_trigger)
def _generate_capture_triggers(self, player_coalition: str, enemy_coalition: str) -> None:
def _generate_capture_triggers(
self, player_coalition: str, enemy_coalition: str
) -> None:
"""Creates a pair of triggers for each control point of `cls.capture_zone_types`.
One for the initial capture of a control point, and one if it is recaptured.
Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua`
@@ -155,33 +167,41 @@ class TriggersGenerator:
defending_coalition = enemy_coalition
defend_coalition_int = 1
trigger_zone = self.mission.triggers.add_triggerzone(cp.position, radius=3000, hidden=False, name="CAPTURE")
trigger_zone = self.mission.triggers.add_triggerzone(
cp.position, radius=3000, hidden=False, name="CAPTURE"
)
flag = self.get_capture_zone_flag()
capture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
capture_trigger.add_condition(AllOfCoalitionOutsideZone(defending_coalition, trigger_zone.id))
capture_trigger.add_condition(PartOfCoalitionInZone(attacking_coalition, trigger_zone.id, unit_type="GROUND"))
capture_trigger.add_condition(
AllOfCoalitionOutsideZone(defending_coalition, trigger_zone.id)
)
capture_trigger.add_condition(
PartOfCoalitionInZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
capture_trigger.add_condition(FlagIsFalse(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"'
)
capture_trigger.add_action(DoScript(
script_string
)
)
capture_trigger.add_action(DoScript(script_string))
capture_trigger.add_action(SetFlag(flag=flag))
self.mission.triggerrules.triggers.append(capture_trigger)
recapture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
recapture_trigger.add_condition(AllOfCoalitionOutsideZone(attacking_coalition, trigger_zone.id))
recapture_trigger.add_condition(PartOfCoalitionInZone(defending_coalition, trigger_zone.id, unit_type="GROUND"))
recapture_trigger.add_condition(
AllOfCoalitionOutsideZone(attacking_coalition, trigger_zone.id)
)
recapture_trigger.add_condition(
PartOfCoalitionInZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
recapture_trigger.add_condition(FlagIsTrue(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"'
)
recapture_trigger.add_action(DoScript(
script_string
)
)
recapture_trigger.add_action(DoScript(script_string))
recapture_trigger.add_action(ClearFlag(flag=flag))
self.mission.triggerrules.triggers.append(recapture_trigger)
@@ -190,10 +210,14 @@ class TriggersGenerator:
enemy_coalition = "red"
player_cp, enemy_cp = self.game.theater.closest_opposing_control_points()
self.mission.coalition["blue"].bullseye = {"x": enemy_cp.position.x,
"y": enemy_cp.position.y}
self.mission.coalition["red"].bullseye = {"x": player_cp.position.x,
"y": player_cp.position.y}
self.mission.coalition["blue"].bullseye = {
"x": enemy_cp.position.x,
"y": enemy_cp.position.y,
}
self.mission.coalition["red"].bullseye = {
"x": player_cp.position.x,
"y": player_cp.position.y,
}
self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition)

View File

@@ -3,4 +3,4 @@
def meters_to_feet(meters: float) -> float:
"""Convers meters to feet."""
return meters * 3.28084
return meters * 3.28084

View File

@@ -103,7 +103,9 @@ class VisualGenerator:
if from_cp.is_global or to_cp.is_global:
continue
plane_start, heading, distance = Conflict.frontline_vector(from_cp, to_cp, self.game.theater)
plane_start, heading, distance = Conflict.frontline_vector(
from_cp, to_cp, self.game.theater
)
if not plane_start:
continue
@@ -112,7 +114,9 @@ class VisualGenerator:
for k, v in FRONT_SMOKE_TYPE_CHANCES.items():
if random.randint(0, 100) <= k:
pos = position.random_point_within(FRONT_SMOKE_RANDOM_SPREAD, FRONT_SMOKE_RANDOM_SPREAD)
pos = position.random_point_within(
FRONT_SMOKE_RANDOM_SPREAD, FRONT_SMOKE_RANDOM_SPREAD
)
if not self.game.theater.is_on_land(pos):
break
@@ -120,7 +124,8 @@ class VisualGenerator:
self.mission.country(self.game.enemy_country),
"",
_type=v,
position=pos)
position=pos,
)
break
def _generate_stub_planes(self):
@@ -138,7 +143,14 @@ class VisualGenerator:
def generate_target_smokes(self, target):
spread = target.size * DESTINATION_SMOKE_DISTANCE_FACTOR
for _ in range(0, int(target.size * DESTINATION_SMOKE_AMOUNT_FACTOR * (1.1 - target.base.strength))):
for _ in range(
0,
int(
target.size
* DESTINATION_SMOKE_AMOUNT_FACTOR
* (1.1 - target.base.strength)
),
):
for k, v in DESTINATION_SMOKE_TYPE_CHANCES.items():
if random.randint(0, 100) <= k:
position = target.position.random_point_within(0, spread)
@@ -149,7 +161,8 @@ class VisualGenerator:
self.mission.country(self.game.enemy_country),
"",
_type=v,
position=position)
position=position,
)
break
def generate_transportation_marker(self, at: Point):
@@ -157,7 +170,7 @@ class VisualGenerator:
self.mission.country(self.game.player_country),
"",
_type=MarkerSmoke,
position=at
position=at,
)
def generate_transportation_destination(self, at: Point):
@@ -166,7 +179,7 @@ class VisualGenerator:
self.mission.country(self.game.player_country),
"",
_type=Outpost,
position=at
position=at,
)
def generate(self):