diff --git a/changelog.md b/changelog.md index abfac327..bd9a6626 100644 --- a/changelog.md +++ b/changelog.md @@ -13,13 +13,14 @@ Saves from 4.0.0 are compatible with 4.0.1. ## Features/Improvements * **[Plugins]** Increased time JTAC Autolase messages stay visible on the UI. +* **[Mission Generation]** Improvements for better support of the Skynet Plugin and long range SAMs are now acting as EWR * **[UI]** Added ability to take notes and have those notes appear as a kneeboard page. * **[UI]** Hovering over the weather information now dispalys the cloud base (meters and feet). * **[UI]** Google search link added to unit information when there is no information provided. ## Fixes * **[UI]** Statistics window tick marks are now always integers. - +* **[Mission Generation]** The lua data for other plugins is now generated correctly * **[Flight Planning]** Fixed potential issue with angles > 360° or < 0° being generated when summing two angles. # 4.0.0 diff --git a/game/game.py b/game/game.py index 3a783e40..6d2aa329 100644 --- a/game/game.py +++ b/game/game.py @@ -483,7 +483,7 @@ class Game: self.current_unit_id += 1 return self.current_unit_id - def next_group_id(self): + def next_group_id(self) -> int: """ Next unit id for pre-generated units """ diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index df637cbc..49fb8fd9 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -460,9 +460,9 @@ class CoastalSiteGroundObject(TheaterGroundObject): return False -# TODO: Differentiate types. -# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each -# be split into their own types. +# The SamGroundObject represents all type of AA +# The TGO can have multiple types of units (AAA,SAM,Support...) +# Differentiation can be made during generation with the airdefensegroupgenerator class SamGroundObject(TheaterGroundObject): def __init__( self, @@ -481,18 +481,6 @@ class SamGroundObject(TheaterGroundObject): dcs_identifier="AA", sea_object=False, ) - # Set by the SAM unit generator if the generated group is compatible - # with Skynet. - self.skynet_capable = False - - @property - def group_name(self) -> str: - if self.skynet_capable: - # Prefix the group names of SAM sites with the side color so Skynet - # can find them. - return f"{self.faction_color}|SAM|{self.group_id}" - else: - return super().group_name def mission_types(self, for_player: bool) -> Iterator[FlightType]: from gen.flights.flight import FlightType diff --git a/gen/sam/airdefensegroupgenerator.py b/gen/sam/airdefensegroupgenerator.py index a62a5f11..7d269ece 100644 --- a/gen/sam/airdefensegroupgenerator.py +++ b/gen/sam/airdefensegroupgenerator.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from enum import Enum from typing import Iterator, List @@ -9,11 +11,31 @@ from game.theater.theatergroundobject import SamGroundObject from gen.sam.group_generator import GroupGenerator +class SkynetRole(Enum): + #: A radar SAM that should be controlled by Skynet. + Sam = "Sam" + + #: A radar SAM that should be controlled and used as an EWR by Skynet. + SamAsEwr = "SamAsEwr" + + #: An air defense unit that should be used as point defense by Skynet. + PointDefense = "PD" + + #: All other types of groups that might be present in a SAM TGO. This includes + #: SHORADS, AAA, supply trucks, etc. Anything that shouldn't be controlled by Skynet + #: should use this role. + NoSkynetBehavior = "NoSkynetBehavior" + + class AirDefenseRange(Enum): - AAA = "AAA" - Short = "short" - Medium = "medium" - Long = "long" + AAA = ("AAA", SkynetRole.NoSkynetBehavior) + Short = ("short", SkynetRole.NoSkynetBehavior) + Medium = ("medium", SkynetRole.Sam) + Long = ("long", SkynetRole.SamAsEwr) + + def __init__(self, description: str, default_role: SkynetRole) -> None: + self.range_name = description + self.default_role = default_role class AirDefenseGroupGenerator(GroupGenerator, ABC): @@ -24,18 +46,32 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC): price: int def __init__(self, game: Game, ground_object: SamGroundObject) -> None: - ground_object.skynet_capable = True super().__init__(game, ground_object) + self.vg.name = self.group_name_for_role(self.vg.id, self.primary_group_role()) self.auxiliary_groups: List[VehicleGroup] = [] - def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup: - group = VehicleGroup( - self.game.next_group_id(), "|".join([self.go.group_name, name_suffix]) - ) + def add_auxiliary_group(self, role: SkynetRole) -> VehicleGroup: + gid = self.game.next_group_id() + group = VehicleGroup(gid, self.group_name_for_role(gid, role)) self.auxiliary_groups.append(group) return group + def group_name_for_role(self, gid: int, role: SkynetRole) -> str: + if role is SkynetRole.NoSkynetBehavior: + # No special naming needed for air defense groups that don't participate in + # Skynet. + return f"{self.go.group_name}|{gid}" + + # For those that do, we need a prefix of `$COLOR|SAM| so our Skynet config picks + # the group up at all. To support PDs we need to append the ID of the TGO so + # that the PD will know which group it's protecting. We then append the role so + # our config knows what to do with the group, and finally the GID of *this* + # group to ensure no conflicts. + return "|".join( + [self.go.faction_color, "SAM", str(self.go.group_id), role.value, str(gid)] + ) + def get_generated_group(self) -> VehicleGroup: raise RuntimeError( "Deprecated call to AirDefenseGroupGenerator.get_generated_group " @@ -52,3 +88,7 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC): @abstractmethod def range(cls) -> AirDefenseRange: ... + + @classmethod + def primary_group_role(cls) -> SkynetRole: + return cls.range().default_role diff --git a/gen/sam/sam_hawk.py b/gen/sam/sam_hawk.py index 01e463e1..ea05f726 100644 --- a/gen/sam/sam_hawk.py +++ b/gen/sam/sam_hawk.py @@ -6,6 +6,7 @@ from dcs.vehicles import AirDefence from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) @@ -41,7 +42,7 @@ class HawkGenerator(AirDefenseGroupGenerator): ) # Triple A for close range defense - aa_group = self.add_auxiliary_group("AA") + aa_group = self.add_auxiliary_group(SkynetRole.NoSkynetBehavior) self.add_unit_to_group( aa_group, AirDefence.Vulcan, diff --git a/gen/sam/sam_hq7.py b/gen/sam/sam_hq7.py index d05aecd8..be5eeb6a 100644 --- a/gen/sam/sam_hq7.py +++ b/gen/sam/sam_hq7.py @@ -6,6 +6,7 @@ from dcs.vehicles import AirDefence from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) @@ -34,7 +35,7 @@ class HQ7Generator(AirDefenseGroupGenerator): ) # Triple A for close range defense - aa_group = self.add_auxiliary_group("AA") + aa_group = self.add_auxiliary_group(SkynetRole.NoSkynetBehavior) self.add_unit_to_group( aa_group, AirDefence.Ural_375_ZU_23, diff --git a/gen/sam/sam_patriot.py b/gen/sam/sam_patriot.py index 21f6cd18..aafeb79c 100644 --- a/gen/sam/sam_patriot.py +++ b/gen/sam/sam_patriot.py @@ -6,6 +6,7 @@ from dcs.vehicles import AirDefence from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) @@ -69,7 +70,7 @@ class PatriotGenerator(AirDefenseGroupGenerator): ) # Short range protection for high value site - aa_group = self.add_auxiliary_group("AA") + aa_group = self.add_auxiliary_group(SkynetRole.NoSkynetBehavior) num_launchers = random.randint(3, 4) positions = self.get_circular_position( num_launchers, launcher_distance=200, coverage=360 diff --git a/gen/sam/sam_rapier.py b/gen/sam/sam_rapier.py index 0e361459..af3965e4 100644 --- a/gen/sam/sam_rapier.py +++ b/gen/sam/sam_rapier.py @@ -5,6 +5,7 @@ from dcs.vehicles import AirDefence from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) @@ -49,3 +50,7 @@ class RapierGenerator(AirDefenseGroupGenerator): @classmethod def range(cls) -> AirDefenseRange: return AirDefenseRange.Short + + @classmethod + def primary_group_role(cls) -> SkynetRole: + return SkynetRole.Sam diff --git a/gen/sam/sam_roland.py b/gen/sam/sam_roland.py index 4a88cfd4..e2e704af 100644 --- a/gen/sam/sam_roland.py +++ b/gen/sam/sam_roland.py @@ -3,6 +3,7 @@ from dcs.vehicles import AirDefence, Unarmed from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) @@ -40,3 +41,7 @@ class RolandGenerator(AirDefenseGroupGenerator): @classmethod def range(cls) -> AirDefenseRange: return AirDefenseRange.Short + + @classmethod + def primary_group_role(cls) -> SkynetRole: + return SkynetRole.Sam diff --git a/gen/sam/sam_sa10.py b/gen/sam/sam_sa10.py index 6daf8bfb..35611e83 100644 --- a/gen/sam/sam_sa10.py +++ b/gen/sam/sam_sa10.py @@ -8,6 +8,7 @@ from game.theater import SamGroundObject from gen.sam.airdefensegroupgenerator import ( AirDefenseRange, AirDefenseGroupGenerator, + SkynetRole, ) from pydcs_extensions.highdigitsams import highdigitsams @@ -76,7 +77,7 @@ class SA10Generator(AirDefenseGroupGenerator): def generate_defensive_groups(self) -> None: # AAA for defending against close targets. - aa_group = self.add_auxiliary_group("AA") + aa_group = self.add_auxiliary_group(SkynetRole.NoSkynetBehavior) num_launchers = random.randint(6, 8) positions = self.get_circular_position( num_launchers, launcher_distance=210, coverage=360 @@ -101,7 +102,7 @@ class Tier2SA10Generator(SA10Generator): super().generate_defensive_groups() # SA-15 for both shorter range targets and point defense. - pd_group = self.add_auxiliary_group("PD") + pd_group = self.add_auxiliary_group(SkynetRole.PointDefense) num_launchers = random.randint(2, 4) positions = self.get_circular_position( num_launchers, launcher_distance=140, coverage=360 @@ -123,7 +124,7 @@ class Tier3SA10Generator(SA10Generator): def generate_defensive_groups(self) -> None: # AAA for defending against close targets. - aa_group = self.add_auxiliary_group("AA") + aa_group = self.add_auxiliary_group(SkynetRole.NoSkynetBehavior) num_launchers = random.randint(6, 8) positions = self.get_circular_position( num_launchers, launcher_distance=210, coverage=360 @@ -138,7 +139,7 @@ class Tier3SA10Generator(SA10Generator): ) # SA-15 for both shorter range targets and point defense. - pd_group = self.add_auxiliary_group("PD") + pd_group = self.add_auxiliary_group(SkynetRole.PointDefense) num_launchers = random.randint(2, 4) positions = self.get_circular_position( num_launchers, launcher_distance=140, coverage=360 diff --git a/resources/plugins/skynetiads/skynetiads-config.lua b/resources/plugins/skynetiads/skynetiads-config.lua index aa0ce992..f083c6f9 100644 --- a/resources/plugins/skynetiads/skynetiads-config.lua +++ b/resources/plugins/skynetiads/skynetiads-config.lua @@ -93,9 +93,28 @@ if dcsLiberation and SkynetIADS then for i = 1, #sites do local site = sites[i] local name = site:getDCSName() + + if string.match(name, "|SamAsEwr|") then + env.info(string.format("DCSLiberation|Skynet-IADS plugin - %s now acting as EWR", name)) + site:setActAsEW(true) + end + if not string.match(name, "|PD") then - env.info(string.format("DCSLiberation|Skynet-IADS plugin - Checking %s for PD", name)) - local pds = iads:getSAMSitesByPrefix(name .. "|PD") + -- Name is prefixed with `$color|SAM|$tgoid`. For pre-4.1 generated + -- campaigns that's the full name of the primary SAM and any PD are just + -- that name suffixed with |PD. + -- + -- For 4.1+ generated campaigns the name will be + -- `$color|SAM|$tgoid|$role|$gid`, so we need to replace the content + -- beginning with the third pipe with `|PD` to find our PDs. + local first_pipe = string.find(name, "|") + local second_pipe = string.find(name, "|", first_pipe + 1) + local third_pipe = string.find(name, "|", second_pipe + 1) + local pd_prefix = name .. "|PD" + if third_pipe ~= nil then + pd_prefix = string.sub(name, 1, third_pipe) .. "PD" + end + local pds = iads:getSAMSitesByPrefix(pd_prefix) for j = 1, #pds do pd = pds[j] env.info(string.format("DCSLiberation|Skynet-IADS plugin - Adding %s as PD for %s", pd:getDCSName(), name))