mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
- factor out own class for the iadsnetwork within the conflicttheater - This class will handle all Skynet related things - no specific group_name handling necessary in future - make iadsbuilding own TGO class because SAM & EWRs are Vehicle Groups. IADS Elements dont have any groups attached. - added command center, connection node and power source as Ground objects which can be added by the campaign designer - adjust lua generator to support new iads units - parse the campaign yaml to get the iads network information - use the range as fallback if no yaml information was found - complete rewrite of the skynet lua script - allow destruction of iads network to be persistent over all rounds - modified the presetlocation handling: the wrapper PresetLocation for PointWithHeading now stores the original name from the campaign miz to have the ability to process campaign yaml configurations based on the ground unit - Implementation of the UI representation for the IADS Network - Give user the option to enable or disable advanced iads - Extended the layout system: Implement Sub task handling to support PD
328 lines
12 KiB
Python
328 lines
12 KiB
Python
from __future__ import annotations
|
|
from collections import defaultdict
|
|
|
|
import logging
|
|
import os
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
from dcs import Mission
|
|
from dcs.action import DoScript, DoScriptFile
|
|
from dcs.translation import String
|
|
from dcs.triggers import TriggerStart
|
|
|
|
from game.ato import FlightType
|
|
from game.plugins import LuaPluginManager
|
|
from game.theater import TheaterGroundObject
|
|
from game.utils import escape_string_for_lua
|
|
|
|
from .aircraft.flightdata import FlightData
|
|
from .airsupport import AirSupport
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
|
|
|
|
class LuaGenerator:
|
|
def __init__(
|
|
self,
|
|
game: Game,
|
|
mission: Mission,
|
|
air_support: AirSupport,
|
|
flights: list[FlightData],
|
|
) -> None:
|
|
self.game = game
|
|
self.mission = mission
|
|
self.air_support = air_support
|
|
self.flights = flights
|
|
self.plugin_scripts: list[str] = []
|
|
|
|
def generate(self) -> None:
|
|
self.generate_plugin_data()
|
|
self.inject_plugins()
|
|
|
|
def generate_plugin_data(self) -> None:
|
|
lua_data = LuaData("dcsLiberation")
|
|
|
|
install_path = lua_data.add_item("installPath")
|
|
install_path.set_value(os.path.abspath("."))
|
|
|
|
lua_data.add_item("Airbases")
|
|
lua_data.add_item("Carriers")
|
|
|
|
tankers_object = lua_data.add_item("Tankers")
|
|
for tanker in self.air_support.tankers:
|
|
tanker_item = tankers_object.add_item()
|
|
tanker_item.add_key_value("dcsGroupName", tanker.group_name)
|
|
tanker_item.add_key_value("callsign", tanker.callsign)
|
|
tanker_item.add_key_value("variant", tanker.variant)
|
|
tanker_item.add_key_value("radio", str(tanker.freq.mhz))
|
|
tanker_item.add_key_value(
|
|
"tacan", str(tanker.tacan.number) + tanker.tacan.band.name
|
|
)
|
|
|
|
awacs_object = lua_data.add_item("AWACs")
|
|
for awacs in self.air_support.awacs:
|
|
awacs_item = awacs_object.add_item()
|
|
awacs_item.add_key_value("dcsGroupName", awacs.group_name)
|
|
awacs_item.add_key_value("callsign", awacs.callsign)
|
|
awacs_item.add_key_value("radio", str(awacs.freq.mhz))
|
|
|
|
jtacs_object = lua_data.add_item("JTACs")
|
|
for jtac in self.air_support.jtacs:
|
|
jtac_item = jtacs_object.add_item()
|
|
jtac_item.add_key_value("dcsGroupName", jtac.group_name)
|
|
jtac_item.add_key_value("callsign", jtac.callsign)
|
|
jtac_item.add_key_value("zone", jtac.region)
|
|
jtac_item.add_key_value("dcsUnit", jtac.unit_name)
|
|
jtac_item.add_key_value("laserCode", jtac.code)
|
|
|
|
target_points = lua_data.add_item("TargetPoints")
|
|
for flight in self.flights:
|
|
if flight.friendly and flight.flight_type in [
|
|
FlightType.ANTISHIP,
|
|
FlightType.DEAD,
|
|
FlightType.SEAD,
|
|
FlightType.STRIKE,
|
|
]:
|
|
flight_type = str(flight.flight_type)
|
|
flight_target = flight.package.target
|
|
if flight_target:
|
|
flight_target_name = None
|
|
flight_target_type = None
|
|
if isinstance(flight_target, TheaterGroundObject):
|
|
flight_target_name = flight_target.obj_name
|
|
flight_target_type = (
|
|
flight_type + f" TGT ({flight_target.category})"
|
|
)
|
|
elif hasattr(flight_target, "name"):
|
|
flight_target_name = flight_target.name
|
|
flight_target_type = flight_type + " TGT (Airbase)"
|
|
target_item = target_points.add_item()
|
|
if flight_target_name:
|
|
target_item.add_key_value("name", flight_target_name)
|
|
if flight_target_type:
|
|
target_item.add_key_value("type", flight_target_type)
|
|
target_item.add_key_value(
|
|
"positionX", str(flight_target.position.x)
|
|
)
|
|
target_item.add_key_value(
|
|
"positionY", str(flight_target.position.y)
|
|
)
|
|
|
|
for cp in self.game.theater.controlpoints:
|
|
coalition_object = (
|
|
lua_data.get_or_create_item("BlueAA")
|
|
if cp.captured
|
|
else lua_data.get_or_create_item("RedAA")
|
|
)
|
|
for ground_object in cp.ground_objects:
|
|
if ground_object.might_have_aa and not ground_object.is_dead:
|
|
for g in ground_object.groups:
|
|
threat_range = ground_object.threat_range(g)
|
|
|
|
if not threat_range:
|
|
continue
|
|
|
|
aa_item = coalition_object.add_item()
|
|
aa_item.add_key_value("name", ground_object.name)
|
|
aa_item.add_key_value("range", str(threat_range.meters))
|
|
aa_item.add_key_value(
|
|
"positionX", str(ground_object.position.x)
|
|
)
|
|
aa_item.add_key_value(
|
|
"positionY", str(ground_object.position.y)
|
|
)
|
|
|
|
# Generate IADS Lua Item
|
|
iads_object = lua_data.add_item("IADS")
|
|
for node in self.game.theater.iads_network.skynet_nodes(self.game):
|
|
coalition = iads_object.get_or_create_item("BLUE" if node.player else "RED")
|
|
iads_type = coalition.get_or_create_item(node.iads_role.value)
|
|
iads_element = iads_type.add_item()
|
|
iads_element.add_key_value("dcsGroupName", node.dcs_name)
|
|
for role, connections in node.connections.items():
|
|
iads_element.add_data_array(role, connections)
|
|
|
|
trigger = TriggerStart(comment="Set DCS Liberation data")
|
|
trigger.add_action(DoScript(String(lua_data.create_operations_lua())))
|
|
self.mission.triggerrules.triggers.append(trigger)
|
|
|
|
def inject_lua_trigger(self, contents: str, comment: str) -> None:
|
|
trigger = TriggerStart(comment=comment)
|
|
trigger.add_action(DoScript(String(contents)))
|
|
self.mission.triggerrules.triggers.append(trigger)
|
|
|
|
def bypass_plugin_script(self, mnemonic: str) -> None:
|
|
self.plugin_scripts.append(mnemonic)
|
|
|
|
def inject_plugin_script(
|
|
self, plugin_mnemonic: str, script: str, script_mnemonic: str
|
|
) -> None:
|
|
if script_mnemonic in self.plugin_scripts:
|
|
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
|
return
|
|
|
|
self.plugin_scripts.append(script_mnemonic)
|
|
|
|
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
|
|
|
script_path = Path(plugin_path, script)
|
|
if not script_path.exists():
|
|
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
|
|
return
|
|
|
|
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
|
filename = script_path.resolve()
|
|
fileref = self.mission.map_resource.add_resource_file(filename)
|
|
trigger.add_action(DoScriptFile(fileref))
|
|
self.mission.triggerrules.triggers.append(trigger)
|
|
|
|
def inject_plugins(self) -> None:
|
|
for plugin in LuaPluginManager.plugins():
|
|
if plugin.enabled:
|
|
plugin.inject_scripts(self)
|
|
plugin.inject_configuration(self)
|
|
|
|
|
|
class LuaValue:
|
|
key: Optional[str]
|
|
value: str | list[str]
|
|
|
|
def __init__(self, key: Optional[str], value: str | list[str]):
|
|
self.key = key
|
|
self.value = value
|
|
|
|
def serialize(self) -> str:
|
|
serialized_value = self.key + " = " if self.key else ""
|
|
if isinstance(self.value, str):
|
|
serialized_value += f'"{escape_string_for_lua(self.value)}"'
|
|
else:
|
|
escaped_values = [f'"{escape_string_for_lua(v)}"' for v in self.value]
|
|
serialized_value += "{" + ", ".join(escaped_values) + "}"
|
|
return serialized_value
|
|
|
|
|
|
class LuaItem(ABC):
|
|
value: LuaValue | list[LuaValue]
|
|
name: Optional[str]
|
|
|
|
def __init__(self, name: Optional[str]):
|
|
self.value = []
|
|
self.name = name
|
|
|
|
def set_value(self, value: str) -> None:
|
|
self.value = LuaValue(None, value)
|
|
|
|
def add_data_array(self, key: str, values: list[str]) -> None:
|
|
self._add_value(LuaValue(key, values))
|
|
|
|
def add_key_value(self, key: str, value: str) -> None:
|
|
self._add_value(LuaValue(key, value))
|
|
|
|
def _add_value(self, value: LuaValue) -> None:
|
|
if isinstance(self.value, list):
|
|
self.value.append(value)
|
|
else:
|
|
self.value = value
|
|
|
|
@abstractmethod
|
|
def add_item(self, item_name: Optional[str] = None) -> LuaItem:
|
|
"""adds a new item to the LuaArray without checking the existence"""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def get_item(self, item_name: str) -> Optional[LuaItem]:
|
|
"""gets item from LuaArray. Returns None if it does not exist"""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem:
|
|
"""gets item from the LuaArray or creates one if it does not exist already"""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def serialize(self) -> str:
|
|
if isinstance(self.value, LuaValue):
|
|
return self.value.serialize()
|
|
else:
|
|
serialized_data = [d.serialize() for d in self.value]
|
|
return "{" + ", ".join(serialized_data) + "}"
|
|
|
|
|
|
class LuaData(LuaItem):
|
|
objects: list[LuaData]
|
|
base_name: Optional[str]
|
|
|
|
def __init__(self, name: Optional[str], is_base_name: bool = True):
|
|
self.objects = []
|
|
self.base_name = name if is_base_name else None
|
|
super().__init__(name)
|
|
|
|
def add_item(self, item_name: Optional[str] = None) -> LuaItem:
|
|
item = LuaData(item_name, False)
|
|
self.objects.append(item)
|
|
return item
|
|
|
|
def get_item(self, item_name: str) -> Optional[LuaItem]:
|
|
for lua_object in self.objects:
|
|
if lua_object.name == item_name:
|
|
return lua_object
|
|
return None
|
|
|
|
def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem:
|
|
if item_name:
|
|
item = self.get_item(item_name)
|
|
if item:
|
|
return item
|
|
return self.add_item(item_name)
|
|
|
|
def serialize(self, level: int = 0) -> str:
|
|
"""serialize the LuaData to a string"""
|
|
serialized_data: list[str] = []
|
|
serialized_name = ""
|
|
linebreak = "\n"
|
|
tab = "\t"
|
|
tab_end = ""
|
|
for _ in range(level):
|
|
tab += "\t"
|
|
tab_end += "\t"
|
|
if self.base_name:
|
|
# Only used for initialization of the object in lua
|
|
serialized_name += self.base_name + " = "
|
|
if self.objects:
|
|
# nested objects
|
|
serialized_objects = [o.serialize(level + 1) for o in self.objects]
|
|
if self.name:
|
|
if self.name is not self.base_name:
|
|
serialized_name += self.name + " = "
|
|
serialized_data.append(
|
|
serialized_name
|
|
+ "{"
|
|
+ linebreak
|
|
+ tab
|
|
+ ("," + linebreak + tab).join(serialized_objects)
|
|
+ linebreak
|
|
+ tab_end
|
|
+ "}"
|
|
)
|
|
else:
|
|
# key with value
|
|
if self.name:
|
|
serialized_data.append(self.name + " = " + super().serialize())
|
|
# only value
|
|
else:
|
|
serialized_data.append(super().serialize())
|
|
|
|
return "\n".join(serialized_data)
|
|
|
|
def create_operations_lua(self) -> str:
|
|
"""crates the liberation lua script for the dcs mission"""
|
|
lua_prefix = """
|
|
-- setting configuration table
|
|
env.info("DCSLiberation|: setting configuration table")
|
|
"""
|
|
|
|
return lua_prefix + self.serialize()
|