diff --git a/game/dcs/groundunittype.py b/game/dcs/groundunittype.py index 2cf94b5b..35eea894 100644 --- a/game/dcs/groundunittype.py +++ b/game/dcs/groundunittype.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging from dataclasses import dataclass from pathlib import Path -from typing import Type, Iterator +from typing import Any, Optional, Type, Iterator import yaml from dcs.unittype import VehicleType @@ -13,9 +13,47 @@ from game.data.units import UnitClass from game.dcs.unittype import UnitType +@dataclass +class SkynetProperties: + can_engage_harm: Optional[str] = None + can_engage_air_weapon: Optional[str] = None + go_live_range_in_percent: Optional[str] = None + engagement_zone: Optional[str] = None + autonomous_behaviour: Optional[str] = None + harm_detection_chance: Optional[str] = None + + @classmethod + def from_data(cls, data: dict[str, Any]) -> SkynetProperties: + props = SkynetProperties() + if "can_engage_harm" in data: + props.can_engage_harm = str(data["can_engage_harm"]).lower() + if "can_engage_air_weapon" in data: + props.can_engage_air_weapon = str(data["can_engage_air_weapon"]).lower() + if "go_live_range_in_percent" in data: + props.go_live_range_in_percent = str(data["go_live_range_in_percent"]) + if "engagement_zone" in data: + props.engagement_zone = str(data["engagement_zone"]) + if "autonomous_behaviour" in data: + props.autonomous_behaviour = str(data["autonomous_behaviour"]) + if "harm_detection_chance" in data: + props.harm_detection_chance = str(data["harm_detection_chance"]) + return props + + def to_dict(self) -> dict[str, str]: + properties: dict[str, str] = {} + for key, value in self.__dict__.items(): + if value is not None: + properties[key] = value + return properties + + def __hash__(self) -> int: + return hash(id(self)) + + @dataclass(frozen=True) class GroundUnitType(UnitType[Type[VehicleType]]): spawn_weight: int + skynet_properties: SkynetProperties @classmethod def named(cls, name: str) -> GroundUnitType: @@ -76,4 +114,7 @@ class GroundUnitType(UnitType[Type[VehicleType]]): manufacturer=data.get("manufacturer", "No data."), role=data.get("role", "No data."), price=data.get("price", 1), + skynet_properties=SkynetProperties.from_data( + data.get("skynet_properties", {}) + ), ) diff --git a/game/missiongenerator/luagenerator.py b/game/missiongenerator/luagenerator.py index cb9547b0..2eb7f324 100644 --- a/game/missiongenerator/luagenerator.py +++ b/game/missiongenerator/luagenerator.py @@ -1,5 +1,4 @@ from __future__ import annotations -from collections import defaultdict import logging import os @@ -15,6 +14,7 @@ from dcs.triggers import TriggerStart from game.ato import FlightType from game.plugins import LuaPluginManager from game.theater import TheaterGroundObject +from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua from .aircraft.flightdata import FlightData @@ -142,6 +142,10 @@ class LuaGenerator: 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) + if node.iads_role in [IadsRole.SAM, IadsRole.SAM_AS_EWR]: + # add additional SkynetProperties to SAM Sites + for property, value in node.properties.items(): + iads_element.add_key_value(property, value) for role, connections in node.connections.items(): iads_element.add_data_array(role, connections) diff --git a/game/server/iadsnetwork/models.py b/game/server/iadsnetwork/models.py index ada32c1b..a00321ff 100644 --- a/game/server/iadsnetwork/models.py +++ b/game/server/iadsnetwork/models.py @@ -46,8 +46,8 @@ class IadsConnectionJs(BaseModel): node=tgo.id, connected=connection.ground_object.id, active=( - tgo.alive_unit_count > 0 - and connection.ground_object.alive_unit_count > 0 + network_node.group.alive_units > 0 + and connection.alive_units > 0 ), blue=tgo.is_friendly(True), is_power="power" diff --git a/game/theater/iadsnetwork/iadsnetwork.py b/game/theater/iadsnetwork/iadsnetwork.py index 4a6bcc76..8de9dc54 100644 --- a/game/theater/iadsnetwork/iadsnetwork.py +++ b/game/theater/iadsnetwork/iadsnetwork.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Iterator, Optional from uuid import UUID import uuid from game.theater.iadsnetwork.iadsrole import IadsRole - +from game.dcs.groundunittype import GroundUnitType from game.theater.theatergroundobject import ( IadsBuildingGroundObject, IadsGroundObject, @@ -31,6 +31,7 @@ class SkynetNode: dcs_name: str player: bool iads_role: IadsRole + properties: dict[str, str] = field(default_factory=dict) connections: dict[str, list[str]] = field(default_factory=lambda: defaultdict(list)) @staticmethod @@ -42,18 +43,30 @@ class SkynetNode: IadsRole.POWER_SOURCE, ]: # Use UnitName for EWR, CommandCenter, Comms, Power - return group.units[0].unit_name + for unit in group.units: + # Check for alive units in the group + if unit.alive: + return unit.unit_name + if group.units[0].is_static: + # Statics will be placed as dead unit + return group.units[0].unit_name + # If no alive unit is available and not static raise error + raise IadsNetworkException("Group has no skynet usable units") else: # Use the GroupName for SAMs, SAMAsEWR and PDs return group.group_name @classmethod def from_group(cls, group: IadsGroundGroup) -> SkynetNode: - return cls( + node = cls( cls.dcs_name_for_group(group), group.ground_object.is_friendly(True), group.iads_role, ) + unit_type = group.units[0].unit_type + if unit_type is not None and isinstance(unit_type, GroundUnitType): + node.properties = unit_type.skynet_properties.to_dict() + return node class IadsNetworkNode: @@ -102,24 +115,22 @@ class IadsNetwork: """Get all skynet nodes from the IADS Network""" skynet_nodes: list[SkynetNode] = [] for node in self.nodes: - if game.iads_considerate_culling(node.group.ground_object) or ( - node.group.units[0].is_vehicle and not node.group.units[0].alive - ): - # Skip + if game.iads_considerate_culling(node.group.ground_object): + # Skip culled ground objects + continue + try: + skynet_node = SkynetNode.from_group(node.group) + for connection in node.connections.values(): + if connection.ground_object.is_friendly( + skynet_node.player + ) and not game.iads_considerate_culling(connection.ground_object): + skynet_node.connections[connection.iads_role.value].append( + SkynetNode.dcs_name_for_group(connection) + ) + skynet_nodes.append(skynet_node) + except IadsNetworkException: + # Node not skynet compatible continue - skynet_node = SkynetNode.from_group(node.group) - for connection in node.connections.values(): - if ( - connection.ground_object.is_friendly(skynet_node.player) - and not game.iads_considerate_culling(connection.ground_object) - and not ( - connection.units[0].is_vehicle and not connection.units[0].alive - ) - ): - skynet_node.connections[connection.iads_role.value].append( - SkynetNode.dcs_name_for_group(connection) - ) - skynet_nodes.append(skynet_node) return skynet_nodes def update_tgo(self, tgo: TheaterGroundObject) -> None: diff --git a/resources/plugins/skynetiads/skynetiads-config.lua b/resources/plugins/skynetiads/skynetiads-config.lua index 38365c80..e8375f12 100644 --- a/resources/plugins/skynetiads/skynetiads-config.lua +++ b/resources/plugins/skynetiads/skynetiads-config.lua @@ -40,6 +40,28 @@ if dcsLiberation and SkynetIADS then -- actual configuration code local function initializeIADSElement(iads, iads_unit, element) + if iads_unit == nil then + -- skip processing of units which can not be handled by skynet + return + end + if element.engagement_zone then + iads_unit:setEngagementZone(element.engagement_zone) + end + if element.can_engage_harm then + iads_unit:setCanEngageHARM(element.can_engage_harm) + end + if element.harm_detection_chance then + iads_unit:setHARMDetectionChance(tonumber(element.harm_detection_chance)) + end + if element.can_engage_air_weapon then + iads_unit:setCanEngageAirWeapons(element.can_engage_air_weapon) + end + if element.go_live_range_in_percent then + iads_unit:setGoLiveRangeInPercent(tonumber(element.go_live_range_in_percent)) + end + if element.autonomous_behaviour then + iads_unit:setAutonomousBehaviour(element.autonomous_behaviour) + end if element.ConnectionNode then for i,cn in pairs(element.ConnectionNode) do env.info(string.format("DCSLiberation|Skynet-IADS plugin - adding IADS ConnectionNode %s", cn)) diff --git a/resources/units/ground_units/Patriot str.yaml b/resources/units/ground_units/Patriot str.yaml index 802ecf59..a32e05a8 100644 --- a/resources/units/ground_units/Patriot str.yaml +++ b/resources/units/ground_units/Patriot str.yaml @@ -2,3 +2,10 @@ class: SearchRadar price: 22 variants: SAM Patriot STR: null +skynet_properties: # Override skynet default properties + can_engage_harm: true + # can_engage_air_weapon: true # https://github.com/walder/Skynet-IADS/tree/develop#engage-air-weapons + go_live_range_in_percent: 100 + harm_detection_chance: 90 + engagement_zone: SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE # https://github.com/walder/Skynet-IADS/tree/develop#engagement-zone + autonomous_behaviour: SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI # https://github.com/walder/Skynet-IADS/tree/develop#autonomous-mode-behaviour