operation refactoring

This commit is contained in:
walterroach 2020-11-21 17:00:22 -06:00
parent 8889e35f9e
commit f6e0dbbb6a
12 changed files with 268 additions and 234 deletions

View File

@ -92,7 +92,7 @@ class Event:
self.operation.is_awacs_enabled = self.is_awacs_enabled self.operation.is_awacs_enabled = self.is_awacs_enabled
self.operation.ca_slots = self.ca_slots self.operation.ca_slots = self.ca_slots
self.operation.prepare(self.game.theater.terrain, is_quick=False) self.operation.prepare(self.game)
self.operation.generate() self.operation.generate()
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz")) self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
self.environment_settings = self.operation.environment_settings self.environment_settings = self.operation.environment_settings

View File

@ -1,11 +1,10 @@
from typing import List, Type from typing import List, Type
from dcs.task import CAP, CAS, Task from dcs.task import CAP, CAS, Task
from game.operation.operation import Operation
from game import db
# from game.operation.frontlineattack import FrontlineAttackOperation
from .event import Event
from ..debriefing import Debriefing from ..debriefing import Debriefing
from .event import Event
class FrontlineAttackEvent(Event): class FrontlineAttackEvent(Event):
@ -38,12 +37,6 @@ class FrontlineAttackEvent(Event):
if self.to_cp.captured: if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1) self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, flights: db.TaskForceDict): def player_attacking(self):
assert self.departure_cp is not None assert self.departure_cp is not None
op = FrontlineAttackOperation(game=self.game, self.operation = Operation(departure_cp=self.departure_cp,)
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
self.operation = op

View File

@ -1,7 +1,9 @@
from __future__ import annotations
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from typing import List, Optional, Set from typing import TYPE_CHECKING, Iterable, List, Optional, Set
from dcs import Mission from dcs import Mission
from dcs.action import DoScript, DoScriptFile from dcs.action import DoScript, DoScriptFile
@ -9,11 +11,9 @@ from dcs.coalition import Coalition
from dcs.countries import country_dict from dcs.countries import country_dict
from dcs.lua.parse import loads from dcs.lua.parse import loads
from dcs.mapping import Point from dcs.mapping import Point
from dcs.terrain.terrain import Terrain
from dcs.translation import String from dcs.translation import String
from dcs.triggers import TriggerStart from dcs.triggers import TriggerStart
from dcs.unittype import UnitType from dcs.unittype import UnitType
from game.plugins import LuaPluginManager from game.plugins import LuaPluginManager
from game.theater import ControlPoint from game.theater import ControlPoint
from gen import Conflict, FlightType, VisualGenerator from gen import Conflict, FlightType, VisualGenerator
@ -30,18 +30,19 @@ from gen.kneeboard import KneeboardGenerator
from gen.radios import RadioFrequency, RadioRegistry from gen.radios import RadioFrequency, RadioRegistry
from gen.tacan import TacanRegistry from gen.tacan import TacanRegistry
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
from .. import db from .. import db
from ..debriefing import Debriefing from ..debriefing import Debriefing
if TYPE_CHECKING:
from game import Game
class Operation: class Operation:
attackers_starting_position = None # type: db.StartingPosition attackers_starting_position = None # type: db.StartingPosition
defenders_starting_position = None # type: db.StartingPosition defenders_starting_position = None # type: db.StartingPosition
current_mission = None # type: Mission current_mission = None # type: Mission
regular_mission = None # type: Mission
quick_mission = None # type: Mission
conflict = None # type: Conflict
airgen = None # type: AircraftConflictGenerator airgen = None # type: AircraftConflictGenerator
triggersgen = None # type: TriggersGenerator triggersgen = None # type: TriggersGenerator
airsupportgen = None # type: AirSupportConflictGenerator airsupportgen = None # type: AirSupportConflictGenerator
@ -51,7 +52,7 @@ class Operation:
forcedoptionsgen = None # type: ForcedOptionsGenerator forcedoptionsgen = None # type: ForcedOptionsGenerator
radio_registry: Optional[RadioRegistry] = None radio_registry: Optional[RadioRegistry] = None
tacan_registry: Optional[TacanRegistry] = None tacan_registry: Optional[TacanRegistry] = None
game = None # type: Game
environment_settings = None environment_settings = None
trigger_radius = TRIGGER_RADIUS_MEDIUM trigger_radius = TRIGGER_RADIUS_MEDIUM
is_quick = None is_quick = None
@ -59,23 +60,35 @@ class Operation:
ca_slots = 0 ca_slots = 0
def __init__(self, def __init__(self,
game,
attacker_name: str,
defender_name: str,
from_cp: ControlPoint,
departure_cp: ControlPoint, departure_cp: ControlPoint,
to_cp: ControlPoint): ):
self.game = game
self.attacker_name = attacker_name
self.attacker_country = db.FACTIONS[attacker_name].country
self.defender_name = defender_name
self.defender_country = db.FACTIONS[defender_name].country
print(self.defender_country, self.attacker_country)
self.from_cp = from_cp
self.departure_cp = departure_cp self.departure_cp = departure_cp
self.to_cp = to_cp
self.is_quick = False
self.plugin_scripts: List[str] = [] self.plugin_scripts: List[str] = []
self.jtacs: List[JtacInfo] = []
@classmethod
def prepare(cls, game: Game):
with open("resources/default_options.lua", "r") 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 conflicts(cls) -> Iterable[Conflict]:
assert cls.game
for frontline in cls.game.theater.conflicts():
yield Conflict(
cls.game.theater,
frontline.control_point_a,
frontline.control_point_b,
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_country,
cls.game.enemy_country,
frontline.position
)
def units_of(self, country_name: str) -> List[UnitType]: def units_of(self, country_name: str) -> List[UnitType]:
return [] return []
@ -87,51 +100,21 @@ class Operation:
def is_player_attack(self) -> bool: def is_player_attack(self) -> bool:
return self.from_cp.captured return self.from_cp.captured
def initialize(self, mission: Mission, conflict: Conflict): @classmethod
self.current_mission = mission def _set_mission(cls, mission: Mission) -> None:
self.conflict = conflict cls.current_mission = mission
# self.briefinggen = BriefingGenerator(self.current_mission, self.game) Is it safe to remove this, or does it also break save compat?
def prepare(self, terrain: Terrain, is_quick: bool): @classmethod
with open("resources/default_options.lua", "r") as f: def _setup_mission_coalitions(cls):
options_dict = loads(f.read())["options"] cls.current_mission.coalition["blue"] = Coalition("blue")
cls.current_mission.coalition["red"] = Coalition("red")
self.current_mission = Mission(terrain) p_country = cls.game.player_country
e_country = cls.game.enemy_country
print(self.game.player_country) cls.current_mission.coalition["blue"].add_country(
print(country_dict[db.country_id_from_name(self.game.player_country)]) country_dict[db.country_id_from_name(p_country)]())
print(country_dict[db.country_id_from_name(self.game.player_country)]()) cls.current_mission.coalition["red"].add_country(
country_dict[db.country_id_from_name(e_country)]())
# Setup coalition :
self.current_mission.coalition["blue"] = Coalition("blue")
self.current_mission.coalition["red"] = Coalition("red")
p_country = self.game.player_country
e_country = self.game.enemy_country
self.current_mission.coalition["blue"].add_country(country_dict[db.country_id_from_name(p_country)]())
self.current_mission.coalition["red"].add_country(country_dict[db.country_id_from_name(e_country)]())
print([c for c in self.current_mission.coalition["blue"].countries.keys()])
print([c for c in self.current_mission.coalition["red"].countries.keys()])
if is_quick:
self.quick_mission = self.current_mission
else:
self.regular_mission = self.current_mission
self.current_mission.options.load_from_dict(options_dict)
self.is_quick = is_quick
if is_quick:
self.attackers_starting_position = None
self.defenders_starting_position = None
else:
self.attackers_starting_position = self.departure_cp.at
# TODO: Is this possible?
if self.to_cp is not None:
self.defenders_starting_position = self.to_cp.at
else:
self.defenders_starting_position = None
def inject_lua_trigger(self, contents: str, comment: str) -> None: def inject_lua_trigger(self, contents: str, comment: str) -> None:
trigger = TriggerStart(comment=comment) trigger = TriggerStart(comment=comment)
@ -161,7 +144,8 @@ class Operation:
trigger = TriggerStart(comment=f"Load {script_mnemonic}") trigger = TriggerStart(comment=f"Load {script_mnemonic}")
filename = script_path.resolve() filename = script_path.resolve()
fileref = self.current_mission.map_resource.add_resource_file(filename) fileref = self.current_mission.map_resource.add_resource_file(
filename)
trigger.add_action(DoScriptFile(fileref)) trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger) self.current_mission.triggerrules.triggers.append(trigger)
@ -171,13 +155,13 @@ class Operation:
airsupportgen: AirSupportConflictGenerator, airsupportgen: AirSupportConflictGenerator,
jtacs: List[JtacInfo], jtacs: List[JtacInfo],
airgen: AircraftConflictGenerator, airgen: AircraftConflictGenerator,
): ):
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings) """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)
""" """
gens: List[MissionInfoGenerator] = [ gens: List[MissionInfoGenerator] = [
KneeboardGenerator(self.current_mission, self.game), KneeboardGenerator(self.current_mission, self.game),
BriefingGenerator(self.current_mission, self.game) BriefingGenerator(self.current_mission, self.game)
] ]
for gen in gens: for gen in gens:
for dynamic_runway in groundobjectgen.runways.values(): for dynamic_runway in groundobjectgen.runways.values():
gen.add_dynamic_runway(dynamic_runway) gen.add_dynamic_runway(dynamic_runway)
@ -195,15 +179,46 @@ class Operation:
for flight in airgen.flights: for flight in airgen.flights:
gen.add_flight(flight) gen.add_flight(flight)
gen.generate() gen.generate()
@classmethod
def create_radio_registries(cls) -> None:
unique_map_frequencies = set() # type: Set[RadioFrequency]
cls._create_tacan_registry(unique_map_frequencies)
cls._create_radio_registry(unique_map_frequencies)
def assign_channels_to_flights(self, flights: List[FlightData],
air_support: AirSupport) -> None:
"""Assigns preset radio channels for client flights."""
for flight in flights:
if not flight.client_units:
continue
self.assign_channels_to_flight(flight, air_support)
def generate(self): def assign_channels_to_flight(self, flight: FlightData,
radio_registry = RadioRegistry() air_support: AirSupport) -> None:
tacan_registry = TacanRegistry() """Assigns preset radio channels for a client flight."""
airframe = flight.aircraft_type
try:
aircraft_data = AIRCRAFT_DATA[airframe.id]
except KeyError:
logging.warning(f"No aircraft data for {airframe.id}")
return
if aircraft_data.channel_allocator is not None:
aircraft_data.channel_allocator.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)
# Dedup beacon/radio frequencies, since some maps have some frequencies
# used multiple times.
beacons = load_beacons_for_terrain(self.game.theater.terrain.name)
unique_map_frequencies: Set[RadioFrequency] = set()
for beacon in beacons: for beacon in beacons:
unique_map_frequencies.add(beacon.frequency) unique_map_frequencies.add(beacon.frequency)
if beacon.is_tacan: if beacon.is_tacan:
@ -211,36 +226,35 @@ class Operation:
logging.error( logging.error(
f"TACAN beacon has no channel: {beacon.callsign}") f"TACAN beacon has no channel: {beacon.callsign}")
else: else:
tacan_registry.reserve(beacon.tacan_channel) cls.tacan_registry.reserve(beacon.tacan_channel)
for airfield, data in AIRFIELD_DATA.items(): @classmethod
if data.theater == self.game.theater.terrain.name: 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:
unique_map_frequencies.add(data.atc.hf) unique_map_frequencies.add(data.atc.hf)
unique_map_frequencies.add(data.atc.vhf_fm) unique_map_frequencies.add(data.atc.vhf_fm)
unique_map_frequencies.add(data.atc.vhf_am) unique_map_frequencies.add(data.atc.vhf_am)
unique_map_frequencies.add(data.atc.uhf) unique_map_frequencies.add(data.atc.uhf)
# No need to reserve ILS or TACAN because those are in the # No need to reserve ILS or TACAN because those are in the
# beacon list. # beacon list.
unique_map_frequencies: Set[RadioFrequency] = set()
for frequency in unique_map_frequencies: for frequency in unique_map_frequencies:
radio_registry.reserve(frequency) cls.radio_registry.reserve(frequency)
# Set mission time and weather conditions. @classmethod
EnvironmentGenerator(self.current_mission, def _generate_ground_units(cls):
self.game.conditions).generate() cls.groundobjectgen = GroundObjectsGenerator(
cls.current_mission,
# Generate ground object first cls.game,
cls.radio_registry,
groundobjectgen = GroundObjectsGenerator( cls.tacan_registry
self.current_mission,
self.conflict,
self.game,
radio_registry,
tacan_registry
) )
groundobjectgen.generate() cls.groundobjectgen.generate()
# Generate destroyed units def _generate_destroyed_units(self) -> None:
"""Add destroyed units to the Mission"""
for d in self.game.get_destroyed_units(): for d in self.game.get_destroyed_units():
try: try:
utype = db.unit_type_from_name(d["type"]) utype = db.unit_type_from_name(d["type"])
@ -250,7 +264,8 @@ class Operation:
pos = Point(d["x"], d["z"]) pos = Point(d["x"], d["z"])
if utype is not None and not self.game.position_culled(pos) and self.game.settings.perf_destroyed_units: if utype is not None and not self.game.position_culled(pos) and self.game.settings.perf_destroyed_units:
self.current_mission.static_group( self.current_mission.static_group(
country=self.current_mission.country(self.game.player_country), country=self.current_mission.country(
self.game.player_country),
name="", name="",
_type=utype, _type=utype,
hidden=True, hidden=True,
@ -258,44 +273,25 @@ class Operation:
heading=d["orientation"], heading=d["orientation"],
dead=True, dead=True,
) )
def generate(self):
self.create_radio_registries()
# Set mission time and weather conditions.
EnvironmentGenerator(self.current_mission,
self.game.conditions).generate()
self._generate_ground_units()
self._generate_destroyed_units()
self._generate_air_units()
self.assign_channels_to_flights(self.airgen.flights,
self.airsupportgen.air_support)
self._generate_ground_conflicts()
# Air Support (Tanker & Awacs) # TODO: This is silly, once Bulls position is defined without Conflict this should be removed.
airsupportgen = AirSupportConflictGenerator( default_conflict = [i for i in self.conflicts()][0]
self.current_mission, self.conflict, self.game, radio_registry, # Triggers
tacan_registry) triggersgen = TriggersGenerator(self.current_mission, default_conflict,
airsupportgen.generate(self.is_awacs_enabled) self.game)
triggersgen.generate()
# Generate Activity on the map
airgen = AircraftConflictGenerator(
self.current_mission, self.conflict, self.game.settings, self.game,
radio_registry)
airgen.generate_flights(
self.current_mission.country(self.game.player_country),
self.game.blue_ato,
groundobjectgen.runways
)
airgen.generate_flights(
self.current_mission.country(self.game.enemy_country),
self.game.red_ato,
groundobjectgen.runways
)
# Generate ground units on frontline everywhere
jtacs: List[JtacInfo] = []
for front_line in self.game.theater.conflicts(True):
player_cp = front_line.control_point_a
enemy_cp = front_line.control_point_b
conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name,
self.current_mission.country(self.attacker_country),
self.current_mission.country(self.defender_country),
player_cp, enemy_cp, 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]
groundConflictGen = GroundConflictGenerator(self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id])
groundConflictGen.generate()
jtacs.extend(groundConflictGen.jtacs)
# Setup combined arms parameters # Setup combined arms parameters
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0 self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
@ -304,34 +300,32 @@ class Operation:
else: else:
self.current_mission.groundControl.red_tactical_commander = self.ca_slots self.current_mission.groundControl.red_tactical_commander = self.ca_slots
# Triggers
triggersgen = TriggersGenerator(self.current_mission, self.conflict,
self.game)
triggersgen.generate()
# Options # Options
forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, self.game)
self.conflict, self.game)
forcedoptionsgen.generate() forcedoptionsgen.generate()
# Generate Visuals Smoke Effects # Generate Visuals Smoke Effects
visualgen = VisualGenerator(self.current_mission, self.conflict, visualgen = VisualGenerator(self.current_mission, self.game)
self.game)
if self.game.settings.perf_smoke_gen: if self.game.settings.perf_smoke_gen:
visualgen.generate() visualgen.generate()
luaData = {} self.notify_info_generators(
luaData["AircraftCarriers"] = {} self.groundobjectgen,
luaData["Tankers"] = {} self.airsupportgen,
luaData["AWACs"] = {} self.jtacs,
luaData["JTACs"] = {} self.airgen
luaData["TargetPoints"] = {} )
self.assign_channels_to_flights(airgen.flights, luaData = {
airsupportgen.air_support) "AircraftCarriers": {},
"Tankers": {},
"AWACs": {},
"JTACs": {},
"TargetPoints": {},
}
for tanker in airsupportgen.air_support.tankers: for tanker in self.airsupportgen.air_support.tankers:
luaData["Tankers"][tanker.callsign] = { luaData["Tankers"][tanker.callsign] = {
"dcsGroupName": tanker.dcsGroupName, "dcsGroupName": tanker.dcsGroupName,
"callsign": tanker.callsign, "callsign": tanker.callsign,
"variant": tanker.variant, "variant": tanker.variant,
@ -340,15 +334,15 @@ class Operation:
} }
if self.is_awacs_enabled: if self.is_awacs_enabled:
for awacs in airsupportgen.air_support.awacs: for awacs in self.airsupportgen.air_support.awacs:
luaData["AWACs"][awacs.callsign] = { luaData["AWACs"][awacs.callsign] = {
"dcsGroupName": awacs.dcsGroupName, "dcsGroupName": awacs.dcsGroupName,
"callsign": awacs.callsign, "callsign": awacs.callsign,
"radio": awacs.freq.mhz "radio": awacs.freq.mhz
} }
for jtac in jtacs: for jtac in self.jtacs:
luaData["JTACs"][jtac.callsign] = { luaData["JTACs"][jtac.callsign] = {
"dcsGroupName": jtac.dcsGroupName, "dcsGroupName": jtac.dcsGroupName,
"callsign": jtac.callsign, "callsign": jtac.callsign,
"zone": jtac.region, "zone": jtac.region,
@ -356,7 +350,7 @@ class Operation:
"laserCode": jtac.code "laserCode": jtac.code
} }
for flight in airgen.flights: for flight in self.airgen.flights:
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]: if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]:
flightType = flight.flight_type.name flightType = flight.flight_type.name
flightTarget = flight.package.target flightTarget = flight.package.target
@ -365,14 +359,15 @@ class Operation:
flightTargetType = None flightTargetType = None
if hasattr(flightTarget, 'obj_name'): if hasattr(flightTarget, 'obj_name'):
flightTargetName = flightTarget.obj_name flightTargetName = flightTarget.obj_name
flightTargetType = flightType + f" TGT ({flightTarget.category})" flightTargetType = flightType + \
f" TGT ({flightTarget.category})"
elif hasattr(flightTarget, 'name'): elif hasattr(flightTarget, 'name'):
flightTargetName = flightTarget.name flightTargetName = flightTarget.name
flightTargetType = flightType + " TGT (Airbase)" flightTargetType = flightType + " TGT (Airbase)"
luaData["TargetPoints"][flightTargetName] = { luaData["TargetPoints"][flightTargetName] = {
"name": flightTargetName, "name": flightTargetName,
"type": flightTargetType, "type": flightTargetType,
"position": { "x": flightTarget.position.x, "y": flightTarget.position.y} "position": {"x": flightTarget.position.x, "y": flightTarget.position.y}
} }
# set a LUA table with data from Liberation that we want to set # set a LUA table with data from Liberation that we want to set
@ -398,7 +393,7 @@ dcsLiberation.Tankers = {
""" """
for key in luaData["Tankers"]: for key in luaData["Tankers"]:
data = luaData["Tankers"][key] data = luaData["Tankers"][key]
dcsGroupName= data["dcsGroupName"] dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"] callsign = data["callsign"]
variant = data["variant"] variant = data["variant"]
tacan = data["tacan"] tacan = data["tacan"]
@ -415,7 +410,7 @@ dcsLiberation.AWACs = {
""" """
for key in luaData["AWACs"]: for key in luaData["AWACs"]:
data = luaData["AWACs"][key] data = luaData["AWACs"][key]
dcsGroupName= data["dcsGroupName"] dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"] callsign = data["callsign"]
radio = data["radio"] radio = data["radio"]
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n" lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n"
@ -430,7 +425,7 @@ dcsLiberation.JTACs = {
""" """
for key in luaData["JTACs"]: for key in luaData["JTACs"]:
data = luaData["JTACs"][key] data = luaData["JTACs"][key]
dcsGroupName= data["dcsGroupName"] dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"] callsign = data["callsign"]
zone = data["zone"] zone = data["zone"]
laserCode = data["laserCode"] laserCode = data["laserCode"]
@ -467,7 +462,6 @@ dcsLiberation.TargetPoints = {
""" """
trigger = TriggerStart(comment="Set DCS Liberation data") trigger = TriggerStart(comment="Set DCS Liberation data")
trigger.add_action(DoScript(String(lua))) trigger.add_action(DoScript(String(lua)))
self.current_mission.triggerrules.triggers.append(trigger) self.current_mission.triggerrules.triggers.append(trigger)
@ -478,30 +472,57 @@ dcsLiberation.TargetPoints = {
plugin.inject_scripts(self) plugin.inject_scripts(self)
plugin.inject_configuration(self) plugin.inject_configuration(self)
self.assign_channels_to_flights(airgen.flights, @classmethod
airsupportgen.air_support) def _generate_air_units(cls) -> None:
self.notify_info_generators(groundobjectgen, airsupportgen, jtacs, airgen) """Generate the air units for the Operation"""
# TODO: this is silly, once AirSupportConflictGenerator doesn't require Conflict this can be removed.
default_conflict = [i for i in cls.conflicts()][0]
def assign_channels_to_flights(self, flights: List[FlightData], # Air Support (Tanker & Awacs)
air_support: AirSupport) -> None: cls.airsupportgen = AirSupportConflictGenerator(
"""Assigns preset radio channels for client flights.""" cls.current_mission, default_conflict, cls.game, cls.radio_registry,
for flight in flights: cls.tacan_registry)
if not flight.client_units: cls.airsupportgen.generate(cls.is_awacs_enabled)
continue
self.assign_channels_to_flight(flight, air_support)
def assign_channels_to_flight(self, flight: FlightData, # Generate Aircraft Activity on the map
air_support: AirSupport) -> None: cls.airgen = AircraftConflictGenerator(
"""Assigns preset radio channels for a client flight.""" cls.current_mission, cls.game.settings, cls.game,
airframe = flight.aircraft_type cls.radio_registry)
try: cls.airgen.generate_flights(
aircraft_data = AIRCRAFT_DATA[airframe.id] cls.current_mission.country(cls.game.player_country),
except KeyError: cls.game.blue_ato,
logging.warning(f"No aircraft data for {airframe.id}") cls.groundobjectgen.runways
return )
cls.airgen.generate_flights(
cls.current_mission.country(cls.game.enemy_country),
cls.game.red_ato,
cls.groundobjectgen.runways
)
if aircraft_data.channel_allocator is not None: def _generate_ground_conflicts(self) -> None:
aircraft_data.channel_allocator.assign_channels_for_flight( """For each frontline in the Operation, generate the ground conflicts and JTACs"""
flight, air_support self.jtacs: List[JtacInfo] = []
for front_line in self.game.theater.conflicts(True):
player_cp = front_line.control_point_a
enemy_cp = front_line.control_point_b
conflict = Conflict.frontline_cas_conflict(
self.game.player_name,
self.game.enemy_name,
self.current_mission.country(self.game.player_country),
self.current_mission.country(self.game.enemy_country),
player_cp,
enemy_cp,
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 = GroundConflictGenerator(
self.current_mission,
conflict, self.game,
player_gp, enemy_gp,
player_cp.stances[enemy_cp.id]
)
ground_conflict_gen.generate()
self.jtacs.extend(ground_conflict_gen.jtacs)

View File

@ -160,6 +160,9 @@ class ControlPoint(MissionTarget):
self.stances: Dict[int, CombatStance] = {} self.stances: Dict[int, CombatStance] = {}
self.airport = None self.airport = None
self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None
def __repr__(self):
return f"<{__class__}: {self.name}>"
@property @property
def ground_objects(self) -> List[TheaterGroundObject]: def ground_objects(self) -> List[TheaterGroundObject]:

View File

@ -647,12 +647,11 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"]
class AircraftConflictGenerator: class AircraftConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings, def __init__(self, mission: Mission, settings: Settings,
game: Game, radio_registry: RadioRegistry): game: Game, radio_registry: RadioRegistry):
self.m = mission self.m = mission
self.game = game self.game = game
self.settings = settings self.settings = settings
self.conflict = conflict
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.flights: List[FlightData] = [] self.flights: List[FlightData] = []

View File

@ -75,10 +75,33 @@ class GroundConflictGenerator:
self.enemy_planned_combat_groups = enemy_planned_combat_groups self.enemy_planned_combat_groups = enemy_planned_combat_groups
self.player_planned_combat_groups = player_planned_combat_groups self.player_planned_combat_groups = player_planned_combat_groups
self.player_stance = CombatStance(player_stance) self.player_stance = CombatStance(player_stance)
self.enemy_stance = random.choice([CombatStance.AGGRESSIVE, CombatStance.AGGRESSIVE, CombatStance.AGGRESSIVE, CombatStance.ELIMINATION, CombatStance.BREAKTHROUGH]) if len(enemy_planned_combat_groups) > len(player_planned_combat_groups) else random.choice([CombatStance.DEFENSIVE, CombatStance.DEFENSIVE, CombatStance.DEFENSIVE, CombatStance.AMBUSH, CombatStance.AGGRESSIVE]) self.enemy_stance = self._enemy_stance()
self.game = game self.game = game
self.jtacs: List[JtacInfo] = [] self.jtacs: List[JtacInfo] = []
def _enemy_stance(self):
"""Picks the enemy stance according to the number of planned groups on the frontline for each side"""
if len(self.enemy_planned_combat_groups) > len(self.player_planned_combat_groups):
return random.choice(
[
CombatStance.AGGRESSIVE,
CombatStance.AGGRESSIVE,
CombatStance.AGGRESSIVE,
CombatStance.ELIMINATION,
CombatStance.BREAKTHROUGH
]
)
else:
return random.choice(
[
CombatStance.DEFENSIVE,
CombatStance.DEFENSIVE,
CombatStance.DEFENSIVE,
CombatStance.AMBUSH,
CombatStance.AGGRESSIVE
]
)
def _group_point(self, point) -> Point: def _group_point(self, point) -> Point:
distance = random.randint( distance = random.randint(
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]), int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
@ -266,7 +289,7 @@ class GroundConflictGenerator:
hold_2.number = 3 hold_2.number = 3
dcs_group.add_trigger_action(hold_2) dcs_group.add_trigger_action(hold_2)
retreat_task = GoToWaypoint(toIndex=3) retreat_task = GoToWaypoint(to_index=3)
retreat_task.number = 4 retreat_task.number = 4
dcs_group.add_trigger_action(retreat_task) dcs_group.add_trigger_action(retreat_task)
@ -362,7 +385,7 @@ class GroundConflictGenerator:
dcs_group.add_waypoint(self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)), PointAction.OffRoad) dcs_group.add_waypoint(self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)), PointAction.OffRoad)
# Fallback task # Fallback task
fallback = ControlledTask(GoToWaypoint(toIndex=len(dcs_group.points))) fallback = ControlledTask(GoToWaypoint(to_index=len(dcs_group.points)))
fallback.enabled = False fallback.enabled = False
dcs_group.add_trigger_action(Hold()) dcs_group.add_trigger_action(Hold())
dcs_group.add_trigger_action(fallback) dcs_group.add_trigger_action(fallback)

View File

@ -157,6 +157,24 @@ class Conflict:
return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position)) return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position))
@classmethod
def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
assert cls.has_frontline_between(from_cp, to_cp)
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
return cls(
position=position,
heading=heading,
distance=distance,
theater=theater,
from_cp=from_cp,
to_cp=to_cp,
attackers_side=attacker_name,
defenders_side=defender_name,
attackers_country=attacker,
defenders_country=defender,
)
@classmethod @classmethod
def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point: def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
pos = initial pos = initial

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import logging import logging
import typing from typing import TYPE_CHECKING
from enum import IntEnum from enum import IntEnum
from dcs.mission import Mission from dcs.mission import Mission
@ -7,6 +9,8 @@ from dcs.forcedoptions import ForcedOptions
from .conflictgen import * from .conflictgen import *
if TYPE_CHECKING:
from game.game import Game
class Labels(IntEnum): class Labels(IntEnum):
Off = 0 Off = 0
@ -16,9 +20,8 @@ class Labels(IntEnum):
class ForcedOptionsGenerator: class ForcedOptionsGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game): def __init__(self, mission: Mission, game: Game):
self.mission = mission self.mission = mission
self.conflict = conflict
self.game = game self.game = game
def _set_options_view(self): def _set_options_view(self):

View File

@ -353,40 +353,16 @@ class GroundObjectsGenerator:
locations for spawning ground objects, determining their types, and creating locations for spawning ground objects, determining their types, and creating
the appropriate generators. the appropriate generators.
""" """
FARP_CAPACITY = 4
def __init__(self, mission: Mission, conflict: Conflict, game: Game, def __init__(self, mission: Mission, game: Game,
radio_registry: RadioRegistry, tacan_registry: TacanRegistry): radio_registry: RadioRegistry, tacan_registry: TacanRegistry):
self.m = mission self.m = mission
self.conflict = conflict
self.game = game self.game = game
self.radio_registry = radio_registry self.radio_registry = radio_registry
self.tacan_registry = tacan_registry self.tacan_registry = tacan_registry
self.icls_alloc = iter(range(1, 21)) self.icls_alloc = iter(range(1, 21))
self.runways: Dict[str, RunwayData] = {} self.runways: Dict[str, RunwayData] = {}
def generate_farps(self, number_of_units=1) -> Iterator[StaticGroup]:
if self.conflict.is_vector:
center = self.conflict.center
heading = self.conflict.heading - 90
else:
center, heading = self.conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater)
heading -= 90
initial_position = center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE)
position = self.conflict.find_ground_position(initial_position, heading)
if not position:
position = initial_position
for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)):
position = position.point_from_heading(0, i * 275)
yield self.m.farp(
country=self.m.country(self.game.player_country),
name="FARP",
position=position,
)
def generate(self): def generate(self):
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if cp.captured: if cp.captured:

View File

@ -32,7 +32,7 @@ class Silence(Option):
class TriggersGenerator: class TriggersGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game): def __init__(self, mission: Mission, conflict: Conflict, game):
self.mission = mission self.mission = mission
self.conflict = conflict self.conflict = conflict # TODO: Move conflict out of this class. Only needed for bullseye position
self.game = game self.game = game
def _set_allegiances(self, player_coalition: str, enemy_coalition: str): def _set_allegiances(self, player_coalition: str, enemy_coalition: str):

View File

@ -92,9 +92,8 @@ def turn_heading(heading, fac):
class VisualGenerator: class VisualGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game: Game): def __init__(self, mission: Mission, game: Game):
self.mission = mission self.mission = mission
self.conflict = conflict
self.game = game self.game = game
def _generate_frontline_smokes(self): def _generate_frontline_smokes(self):

View File

@ -233,8 +233,7 @@ class QTopPanel(QFrame):
game_event.is_awacs_enabled = True game_event.is_awacs_enabled = True
game_event.ca_slots = 1 game_event.ca_slots = 1
game_event.departure_cp = self.game.theater.controlpoints[0] game_event.departure_cp = self.game.theater.controlpoints[0]
# game_event.player_attacking({CAS: {}, CAP: {}}) game_event.player_attacking()
game_event.depart_from = self.game.theater.controlpoints[0]
self.game.initiate_event(game_event) self.game.initiate_event(game_event)
waiting = QWaitingForMissionResultWindow(game_event, self.game) waiting = QWaitingForMissionResultWindow(game_event, self.game)