mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge pull request #437 from walterroach/operation_refactor
Operation refactor
This commit is contained in:
commit
17fe977b06
14
game/event/airwar.py
Normal file
14
game/event/airwar.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .event import Event
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game.theater import ConflictTheater
|
||||||
|
|
||||||
|
|
||||||
|
class AirWarEvent(Event):
|
||||||
|
"""Event handler for the air battle"""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "AirWar"
|
||||||
@ -15,10 +15,11 @@ from game.theater import ControlPoint
|
|||||||
from gen import AirTaskingOrder
|
from gen import AirTaskingOrder
|
||||||
from gen.ground_forces.combat_stance import CombatStance
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
from ..unitmap import UnitMap
|
from ..unitmap import UnitMap
|
||||||
|
from game.operation.operation import Operation
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..game import Game
|
from ..game import Game
|
||||||
from game.operation.operation import Operation
|
|
||||||
|
|
||||||
DIFFICULTY_LOG_BASE = 1.1
|
DIFFICULTY_LOG_BASE = 1.1
|
||||||
EVENT_DEPARTURE_MAX_DISTANCE = 340000
|
EVENT_DEPARTURE_MAX_DISTANCE = 340000
|
||||||
@ -32,21 +33,16 @@ STRONG_DEFEAT_INFLUENCE = 0.5
|
|||||||
class Event:
|
class Event:
|
||||||
silent = False
|
silent = False
|
||||||
informational = False
|
informational = False
|
||||||
is_awacs_enabled = False
|
|
||||||
ca_slots = 0
|
|
||||||
|
|
||||||
game = None # type: Game
|
game = None # type: Game
|
||||||
location = None # type: Point
|
location = None # type: Point
|
||||||
from_cp = None # type: ControlPoint
|
from_cp = None # type: ControlPoint
|
||||||
to_cp = None # type: ControlPoint
|
to_cp = None # type: ControlPoint
|
||||||
|
|
||||||
operation = None # type: Operation
|
|
||||||
difficulty = 1 # type: int
|
difficulty = 1 # type: int
|
||||||
BONUS_BASE = 5
|
BONUS_BASE = 5
|
||||||
|
|
||||||
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
|
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.departure_cp: Optional[ControlPoint] = None
|
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.to_cp = target_cp
|
self.to_cp = target_cp
|
||||||
self.location = location
|
self.location = location
|
||||||
@ -57,46 +53,17 @@ class Event:
|
|||||||
def is_player_attacking(self) -> bool:
|
def is_player_attacking(self) -> bool:
|
||||||
return self.attacker_name == self.game.player_name
|
return self.attacker_name == self.game.player_name
|
||||||
|
|
||||||
@property
|
|
||||||
def enemy_cp(self) -> Optional[ControlPoint]:
|
|
||||||
if self.attacker_name == self.game.player_name:
|
|
||||||
return self.to_cp
|
|
||||||
else:
|
|
||||||
return self.departure_cp
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tasks(self) -> List[Type[Task]]:
|
def tasks(self) -> List[Type[Task]]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@property
|
|
||||||
def global_cp_available(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_departure_available_from(self, cp: ControlPoint) -> bool:
|
|
||||||
if not cp.captured:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.location.distance_to_point(cp.position) > EVENT_DEPARTURE_MAX_DISTANCE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if cp.is_global and not self.global_cp_available:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def bonus(self) -> int:
|
def bonus(self) -> int:
|
||||||
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
||||||
|
|
||||||
def is_successful(self, debriefing: Debriefing) -> bool:
|
|
||||||
return self.operation.is_successful(debriefing)
|
|
||||||
|
|
||||||
def generate(self) -> UnitMap:
|
def generate(self) -> UnitMap:
|
||||||
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
Operation.prepare(self.game)
|
||||||
self.operation.ca_slots = self.ca_slots
|
unit_map = Operation.generate()
|
||||||
|
Operation.current_mission.save(
|
||||||
self.operation.prepare(self.game)
|
|
||||||
unit_map = self.operation.generate()
|
|
||||||
self.operation.current_mission.save(
|
|
||||||
persistency.mission_path_for("liberation_nextturn.miz"))
|
persistency.mission_path_for("liberation_nextturn.miz"))
|
||||||
return unit_map
|
return unit_map
|
||||||
|
|
||||||
|
|||||||
@ -1,42 +1,11 @@
|
|||||||
from typing import List, Type
|
|
||||||
|
|
||||||
from dcs.task import CAP, CAS, Task
|
|
||||||
from game.operation.operation import Operation
|
|
||||||
|
|
||||||
from ..debriefing import Debriefing
|
|
||||||
from .event import Event
|
from .event import Event
|
||||||
|
|
||||||
|
|
||||||
class FrontlineAttackEvent(Event):
|
class FrontlineAttackEvent(Event):
|
||||||
|
"""
|
||||||
@property
|
An event centered on a FrontLine Conflict.
|
||||||
def tasks(self) -> List[Type[Task]]:
|
Currently the same as its parent, but here for legacy compatibility as well as to allow for
|
||||||
if self.is_player_attacking:
|
future unique Event handling
|
||||||
return [CAS, CAP]
|
"""
|
||||||
else:
|
|
||||||
return [CAP]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def global_cp_available(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Frontline attack"
|
return "Frontline attack"
|
||||||
|
|
||||||
def is_successful(self, debriefing: Debriefing):
|
|
||||||
attackers_success = True
|
|
||||||
if self.from_cp.captured:
|
|
||||||
return attackers_success
|
|
||||||
else:
|
|
||||||
return not attackers_success
|
|
||||||
|
|
||||||
def commit(self, debriefing: Debriefing):
|
|
||||||
super(FrontlineAttackEvent, self).commit(debriefing)
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
if self.to_cp.captured:
|
|
||||||
self.to_cp.base.affect_strength(-0.1)
|
|
||||||
|
|
||||||
def player_attacking(self):
|
|
||||||
assert self.departure_cp is not None
|
|
||||||
self.operation = Operation(departure_cp=self.departure_cp,)
|
|
||||||
|
|||||||
@ -182,8 +182,7 @@ class Game:
|
|||||||
def finish_event(self, event: Event, debriefing: Debriefing):
|
def finish_event(self, event: Event, debriefing: Debriefing):
|
||||||
logging.info("Finishing event {}".format(event))
|
logging.info("Finishing event {}".format(event))
|
||||||
event.commit(debriefing)
|
event.commit(debriefing)
|
||||||
if event.is_successful(debriefing):
|
self.budget += event.bonus()
|
||||||
self.budget += event.bonus()
|
|
||||||
|
|
||||||
if event in self.events:
|
if event in self.events:
|
||||||
self.events.remove(event)
|
self.events.remove(event)
|
||||||
@ -194,7 +193,7 @@ class Game:
|
|||||||
if isinstance(event, Event):
|
if isinstance(event, Event):
|
||||||
return event and event.attacker_name and event.attacker_name == self.player_name
|
return event and event.attacker_name and event.attacker_name == self.player_name
|
||||||
else:
|
else:
|
||||||
return event and event.name and event.name == self.player_name
|
raise RuntimeError(f"{event} was passed when an Event type was expected")
|
||||||
|
|
||||||
def on_load(self) -> None:
|
def on_load(self) -> None:
|
||||||
LuaPluginManager.load_settings(self.settings)
|
LuaPluginManager.load_settings(self.settings)
|
||||||
|
|||||||
@ -14,9 +14,7 @@ from dcs.lua.parse import loads
|
|||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
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 game.plugins import LuaPluginManager
|
from game.plugins import LuaPluginManager
|
||||||
from game.theater import ControlPoint
|
|
||||||
from gen import Conflict, FlightType, VisualGenerator
|
from gen import Conflict, FlightType, VisualGenerator
|
||||||
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
||||||
from gen.airfields import AIRFIELD_DATA
|
from gen.airfields import AIRFIELD_DATA
|
||||||
@ -41,9 +39,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
attackers_starting_position = None # type: db.StartingPosition
|
"""Static class for managing the final Mission generation"""
|
||||||
defenders_starting_position = None # type: db.StartingPosition
|
|
||||||
|
|
||||||
current_mission = None # type: Mission
|
current_mission = None # type: Mission
|
||||||
airgen = None # type: AircraftConflictGenerator
|
airgen = None # type: AircraftConflictGenerator
|
||||||
triggersgen = None # type: TriggersGenerator
|
triggersgen = None # type: TriggersGenerator
|
||||||
@ -58,16 +54,13 @@ class Operation:
|
|||||||
environment_settings = None
|
environment_settings = None
|
||||||
trigger_radius = TRIGGER_RADIUS_MEDIUM
|
trigger_radius = TRIGGER_RADIUS_MEDIUM
|
||||||
is_quick = None
|
is_quick = None
|
||||||
is_awacs_enabled = False
|
player_awacs_enabled = True
|
||||||
ca_slots = 0
|
# TODO: #436 Generate Air Support for red
|
||||||
|
enemy_awacs_enabled = True
|
||||||
|
ca_slots = 1
|
||||||
unit_map: UnitMap
|
unit_map: UnitMap
|
||||||
|
jtacs: List[JtacInfo] = []
|
||||||
def __init__(self,
|
plugin_scripts: List[str] = []
|
||||||
departure_cp: ControlPoint,
|
|
||||||
):
|
|
||||||
self.departure_cp = departure_cp
|
|
||||||
self.plugin_scripts: List[str] = []
|
|
||||||
self.jtacs: List[JtacInfo] = []
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepare(cls, game: Game):
|
def prepare(cls, game: Game):
|
||||||
@ -93,12 +86,6 @@ class Operation:
|
|||||||
frontline.position
|
frontline.position
|
||||||
)
|
)
|
||||||
|
|
||||||
def units_of(self, country_name: str) -> List[UnitType]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def is_successful(self, debriefing: Debriefing) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_mission(cls, mission: Mission) -> None:
|
def _set_mission(cls, mission: Mission) -> None:
|
||||||
cls.current_mission = mission
|
cls.current_mission = mission
|
||||||
@ -115,22 +102,25 @@ class Operation:
|
|||||||
cls.current_mission.coalition["red"].add_country(
|
cls.current_mission.coalition["red"].add_country(
|
||||||
country_dict[db.country_id_from_name(e_country)]())
|
country_dict[db.country_id_from_name(e_country)]())
|
||||||
|
|
||||||
def inject_lua_trigger(self, contents: str, comment: str) -> None:
|
@classmethod
|
||||||
|
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
||||||
trigger = TriggerStart(comment=comment)
|
trigger = TriggerStart(comment=comment)
|
||||||
trigger.add_action(DoScript(String(contents)))
|
trigger.add_action(DoScript(String(contents)))
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
cls.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
def bypass_plugin_script(self, mnemonic: str) -> None:
|
@classmethod
|
||||||
self.plugin_scripts.append(mnemonic)
|
def bypass_plugin_script(cls, mnemonic: str) -> None:
|
||||||
|
cls.plugin_scripts.append(mnemonic)
|
||||||
|
|
||||||
def inject_plugin_script(self, plugin_mnemonic: str, script: str,
|
@classmethod
|
||||||
|
def inject_plugin_script(cls, plugin_mnemonic: str, script: str,
|
||||||
script_mnemonic: str) -> None:
|
script_mnemonic: str) -> None:
|
||||||
if script_mnemonic in self.plugin_scripts:
|
if script_mnemonic in cls.plugin_scripts:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"Skipping already loaded {script} for {plugin_mnemonic}"
|
f"Skipping already loaded {script} for {plugin_mnemonic}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.plugin_scripts.append(script_mnemonic)
|
cls.plugin_scripts.append(script_mnemonic)
|
||||||
|
|
||||||
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
||||||
|
|
||||||
@ -143,13 +133,14 @@ 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(
|
fileref = cls.current_mission.map_resource.add_resource_file(
|
||||||
filename)
|
filename)
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
trigger.add_action(DoScriptFile(fileref))
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
cls.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def notify_info_generators(
|
def notify_info_generators(
|
||||||
self,
|
cls,
|
||||||
groundobjectgen: GroundObjectsGenerator,
|
groundobjectgen: GroundObjectsGenerator,
|
||||||
airsupportgen: AirSupportConflictGenerator,
|
airsupportgen: AirSupportConflictGenerator,
|
||||||
jtacs: List[JtacInfo],
|
jtacs: List[JtacInfo],
|
||||||
@ -158,8 +149,8 @@ class Operation:
|
|||||||
"""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(cls.current_mission, cls.game),
|
||||||
BriefingGenerator(self.current_mission, self.game)
|
BriefingGenerator(cls.current_mission, cls.game)
|
||||||
]
|
]
|
||||||
for gen in gens:
|
for gen in gens:
|
||||||
for dynamic_runway in groundobjectgen.runways.values():
|
for dynamic_runway in groundobjectgen.runways.values():
|
||||||
@ -168,7 +159,7 @@ class Operation:
|
|||||||
for tanker in airsupportgen.air_support.tankers:
|
for tanker in airsupportgen.air_support.tankers:
|
||||||
gen.add_tanker(tanker)
|
gen.add_tanker(tanker)
|
||||||
|
|
||||||
if self.is_awacs_enabled:
|
if cls.player_awacs_enabled:
|
||||||
for awacs in airsupportgen.air_support.awacs:
|
for awacs in airsupportgen.air_support.awacs:
|
||||||
gen.add_awacs(awacs)
|
gen.add_awacs(awacs)
|
||||||
|
|
||||||
@ -189,15 +180,17 @@ class Operation:
|
|||||||
cls._create_tacan_registry(unique_map_frequencies)
|
cls._create_tacan_registry(unique_map_frequencies)
|
||||||
cls._create_radio_registry(unique_map_frequencies)
|
cls._create_radio_registry(unique_map_frequencies)
|
||||||
|
|
||||||
def assign_channels_to_flights(self, flights: List[FlightData],
|
@classmethod
|
||||||
|
def assign_channels_to_flights(cls, flights: List[FlightData],
|
||||||
air_support: AirSupport) -> None:
|
air_support: AirSupport) -> None:
|
||||||
"""Assigns preset radio channels for client flights."""
|
"""Assigns preset radio channels for client flights."""
|
||||||
for flight in flights:
|
for flight in flights:
|
||||||
if not flight.client_units:
|
if not flight.client_units:
|
||||||
continue
|
continue
|
||||||
self.assign_channels_to_flight(flight, air_support)
|
cls.assign_channels_to_flight(flight, air_support)
|
||||||
|
|
||||||
def assign_channels_to_flight(self, flight: FlightData,
|
@staticmethod
|
||||||
|
def assign_channels_to_flight(flight: FlightData,
|
||||||
air_support: AirSupport) -> None:
|
air_support: AirSupport) -> None:
|
||||||
"""Assigns preset radio channels for a client flight."""
|
"""Assigns preset radio channels for a client flight."""
|
||||||
airframe = flight.aircraft_type
|
airframe = flight.aircraft_type
|
||||||
@ -235,12 +228,11 @@ class Operation:
|
|||||||
def _create_radio_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None:
|
def _create_radio_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None:
|
||||||
cls.radio_registry = RadioRegistry()
|
cls.radio_registry = RadioRegistry()
|
||||||
for data in AIRFIELD_DATA.values():
|
for data in AIRFIELD_DATA.values():
|
||||||
if data.theater == cls.game.theater.terrain.name:
|
if data.theater == cls.game.theater.terrain.name and data.atc:
|
||||||
if data.atc:
|
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.
|
||||||
|
|
||||||
@ -254,19 +246,20 @@ class Operation:
|
|||||||
)
|
)
|
||||||
cls.groundobjectgen.generate()
|
cls.groundobjectgen.generate()
|
||||||
|
|
||||||
def _generate_destroyed_units(self) -> None:
|
@classmethod
|
||||||
|
def _generate_destroyed_units(cls) -> None:
|
||||||
"""Add destroyed units to the Mission"""
|
"""Add destroyed units to the Mission"""
|
||||||
for d in self.game.get_destroyed_units():
|
for d in cls.game.get_destroyed_units():
|
||||||
try:
|
try:
|
||||||
utype = db.unit_type_from_name(d["type"])
|
utype = db.unit_type_from_name(d["type"])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
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 cls.game.position_culled(pos) and cls.game.settings.perf_destroyed_units:
|
||||||
self.current_mission.static_group(
|
cls.current_mission.static_group(
|
||||||
country=self.current_mission.country(
|
country=cls.current_mission.country(
|
||||||
self.game.player_country),
|
cls.game.player_country),
|
||||||
name="",
|
name="",
|
||||||
_type=utype,
|
_type=utype,
|
||||||
hidden=True,
|
hidden=True,
|
||||||
@ -274,63 +267,64 @@ class Operation:
|
|||||||
heading=d["orientation"],
|
heading=d["orientation"],
|
||||||
dead=True,
|
dead=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate(self) -> UnitMap:
|
@classmethod
|
||||||
|
def generate(cls) -> UnitMap:
|
||||||
"""Build the final Mission to be exported"""
|
"""Build the final Mission to be exported"""
|
||||||
self.create_unit_map()
|
cls.create_unit_map()
|
||||||
self.create_radio_registries()
|
cls.create_radio_registries()
|
||||||
# Set mission time and weather conditions.
|
# Set mission time and weather conditions.
|
||||||
EnvironmentGenerator(self.current_mission,
|
EnvironmentGenerator(cls.current_mission,
|
||||||
self.game.conditions).generate()
|
cls.game.conditions).generate()
|
||||||
self._generate_ground_units()
|
cls._generate_ground_units()
|
||||||
self._generate_destroyed_units()
|
cls._generate_destroyed_units()
|
||||||
self._generate_air_units()
|
cls._generate_air_units()
|
||||||
self.assign_channels_to_flights(self.airgen.flights,
|
cls.assign_channels_to_flights(cls.airgen.flights,
|
||||||
self.airsupportgen.air_support)
|
cls.airsupportgen.air_support)
|
||||||
self._generate_ground_conflicts()
|
cls._generate_ground_conflicts()
|
||||||
|
|
||||||
# TODO: This is silly, once Bulls position is defined without Conflict this should be removed.
|
# TODO: This is silly, once Bulls position is defined without Conflict this should be removed.
|
||||||
default_conflict = [i for i in self.conflicts()][0]
|
default_conflict = [i for i in cls.conflicts()][0]
|
||||||
# Triggers
|
# Triggers
|
||||||
triggersgen = TriggersGenerator(self.current_mission, default_conflict,
|
triggersgen = TriggersGenerator(cls.current_mission, default_conflict,
|
||||||
self.game)
|
cls.game)
|
||||||
triggersgen.generate()
|
triggersgen.generate()
|
||||||
|
|
||||||
# Setup combined arms parameters
|
# Setup combined arms parameters
|
||||||
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
|
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
|
||||||
if self.game.player_country in [country.name for country in self.current_mission.coalition["blue"].countries.values()]:
|
if cls.game.player_country in [country.name for country in cls.current_mission.coalition["blue"].countries.values()]:
|
||||||
self.current_mission.groundControl.blue_tactical_commander = self.ca_slots
|
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
|
||||||
else:
|
else:
|
||||||
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
|
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
forcedoptionsgen = ForcedOptionsGenerator(
|
forcedoptionsgen = ForcedOptionsGenerator(
|
||||||
self.current_mission, self.game)
|
cls.current_mission, cls.game)
|
||||||
forcedoptionsgen.generate()
|
forcedoptionsgen.generate()
|
||||||
|
|
||||||
# Generate Visuals Smoke Effects
|
# Generate Visuals Smoke Effects
|
||||||
visualgen = VisualGenerator(self.current_mission, self.game)
|
visualgen = VisualGenerator(cls.current_mission, cls.game)
|
||||||
if self.game.settings.perf_smoke_gen:
|
if cls.game.settings.perf_smoke_gen:
|
||||||
visualgen.generate()
|
visualgen.generate()
|
||||||
|
|
||||||
self.generate_lua(self.airgen, self.airsupportgen, self.jtacs)
|
cls.generate_lua(cls.airgen, cls.airsupportgen, cls.jtacs)
|
||||||
|
|
||||||
# Inject Plugins Lua Scripts and data
|
# Inject Plugins Lua Scripts and data
|
||||||
for plugin in LuaPluginManager.plugins():
|
for plugin in LuaPluginManager.plugins():
|
||||||
if plugin.enabled:
|
if plugin.enabled:
|
||||||
plugin.inject_scripts(self)
|
plugin.inject_scripts(cls)
|
||||||
plugin.inject_configuration(self)
|
plugin.inject_configuration(cls)
|
||||||
|
|
||||||
self.assign_channels_to_flights(self.airgen.flights,
|
cls.assign_channels_to_flights(cls.airgen.flights,
|
||||||
self.airsupportgen.air_support)
|
cls.airsupportgen.air_support)
|
||||||
self.notify_info_generators(
|
cls.notify_info_generators(
|
||||||
self.groundobjectgen,
|
cls.groundobjectgen,
|
||||||
self.airsupportgen,
|
cls.airsupportgen,
|
||||||
self.jtacs,
|
cls.jtacs,
|
||||||
self.airgen
|
cls.airgen
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.unit_map
|
return cls.unit_map
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_air_units(cls) -> None:
|
def _generate_air_units(cls) -> None:
|
||||||
@ -343,7 +337,7 @@ class Operation:
|
|||||||
cls.airsupportgen = AirSupportConflictGenerator(
|
cls.airsupportgen = AirSupportConflictGenerator(
|
||||||
cls.current_mission, default_conflict, cls.game, cls.radio_registry,
|
cls.current_mission, default_conflict, cls.game, cls.radio_registry,
|
||||||
cls.tacan_registry)
|
cls.tacan_registry)
|
||||||
cls.airsupportgen.generate(cls.is_awacs_enabled)
|
cls.airsupportgen.generate()
|
||||||
|
|
||||||
# Generate Aircraft Activity on the map
|
# Generate Aircraft Activity on the map
|
||||||
cls.airgen = AircraftConflictGenerator(
|
cls.airgen = AircraftConflictGenerator(
|
||||||
@ -364,33 +358,35 @@ class Operation:
|
|||||||
cls.current_mission.country(cls.game.player_country),
|
cls.current_mission.country(cls.game.player_country),
|
||||||
cls.current_mission.country(cls.game.enemy_country))
|
cls.current_mission.country(cls.game.enemy_country))
|
||||||
|
|
||||||
def _generate_ground_conflicts(self) -> None:
|
@classmethod
|
||||||
|
def _generate_ground_conflicts(cls) -> None:
|
||||||
"""For each frontline in the Operation, generate the ground conflicts and JTACs"""
|
"""For each frontline in the Operation, generate the ground conflicts and JTACs"""
|
||||||
for front_line in self.game.theater.conflicts(True):
|
for front_line in cls.game.theater.conflicts(True):
|
||||||
player_cp = front_line.control_point_a
|
player_cp = front_line.control_point_a
|
||||||
enemy_cp = front_line.control_point_b
|
enemy_cp = front_line.control_point_b
|
||||||
conflict = Conflict.frontline_cas_conflict(
|
conflict = Conflict.frontline_cas_conflict(
|
||||||
self.game.player_name,
|
cls.game.player_name,
|
||||||
self.game.enemy_name,
|
cls.game.enemy_name,
|
||||||
self.current_mission.country(self.game.player_country),
|
cls.current_mission.country(cls.game.player_country),
|
||||||
self.current_mission.country(self.game.enemy_country),
|
cls.current_mission.country(cls.game.enemy_country),
|
||||||
player_cp,
|
player_cp,
|
||||||
enemy_cp,
|
enemy_cp,
|
||||||
self.game.theater
|
cls.game.theater
|
||||||
)
|
)
|
||||||
# Generate frontline ops
|
# Generate frontline ops
|
||||||
player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
|
player_gp = cls.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]
|
enemy_gp = cls.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
|
||||||
ground_conflict_gen = GroundConflictGenerator(
|
ground_conflict_gen = GroundConflictGenerator(
|
||||||
self.current_mission,
|
cls.current_mission,
|
||||||
conflict, self.game,
|
conflict, cls.game,
|
||||||
player_gp, enemy_gp,
|
player_gp, enemy_gp,
|
||||||
player_cp.stances[enemy_cp.id]
|
player_cp.stances[enemy_cp.id]
|
||||||
)
|
)
|
||||||
ground_conflict_gen.generate()
|
ground_conflict_gen.generate()
|
||||||
self.jtacs.extend(ground_conflict_gen.jtacs)
|
cls.jtacs.extend(ground_conflict_gen.jtacs)
|
||||||
|
|
||||||
def generate_lua(self, airgen: AircraftConflictGenerator,
|
@classmethod
|
||||||
|
def generate_lua(cls, airgen: AircraftConflictGenerator,
|
||||||
airsupportgen: AirSupportConflictGenerator,
|
airsupportgen: AirSupportConflictGenerator,
|
||||||
jtacs: List[JtacInfo]) -> None:
|
jtacs: List[JtacInfo]) -> None:
|
||||||
# TODO: Refactor this
|
# TODO: Refactor this
|
||||||
@ -411,7 +407,7 @@ class Operation:
|
|||||||
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name
|
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_awacs_enabled:
|
if airsupportgen.air_support.awacs:
|
||||||
for awacs in airsupportgen.air_support.awacs:
|
for awacs in airsupportgen.air_support.awacs:
|
||||||
luaData["AWACs"][awacs.callsign] = {
|
luaData["AWACs"][awacs.callsign] = {
|
||||||
"dcsGroupName": awacs.dcsGroupName,
|
"dcsGroupName": awacs.dcsGroupName,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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
|
from typing import List, Optional, TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ class LuaPluginWorkOrder:
|
|||||||
self.mnemonic = mnemonic
|
self.mnemonic = mnemonic
|
||||||
self.disable = disable
|
self.disable = disable
|
||||||
|
|
||||||
def work(self, operation: Operation) -> None:
|
def work(self, operation: Type[Operation]) -> None:
|
||||||
if self.disable:
|
if self.disable:
|
||||||
operation.bypass_plugin_script(self.mnemonic)
|
operation.bypass_plugin_script(self.mnemonic)
|
||||||
else:
|
else:
|
||||||
@ -144,11 +144,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: Operation) -> None:
|
def inject_scripts(self, operation: Type[Operation]) -> None:
|
||||||
for work_order in self.definition.work_orders:
|
for work_order in self.definition.work_orders:
|
||||||
work_order.work(operation)
|
work_order.work(operation)
|
||||||
|
|
||||||
def inject_configuration(self, operation: Operation) -> None:
|
def inject_configuration(self, operation: Type[Operation]) -> None:
|
||||||
# inject the plugin options
|
# inject the plugin options
|
||||||
if self.options:
|
if self.options:
|
||||||
option_decls = []
|
option_decls = []
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast
|
||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
from dcs.countries import (
|
from dcs.countries import (
|
||||||
@ -470,6 +470,43 @@ class ConflictTheater:
|
|||||||
closest = control_point
|
closest = control_point
|
||||||
closest_distance = distance
|
closest_distance = distance
|
||||||
return closest
|
return closest
|
||||||
|
|
||||||
|
def closest_opposing_control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||||
|
"""
|
||||||
|
Returns a tuple of the two nearest opposing ControlPoints in theater.
|
||||||
|
(player_cp, enemy_cp)
|
||||||
|
"""
|
||||||
|
all_cp_min_distances = {}
|
||||||
|
for idx, control_point in enumerate(self.controlpoints):
|
||||||
|
distances = {}
|
||||||
|
closest_distance = None
|
||||||
|
for i, cp in enumerate(self.controlpoints):
|
||||||
|
if i != idx and cp.captured is not control_point.captured:
|
||||||
|
dist = cp.position.distance_to_point(control_point.position)
|
||||||
|
if not closest_distance:
|
||||||
|
closest_distance = dist
|
||||||
|
distances[cp.id] = dist
|
||||||
|
if dist < closest_distance:
|
||||||
|
distances[cp.id] = dist
|
||||||
|
closest_cp_id = min(distances, key=distances.get)
|
||||||
|
|
||||||
|
all_cp_min_distances[(control_point.id, closest_cp_id)] = distances[closest_cp_id]
|
||||||
|
closest_opposing_cps = [
|
||||||
|
self.find_control_point_by_id(i)
|
||||||
|
for i
|
||||||
|
in min(all_cp_min_distances, key=all_cp_min_distances.get)
|
||||||
|
] # type: List[ControlPoint]
|
||||||
|
assert len(closest_opposing_cps) == 2
|
||||||
|
if closest_opposing_cps[0].captured:
|
||||||
|
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
||||||
|
else:
|
||||||
|
return cast(Tuple[ControlPoint, ControlPoint], tuple(reversed(closest_opposing_cps)))
|
||||||
|
|
||||||
|
def find_control_point_by_id(self, id: int) -> ControlPoint:
|
||||||
|
for i in self.controlpoints:
|
||||||
|
if i.id == id:
|
||||||
|
return i
|
||||||
|
raise RuntimeError(f"Cannot find ControlPoint with ID {id}")
|
||||||
|
|
||||||
def add_json_cp(self, theater, p: dict) -> ControlPoint:
|
def add_json_cp(self, theater, p: dict) -> ControlPoint:
|
||||||
cp: ControlPoint
|
cp: ControlPoint
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List, Type
|
from typing import List, Type
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ class AirSupportConflictGenerator:
|
|||||||
def support_tasks(cls) -> List[Type[MainTask]]:
|
def support_tasks(cls) -> List[Type[MainTask]]:
|
||||||
return [Refueling, AWACS]
|
return [Refueling, AWACS]
|
||||||
|
|
||||||
def generate(self, is_awacs_enabled):
|
def generate(self):
|
||||||
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
|
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
|
||||||
|
|
||||||
fallback_tanker_number = 0
|
fallback_tanker_number = 0
|
||||||
@ -120,26 +121,26 @@ class AirSupportConflictGenerator:
|
|||||||
|
|
||||||
self.air_support.tankers.append(TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan))
|
self.air_support.tankers.append(TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan))
|
||||||
|
|
||||||
if is_awacs_enabled:
|
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
|
||||||
try:
|
if awacs_unit:
|
||||||
freq = self.radio_registry.alloc_uhf()
|
freq = self.radio_registry.alloc_uhf()
|
||||||
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
|
|
||||||
awacs_flight = self.mission.awacs_flight(
|
awacs_flight = self.mission.awacs_flight(
|
||||||
country=self.mission.country(self.game.player_country),
|
country=self.mission.country(self.game.player_country),
|
||||||
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
|
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
|
||||||
plane_type=awacs_unit,
|
plane_type=awacs_unit,
|
||||||
altitude=AWACS_ALT,
|
altitude=AWACS_ALT,
|
||||||
airport=None,
|
airport=None,
|
||||||
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
|
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
|
||||||
frequency=freq.mhz,
|
frequency=freq.mhz,
|
||||||
start_type=StartType.Warm,
|
start_type=StartType.Warm,
|
||||||
)
|
)
|
||||||
awacs_flight.set_frequency(freq.mhz)
|
awacs_flight.set_frequency(freq.mhz)
|
||||||
|
|
||||||
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.awacs.append(AwacsInfo(
|
self.air_support.awacs.append(AwacsInfo(
|
||||||
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
|
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
|
||||||
except:
|
else:
|
||||||
print("No AWACS for faction")
|
logging.warning("No AWACS for faction")
|
||||||
@ -8,37 +8,8 @@ from dcs.mapping import Point
|
|||||||
from game.theater.conflicttheater import ConflictTheater, FrontLine
|
from game.theater.conflicttheater import ConflictTheater, FrontLine
|
||||||
from game.theater.controlpoint import ControlPoint
|
from game.theater.controlpoint import ControlPoint
|
||||||
|
|
||||||
AIR_DISTANCE = 40000
|
|
||||||
|
|
||||||
CAPTURE_AIR_ATTACKERS_DISTANCE = 25000
|
|
||||||
CAPTURE_AIR_DEFENDERS_DISTANCE = 60000
|
|
||||||
STRIKE_AIR_ATTACKERS_DISTANCE = 45000
|
|
||||||
STRIKE_AIR_DEFENDERS_DISTANCE = 25000
|
|
||||||
|
|
||||||
CAP_CAS_DISTANCE = 10000, 120000
|
|
||||||
|
|
||||||
GROUND_INTERCEPT_SPREAD = 5000
|
|
||||||
GROUND_DISTANCE_FACTOR = 1.4
|
|
||||||
GROUND_DISTANCE = 2000
|
|
||||||
|
|
||||||
GROUND_ATTACK_DISTANCE = 25000, 13000
|
|
||||||
|
|
||||||
TRANSPORT_FRONTLINE_DIST = 1800
|
|
||||||
|
|
||||||
INTERCEPT_ATTACKERS_HEADING = -45, 45
|
|
||||||
INTERCEPT_DEFENDERS_HEADING = -10, 10
|
|
||||||
INTERCEPT_CONFLICT_DISTANCE = 50000
|
|
||||||
INTERCEPT_ATTACKERS_DISTANCE = 100000
|
|
||||||
INTERCEPT_MAX_DISTANCE = 160000
|
|
||||||
INTERCEPT_MIN_DISTANCE = 100000
|
|
||||||
|
|
||||||
NAVAL_INTERCEPT_DISTANCE_FACTOR = 1
|
|
||||||
NAVAL_INTERCEPT_DISTANCE_MAX = 40000
|
|
||||||
NAVAL_INTERCEPT_STEP = 5000
|
|
||||||
|
|
||||||
FRONTLINE_LENGTH = 80000
|
FRONTLINE_LENGTH = 80000
|
||||||
FRONTLINE_MIN_CP_DISTANCE = 5000
|
|
||||||
FRONTLINE_DISTANCE_STRENGTH_FACTOR = 0.7
|
|
||||||
|
|
||||||
|
|
||||||
def _opposite_heading(h):
|
def _opposite_heading(h):
|
||||||
@ -98,10 +69,6 @@ class Conflict:
|
|||||||
def opposite_heading(self) -> int:
|
def opposite_heading(self) -> int:
|
||||||
return _heading_sum(self.heading, 180)
|
return _heading_sum(self.heading, 180)
|
||||||
|
|
||||||
@property
|
|
||||||
def to_size(self):
|
|
||||||
return self.to_cp.size * GROUND_DISTANCE_FACTOR
|
|
||||||
|
|
||||||
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> Point:
|
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> Point:
|
||||||
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from PySide2.QtWidgets import (
|
|||||||
|
|
||||||
import qt_ui.uiconstants as CONST
|
import qt_ui.uiconstants as CONST
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.event import CAP, CAS, FrontlineAttackEvent
|
from game.event.airwar import AirWarEvent
|
||||||
from gen.ato import Package
|
from gen.ato import Package
|
||||||
from gen.flights.traveltime import TotEstimator
|
from gen.flights.traveltime import TotEstimator
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
@ -214,26 +214,14 @@ class QTopPanel(QFrame):
|
|||||||
if negative_starts:
|
if negative_starts:
|
||||||
if not self.confirm_negative_start_time(negative_starts):
|
if not self.confirm_negative_start_time(negative_starts):
|
||||||
return
|
return
|
||||||
|
closest_cps = self.game.theater.closest_opposing_control_points()
|
||||||
# TODO: Refactor this nonsense.
|
game_event = AirWarEvent(
|
||||||
game_event = None
|
self.game,
|
||||||
for event in self.game.events:
|
closest_cps[0],
|
||||||
if isinstance(event,
|
closest_cps[1],
|
||||||
FrontlineAttackEvent) and event.is_player_attacking:
|
self.game.theater.controlpoints[0].position,
|
||||||
game_event = event
|
self.game.player_name,
|
||||||
# TODO: Why does this exist?
|
self.game.enemy_name)
|
||||||
if game_event is None:
|
|
||||||
game_event = FrontlineAttackEvent(
|
|
||||||
self.game,
|
|
||||||
self.game.theater.controlpoints[0],
|
|
||||||
self.game.theater.controlpoints[1],
|
|
||||||
self.game.theater.controlpoints[1].position,
|
|
||||||
self.game.player_name,
|
|
||||||
self.game.enemy_name)
|
|
||||||
game_event.is_awacs_enabled = True
|
|
||||||
game_event.ca_slots = 1
|
|
||||||
game_event.departure_cp = self.game.theater.controlpoints[0]
|
|
||||||
game_event.player_attacking()
|
|
||||||
|
|
||||||
unit_map = self.game.initiate_event(game_event)
|
unit_map = self.game.initiate_event(game_event)
|
||||||
waiting = QWaitingForMissionResultWindow(game_event, self.game,
|
waiting = QWaitingForMissionResultWindow(game_event, self.game,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user