Will now generate control point trigger zones and AI aircraft for the Pretense campaign.

This commit is contained in:
MetalStormGhost 2023-09-09 16:04:44 +03:00
parent 8402e5d9bb
commit db95fc17dc
5 changed files with 143 additions and 293 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import random
from datetime import datetime
from functools import cached_property
from typing import Any, Dict, List, TYPE_CHECKING, Tuple
@ -33,18 +34,17 @@ from game.theater.controlpoint import (
Fob,
)
from game.unitmap import UnitMap
from .aircraftpainter import AircraftPainter
from .flightdata import FlightData
from .flightgroupconfigurator import FlightGroupConfigurator
from .flightgroupspawner import FlightGroupSpawner
from ...data.weapons import WeaponType
from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter
from game.missiongenerator.aircraft.flightdata import FlightData
from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner
from game.data.weapons import WeaponType
if TYPE_CHECKING:
from game import Game
from game.squadrons import Squadron
class AircraftGenerator:
class PretenseAircraftGenerator:
def __init__(
self,
mission: Mission,
@ -101,6 +101,7 @@ class AircraftGenerator:
def generate_flights(
self,
country: Country,
cp: ControlPoint,
ato: AirTaskingOrder,
dynamic_runways: Dict[str, RunwayData],
) -> None:
@ -115,6 +116,61 @@ class AircraftGenerator:
ato: The ATO to spawn aircraft for.
dynamic_runways: Runway data for carriers and FARPs.
"""
num_of_sead = 0
num_of_cas = 0
num_of_strike = 0
num_of_cap = 0
for squadron in cp.squadrons:
squadron.owned_aircraft += 1
squadron.untasked_aircraft += 1
package = Package(cp, squadron.flight_db, auto_asap=False)
mission_types = squadron.auto_assignable_mission_types
if (
FlightType.TRANSPORT in mission_types
or FlightType.AIR_ASSAULT in mission_types
):
flight_type = FlightType.TRANSPORT
elif (
FlightType.SEAD in mission_types
or FlightType.SEAD_SWEEP
or FlightType.SEAD_ESCORT in mission_types
) and num_of_sead < 2:
flight_type = FlightType.SEAD
num_of_sead += 1
elif FlightType.DEAD in mission_types and num_of_sead < 2:
flight_type = FlightType.DEAD
num_of_sead += 1
elif (
FlightType.CAS in mission_types or FlightType.BAI in mission_types
) and num_of_cas < 2:
flight_type = FlightType.CAS
num_of_cas += 1
elif (
FlightType.STRIKE in mission_types
or FlightType.OCA_RUNWAY in mission_types
or FlightType.OCA_AIRCRAFT in mission_types
) and num_of_strike < 2:
flight_type = FlightType.STRIKE
num_of_strike += 1
elif (
FlightType.BARCAP in mission_types
or FlightType.TARCAP in mission_types
or FlightType.ESCORT in mission_types
) and num_of_cap < 2:
flight_type = FlightType.BARCAP
num_of_cap += 1
else:
flight_type = random.choice(list(mission_types))
flight = Flight(
package, squadron, 1, flight_type, StartType.COLD, divert=cp
)
flight.state = WaitingForStart(
flight, self.game.settings, self.game.conditions.start_time
)
package.add_flight(flight)
ato.add_package(package)
self._reserve_frequencies_and_tacan(ato)
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
@ -134,103 +190,6 @@ class AircraftGenerator:
flight, country, dynamic_runways
)
self.unit_map.add_aircraft(group, flight)
if (
package.primary_flight is not None
and package.primary_flight.flight_plan.is_formation(
package.primary_flight.flight_plan
)
):
splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}")
splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}"))
splittrigger.add_condition(Or())
splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}"))
splittrigger.add_condition(GroupDead(package.primary_flight.group_id))
for flight in package.flights:
if flight.flight_type in [
FlightType.ESCORT,
FlightType.SEAD_ESCORT,
]:
splittrigger.add_action(AITaskPush(flight.group_id, 1))
if len(splittrigger.actions) > 0:
self.mission.triggerrules.triggers.append(splittrigger)
def spawn_unused_aircraft(
self, player_country: Country, enemy_country: Country
) -> None:
for control_point in self.game.theater.controlpoints:
if not (
isinstance(control_point, Airfield) or isinstance(control_point, Fob)
):
continue
if control_point.captured:
country = player_country
else:
country = enemy_country
for squadron in control_point.squadrons:
try:
self._spawn_unused_for(squadron, country)
except NoParkingSlotError:
# If we run out of parking, stop spawning aircraft at this base.
break
def _spawn_unused_for(self, squadron: Squadron, country: Country) -> None:
assert isinstance(squadron.location, Airfield) or isinstance(
squadron.location, Fob
)
if (
squadron.coalition.player
and self.game.settings.perf_disable_untasked_blufor_aircraft
):
return
elif (
not squadron.coalition.player
and self.game.settings.perf_disable_untasked_opfor_aircraft
):
return
for _ in range(squadron.untasked_aircraft):
# Creating a flight even those this isn't a fragged mission lets us
# reuse the existing debriefing code.
# TODO: Special flight type?
flight = Flight(
Package(squadron.location, self.game.db.flights),
squadron,
1,
FlightType.BARCAP,
StartType.COLD,
divert=None,
claim_inv=False,
)
flight.state = Completed(flight, self.game.settings)
group = FlightGroupSpawner(
flight,
country,
self.mission,
self.helipads,
self.ground_spawns_roadbase,
self.ground_spawns,
self.mission_data,
).create_idle_aircraft()
if group:
if (
not squadron.coalition.player
and squadron.aircraft.flyable
and (
self.game.settings.enable_squadron_pilot_limits
or squadron.number_of_available_pilots > 0
)
and self.game.settings.untasked_opfor_client_slots
):
flight.state = WaitingForStart(
flight, self.game.settings, self.game.conditions.start_time
)
group.uncontrolled = False
group.units[0].skill = Skill.Client
AircraftPainter(flight, group).apply_livery()
self.unit_map.add_aircraft(group, flight)
def create_and_configure_flight(
self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData]
@ -245,21 +204,21 @@ class AircraftGenerator:
self.ground_spawns,
self.mission_data,
).create_flight_group()
self.flights.append(
FlightGroupConfigurator(
flight,
group,
self.game,
self.mission,
self.time,
self.radio_registry,
self.tacan_registy,
self.laser_code_registry,
self.mission_data,
dynamic_runways,
self.use_client,
).configure()
)
# self.flights.append(
# FlightGroupConfigurator(
# flight,
# group,
# self.game,
# self.mission,
# self.time,
# self.radio_registry,
# self.tacan_registy,
# self.laser_code_registry,
# self.mission_data,
# dynamic_runways,
# self.use_client,
# ).configure()
# )
if self.ewrj:
self._track_ewrj_flight(flight, group)

View File

@ -6,6 +6,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, cast
import dcs.lua
from dataclasses import field
from dcs import Mission, Point
from dcs.coalition import Coalition
from dcs.countries import country_dict
@ -13,38 +14,40 @@ from dcs.task import OptReactOnThreat
from game.atcdata import AtcData
from game.dcs.beacons import Beacons
from game.dcs.helpers import unit_type_from_name
from game.missiongenerator.aircraft.aircraftgenerator import (
AircraftGenerator,
)
from game.naming import namegen
from game.radio.radios import RadioFrequency, RadioRegistry, MHz
from game.radio.tacan import TacanRegistry
from game.theater import Airfield
from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
from .cargoshipgenerator import CargoShipGenerator
from .convoygenerator import ConvoyGenerator
from .drawingsgenerator import DrawingsGenerator
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 .missiondata import MissionData
from .tgogenerator import TgoGenerator
from .triggergenerator import TriggerGenerator
from .visualsgenerator import VisualsGenerator
from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator
from game.missiongenerator.briefinggenerator import (
BriefingGenerator,
MissionInfoGenerator,
)
from game.missiongenerator.convoygenerator import ConvoyGenerator
from game.missiongenerator.environmentgenerator import EnvironmentGenerator
from game.missiongenerator.flotgenerator import FlotGenerator
from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator
from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription,
)
from game.missiongenerator.kneeboard import KneeboardGenerator
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
from game.missiongenerator.luagenerator import LuaGenerator
from game.missiongenerator.missiondata import MissionData
from game.missiongenerator.tgogenerator import TgoGenerator
from .pretensetriggergenerator import PretenseTriggerGenerator
from game.missiongenerator.visualsgenerator import VisualsGenerator
from ..ato import Flight
from ..radio.TacanContainer import TacanContainer
if TYPE_CHECKING:
from game import Game
class MissionGenerator:
class PretenseMissionGenerator:
def __init__(self, game: Game, time: datetime) -> None:
self.game = game
self.time = time
@ -94,20 +97,16 @@ class MissionGenerator:
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()
self.generate_air_units(tgo_generator)
TriggerGenerator(self.mission, self.game).generate()
PretenseTriggerGenerator(self.mission, self.game).generate()
ForcedOptionsGenerator(self.mission, self.game).generate()
VisualsGenerator(self.mission, self.game).generate()
LuaGenerator(self.game, self.mission, self.mission_data).generate()
DrawingsGenerator(self.mission, self.game).generate()
self.setup_combined_arms()
@ -119,22 +118,6 @@ class MissionGenerator:
return self.unit_map
@staticmethod
def _configure_ewrj(gen: AircraftGenerator) -> None:
for groups in gen.ewrj_package_dict.values():
optrot = groups[0].points[0].tasks[0]
assert isinstance(optrot, OptReactOnThreat)
if (
len(groups) == 1
and optrot.value != OptReactOnThreat.Values.PassiveDefense
):
# primary flight with no EWR-Jamming capability
continue
for group in groups:
group.points[0].tasks[0] = OptReactOnThreat(
OptReactOnThreat.Values.PassiveDefense
)
def setup_mission_coalitions(self) -> None:
self.mission.coalition["blue"] = Coalition(
"blue", bullseye=self.game.blue.bullseye.to_pydcs()
@ -232,7 +215,7 @@ class MissionGenerator:
"""Generate the air units for the Operation"""
# Generate Aircraft Activity on the map
aircraft_generator = AircraftGenerator(
aircraft_generator = PretenseAircraftGenerator(
self.mission,
self.game.settings,
self.game,
@ -247,22 +230,25 @@ class MissionGenerator:
ground_spawns=tgo_generator.ground_spawns,
)
# Clear parking slots and ATOs
aircraft_generator.clear_parking_slots()
self.game.blue.ato.clear()
self.game.red.ato.clear()
aircraft_generator.generate_flights(
self.p_country,
self.game.blue.ato,
tgo_generator.runways,
)
aircraft_generator.generate_flights(
self.e_country,
self.game.red.ato,
tgo_generator.runways,
)
aircraft_generator.spawn_unused_aircraft(
self.p_country,
self.e_country,
)
for cp in self.game.theater.controlpoints:
if cp.captured:
ato = self.game.blue.ato
cp_country = self.p_country
else:
ato = self.game.red.ato
cp_country = self.e_country
print(f"Generating flights for {cp_country.name} at {cp}")
aircraft_generator.generate_flights(
cp_country,
cp,
ato,
tgo_generator.runways,
)
self.mission_data.flights = aircraft_generator.flights
@ -274,35 +260,6 @@ class MissionGenerator:
if self.game.settings.plugins.get("ewrj"):
self._configure_ewrj(aircraft_generator)
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 = 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"]), self.mission.terrain)
if utype is not None and not self.game.position_culled(pos):
self.mission.static_group(
country=self.p_country,
name="",
_type=utype,
hidden=True,
position=pos,
heading=d["orientation"],
dead=True,
)
def notify_info_generators(
self,
) -> None:

View File

@ -56,7 +56,7 @@ class Silence(Option):
Key = 7
class TriggerGenerator:
class PretenseTriggerGenerator:
capture_zone_types = (Fob, Airfield)
capture_zone_flag = 600
@ -146,38 +146,7 @@ class TriggerGenerator:
v += 1
self.mission.triggerrules.triggers.append(mark_trigger)
def _generate_clear_statics_trigger(self, scenery_clear_zones: List[Point]) -> None:
for zone_center in scenery_clear_zones:
trigger_zone = self.mission.triggers.add_triggerzone(
zone_center,
radius=TRIGGER_RADIUS_CLEAR_SCENERY,
hidden=False,
name="CLEAR",
)
clear_trigger = TriggerCondition(Event.NoEvent, "Clear Trigger")
clear_flag = self.get_capture_zone_flag()
clear_trigger.add_condition(TimeSinceFlag(clear_flag, 30))
clear_trigger.add_action(ClearFlag(clear_flag))
clear_trigger.add_action(SetFlag(clear_flag))
clear_trigger.add_action(
RemoveSceneObjects(
objects_mask=RemoveSceneObjectsMask.OBJECTS_ONLY,
zone=trigger_zone.id,
)
)
clear_trigger.add_action(
SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id)
)
self.mission.triggerrules.triggers.append(clear_trigger)
enable_clear_trigger = TriggerOnce(Event.NoEvent, "Enable Clear Trigger")
enable_clear_trigger.add_condition(TimeAfter(30))
enable_clear_trigger.add_action(ClearFlag(clear_flag))
enable_clear_trigger.add_action(SetFlag(clear_flag))
# clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),))
self.mission.triggerrules.triggers.append(enable_clear_trigger)
def _generate_capture_triggers(
def _generate_pretense_zone_triggers(
self, player_coalition: str, enemy_coalition: str
) -> None:
"""Creates a pair of triggers for each control point of `cls.capture_zone_types`.
@ -185,62 +154,16 @@ class TriggerGenerator:
Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua`
"""
for cp in self.game.theater.controlpoints:
if isinstance(cp, self.capture_zone_types) and not cp.is_carrier:
if cp.captured:
attacking_coalition = enemy_coalition
attack_coalition_int = 1 # 1 is the Event int for Red
defending_coalition = player_coalition
defend_coalition_int = 2 # 2 is the Event int for Blue
else:
attacking_coalition = player_coalition
attack_coalition_int = 2
defending_coalition = enemy_coalition
defend_coalition_int = 1
if isinstance(cp, self.capture_zone_types) and not cp.is_fleet:
zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.149}
trigger_zone = self.mission.triggers.add_triggerzone(
cp.position,
radius=TRIGGER_RADIUS_CAPTURE,
hidden=False,
name="CAPTURE",
name=cp.name,
color=zone_color,
)
flag = self.get_capture_zone_flag()
capture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
capture_trigger.add_condition(
AllOfCoalitionOutsideZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
capture_trigger.add_condition(
PartOfCoalitionInZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
capture_trigger.add_condition(FlagIsFalse(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"'
)
capture_trigger.add_action(DoScript(script_string))
capture_trigger.add_action(SetFlag(flag=flag))
self.mission.triggerrules.triggers.append(capture_trigger)
recapture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger")
recapture_trigger.add_condition(
AllOfCoalitionOutsideZone(
attacking_coalition, trigger_zone.id, unit_type="GROUND"
)
)
recapture_trigger.add_condition(
PartOfCoalitionInZone(
defending_coalition, trigger_zone.id, unit_type="GROUND"
)
)
recapture_trigger.add_condition(FlagIsTrue(flag=flag))
script_string = String(
f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"'
)
recapture_trigger.add_action(DoScript(script_string))
recapture_trigger.add_action(ClearFlag(flag=flag))
self.mission.triggerrules.triggers.append(recapture_trigger)
def generate(self) -> None:
player_coalition = "blue"
@ -249,13 +172,7 @@ class TriggerGenerator:
self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition)
self._gen_markers()
self._generate_capture_triggers(player_coalition, enemy_coalition)
if self.game.settings.ground_start_scenery_remove_triggers:
try:
self._generate_clear_statics_trigger(self.game.scenery_clear_zones)
self.game.scenery_clear_zones.clear()
except AttributeError:
logging.info(f"Unable to create Clear Statics triggers")
self._generate_pretense_zone_triggers(player_coalition, enemy_coalition)
@classmethod
def get_capture_zone_flag(cls) -> int:

View File

@ -32,6 +32,7 @@ def load_icons():
"./resources/ui/misc/" + get_theme_icons() + "/github.png"
)
ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png")
ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png")
ICONS["Control Points"] = QPixmap(
"./resources/ui/misc/" + get_theme_icons() + "/circle.png"

View File

@ -21,6 +21,7 @@ from game import Game, VERSION, persistency, Migrator
from game.debriefing import Debriefing
from game.game import TurnState
from game.layout import LAYOUTS
from game.pretense.pretensemissiongenerator import PretenseMissionGenerator
from game.server import EventStream, GameContext
from game.server.dependencies import QtCallbacks, QtContext
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
@ -41,6 +42,7 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard
from qt_ui.windows.newgame.QNewPretenseWizard import NewPretenseWizard
from qt_ui.windows.notes.QNotesWindow import QNotesWindow
from qt_ui.windows.preferences.QLiberationPreferencesWindow import (
QLiberationPreferencesWindow,
@ -193,6 +195,10 @@ class QLiberationWindow(QMainWindow):
lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/")
)
self.newPretenseAction = QAction("&New Pretense Campaign", self)
self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense"]))
self.newPretenseAction.triggered.connect(self.newPretenseCampaign)
self.openLogsAction = QAction("Show &logs", self)
self.openLogsAction.triggered.connect(self.showLogsDialog)
@ -234,6 +240,7 @@ class QLiberationWindow(QMainWindow):
self.links_bar.addAction(self.openDiscordAction)
self.links_bar.addAction(self.openGithubAction)
self.links_bar.addAction(self.ukraineAction)
self.links_bar.addAction(self.newPretenseAction)
self.actions_bar = self.addToolBar("Actions")
self.actions_bar.addAction(self.openSettingsAction)
@ -303,6 +310,15 @@ class QLiberationWindow(QMainWindow):
wizard.show()
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
def newPretenseCampaign(self):
output = persistency.mission_path_for("pretense_campaign.miz")
PretenseMissionGenerator(
self.game, self.game.conditions.start_time
).generate_miz(output)
title = "Pretense campaign generated"
msg = f"A Pretense campaign mission has been successfully generated in {output}"
QMessageBox.information(QApplication.focusWidget(), title, msg, QMessageBox.Ok)
def openFile(self):
if self.game is not None and self.game.savepath:
save_dir = self.game.savepath