mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Move mission generation code into game.
Operation has been renamed MissionGenerator and is no longer a static class.
This commit is contained in:
parent
b0787d9a3f
commit
74291271e3
@ -16,7 +16,7 @@ class FlightType(Enum):
|
|||||||
|
|
||||||
* flightplan.py: Add waypoint population in generate_flight_plan. Add a new flight
|
* flightplan.py: Add waypoint population in generate_flight_plan. Add a new flight
|
||||||
plan type if necessary, though most are a subclass of StrikeFlightPlan.
|
plan type if necessary, though most are a subclass of StrikeFlightPlan.
|
||||||
* aircraft.py: Add a configuration method and call it in setup_flight_group. This is
|
* aircraftgenerator.py: Add a configuration method and call it in setup_flight_group. This is
|
||||||
responsible for configuring waypoint 0 actions like setting ROE, threat reaction,
|
responsible for configuring waypoint 0 actions like setting ROE, threat reaction,
|
||||||
and mission abort parameters (winchester, bingo, etc).
|
and mission abort parameters (winchester, bingo, etc).
|
||||||
* Implementations of MissionTarget.mission_types: A mission type can only be planned
|
* Implementations of MissionTarget.mission_types: A mission type can only be planned
|
||||||
@ -28,7 +28,7 @@ class FlightType(Enum):
|
|||||||
You may also need to update:
|
You may also need to update:
|
||||||
|
|
||||||
* flightwaypointtype.py: Add a new waypoint type if necessary. Most mission types
|
* flightwaypointtype.py: Add a new waypoint type if necessary. Most mission types
|
||||||
will need these, as aircraft.py uses the ingress point type to specialize AI
|
will need these, as aircraftgenerator.py uses the ingress point type to specialize AI
|
||||||
tasks, and non-strike-like missions will need more specialized control.
|
tasks, and non-strike-like missions will need more specialized control.
|
||||||
* ai_flight_planner.py: Use the new mission type in propose_missions so the AI will
|
* ai_flight_planner.py: Use the new mission type in propose_missions so the AI will
|
||||||
plan the new mission type.
|
plan the new mission type.
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Optional, Sequence, Union
|
from typing import Optional, Sequence, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
|
|
||||||
from game.theater import ControlPoint, MissionTarget
|
|
||||||
from game.utils import Distance, meters
|
from game.utils import Distance, meters
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.theater import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypoint:
|
class FlightWaypoint:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class FlightWaypointType(Enum):
|
|||||||
* waypointbuilder.py: Add a builder to simplify construction of the new waypoint
|
* waypointbuilder.py: Add a builder to simplify construction of the new waypoint
|
||||||
type unless the new waypoint type will be a parameter to an existing builder
|
type unless the new waypoint type will be a parameter to an existing builder
|
||||||
method (such as how escort ingress waypoints work).
|
method (such as how escort ingress waypoints work).
|
||||||
* aircraft.py: Associate AI actions with the new waypoint type by subclassing
|
* aircraftgenerator.py: Associate AI actions with the new waypoint type by subclassing
|
||||||
PydcsWaypointBuilder and using it in PydcsWaypointBuilder.for_waypoint.
|
PydcsWaypointBuilder and using it in PydcsWaypointBuilder.for_waypoint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
from game.ato import Flight, FlightType
|
from game.ato import Flight, FlightType
|
||||||
from game.ato.packagewaypoints import PackageWaypoints
|
from game.ato.packagewaypoints import PackageWaypoints
|
||||||
from game.theater import MissionTarget
|
|
||||||
from game.utils import Speed
|
from game.utils import Speed
|
||||||
from gen.flights.flightplan import FormationFlightPlan
|
from gen.flights.flightplan import FormationFlightPlan
|
||||||
from gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.theater import MissionTarget
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Package:
|
class Package:
|
||||||
|
|||||||
@ -40,9 +40,9 @@ from game.utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gen.aircraft import FlightData
|
from game.missiongenerator.aircraftgenerator import FlightData
|
||||||
from gen.airsupport import AirSupport
|
from game.missiongenerator.airsupport import AirSupport
|
||||||
from gen.radios import Radio, RadioFrequency, RadioRegistry
|
from game.radio.radios import Radio, RadioFrequency, RadioRegistry
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -63,7 +63,7 @@ class RadioConfig:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_radio(cls, name: Optional[str]) -> Optional[Radio]:
|
def make_radio(cls, name: Optional[str]) -> Optional[Radio]:
|
||||||
from gen.radios import get_radio
|
from game.radio.radios import get_radio
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
return None
|
return None
|
||||||
@ -255,7 +255,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
return min(Speed.from_mach(0.35, altitude), max_speed * 0.7)
|
return min(Speed.from_mach(0.35, altitude), max_speed * 0.7)
|
||||||
|
|
||||||
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
||||||
from gen.radios import ChannelInUseError, kHz
|
from game.radio.radios import ChannelInUseError, kHz
|
||||||
|
|
||||||
if self.intra_flight_radio is not None:
|
if self.intra_flight_radio is not None:
|
||||||
return radio_registry.alloc_for_radio(self.intra_flight_radio)
|
return radio_registry.alloc_for_radio(self.intra_flight_radio)
|
||||||
|
|||||||
@ -8,10 +8,10 @@ from dcs.task import Task
|
|||||||
|
|
||||||
from game import persistency
|
from game import persistency
|
||||||
from game.debriefing import Debriefing
|
from game.debriefing import Debriefing
|
||||||
from game.operation.operation import Operation
|
|
||||||
from game.theater import ControlPoint
|
from game.theater import ControlPoint
|
||||||
from ..ato.airtaaskingorder import AirTaskingOrder
|
|
||||||
from gen.ground_forces.combat_stance import CombatStance
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
|
from ..ato.airtaaskingorder import AirTaskingOrder
|
||||||
|
from ..missiongenerator import MissionGenerator
|
||||||
from ..unitmap import UnitMap
|
from ..unitmap import UnitMap
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -58,12 +58,9 @@ class Event:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def generate(self) -> UnitMap:
|
def generate(self) -> UnitMap:
|
||||||
Operation.prepare(self.game)
|
return MissionGenerator(self.game).generate_miz(
|
||||||
unit_map = Operation.generate()
|
|
||||||
Operation.current_mission.save(
|
|
||||||
persistency.mission_path_for("liberation_nextturn.miz")
|
persistency.mission_path_for("liberation_nextturn.miz")
|
||||||
)
|
)
|
||||||
return unit_map
|
|
||||||
|
|
||||||
def commit_air_losses(self, debriefing: Debriefing) -> None:
|
def commit_air_losses(self, debriefing: Debriefing) -> None:
|
||||||
for loss in debriefing.air_losses.losses:
|
for loss in debriefing.air_losses.losses:
|
||||||
|
|||||||
12
game/game.py
12
game/game.py
@ -37,7 +37,9 @@ from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
|
|||||||
from .weather import Conditions, TimeOfDay
|
from .weather import Conditions, TimeOfDay
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gen.conflictgen import Conflict
|
from game.missiongenerator.frontlineconflictdescription import (
|
||||||
|
FrontLineConflictDescription,
|
||||||
|
)
|
||||||
from .ato.airtaaskingorder import AirTaskingOrder
|
from .ato.airtaaskingorder import AirTaskingOrder
|
||||||
from .navmesh import NavMesh
|
from .navmesh import NavMesh
|
||||||
from .squadrons import AirWing
|
from .squadrons import AirWing
|
||||||
@ -453,13 +455,17 @@ class Game:
|
|||||||
Compute the current conflict center position(s), mainly used for culling calculation
|
Compute the current conflict center position(s), mainly used for culling calculation
|
||||||
:return: List of points of interests
|
:return: List of points of interests
|
||||||
"""
|
"""
|
||||||
from gen.conflictgen import Conflict
|
from game.missiongenerator.frontlineconflictdescription import (
|
||||||
|
FrontLineConflictDescription,
|
||||||
|
)
|
||||||
|
|
||||||
zones = []
|
zones = []
|
||||||
|
|
||||||
# By default, use the existing frontline conflict position
|
# By default, use the existing frontline conflict position
|
||||||
for front_line in self.theater.conflicts():
|
for front_line in self.theater.conflicts():
|
||||||
position = Conflict.frontline_position(front_line, self.theater)
|
position = FrontLineConflictDescription.frontline_position(
|
||||||
|
front_line, self.theater
|
||||||
|
)
|
||||||
zones.append(position[0])
|
zones.append(position[0])
|
||||||
zones.append(front_line.blue_cp.position)
|
zones.append(front_line.blue_cp.position)
|
||||||
zones.append(front_line.red_cp.position)
|
zones.append(front_line.red_cp.position)
|
||||||
|
|||||||
1
game/missiongenerator/__init__.py
Normal file
1
game/missiongenerator/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .missiongenerator import MissionGenerator
|
||||||
@ -91,13 +91,11 @@ from game.ato.flighttype import FlightType
|
|||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
from gen.lasercoderegistry import LaserCodeRegistry
|
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||||
from gen.radios import RadioFrequency, RadioRegistry
|
|
||||||
from gen.runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
from gen.tacan import TacanBand, TacanRegistry, TacanUsage
|
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||||
from .airsupport import AirSupport, AwacsInfo, TankerInfo
|
from gen.callsigns import callsign_for_support_unit
|
||||||
from .callsigns import callsign_for_support_unit
|
from gen.flights.flightplan import (
|
||||||
from .flights.flightplan import (
|
|
||||||
AwacsFlightPlan,
|
AwacsFlightPlan,
|
||||||
CasFlightPlan,
|
CasFlightPlan,
|
||||||
LoiterFlightPlan,
|
LoiterFlightPlan,
|
||||||
@ -105,8 +103,11 @@ from .flights.flightplan import (
|
|||||||
RefuelingFlightPlan,
|
RefuelingFlightPlan,
|
||||||
SweepFlightPlan,
|
SweepFlightPlan,
|
||||||
)
|
)
|
||||||
from .flights.traveltime import GroundSpeed, TotEstimator
|
from gen.flights.traveltime import GroundSpeed, TotEstimator
|
||||||
from .naming import namegen
|
from gen.naming import namegen
|
||||||
|
|
||||||
|
from .airsupport import AirSupport, AwacsInfo, TankerInfo
|
||||||
|
from .lasercoderegistry import LaserCodeRegistry
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -219,7 +220,7 @@ class FlightData:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AircraftConflictGenerator:
|
class AircraftGenerator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
@ -282,7 +283,7 @@ class AircraftConflictGenerator:
|
|||||||
):
|
):
|
||||||
return DcsStartType.Runway
|
return DcsStartType.Runway
|
||||||
else:
|
else:
|
||||||
return AircraftConflictGenerator._start_type(start_type)
|
return AircraftGenerator._start_type(start_type)
|
||||||
|
|
||||||
def skill_level_for(
|
def skill_level_for(
|
||||||
self, unit: FlyingUnit, pilot: Optional[Pilot], blue: bool
|
self, unit: FlyingUnit, pilot: Optional[Pilot], blue: bool
|
||||||
@ -5,8 +5,8 @@ from datetime import timedelta
|
|||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gen.radios import RadioFrequency
|
from game.radio.radios import RadioFrequency
|
||||||
from gen.tacan import TacanChannel
|
from game.radio.tacan import TacanChannel
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -16,13 +16,14 @@ from dcs.task import (
|
|||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from game.utils import Heading
|
from game.utils import Heading
|
||||||
|
from gen.callsigns import callsign_for_support_unit
|
||||||
|
from gen.flights.ai_flight_planner_db import AEWC_CAPABLE
|
||||||
|
from gen.naming import namegen
|
||||||
|
from game.radio.radios import RadioRegistry
|
||||||
|
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
|
||||||
|
|
||||||
from .airsupport import AirSupport, TankerInfo, AwacsInfo
|
from .airsupport import AirSupport, TankerInfo, AwacsInfo
|
||||||
from .callsigns import callsign_for_support_unit
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
from .conflictgen import Conflict
|
|
||||||
from .flights.ai_flight_planner_db import AEWC_CAPABLE
|
|
||||||
from .naming import namegen
|
|
||||||
from .radios import RadioRegistry
|
|
||||||
from .tacan import TacanBand, TacanRegistry, TacanUsage
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -35,11 +36,11 @@ AWACS_DISTANCE = 150000
|
|||||||
AWACS_ALT = 13000
|
AWACS_ALT = 13000
|
||||||
|
|
||||||
|
|
||||||
class AirSupportConflictGenerator:
|
class AirSupportGenerator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
conflict: Conflict,
|
conflict: FrontLineConflictDescription,
|
||||||
game: Game,
|
game: Game,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
tacan_registry: TacanRegistry,
|
tacan_registry: TacanRegistry,
|
||||||
@ -4,8 +4,8 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterable, Optional
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from gen.radios import RadioFrequency
|
from game.radio.radios import RadioFrequency
|
||||||
from gen.tacan import TacanBand, TacanChannel
|
from game.radio.tacan import TacanBand, TacanChannel
|
||||||
|
|
||||||
|
|
||||||
BEACONS_RESOURCE_PATH = Path("resources/dcs/beacons")
|
BEACONS_RESOURCE_PATH = Path("resources/dcs/beacons")
|
||||||
@ -12,13 +12,14 @@ from dcs.mission import Mission
|
|||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
|
|
||||||
from game.theater import ControlPoint, FrontLine
|
from game.theater import ControlPoint, FrontLine
|
||||||
from .aircraft import FlightData
|
|
||||||
from .airsupportgen import AwacsInfo, TankerInfo
|
|
||||||
from .armor import JtacInfo
|
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from .ground_forces.combat_stance import CombatStance
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
from .radios import RadioFrequency
|
from game.radio.radios import RadioFrequency
|
||||||
from .runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
|
|
||||||
|
from .aircraftgenerator import FlightData
|
||||||
|
from .airsupportgenerator import AwacsInfo, TankerInfo
|
||||||
|
from .flotgenerator import JtacInfo
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
@ -40,13 +39,14 @@ from gen.ground_forces.ai_ground_planner import (
|
|||||||
CombatGroup,
|
CombatGroup,
|
||||||
CombatGroupRole,
|
CombatGroupRole,
|
||||||
)
|
)
|
||||||
|
from gen.callsigns import callsign_for_support_unit
|
||||||
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
|
from gen.naming import namegen
|
||||||
|
from game.radio.radios import RadioRegistry
|
||||||
|
|
||||||
from .airsupport import AirSupport, JtacInfo
|
from .airsupport import AirSupport, JtacInfo
|
||||||
from .callsigns import callsign_for_support_unit
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
from .conflictgen import Conflict
|
|
||||||
from .ground_forces.combat_stance import CombatStance
|
|
||||||
from .lasercoderegistry import LaserCodeRegistry
|
from .lasercoderegistry import LaserCodeRegistry
|
||||||
from .naming import namegen
|
|
||||||
from .radios import MHz, RadioFrequency, RadioRegistry
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -69,11 +69,11 @@ RANDOM_OFFSET_ATTACK = 250
|
|||||||
INFANTRY_GROUP_SIZE = 5
|
INFANTRY_GROUP_SIZE = 5
|
||||||
|
|
||||||
|
|
||||||
class GroundConflictGenerator:
|
class FlotGenerator:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
conflict: Conflict,
|
conflict: FrontLineConflictDescription,
|
||||||
game: Game,
|
game: Game,
|
||||||
player_planned_combat_groups: List[CombatGroup],
|
player_planned_combat_groups: List[CombatGroup],
|
||||||
enemy_planned_combat_groups: List[CombatGroup],
|
enemy_planned_combat_groups: List[CombatGroup],
|
||||||
@ -97,11 +97,11 @@ class GroundConflictGenerator:
|
|||||||
self.laser_code_registry = laser_code_registry
|
self.laser_code_registry = laser_code_registry
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
position = Conflict.frontline_position(
|
position = FrontLineConflictDescription.frontline_position(
|
||||||
self.conflict.front_line, self.game.theater
|
self.conflict.front_line, self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
frontline_vector = Conflict.frontline_vector(
|
frontline_vector = FrontLineConflictDescription.frontline_vector(
|
||||||
self.conflict.front_line, self.game.theater
|
self.conflict.front_line, self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -712,7 +712,7 @@ class GroundConflictGenerator:
|
|||||||
desired_point = shifted.point_from_heading(
|
desired_point = shifted.point_from_heading(
|
||||||
spawn_heading.degrees, distance_from_frontline
|
spawn_heading.degrees, distance_from_frontline
|
||||||
)
|
)
|
||||||
return Conflict.find_ground_position(
|
return FrontLineConflictDescription.find_ground_position(
|
||||||
desired_point, combat_width, heading, self.conflict.theater
|
desired_point, combat_width, heading, self.conflict.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ from game.utils import Heading
|
|||||||
FRONTLINE_LENGTH = 80000
|
FRONTLINE_LENGTH = 80000
|
||||||
|
|
||||||
|
|
||||||
class Conflict:
|
class FrontLineConflictDescription:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
@ -95,7 +95,7 @@ class Conflict:
|
|||||||
defender: Country,
|
defender: Country,
|
||||||
front_line: FrontLine,
|
front_line: FrontLine,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
) -> Conflict:
|
) -> FrontLineConflictDescription:
|
||||||
assert cls.has_frontline_between(front_line.blue_cp, front_line.red_cp)
|
assert cls.has_frontline_between(front_line.blue_cp, front_line.red_cp)
|
||||||
position, heading, distance = cls.frontline_vector(front_line, theater)
|
position, heading, distance = cls.frontline_vector(front_line, theater)
|
||||||
conflict = cls(
|
conflict = cls(
|
||||||
@ -42,14 +42,15 @@ from game.theater import ConflictTheater, TheaterGroundObject, LatLon
|
|||||||
from game.theater.bullseye import Bullseye
|
from game.theater.bullseye import Bullseye
|
||||||
from game.utils import meters
|
from game.utils import meters
|
||||||
from game.weather import Weather
|
from game.weather import Weather
|
||||||
from .aircraft import FlightData
|
|
||||||
from .airsupportgen import AwacsInfo, TankerInfo
|
|
||||||
from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator
|
|
||||||
from game.ato.flighttype import FlightType
|
from game.ato.flighttype import FlightType
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from .radios import RadioFrequency
|
from game.radio.radios import RadioFrequency
|
||||||
from .runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
|
|
||||||
|
from .aircraftgenerator import FlightData
|
||||||
|
from .airsupportgenerator import AwacsInfo, TankerInfo
|
||||||
|
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
309
game/missiongenerator/luagenerator.py
Normal file
309
game/missiongenerator/luagenerator.py
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from dcs import Mission
|
||||||
|
from dcs.action import DoScript, DoScriptFile
|
||||||
|
from dcs.translation import String
|
||||||
|
from dcs.triggers import TriggerStart
|
||||||
|
|
||||||
|
from game.ato import FlightType
|
||||||
|
from game.plugins import LuaPluginManager
|
||||||
|
from game.theater import TheaterGroundObject
|
||||||
|
|
||||||
|
from .aircraftgenerator import FlightData
|
||||||
|
from .airsupport import AirSupport
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
|
class LuaGenerator:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
mission: Mission,
|
||||||
|
air_support: AirSupport,
|
||||||
|
flights: list[FlightData],
|
||||||
|
) -> None:
|
||||||
|
self.game = game
|
||||||
|
self.mission = mission
|
||||||
|
self.air_support = air_support
|
||||||
|
self.flights = flights
|
||||||
|
self.plugin_scripts: list[str] = []
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
self.generate_plugin_data()
|
||||||
|
self.inject_plugins()
|
||||||
|
|
||||||
|
def generate_plugin_data(self) -> None:
|
||||||
|
# TODO: Refactor this
|
||||||
|
lua_data = {
|
||||||
|
"AircraftCarriers": {},
|
||||||
|
"Tankers": {},
|
||||||
|
"AWACs": {},
|
||||||
|
"JTACs": {},
|
||||||
|
"TargetPoints": {},
|
||||||
|
"RedAA": {},
|
||||||
|
"BlueAA": {},
|
||||||
|
} # type: ignore
|
||||||
|
|
||||||
|
for i, tanker in enumerate(self.air_support.tankers):
|
||||||
|
lua_data["Tankers"][i] = {
|
||||||
|
"dcsGroupName": tanker.group_name,
|
||||||
|
"callsign": tanker.callsign,
|
||||||
|
"variant": tanker.variant,
|
||||||
|
"radio": tanker.freq.mhz,
|
||||||
|
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, awacs in enumerate(self.air_support.awacs):
|
||||||
|
lua_data["AWACs"][i] = {
|
||||||
|
"dcsGroupName": awacs.group_name,
|
||||||
|
"callsign": awacs.callsign,
|
||||||
|
"radio": awacs.freq.mhz,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, jtac in enumerate(self.air_support.jtacs):
|
||||||
|
lua_data["JTACs"][i] = {
|
||||||
|
"dcsGroupName": jtac.group_name,
|
||||||
|
"callsign": jtac.callsign,
|
||||||
|
"zone": jtac.region,
|
||||||
|
"dcsUnit": jtac.unit_name,
|
||||||
|
"laserCode": jtac.code,
|
||||||
|
"radio": jtac.freq.mhz,
|
||||||
|
}
|
||||||
|
flight_count = 0
|
||||||
|
for flight in self.flights:
|
||||||
|
if flight.friendly and flight.flight_type in [
|
||||||
|
FlightType.ANTISHIP,
|
||||||
|
FlightType.DEAD,
|
||||||
|
FlightType.SEAD,
|
||||||
|
FlightType.STRIKE,
|
||||||
|
]:
|
||||||
|
flight_type = str(flight.flight_type)
|
||||||
|
flight_target = flight.package.target
|
||||||
|
if flight_target:
|
||||||
|
flight_target_name = None
|
||||||
|
flight_target_type = None
|
||||||
|
if isinstance(flight_target, TheaterGroundObject):
|
||||||
|
flight_target_name = flight_target.obj_name
|
||||||
|
flight_target_type = (
|
||||||
|
flight_type + f" TGT ({flight_target.category})"
|
||||||
|
)
|
||||||
|
elif hasattr(flight_target, "name"):
|
||||||
|
flight_target_name = flight_target.name
|
||||||
|
flight_target_type = flight_type + " TGT (Airbase)"
|
||||||
|
lua_data["TargetPoints"][flight_count] = {
|
||||||
|
"name": flight_target_name,
|
||||||
|
"type": flight_target_type,
|
||||||
|
"position": {
|
||||||
|
"x": flight_target.position.x,
|
||||||
|
"y": flight_target.position.y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flight_count += 1
|
||||||
|
|
||||||
|
for cp in self.game.theater.controlpoints:
|
||||||
|
for ground_object in cp.ground_objects:
|
||||||
|
if ground_object.might_have_aa and not ground_object.is_dead:
|
||||||
|
for g in ground_object.groups:
|
||||||
|
threat_range = ground_object.threat_range(g)
|
||||||
|
|
||||||
|
if not threat_range:
|
||||||
|
continue
|
||||||
|
|
||||||
|
faction = "BlueAA" if cp.captured else "RedAA"
|
||||||
|
|
||||||
|
lua_data[faction][g.name] = {
|
||||||
|
"name": ground_object.name,
|
||||||
|
"range": threat_range.meters,
|
||||||
|
"position": {
|
||||||
|
"x": ground_object.position.x,
|
||||||
|
"y": ground_object.position.y,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# set a LUA table with data from Liberation that we want to set
|
||||||
|
# at the moment it contains Liberation's install path, and an overridable
|
||||||
|
# definition for the JTACAutoLase function later, we'll add data about the units
|
||||||
|
# and points having been generated, in order to facilitate the configuration of
|
||||||
|
# the plugin lua scripts
|
||||||
|
state_location = "[[" + os.path.abspath(".") + "]]"
|
||||||
|
lua = (
|
||||||
|
"""
|
||||||
|
-- setting configuration table
|
||||||
|
env.info("DCSLiberation|: setting configuration table")
|
||||||
|
|
||||||
|
-- all data in this table is overridable.
|
||||||
|
dcsLiberation = {}
|
||||||
|
|
||||||
|
-- the base location for state.json; if non-existent, it'll be replaced with
|
||||||
|
-- LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
||||||
|
dcsLiberation.installPath="""
|
||||||
|
+ state_location
|
||||||
|
+ """
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# Process the tankers
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the tankers generated by Liberation
|
||||||
|
dcsLiberation.Tankers = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["Tankers"]:
|
||||||
|
data = lua_data["Tankers"][key]
|
||||||
|
dcs_group_name = data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
variant = data["variant"]
|
||||||
|
tacan = data["tacan"]
|
||||||
|
radio = data["radio"]
|
||||||
|
lua += (
|
||||||
|
f" {{dcsGroupName='{dcs_group_name}', callsign='{callsign}', "
|
||||||
|
f"variant='{variant}', tacan='{tacan}', radio='{radio}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the AWACSes
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the AWACs generated by Liberation
|
||||||
|
dcsLiberation.AWACs = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["AWACs"]:
|
||||||
|
data = lua_data["AWACs"][key]
|
||||||
|
dcs_group_name = data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
radio = data["radio"]
|
||||||
|
lua += (
|
||||||
|
f" {{dcsGroupName='{dcs_group_name}', callsign='{callsign}', "
|
||||||
|
f"radio='{radio}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the JTACs
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the JTACs generated by Liberation
|
||||||
|
dcsLiberation.JTACs = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["JTACs"]:
|
||||||
|
data = lua_data["JTACs"][key]
|
||||||
|
dcs_group_name = data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
zone = data["zone"]
|
||||||
|
laser_code = data["laserCode"]
|
||||||
|
dcs_unit = data["dcsUnit"]
|
||||||
|
radio = data["radio"]
|
||||||
|
lua += (
|
||||||
|
f" {{dcsGroupName='{dcs_group_name}', callsign='{callsign}', "
|
||||||
|
f"zone={repr(zone)}, laserCode='{laser_code}', dcsUnit='{dcs_unit}', "
|
||||||
|
f"radio='{radio}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the Target Points
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the target points generated by Liberation
|
||||||
|
dcsLiberation.TargetPoints = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["TargetPoints"]:
|
||||||
|
data = lua_data["TargetPoints"][key]
|
||||||
|
name = data["name"]
|
||||||
|
point_type = data["type"]
|
||||||
|
position_x = data["position"]["x"]
|
||||||
|
position_y = data["position"]["y"]
|
||||||
|
lua += (
|
||||||
|
f" {{name='{name}', pointType='{point_type}', "
|
||||||
|
f"positionX='{position_x}', positionY='{position_y}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the airbases generated by Liberation
|
||||||
|
-- dcsLiberation.Airbases = {}
|
||||||
|
|
||||||
|
-- list the aircraft carriers generated by Liberation
|
||||||
|
-- dcsLiberation.Carriers = {}
|
||||||
|
|
||||||
|
-- list the Red AA generated by Liberation
|
||||||
|
dcsLiberation.RedAA = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["RedAA"]:
|
||||||
|
data = lua_data["RedAA"][key]
|
||||||
|
name = data["name"]
|
||||||
|
radius = data["range"]
|
||||||
|
position_x = data["position"]["x"]
|
||||||
|
position_y = data["position"]["y"]
|
||||||
|
lua += (
|
||||||
|
f" {{dcsGroupName='{key}', name='{name}', range='{radius}', "
|
||||||
|
f"positionX='{position_x}', positionY='{position_y}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the Blue AA generated by Liberation
|
||||||
|
dcsLiberation.BlueAA = {
|
||||||
|
"""
|
||||||
|
for key in lua_data["BlueAA"]:
|
||||||
|
data = lua_data["BlueAA"][key]
|
||||||
|
name = data["name"]
|
||||||
|
radius = data["range"]
|
||||||
|
position_x = data["position"]["x"]
|
||||||
|
position_y = data["position"]["y"]
|
||||||
|
lua += (
|
||||||
|
f" {{dcsGroupName='{key}', name='{name}', range='{radius}', "
|
||||||
|
f"positionX='{position_x}', positionY='{position_y}' }}, \n"
|
||||||
|
)
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
trigger = TriggerStart(comment="Set DCS Liberation data")
|
||||||
|
trigger.add_action(DoScript(String(lua)))
|
||||||
|
self.mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
|
def inject_lua_trigger(self, contents: str, comment: str) -> None:
|
||||||
|
trigger = TriggerStart(comment=comment)
|
||||||
|
trigger.add_action(DoScript(String(contents)))
|
||||||
|
self.mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
|
def bypass_plugin_script(self, mnemonic: str) -> None:
|
||||||
|
self.plugin_scripts.append(mnemonic)
|
||||||
|
|
||||||
|
def inject_plugin_script(
|
||||||
|
self, plugin_mnemonic: str, script: str, script_mnemonic: str
|
||||||
|
) -> None:
|
||||||
|
if script_mnemonic in self.plugin_scripts:
|
||||||
|
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.plugin_scripts.append(script_mnemonic)
|
||||||
|
|
||||||
|
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
||||||
|
|
||||||
|
script_path = Path(plugin_path, script)
|
||||||
|
if not script_path.exists():
|
||||||
|
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
|
||||||
|
return
|
||||||
|
|
||||||
|
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
||||||
|
filename = script_path.resolve()
|
||||||
|
fileref = self.mission.map_resource.add_resource_file(filename)
|
||||||
|
trigger.add_action(DoScriptFile(fileref))
|
||||||
|
self.mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
|
def inject_plugins(self) -> None:
|
||||||
|
for plugin in LuaPluginManager.plugins():
|
||||||
|
if plugin.enabled:
|
||||||
|
plugin.inject_scripts(self)
|
||||||
|
plugin.inject_configuration(self)
|
||||||
346
game/missiongenerator/missiongenerator.py
Normal file
346
game/missiongenerator/missiongenerator.py
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
|
import dcs.lua
|
||||||
|
from dcs import Mission, Point
|
||||||
|
from dcs.coalition import Coalition
|
||||||
|
from dcs.countries import country_dict
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||||
|
from game.radio.tacan import TacanRegistry
|
||||||
|
from game.theater.bullseye import Bullseye
|
||||||
|
from game.theater import Airfield, FrontLine
|
||||||
|
from game.unitmap import UnitMap
|
||||||
|
from gen.airfields import AIRFIELD_DATA
|
||||||
|
from gen.naming import namegen
|
||||||
|
from .aircraftgenerator import AircraftGenerator, FlightData
|
||||||
|
from .airsupport import AirSupport
|
||||||
|
from .airsupportgenerator import AirSupportGenerator
|
||||||
|
from .beacons import load_beacons_for_terrain
|
||||||
|
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
|
||||||
|
from .cargoshipgenerator import CargoShipGenerator
|
||||||
|
from .convoygenerator import ConvoyGenerator
|
||||||
|
from .environmentgenerator import EnvironmentGenerator
|
||||||
|
from .flotgenerator import FlotGenerator
|
||||||
|
from .forcedoptionsgenerator import ForcedOptionsGenerator
|
||||||
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
|
from .kneeboard import KneeboardGenerator
|
||||||
|
from .lasercoderegistry import LaserCodeRegistry
|
||||||
|
from .luagenerator import LuaGenerator
|
||||||
|
from .tgogenerator import TgoGenerator
|
||||||
|
from .triggergenerator import TriggerGenerator
|
||||||
|
from .visualsgenerator import VisualsGenerator
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
|
COMBINED_ARMS_SLOTS = 1
|
||||||
|
|
||||||
|
|
||||||
|
class MissionGenerator:
|
||||||
|
def __init__(self, game: Game) -> None:
|
||||||
|
self.game = game
|
||||||
|
self.mission = Mission(game.theater.terrain)
|
||||||
|
self.unit_map = UnitMap()
|
||||||
|
|
||||||
|
self.air_support = AirSupport()
|
||||||
|
|
||||||
|
self.laser_code_registry = LaserCodeRegistry()
|
||||||
|
self.radio_registry = RadioRegistry()
|
||||||
|
self.tacan_registry = TacanRegistry()
|
||||||
|
|
||||||
|
self.generation_started = False
|
||||||
|
|
||||||
|
with open("resources/default_options.lua", "r", encoding="utf-8") as f:
|
||||||
|
self.mission.options.load_from_dict(dcs.lua.loads(f.read())["options"])
|
||||||
|
|
||||||
|
def generate_miz(self, output: Path) -> UnitMap:
|
||||||
|
if self.generation_started:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Mission has already begun generating. To reset, create a new "
|
||||||
|
"MissionSimulation."
|
||||||
|
)
|
||||||
|
self.generation_started = True
|
||||||
|
|
||||||
|
self.setup_mission_coalitions()
|
||||||
|
self.add_airfields_to_unit_map()
|
||||||
|
self.initialize_registries()
|
||||||
|
|
||||||
|
EnvironmentGenerator(self.mission, self.game.conditions).generate()
|
||||||
|
|
||||||
|
tgo_generator = TgoGenerator(
|
||||||
|
self.mission,
|
||||||
|
self.game,
|
||||||
|
self.radio_registry,
|
||||||
|
self.tacan_registry,
|
||||||
|
self.unit_map,
|
||||||
|
)
|
||||||
|
tgo_generator.generate()
|
||||||
|
|
||||||
|
ConvoyGenerator(self.mission, self.game, self.unit_map).generate()
|
||||||
|
CargoShipGenerator(self.mission, self.game, self.unit_map).generate()
|
||||||
|
|
||||||
|
self.generate_destroyed_units()
|
||||||
|
|
||||||
|
# Generate ground conflicts first so the JTACs get the first laser code (1688)
|
||||||
|
# rather than the first player flight with a TGP.
|
||||||
|
self.generate_ground_conflicts()
|
||||||
|
air_support, flights = self.generate_air_units(tgo_generator)
|
||||||
|
|
||||||
|
TriggerGenerator(self.mission, self.game).generate()
|
||||||
|
ForcedOptionsGenerator(self.mission, self.game).generate()
|
||||||
|
VisualsGenerator(self.mission, self.game).generate()
|
||||||
|
LuaGenerator(self.game, self.mission, air_support, flights).generate()
|
||||||
|
|
||||||
|
self.setup_combined_arms()
|
||||||
|
|
||||||
|
self.notify_info_generators(tgo_generator, air_support, flights)
|
||||||
|
|
||||||
|
# TODO: Shouldn't this be first?
|
||||||
|
namegen.reset_numbers()
|
||||||
|
self.mission.save(output)
|
||||||
|
|
||||||
|
return self.unit_map
|
||||||
|
|
||||||
|
def setup_mission_coalitions(self) -> None:
|
||||||
|
self.mission.coalition["blue"] = Coalition(
|
||||||
|
"blue", bullseye=self.game.blue.bullseye.to_pydcs()
|
||||||
|
)
|
||||||
|
self.mission.coalition["red"] = Coalition(
|
||||||
|
"red", bullseye=self.game.red.bullseye.to_pydcs()
|
||||||
|
)
|
||||||
|
self.mission.coalition["neutrals"] = Coalition(
|
||||||
|
"neutrals", bullseye=Bullseye(Point(0, 0)).to_pydcs()
|
||||||
|
)
|
||||||
|
|
||||||
|
p_country = self.game.blue.country_name
|
||||||
|
e_country = self.game.red.country_name
|
||||||
|
self.mission.coalition["blue"].add_country(
|
||||||
|
country_dict[db.country_id_from_name(p_country)]()
|
||||||
|
)
|
||||||
|
self.mission.coalition["red"].add_country(
|
||||||
|
country_dict[db.country_id_from_name(e_country)]()
|
||||||
|
)
|
||||||
|
|
||||||
|
belligerents = [
|
||||||
|
db.country_id_from_name(p_country),
|
||||||
|
db.country_id_from_name(e_country),
|
||||||
|
]
|
||||||
|
for country in country_dict.keys():
|
||||||
|
if country not in belligerents:
|
||||||
|
self.mission.coalition["neutrals"].add_country(country_dict[country]())
|
||||||
|
|
||||||
|
def add_airfields_to_unit_map(self) -> None:
|
||||||
|
for control_point in self.game.theater.controlpoints:
|
||||||
|
if isinstance(control_point, Airfield):
|
||||||
|
self.unit_map.add_airfield(control_point)
|
||||||
|
|
||||||
|
def initialize_registries(self) -> None:
|
||||||
|
unique_map_frequencies: set[RadioFrequency] = set()
|
||||||
|
self.initialize_tacan_registry(unique_map_frequencies)
|
||||||
|
self.initialize_radio_registry(unique_map_frequencies)
|
||||||
|
for frequency in unique_map_frequencies:
|
||||||
|
self.radio_registry.reserve(frequency)
|
||||||
|
|
||||||
|
def initialize_tacan_registry(
|
||||||
|
self, unique_map_frequencies: set[RadioFrequency]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Dedup beacon/radio frequencies, since some maps have some frequencies
|
||||||
|
used multiple times.
|
||||||
|
"""
|
||||||
|
beacons = load_beacons_for_terrain(self.game.theater.terrain.name)
|
||||||
|
for beacon in beacons:
|
||||||
|
unique_map_frequencies.add(beacon.frequency)
|
||||||
|
if beacon.is_tacan:
|
||||||
|
if beacon.channel is None:
|
||||||
|
logging.warning(f"TACAN beacon has no channel: {beacon.callsign}")
|
||||||
|
else:
|
||||||
|
self.tacan_registry.mark_unavailable(beacon.tacan_channel)
|
||||||
|
|
||||||
|
def initialize_radio_registry(
|
||||||
|
self, unique_map_frequencies: set[RadioFrequency]
|
||||||
|
) -> None:
|
||||||
|
for data in AIRFIELD_DATA.values():
|
||||||
|
if data.theater == self.game.theater.terrain.name and data.atc:
|
||||||
|
unique_map_frequencies.add(data.atc.hf)
|
||||||
|
unique_map_frequencies.add(data.atc.vhf_fm)
|
||||||
|
unique_map_frequencies.add(data.atc.vhf_am)
|
||||||
|
unique_map_frequencies.add(data.atc.uhf)
|
||||||
|
# No need to reserve ILS or TACAN because those are in the
|
||||||
|
# beacon list.
|
||||||
|
|
||||||
|
def generate_ground_conflicts(self) -> None:
|
||||||
|
"""Generate FLOTs and JTACs for each active front line."""
|
||||||
|
for front_line in self.game.theater.conflicts():
|
||||||
|
player_cp = front_line.blue_cp
|
||||||
|
enemy_cp = front_line.red_cp
|
||||||
|
conflict = FrontLineConflictDescription.frontline_cas_conflict(
|
||||||
|
self.game.blue.faction.name,
|
||||||
|
self.game.red.faction.name,
|
||||||
|
self.mission.country(self.game.blue.country_name),
|
||||||
|
self.mission.country(self.game.red.country_name),
|
||||||
|
front_line,
|
||||||
|
self.game.theater,
|
||||||
|
)
|
||||||
|
# Generate frontline ops
|
||||||
|
player_gp = self.game.ground_planners[player_cp.id].units_per_cp[
|
||||||
|
enemy_cp.id
|
||||||
|
]
|
||||||
|
enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
|
||||||
|
ground_conflict_gen = FlotGenerator(
|
||||||
|
self.mission,
|
||||||
|
conflict,
|
||||||
|
self.game,
|
||||||
|
player_gp,
|
||||||
|
enemy_gp,
|
||||||
|
player_cp.stances[enemy_cp.id],
|
||||||
|
enemy_cp.stances[player_cp.id],
|
||||||
|
self.unit_map,
|
||||||
|
self.radio_registry,
|
||||||
|
self.air_support,
|
||||||
|
self.laser_code_registry,
|
||||||
|
)
|
||||||
|
ground_conflict_gen.generate()
|
||||||
|
|
||||||
|
def generate_air_units(
|
||||||
|
self, tgo_generator: TgoGenerator
|
||||||
|
) -> tuple[AirSupport, list[FlightData]]:
|
||||||
|
"""Generate the air units for the Operation"""
|
||||||
|
|
||||||
|
# Air Support (Tanker & Awacs)
|
||||||
|
air_support_generator = AirSupportGenerator(
|
||||||
|
self.mission,
|
||||||
|
self.describe_air_conflict(),
|
||||||
|
self.game,
|
||||||
|
self.radio_registry,
|
||||||
|
self.tacan_registry,
|
||||||
|
self.air_support,
|
||||||
|
)
|
||||||
|
air_support_generator.generate()
|
||||||
|
|
||||||
|
# Generate Aircraft Activity on the map
|
||||||
|
aircraft_generator = AircraftGenerator(
|
||||||
|
self.mission,
|
||||||
|
self.game.settings,
|
||||||
|
self.game,
|
||||||
|
self.radio_registry,
|
||||||
|
self.tacan_registry,
|
||||||
|
self.laser_code_registry,
|
||||||
|
self.unit_map,
|
||||||
|
air_support=air_support_generator.air_support,
|
||||||
|
helipads=tgo_generator.helipads,
|
||||||
|
)
|
||||||
|
|
||||||
|
aircraft_generator.clear_parking_slots()
|
||||||
|
|
||||||
|
aircraft_generator.generate_flights(
|
||||||
|
self.mission.country(self.game.blue.country_name),
|
||||||
|
self.game.blue.ato,
|
||||||
|
tgo_generator.runways,
|
||||||
|
)
|
||||||
|
aircraft_generator.generate_flights(
|
||||||
|
self.mission.country(self.game.red.country_name),
|
||||||
|
self.game.red.ato,
|
||||||
|
tgo_generator.runways,
|
||||||
|
)
|
||||||
|
aircraft_generator.spawn_unused_aircraft(
|
||||||
|
self.mission.country(self.game.blue.country_name),
|
||||||
|
self.mission.country(self.game.red.country_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
for flight in aircraft_generator.flights:
|
||||||
|
if not flight.client_units:
|
||||||
|
continue
|
||||||
|
flight.aircraft_type.assign_channels_for_flight(
|
||||||
|
flight, air_support_generator.air_support
|
||||||
|
)
|
||||||
|
|
||||||
|
return air_support_generator.air_support, aircraft_generator.flights
|
||||||
|
|
||||||
|
def generate_destroyed_units(self) -> None:
|
||||||
|
"""Add destroyed units to the Mission"""
|
||||||
|
if not self.game.settings.perf_destroyed_units:
|
||||||
|
return
|
||||||
|
|
||||||
|
for d in self.game.get_destroyed_units():
|
||||||
|
try:
|
||||||
|
type_name = d["type"]
|
||||||
|
if not isinstance(type_name, str):
|
||||||
|
raise TypeError(
|
||||||
|
"Expected the type of the destroyed static to be a string"
|
||||||
|
)
|
||||||
|
utype = db.unit_type_from_name(type_name)
|
||||||
|
except KeyError:
|
||||||
|
logging.warning(f"Destroyed unit has no type: {d}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
pos = Point(cast(float, d["x"]), cast(float, d["z"]))
|
||||||
|
if utype is not None and not self.game.position_culled(pos):
|
||||||
|
self.mission.static_group(
|
||||||
|
country=self.mission.country(self.game.blue.country_name),
|
||||||
|
name="",
|
||||||
|
_type=utype,
|
||||||
|
hidden=True,
|
||||||
|
position=pos,
|
||||||
|
heading=d["orientation"],
|
||||||
|
dead=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def describe_air_conflict(self) -> FrontLineConflictDescription:
|
||||||
|
player_cp, enemy_cp = self.game.theater.closest_opposing_control_points()
|
||||||
|
mid_point = player_cp.position.point_from_heading(
|
||||||
|
player_cp.position.heading_between_point(enemy_cp.position),
|
||||||
|
player_cp.position.distance_to_point(enemy_cp.position) / 2,
|
||||||
|
)
|
||||||
|
return FrontLineConflictDescription(
|
||||||
|
self.game.theater,
|
||||||
|
FrontLine(player_cp, enemy_cp),
|
||||||
|
self.game.blue.faction.name,
|
||||||
|
self.game.red.faction.name,
|
||||||
|
self.mission.country(self.game.blue.country_name),
|
||||||
|
self.mission.country(self.game.red.country_name),
|
||||||
|
mid_point,
|
||||||
|
)
|
||||||
|
|
||||||
|
def notify_info_generators(
|
||||||
|
self,
|
||||||
|
tgo_generator: TgoGenerator,
|
||||||
|
air_support: AirSupport,
|
||||||
|
flights: list[FlightData],
|
||||||
|
) -> None:
|
||||||
|
"""Generates subscribed MissionInfoGenerator objects."""
|
||||||
|
|
||||||
|
gens: list[MissionInfoGenerator] = [
|
||||||
|
KneeboardGenerator(self.mission, self.game),
|
||||||
|
BriefingGenerator(self.mission, self.game),
|
||||||
|
]
|
||||||
|
for gen in gens:
|
||||||
|
for dynamic_runway in tgo_generator.runways.values():
|
||||||
|
gen.add_dynamic_runway(dynamic_runway)
|
||||||
|
|
||||||
|
for tanker in air_support.tankers:
|
||||||
|
if tanker.blue:
|
||||||
|
gen.add_tanker(tanker)
|
||||||
|
|
||||||
|
for aewc in air_support.awacs:
|
||||||
|
if aewc.blue:
|
||||||
|
gen.add_awacs(aewc)
|
||||||
|
|
||||||
|
for jtac in air_support.jtacs:
|
||||||
|
if jtac.blue:
|
||||||
|
gen.add_jtac(jtac)
|
||||||
|
|
||||||
|
for flight in flights:
|
||||||
|
gen.add_flight(flight)
|
||||||
|
gen.generate()
|
||||||
|
|
||||||
|
def setup_combined_arms(self) -> None:
|
||||||
|
self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0
|
||||||
|
self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS
|
||||||
|
self.mission.groundControl.blue_observer = 1
|
||||||
@ -57,9 +57,9 @@ from game.theater.theatergroundobject import (
|
|||||||
)
|
)
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from game.utils import Heading, feet, knots, mps
|
from game.utils import Heading, feet, knots, mps
|
||||||
from .radios import RadioFrequency, RadioRegistry
|
from game.radio.radios import RadioFrequency, RadioRegistry
|
||||||
from .runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
from .tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -183,7 +183,6 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
|
|||||||
def possible_missile_targets(self) -> List[Point]:
|
def possible_missile_targets(self) -> List[Point]:
|
||||||
"""
|
"""
|
||||||
Find enemy control points in range
|
Find enemy control points in range
|
||||||
:param vg: Vehicle group we are searching a target for (There is always only oe group right now)
|
|
||||||
:return: List of possible missile targets
|
:return: List of possible missile targets
|
||||||
"""
|
"""
|
||||||
targets: List[Point] = []
|
targets: List[Point] = []
|
||||||
@ -312,8 +311,8 @@ class SceneryGenerator(BuildingSiteGenerator):
|
|||||||
else:
|
else:
|
||||||
color = {1: 1, 2: 0.2, 3: 0.2, 4: 0.15}
|
color = {1: 1, 2: 0.2, 3: 0.2, 4: 0.15}
|
||||||
|
|
||||||
# Create the smallest valid size trigger zone (16 feet) so that risk of overlap is minimized.
|
# Create the smallest valid size trigger zone (16 feet) so that risk of overlap
|
||||||
# As long as the triggerzone is over the scenery object, we're ok.
|
# is minimized. As long as the triggerzone is over the scenery object, we're ok.
|
||||||
smallest_valid_radius = feet(16).meters
|
smallest_valid_radius = feet(16).meters
|
||||||
|
|
||||||
return self.m.triggers.add_triggerzone(
|
return self.m.triggers.add_triggerzone(
|
||||||
@ -594,7 +593,8 @@ class HelipadGenerator:
|
|||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
|
|
||||||
# Note : Helipad are generated as neutral object in order not to interfer with capture triggers
|
# Note: Helipad are generated as neutral object in order not to interfer with
|
||||||
|
# capture triggers
|
||||||
neutral_country = self.m.country(self.game.neutral_country.name)
|
neutral_country = self.m.country(self.game.neutral_country.name)
|
||||||
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
|
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
|
||||||
for i, helipad in enumerate(self.cp.helipads):
|
for i, helipad in enumerate(self.cp.helipads):
|
||||||
@ -631,7 +631,7 @@ class HelipadGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GroundObjectsGenerator:
|
class TgoGenerator:
|
||||||
"""Creates DCS groups and statics for the theater during mission generation.
|
"""Creates DCS groups and statics for the theater during mission generation.
|
||||||
|
|
||||||
Most of the work of group/static generation is delegated to the other
|
Most of the work of group/static generation is delegated to the other
|
||||||
@ -47,7 +47,7 @@ class Silence(Option):
|
|||||||
Key = 7
|
Key = 7
|
||||||
|
|
||||||
|
|
||||||
class TriggersGenerator:
|
class TriggerGenerator:
|
||||||
capture_zone_types = (Fob,)
|
capture_zone_types = (Fob,)
|
||||||
capture_zone_flag = 600
|
capture_zone_flag = 600
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ from dcs.unittype import StaticType
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
from .conflictgen import Conflict
|
from .frontlineconflictdescription import FrontLineConflictDescription
|
||||||
|
|
||||||
|
|
||||||
class MarkerSmoke(StaticType):
|
class MarkerSmoke(StaticType):
|
||||||
@ -67,7 +67,7 @@ FRONT_SMOKE_TYPE_CHANCES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class VisualGenerator:
|
class VisualsGenerator:
|
||||||
def __init__(self, mission: Mission, game: Game) -> None:
|
def __init__(self, mission: Mission, game: Game) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -79,7 +79,11 @@ class VisualGenerator:
|
|||||||
if from_cp.is_global or to_cp.is_global:
|
if from_cp.is_global or to_cp.is_global:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
plane_start, heading, distance = Conflict.frontline_vector(
|
(
|
||||||
|
plane_start,
|
||||||
|
heading,
|
||||||
|
distance,
|
||||||
|
) = FrontLineConflictDescription.frontline_vector(
|
||||||
front_line, self.game.theater
|
front_line, self.game.theater
|
||||||
)
|
)
|
||||||
if not plane_start:
|
if not plane_start:
|
||||||
@ -105,4 +109,5 @@ class VisualGenerator:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
|
if self.game.settings.perf_smoke_gen:
|
||||||
self._generate_frontline_smokes()
|
self._generate_frontline_smokes()
|
||||||
@ -1,659 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Set, TYPE_CHECKING, cast
|
|
||||||
|
|
||||||
from dcs import Mission
|
|
||||||
from dcs.action import DoScript, DoScriptFile
|
|
||||||
from dcs.coalition import Coalition
|
|
||||||
from dcs.countries import country_dict
|
|
||||||
from dcs.lua.parse import loads
|
|
||||||
from dcs.mapping import Point
|
|
||||||
from dcs.translation import String
|
|
||||||
from dcs.triggers import TriggerStart
|
|
||||||
|
|
||||||
from game.plugins import LuaPluginManager
|
|
||||||
from game.theater.theatergroundobject import TheaterGroundObject
|
|
||||||
from gen.aircraft import AircraftConflictGenerator, FlightData
|
|
||||||
from gen.airfields import AIRFIELD_DATA
|
|
||||||
from gen.airsupport import AirSupport
|
|
||||||
from gen.airsupportgen import AirSupportConflictGenerator
|
|
||||||
from gen.armor import GroundConflictGenerator
|
|
||||||
from gen.beacons import load_beacons_for_terrain
|
|
||||||
from gen.briefinggen import BriefingGenerator, MissionInfoGenerator
|
|
||||||
from gen.cargoshipgen import CargoShipGenerator
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
from gen.convoygen import ConvoyGenerator
|
|
||||||
from gen.environmentgen import EnvironmentGenerator
|
|
||||||
from ..ato.flighttype import FlightType
|
|
||||||
from gen.forcedoptionsgen import ForcedOptionsGenerator
|
|
||||||
from gen.groundobjectsgen import GroundObjectsGenerator
|
|
||||||
from gen.kneeboard import KneeboardGenerator
|
|
||||||
from gen.lasercoderegistry import LaserCodeRegistry
|
|
||||||
from gen.naming import namegen
|
|
||||||
from gen.radios import RadioFrequency, RadioRegistry
|
|
||||||
from gen.tacan import TacanRegistry, TacanUsage
|
|
||||||
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
|
||||||
from gen.visualgen import VisualGenerator
|
|
||||||
from .. import db
|
|
||||||
from ..theater import Airfield, FrontLine
|
|
||||||
from ..theater.bullseye import Bullseye
|
|
||||||
from ..unitmap import UnitMap
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from game import Game
|
|
||||||
|
|
||||||
|
|
||||||
class Operation:
|
|
||||||
"""Static class for managing the final Mission generation"""
|
|
||||||
|
|
||||||
current_mission: Mission
|
|
||||||
airgen: AircraftConflictGenerator
|
|
||||||
airsupportgen: AirSupportConflictGenerator
|
|
||||||
groundobjectgen: GroundObjectsGenerator
|
|
||||||
radio_registry: RadioRegistry
|
|
||||||
tacan_registry: TacanRegistry
|
|
||||||
laser_code_registry: LaserCodeRegistry
|
|
||||||
game: Game
|
|
||||||
trigger_radius = TRIGGER_RADIUS_MEDIUM
|
|
||||||
is_quick = None
|
|
||||||
player_awacs_enabled = True
|
|
||||||
# TODO: #436 Generate Air Support for red
|
|
||||||
enemy_awacs_enabled = True
|
|
||||||
ca_slots = 1
|
|
||||||
unit_map: UnitMap
|
|
||||||
plugin_scripts: List[str] = []
|
|
||||||
air_support = AirSupport()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def prepare(cls, game: Game) -> None:
|
|
||||||
with open("resources/default_options.lua", "r", encoding="utf-8") as f:
|
|
||||||
options_dict = loads(f.read())["options"]
|
|
||||||
cls._set_mission(Mission(game.theater.terrain))
|
|
||||||
cls.game = game
|
|
||||||
cls._setup_mission_coalitions()
|
|
||||||
cls.current_mission.options.load_from_dict(options_dict)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def air_conflict(cls) -> Conflict:
|
|
||||||
assert cls.game
|
|
||||||
player_cp, enemy_cp = cls.game.theater.closest_opposing_control_points()
|
|
||||||
mid_point = player_cp.position.point_from_heading(
|
|
||||||
player_cp.position.heading_between_point(enemy_cp.position),
|
|
||||||
player_cp.position.distance_to_point(enemy_cp.position) / 2,
|
|
||||||
)
|
|
||||||
return Conflict(
|
|
||||||
cls.game.theater,
|
|
||||||
FrontLine(player_cp, enemy_cp),
|
|
||||||
cls.game.blue.faction.name,
|
|
||||||
cls.game.red.faction.name,
|
|
||||||
cls.current_mission.country(cls.game.blue.country_name),
|
|
||||||
cls.current_mission.country(cls.game.red.country_name),
|
|
||||||
mid_point,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _set_mission(cls, mission: Mission) -> None:
|
|
||||||
cls.current_mission = mission
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _setup_mission_coalitions(cls) -> None:
|
|
||||||
cls.current_mission.coalition["blue"] = Coalition(
|
|
||||||
"blue", bullseye=cls.game.blue.bullseye.to_pydcs()
|
|
||||||
)
|
|
||||||
cls.current_mission.coalition["red"] = Coalition(
|
|
||||||
"red", bullseye=cls.game.red.bullseye.to_pydcs()
|
|
||||||
)
|
|
||||||
cls.current_mission.coalition["neutrals"] = Coalition(
|
|
||||||
"neutrals", bullseye=Bullseye(Point(0, 0)).to_pydcs()
|
|
||||||
)
|
|
||||||
|
|
||||||
p_country = cls.game.blue.country_name
|
|
||||||
e_country = cls.game.red.country_name
|
|
||||||
cls.current_mission.coalition["blue"].add_country(
|
|
||||||
country_dict[db.country_id_from_name(p_country)]()
|
|
||||||
)
|
|
||||||
cls.current_mission.coalition["red"].add_country(
|
|
||||||
country_dict[db.country_id_from_name(e_country)]()
|
|
||||||
)
|
|
||||||
|
|
||||||
belligerents = [
|
|
||||||
db.country_id_from_name(p_country),
|
|
||||||
db.country_id_from_name(e_country),
|
|
||||||
]
|
|
||||||
for country in country_dict.keys():
|
|
||||||
if country not in belligerents:
|
|
||||||
cls.current_mission.coalition["neutrals"].add_country(
|
|
||||||
country_dict[country]()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
|
||||||
trigger = TriggerStart(comment=comment)
|
|
||||||
trigger.add_action(DoScript(String(contents)))
|
|
||||||
cls.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def bypass_plugin_script(cls, mnemonic: str) -> None:
|
|
||||||
cls.plugin_scripts.append(mnemonic)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def inject_plugin_script(
|
|
||||||
cls, plugin_mnemonic: str, script: str, script_mnemonic: str
|
|
||||||
) -> None:
|
|
||||||
if script_mnemonic in cls.plugin_scripts:
|
|
||||||
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
|
||||||
else:
|
|
||||||
cls.plugin_scripts.append(script_mnemonic)
|
|
||||||
|
|
||||||
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
|
||||||
|
|
||||||
script_path = Path(plugin_path, script)
|
|
||||||
if not script_path.exists():
|
|
||||||
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
|
|
||||||
return
|
|
||||||
|
|
||||||
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
|
||||||
filename = script_path.resolve()
|
|
||||||
fileref = cls.current_mission.map_resource.add_resource_file(filename)
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
cls.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def notify_info_generators(
|
|
||||||
cls,
|
|
||||||
groundobjectgen: GroundObjectsGenerator,
|
|
||||||
air_support: AirSupport,
|
|
||||||
airgen: AircraftConflictGenerator,
|
|
||||||
) -> None:
|
|
||||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
|
||||||
|
|
||||||
gens: List[MissionInfoGenerator] = [
|
|
||||||
KneeboardGenerator(cls.current_mission, cls.game),
|
|
||||||
BriefingGenerator(cls.current_mission, cls.game),
|
|
||||||
]
|
|
||||||
for gen in gens:
|
|
||||||
for dynamic_runway in groundobjectgen.runways.values():
|
|
||||||
gen.add_dynamic_runway(dynamic_runway)
|
|
||||||
|
|
||||||
for tanker in air_support.tankers:
|
|
||||||
if tanker.blue:
|
|
||||||
gen.add_tanker(tanker)
|
|
||||||
|
|
||||||
for aewc in air_support.awacs:
|
|
||||||
if aewc.blue:
|
|
||||||
gen.add_awacs(aewc)
|
|
||||||
|
|
||||||
for jtac in air_support.jtacs:
|
|
||||||
if jtac.blue:
|
|
||||||
gen.add_jtac(jtac)
|
|
||||||
|
|
||||||
for flight in airgen.flights:
|
|
||||||
gen.add_flight(flight)
|
|
||||||
gen.generate()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_unit_map(cls) -> None:
|
|
||||||
cls.unit_map = UnitMap()
|
|
||||||
for control_point in cls.game.theater.controlpoints:
|
|
||||||
if isinstance(control_point, Airfield):
|
|
||||||
cls.unit_map.add_airfield(control_point)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_radio_registries(cls) -> None:
|
|
||||||
unique_map_frequencies: Set[RadioFrequency] = set()
|
|
||||||
cls._create_tacan_registry(unique_map_frequencies)
|
|
||||||
cls._create_radio_registry(unique_map_frequencies)
|
|
||||||
|
|
||||||
assert cls.radio_registry is not None
|
|
||||||
for frequency in unique_map_frequencies:
|
|
||||||
cls.radio_registry.reserve(frequency)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_laser_code_registry(cls) -> None:
|
|
||||||
cls.laser_code_registry = LaserCodeRegistry()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def assign_channels_to_flights(
|
|
||||||
cls, flights: List[FlightData], air_support: AirSupport
|
|
||||||
) -> None:
|
|
||||||
"""Assigns preset radio channels for client flights."""
|
|
||||||
for flight in flights:
|
|
||||||
if not flight.client_units:
|
|
||||||
continue
|
|
||||||
flight.aircraft_type.assign_channels_for_flight(flight, air_support)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _create_tacan_registry(
|
|
||||||
cls, unique_map_frequencies: Set[RadioFrequency]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Dedup beacon/radio frequencies, since some maps have some frequencies
|
|
||||||
used multiple times.
|
|
||||||
"""
|
|
||||||
cls.tacan_registry = TacanRegistry()
|
|
||||||
beacons = load_beacons_for_terrain(cls.game.theater.terrain.name)
|
|
||||||
|
|
||||||
for beacon in beacons:
|
|
||||||
unique_map_frequencies.add(beacon.frequency)
|
|
||||||
if beacon.is_tacan:
|
|
||||||
if beacon.channel is None:
|
|
||||||
logging.error(f"TACAN beacon has no channel: {beacon.callsign}")
|
|
||||||
else:
|
|
||||||
cls.tacan_registry.mark_unavailable(beacon.tacan_channel)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _create_radio_registry(
|
|
||||||
cls, unique_map_frequencies: Set[RadioFrequency]
|
|
||||||
) -> None:
|
|
||||||
cls.radio_registry = RadioRegistry()
|
|
||||||
for data in AIRFIELD_DATA.values():
|
|
||||||
if data.theater == cls.game.theater.terrain.name and data.atc:
|
|
||||||
unique_map_frequencies.add(data.atc.hf)
|
|
||||||
unique_map_frequencies.add(data.atc.vhf_fm)
|
|
||||||
unique_map_frequencies.add(data.atc.vhf_am)
|
|
||||||
unique_map_frequencies.add(data.atc.uhf)
|
|
||||||
# No need to reserve ILS or TACAN because those are in the
|
|
||||||
# beacon list.
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_ground_units(cls) -> None:
|
|
||||||
cls.groundobjectgen = GroundObjectsGenerator(
|
|
||||||
cls.current_mission,
|
|
||||||
cls.game,
|
|
||||||
cls.radio_registry,
|
|
||||||
cls.tacan_registry,
|
|
||||||
cls.unit_map,
|
|
||||||
)
|
|
||||||
cls.groundobjectgen.generate()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_destroyed_units(cls) -> None:
|
|
||||||
"""Add destroyed units to the Mission"""
|
|
||||||
for d in cls.game.get_destroyed_units():
|
|
||||||
try:
|
|
||||||
type_name = d["type"]
|
|
||||||
if not isinstance(type_name, str):
|
|
||||||
raise TypeError(
|
|
||||||
"Expected the type of the destroyed static to be a string"
|
|
||||||
)
|
|
||||||
utype = db.unit_type_from_name(type_name)
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
pos = Point(cast(float, d["x"]), cast(float, d["z"]))
|
|
||||||
if (
|
|
||||||
utype is not None
|
|
||||||
and not cls.game.position_culled(pos)
|
|
||||||
and cls.game.settings.perf_destroyed_units
|
|
||||||
):
|
|
||||||
cls.current_mission.static_group(
|
|
||||||
country=cls.current_mission.country(cls.game.blue.country_name),
|
|
||||||
name="",
|
|
||||||
_type=utype,
|
|
||||||
hidden=True,
|
|
||||||
position=pos,
|
|
||||||
heading=d["orientation"],
|
|
||||||
dead=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def generate(cls) -> UnitMap:
|
|
||||||
"""Build the final Mission to be exported"""
|
|
||||||
cls.air_support = AirSupport()
|
|
||||||
cls.create_unit_map()
|
|
||||||
cls.create_radio_registries()
|
|
||||||
cls.create_laser_code_registry()
|
|
||||||
# Set mission time and weather conditions.
|
|
||||||
EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate()
|
|
||||||
cls._generate_ground_units()
|
|
||||||
cls._generate_transports()
|
|
||||||
cls._generate_destroyed_units()
|
|
||||||
# Generate ground conflicts first so the JTACs get the first laser code (1688)
|
|
||||||
# rather than the first player flight with a TGP.
|
|
||||||
cls._generate_ground_conflicts()
|
|
||||||
cls._generate_air_units()
|
|
||||||
cls.assign_channels_to_flights(
|
|
||||||
cls.airgen.flights, cls.airsupportgen.air_support
|
|
||||||
)
|
|
||||||
|
|
||||||
# Triggers
|
|
||||||
triggersgen = TriggersGenerator(cls.current_mission, cls.game)
|
|
||||||
triggersgen.generate()
|
|
||||||
|
|
||||||
# Setup combined arms parameters
|
|
||||||
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
|
|
||||||
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
|
|
||||||
cls.current_mission.groundControl.blue_observer = 1
|
|
||||||
|
|
||||||
# Options
|
|
||||||
forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game)
|
|
||||||
forcedoptionsgen.generate()
|
|
||||||
|
|
||||||
# Generate Visuals Smoke Effects
|
|
||||||
visualgen = VisualGenerator(cls.current_mission, cls.game)
|
|
||||||
if cls.game.settings.perf_smoke_gen:
|
|
||||||
visualgen.generate()
|
|
||||||
|
|
||||||
cls.generate_lua(cls.airgen, cls.air_support)
|
|
||||||
|
|
||||||
# Inject Plugins Lua Scripts and data
|
|
||||||
cls.plugin_scripts.clear()
|
|
||||||
for plugin in LuaPluginManager.plugins():
|
|
||||||
if plugin.enabled:
|
|
||||||
plugin.inject_scripts(cls)
|
|
||||||
plugin.inject_configuration(cls)
|
|
||||||
|
|
||||||
cls.assign_channels_to_flights(
|
|
||||||
cls.airgen.flights, cls.airsupportgen.air_support
|
|
||||||
)
|
|
||||||
cls.notify_info_generators(cls.groundobjectgen, cls.air_support, cls.airgen)
|
|
||||||
cls.reset_naming_ids()
|
|
||||||
return cls.unit_map
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_air_units(cls) -> None:
|
|
||||||
"""Generate the air units for the Operation"""
|
|
||||||
|
|
||||||
# Air Support (Tanker & Awacs)
|
|
||||||
assert cls.radio_registry and cls.tacan_registry
|
|
||||||
cls.airsupportgen = AirSupportConflictGenerator(
|
|
||||||
cls.current_mission,
|
|
||||||
cls.air_conflict(),
|
|
||||||
cls.game,
|
|
||||||
cls.radio_registry,
|
|
||||||
cls.tacan_registry,
|
|
||||||
cls.air_support,
|
|
||||||
)
|
|
||||||
cls.airsupportgen.generate()
|
|
||||||
|
|
||||||
# Generate Aircraft Activity on the map
|
|
||||||
cls.airgen = AircraftConflictGenerator(
|
|
||||||
cls.current_mission,
|
|
||||||
cls.game.settings,
|
|
||||||
cls.game,
|
|
||||||
cls.radio_registry,
|
|
||||||
cls.tacan_registry,
|
|
||||||
cls.laser_code_registry,
|
|
||||||
cls.unit_map,
|
|
||||||
air_support=cls.airsupportgen.air_support,
|
|
||||||
helipads=cls.groundobjectgen.helipads,
|
|
||||||
)
|
|
||||||
|
|
||||||
cls.airgen.clear_parking_slots()
|
|
||||||
|
|
||||||
cls.airgen.generate_flights(
|
|
||||||
cls.current_mission.country(cls.game.blue.country_name),
|
|
||||||
cls.game.blue.ato,
|
|
||||||
cls.groundobjectgen.runways,
|
|
||||||
)
|
|
||||||
cls.airgen.generate_flights(
|
|
||||||
cls.current_mission.country(cls.game.red.country_name),
|
|
||||||
cls.game.red.ato,
|
|
||||||
cls.groundobjectgen.runways,
|
|
||||||
)
|
|
||||||
cls.airgen.spawn_unused_aircraft(
|
|
||||||
cls.current_mission.country(cls.game.blue.country_name),
|
|
||||||
cls.current_mission.country(cls.game.red.country_name),
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_ground_conflicts(cls) -> None:
|
|
||||||
"""For each frontline in the Operation, generate the ground conflicts and JTACs"""
|
|
||||||
for front_line in cls.game.theater.conflicts():
|
|
||||||
player_cp = front_line.blue_cp
|
|
||||||
enemy_cp = front_line.red_cp
|
|
||||||
conflict = Conflict.frontline_cas_conflict(
|
|
||||||
cls.game.blue.faction.name,
|
|
||||||
cls.game.red.faction.name,
|
|
||||||
cls.current_mission.country(cls.game.blue.country_name),
|
|
||||||
cls.current_mission.country(cls.game.red.country_name),
|
|
||||||
front_line,
|
|
||||||
cls.game.theater,
|
|
||||||
)
|
|
||||||
# Generate frontline ops
|
|
||||||
player_gp = cls.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
|
|
||||||
enemy_gp = cls.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
|
|
||||||
ground_conflict_gen = GroundConflictGenerator(
|
|
||||||
cls.current_mission,
|
|
||||||
conflict,
|
|
||||||
cls.game,
|
|
||||||
player_gp,
|
|
||||||
enemy_gp,
|
|
||||||
player_cp.stances[enemy_cp.id],
|
|
||||||
enemy_cp.stances[player_cp.id],
|
|
||||||
cls.unit_map,
|
|
||||||
cls.radio_registry,
|
|
||||||
cls.air_support,
|
|
||||||
cls.laser_code_registry,
|
|
||||||
)
|
|
||||||
ground_conflict_gen.generate()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _generate_transports(cls) -> None:
|
|
||||||
"""Generates convoys for unit transfers by road."""
|
|
||||||
ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate()
|
|
||||||
CargoShipGenerator(cls.current_mission, cls.game, cls.unit_map).generate()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reset_naming_ids(cls) -> None:
|
|
||||||
namegen.reset_numbers()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def generate_lua(
|
|
||||||
cls, airgen: AircraftConflictGenerator, air_support: AirSupport
|
|
||||||
) -> None:
|
|
||||||
# TODO: Refactor this
|
|
||||||
luaData = {
|
|
||||||
"AircraftCarriers": {},
|
|
||||||
"Tankers": {},
|
|
||||||
"AWACs": {},
|
|
||||||
"JTACs": {},
|
|
||||||
"TargetPoints": {},
|
|
||||||
"RedAA": {},
|
|
||||||
"BlueAA": {},
|
|
||||||
} # type: ignore
|
|
||||||
|
|
||||||
for i, tanker in enumerate(air_support.tankers):
|
|
||||||
luaData["Tankers"][i] = {
|
|
||||||
"dcsGroupName": tanker.group_name,
|
|
||||||
"callsign": tanker.callsign,
|
|
||||||
"variant": tanker.variant,
|
|
||||||
"radio": tanker.freq.mhz,
|
|
||||||
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, awacs in enumerate(air_support.awacs):
|
|
||||||
luaData["AWACs"][i] = {
|
|
||||||
"dcsGroupName": awacs.group_name,
|
|
||||||
"callsign": awacs.callsign,
|
|
||||||
"radio": awacs.freq.mhz,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, jtac in enumerate(air_support.jtacs):
|
|
||||||
luaData["JTACs"][i] = {
|
|
||||||
"dcsGroupName": jtac.group_name,
|
|
||||||
"callsign": jtac.callsign,
|
|
||||||
"zone": jtac.region,
|
|
||||||
"dcsUnit": jtac.unit_name,
|
|
||||||
"laserCode": jtac.code,
|
|
||||||
"radio": jtac.freq.mhz,
|
|
||||||
}
|
|
||||||
flight_count = 0
|
|
||||||
for flight in airgen.flights:
|
|
||||||
if flight.friendly and flight.flight_type in [
|
|
||||||
FlightType.ANTISHIP,
|
|
||||||
FlightType.DEAD,
|
|
||||||
FlightType.SEAD,
|
|
||||||
FlightType.STRIKE,
|
|
||||||
]:
|
|
||||||
flightType = str(flight.flight_type)
|
|
||||||
flightTarget = flight.package.target
|
|
||||||
if flightTarget:
|
|
||||||
flightTargetName = None
|
|
||||||
flightTargetType = None
|
|
||||||
if isinstance(flightTarget, TheaterGroundObject):
|
|
||||||
flightTargetName = flightTarget.obj_name
|
|
||||||
flightTargetType = (
|
|
||||||
flightType + f" TGT ({flightTarget.category})"
|
|
||||||
)
|
|
||||||
elif hasattr(flightTarget, "name"):
|
|
||||||
flightTargetName = flightTarget.name
|
|
||||||
flightTargetType = flightType + " TGT (Airbase)"
|
|
||||||
luaData["TargetPoints"][flight_count] = {
|
|
||||||
"name": flightTargetName,
|
|
||||||
"type": flightTargetType,
|
|
||||||
"position": {
|
|
||||||
"x": flightTarget.position.x,
|
|
||||||
"y": flightTarget.position.y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
flight_count += 1
|
|
||||||
|
|
||||||
for cp in cls.game.theater.controlpoints:
|
|
||||||
for ground_object in cp.ground_objects:
|
|
||||||
if ground_object.might_have_aa and not ground_object.is_dead:
|
|
||||||
for g in ground_object.groups:
|
|
||||||
threat_range = ground_object.threat_range(g)
|
|
||||||
|
|
||||||
if not threat_range:
|
|
||||||
continue
|
|
||||||
|
|
||||||
faction = "BlueAA" if cp.captured else "RedAA"
|
|
||||||
|
|
||||||
luaData[faction][g.name] = {
|
|
||||||
"name": ground_object.name,
|
|
||||||
"range": threat_range.meters,
|
|
||||||
"position": {
|
|
||||||
"x": ground_object.position.x,
|
|
||||||
"y": ground_object.position.y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# set a LUA table with data from Liberation that we want to set
|
|
||||||
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
|
|
||||||
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
|
|
||||||
state_location = "[[" + os.path.abspath(".") + "]]"
|
|
||||||
lua = (
|
|
||||||
"""
|
|
||||||
-- setting configuration table
|
|
||||||
env.info("DCSLiberation|: setting configuration table")
|
|
||||||
|
|
||||||
-- all data in this table is overridable.
|
|
||||||
dcsLiberation = {}
|
|
||||||
|
|
||||||
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
|
||||||
dcsLiberation.installPath="""
|
|
||||||
+ state_location
|
|
||||||
+ """
|
|
||||||
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
# Process the tankers
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the tankers generated by Liberation
|
|
||||||
dcsLiberation.Tankers = {
|
|
||||||
"""
|
|
||||||
for key in luaData["Tankers"]:
|
|
||||||
data = luaData["Tankers"][key]
|
|
||||||
dcsGroupName = data["dcsGroupName"]
|
|
||||||
callsign = data["callsign"]
|
|
||||||
variant = data["variant"]
|
|
||||||
tacan = data["tacan"]
|
|
||||||
radio = data["radio"]
|
|
||||||
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', variant='{variant}', tacan='{tacan}', radio='{radio}' }}, \n"
|
|
||||||
# lua += f" {{name='{dcsGroupName}', description='{callsign} ({variant})', information='Tacan:{tacan} Radio:{radio}' }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
# Process the AWACSes
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the AWACs generated by Liberation
|
|
||||||
dcsLiberation.AWACs = {
|
|
||||||
"""
|
|
||||||
for key in luaData["AWACs"]:
|
|
||||||
data = luaData["AWACs"][key]
|
|
||||||
dcsGroupName = data["dcsGroupName"]
|
|
||||||
callsign = data["callsign"]
|
|
||||||
radio = data["radio"]
|
|
||||||
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n"
|
|
||||||
# lua += f" {{name='{dcsGroupName}', description='{callsign} (AWACS)', information='Radio:{radio}' }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
# Process the JTACs
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the JTACs generated by Liberation
|
|
||||||
dcsLiberation.JTACs = {
|
|
||||||
"""
|
|
||||||
for key in luaData["JTACs"]:
|
|
||||||
data = luaData["JTACs"][key]
|
|
||||||
dcsGroupName = data["dcsGroupName"]
|
|
||||||
callsign = data["callsign"]
|
|
||||||
zone = data["zone"]
|
|
||||||
laserCode = data["laserCode"]
|
|
||||||
dcsUnit = data["dcsUnit"]
|
|
||||||
radio = data["radio"]
|
|
||||||
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', zone={repr(zone)}, laserCode='{laserCode}', dcsUnit='{dcsUnit}', radio='{radio}' }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
# Process the Target Points
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the target points generated by Liberation
|
|
||||||
dcsLiberation.TargetPoints = {
|
|
||||||
"""
|
|
||||||
for key in luaData["TargetPoints"]:
|
|
||||||
data = luaData["TargetPoints"][key]
|
|
||||||
name = data["name"]
|
|
||||||
pointType = data["type"]
|
|
||||||
positionX = data["position"]["x"]
|
|
||||||
positionY = data["position"]["y"]
|
|
||||||
lua += f" {{name='{name}', pointType='{pointType}', positionX='{positionX}', positionY='{positionY}' }}, \n"
|
|
||||||
# lua += f" {{name='{pointType} {name}', point{{x={positionX}, z={positionY} }} }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the airbases generated by Liberation
|
|
||||||
-- dcsLiberation.Airbases = {}
|
|
||||||
|
|
||||||
-- list the aircraft carriers generated by Liberation
|
|
||||||
-- dcsLiberation.Carriers = {}
|
|
||||||
|
|
||||||
-- list the Red AA generated by Liberation
|
|
||||||
dcsLiberation.RedAA = {
|
|
||||||
"""
|
|
||||||
for key in luaData["RedAA"]:
|
|
||||||
data = luaData["RedAA"][key]
|
|
||||||
name = data["name"]
|
|
||||||
radius = data["range"]
|
|
||||||
positionX = data["position"]["x"]
|
|
||||||
positionY = data["position"]["y"]
|
|
||||||
lua += f" {{dcsGroupName='{key}', name='{name}', range='{radius}', positionX='{positionX}', positionY='{positionY}' }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
-- list the Blue AA generated by Liberation
|
|
||||||
dcsLiberation.BlueAA = {
|
|
||||||
"""
|
|
||||||
for key in luaData["BlueAA"]:
|
|
||||||
data = luaData["BlueAA"][key]
|
|
||||||
name = data["name"]
|
|
||||||
radius = data["range"]
|
|
||||||
positionX = data["position"]["x"]
|
|
||||||
positionY = data["position"]["y"]
|
|
||||||
lua += f" {{dcsGroupName='{key}', name='{name}', range='{radius}', positionX='{positionX}', positionY='{positionY}' }}, \n"
|
|
||||||
lua += "}"
|
|
||||||
|
|
||||||
lua += """
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
trigger = TriggerStart(comment="Set DCS Liberation data")
|
|
||||||
trigger.add_action(DoScript(String(lua)))
|
|
||||||
Operation.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
@ -38,8 +38,8 @@ def _autosave_path() -> str:
|
|||||||
return str(save_dir() / "autosave.liberation")
|
return str(save_dir() / "autosave.liberation")
|
||||||
|
|
||||||
|
|
||||||
def mission_path_for(name: str) -> str:
|
def mission_path_for(name: str) -> Path:
|
||||||
return os.path.join(base_path(), "Missions", name)
|
return Path(base_path()) / "Missions" / name
|
||||||
|
|
||||||
|
|
||||||
def load_game(path: str) -> Optional[Game]:
|
def load_game(path: str) -> Optional[Game]:
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import logging
|
|||||||
import textwrap
|
import textwrap
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional, TYPE_CHECKING, Type
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.operation.operation import Operation
|
from game.missiongenerator.luagenerator import LuaGenerator
|
||||||
|
|
||||||
|
|
||||||
class LuaPluginWorkOrder:
|
class LuaPluginWorkOrder:
|
||||||
@ -22,11 +22,11 @@ class LuaPluginWorkOrder:
|
|||||||
self.mnemonic = mnemonic
|
self.mnemonic = mnemonic
|
||||||
self.disable = disable
|
self.disable = disable
|
||||||
|
|
||||||
def work(self, operation: Type[Operation]) -> None:
|
def work(self, lua_generator: LuaGenerator) -> None:
|
||||||
if self.disable:
|
if self.disable:
|
||||||
operation.bypass_plugin_script(self.mnemonic)
|
lua_generator.bypass_plugin_script(self.mnemonic)
|
||||||
else:
|
else:
|
||||||
operation.inject_plugin_script(
|
lua_generator.inject_plugin_script(
|
||||||
self.parent_mnemonic, self.filename, self.mnemonic
|
self.parent_mnemonic, self.filename, self.mnemonic
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -151,11 +151,11 @@ class LuaPlugin(PluginSettings):
|
|||||||
for option in self.definition.options:
|
for option in self.definition.options:
|
||||||
option.set_settings(self.settings)
|
option.set_settings(self.settings)
|
||||||
|
|
||||||
def inject_scripts(self, operation: Type[Operation]) -> None:
|
def inject_scripts(self, lua_generator: LuaGenerator) -> None:
|
||||||
for work_order in self.definition.work_orders:
|
for work_order in self.definition.work_orders:
|
||||||
work_order.work(operation)
|
work_order.work(lua_generator)
|
||||||
|
|
||||||
def inject_configuration(self, operation: Type[Operation]) -> None:
|
def inject_configuration(self, lua_generator: LuaGenerator) -> None:
|
||||||
# inject the plugin options
|
# inject the plugin options
|
||||||
if self.options:
|
if self.options:
|
||||||
option_decls = []
|
option_decls = []
|
||||||
@ -181,7 +181,9 @@ class LuaPlugin(PluginSettings):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
operation.inject_lua_trigger(lua, f"{self.identifier} plugin configuration")
|
lua_generator.inject_lua_trigger(
|
||||||
|
lua, f"{self.identifier} plugin configuration"
|
||||||
|
)
|
||||||
|
|
||||||
for work_order in self.definition.config_work_orders:
|
for work_order in self.definition.config_work_orders:
|
||||||
work_order.work(operation)
|
work_order.work(lua_generator)
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List
|
||||||
|
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.plugins.luaplugin import LuaPlugin
|
|
||||||
|
from .luaplugin import LuaPlugin
|
||||||
|
|
||||||
|
|
||||||
class LuaPluginManager:
|
class LuaPluginManager:
|
||||||
|
|||||||
@ -4,8 +4,8 @@ from dataclasses import dataclass
|
|||||||
from typing import Optional, Any, TYPE_CHECKING
|
from typing import Optional, Any, TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from gen.aircraft import FlightData
|
from game.missiongenerator.aircraftgenerator import FlightData
|
||||||
from gen.airsupport import AirSupport
|
from game.missiongenerator.airsupport import AirSupport
|
||||||
|
|
||||||
|
|
||||||
class RadioChannelAllocator:
|
class RadioChannelAllocator:
|
||||||
|
|||||||
@ -19,10 +19,6 @@ from dcs.terrain.terrain import Terrain
|
|||||||
from pyproj import CRS, Transformer
|
from pyproj import CRS, Transformer
|
||||||
from shapely import geometry, ops
|
from shapely import geometry, ops
|
||||||
|
|
||||||
from .controlpoint import (
|
|
||||||
ControlPoint,
|
|
||||||
MissionTarget,
|
|
||||||
)
|
|
||||||
from .frontline import FrontLine
|
from .frontline import FrontLine
|
||||||
from .landmap import Landmap, load_landmap, poly_contains
|
from .landmap import Landmap, load_landmap, poly_contains
|
||||||
from .latlon import LatLon
|
from .latlon import LatLon
|
||||||
@ -30,7 +26,8 @@ from .projections import TransverseMercator
|
|||||||
from .seasonalconditions import SeasonalConditions
|
from .seasonalconditions import SeasonalConditions
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from . import TheaterGroundObject
|
from .controlpoint import ControlPoint, MissionTarget
|
||||||
|
from .theatergroundobject import TheaterGroundObject
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
"""Maps generated units back to their Liberation types."""
|
"""Maps generated units back to their Liberation types."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, Optional, Any, Union, TypeVar, Generic
|
from typing import Dict, Optional, Any, TYPE_CHECKING, Union, TypeVar, Generic
|
||||||
|
|
||||||
from dcs.unit import Vehicle, Ship
|
from dcs.unit import Vehicle, Ship
|
||||||
from dcs.unitgroup import FlyingGroup, VehicleGroup, StaticGroup, ShipGroup, MovingGroup
|
from dcs.unitgroup import FlyingGroup, VehicleGroup, StaticGroup, ShipGroup, MovingGroup
|
||||||
@ -11,9 +13,11 @@ from game.dcs.groundunittype import GroundUnitType
|
|||||||
from game.squadrons import Pilot
|
from game.squadrons import Pilot
|
||||||
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
||||||
from game.theater.theatergroundobject import BuildingGroundObject, SceneryGroundObject
|
from game.theater.theatergroundobject import BuildingGroundObject, SceneryGroundObject
|
||||||
from game.transfers import CargoShip, Convoy, TransferOrder
|
|
||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.transfers import CargoShip, Convoy, TransferOrder
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FlyingUnit:
|
class FlyingUnit:
|
||||||
|
|||||||
@ -8,8 +8,8 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
from .radios import MHz, RadioFrequency
|
from game.radio.radios import MHz, RadioFrequency
|
||||||
from .tacan import TacanBand, TacanChannel
|
from game.radio.tacan import TacanBand, TacanChannel
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -46,7 +46,6 @@ from game.ato.flightwaypoint import FlightWaypoint
|
|||||||
from game.ato.flight import Flight
|
from game.ato.flight import Flight
|
||||||
from .traveltime import GroundSpeed, TravelTime
|
from .traveltime import GroundSpeed, TravelTime
|
||||||
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
from .waypointbuilder import StrikeTarget, WaypointBuilder
|
||||||
from ..conflictgen import Conflict, FRONTLINE_LENGTH
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato.package import Package
|
from game.ato.package import Package
|
||||||
@ -1605,7 +1604,13 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, FrontLine):
|
if not isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
ingress, heading, distance = Conflict.frontline_vector(location, self.theater)
|
from game.missiongenerator.frontlineconflictdescription import (
|
||||||
|
FrontLineConflictDescription,
|
||||||
|
)
|
||||||
|
|
||||||
|
ingress, heading, distance = FrontLineConflictDescription.frontline_vector(
|
||||||
|
location, self.theater
|
||||||
|
)
|
||||||
center = ingress.point_from_heading(heading.degrees, distance / 2)
|
center = ingress.point_from_heading(heading.degrees, distance / 2)
|
||||||
egress = ingress.point_from_heading(heading.degrees, distance)
|
egress = ingress.point_from_heading(heading.degrees, distance)
|
||||||
|
|
||||||
@ -1626,6 +1631,8 @@ class FlightPlanBuilder:
|
|||||||
patrol_speed = flight.unit_type.preferred_patrol_speed(ingress_egress_altitude)
|
patrol_speed = flight.unit_type.preferred_patrol_speed(ingress_egress_altitude)
|
||||||
use_agl_ingress_egress = is_helo
|
use_agl_ingress_egress = is_helo
|
||||||
|
|
||||||
|
from game.missiongenerator.frontlineconflictdescription import FRONTLINE_LENGTH
|
||||||
|
|
||||||
return CasFlightPlan(
|
return CasFlightPlan(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
|
|||||||
@ -10,8 +10,8 @@ from dcs.terrain.terrain import Airport
|
|||||||
from game.weather import Conditions
|
from game.weather import Conditions
|
||||||
from game.utils import Heading
|
from game.utils import Heading
|
||||||
from .airfields import AIRFIELD_DATA
|
from .airfields import AIRFIELD_DATA
|
||||||
from .radios import RadioFrequency
|
from game.radio.radios import RadioFrequency
|
||||||
from .tacan import TacanChannel
|
from game.radio.tacan import TacanChannel
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
@ -3,7 +3,9 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel
|
|||||||
from game import Game
|
from game import Game
|
||||||
from game.theater import ControlPointType, BuildingGroundObject
|
from game.theater import ControlPointType, BuildingGroundObject
|
||||||
from game.utils import Distance
|
from game.utils import Distance
|
||||||
from gen.conflictgen import Conflict
|
from game.missiongenerator.frontlineconflictdescription import (
|
||||||
|
FrontLineConflictDescription,
|
||||||
|
)
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.flightwaypoint import FlightWaypoint
|
from game.ato.flightwaypoint import FlightWaypoint
|
||||||
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
|
from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox
|
||||||
@ -65,7 +67,9 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
|||||||
|
|
||||||
if self.include_frontlines:
|
if self.include_frontlines:
|
||||||
for front_line in self.game.theater.conflicts():
|
for front_line in self.game.theater.conflicts():
|
||||||
pos = Conflict.frontline_position(front_line, self.game.theater)[0]
|
pos = FrontLineConflictDescription.frontline_position(
|
||||||
|
front_line, self.game.theater
|
||||||
|
)[0]
|
||||||
wpt = FlightWaypoint(
|
wpt = FlightWaypoint(
|
||||||
FlightWaypointType.CUSTOM,
|
FlightWaypointType.CUSTOM,
|
||||||
pos.x,
|
pos.x,
|
||||||
|
|||||||
@ -114,7 +114,7 @@ def create_mission(terrain: Terrain) -> Path:
|
|||||||
|
|
||||||
mission_path = persistency.mission_path_for(f"export_{terrain.name.lower()}.miz")
|
mission_path = persistency.mission_path_for(f"export_{terrain.name.lower()}.miz")
|
||||||
m.save(mission_path)
|
m.save(mission_path)
|
||||||
return Path(mission_path)
|
return mission_path
|
||||||
|
|
||||||
|
|
||||||
def load_coordinate_data(data: Dict[str, Any]) -> Dict[str, Coordinates]:
|
def load_coordinate_data(data: Dict[str, Any]) -> Dict[str, Coordinates]:
|
||||||
|
|||||||
@ -32,8 +32,7 @@ from typing import Dict, Iterable, Union
|
|||||||
|
|
||||||
import lupa
|
import lupa
|
||||||
|
|
||||||
import game # Needed to resolve cyclic import, for some reason.
|
from game.missiongenerator.beacons import Beacon, BeaconType, BEACONS_RESOURCE_PATH
|
||||||
from gen.beacons import Beacon, BeaconType, BEACONS_RESOURCE_PATH
|
|
||||||
|
|
||||||
THIS_DIR = Path(__file__).parent.resolve()
|
THIS_DIR = Path(__file__).parent.resolve()
|
||||||
SRC_DIR = THIS_DIR.parents[1]
|
SRC_DIR = THIS_DIR.parents[1]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from gen.tacan import (
|
from game.radio.tacan import (
|
||||||
OutOfTacanChannelsError,
|
OutOfTacanChannelsError,
|
||||||
TacanBand,
|
TacanBand,
|
||||||
TacanChannel,
|
TacanChannel,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user