From c37c56c879c05c079fa8e44c12db4978b264fbd2 Mon Sep 17 00:00:00 2001 From: Raffson Date: Tue, 27 Dec 2022 19:48:09 +0100 Subject: [PATCH] Air Assault for C-130 mod Resolves #49 --- game/ato/flight.py | 9 +++- game/ato/flightplans/airassault.py | 22 ++++++--- game/ato/flightplans/waypointbuilder.py | 8 ++-- .../aircraft/aircraftbehavior.py | 7 ++- .../aircraft/waypoints/airassaultingress.py | 28 +++++++++++ .../aircraft/waypoints/waypointgenerator.py | 2 + requirements.txt | 2 +- resources/customized_payloads/Hercules.lua | 46 +++++++++++++++++++ .../plugins/herculescargo/Hercules_Cargo.lua | 6 +-- 9 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 game/missiongenerator/aircraft/waypoints/airassaultingress.py diff --git a/game/ato/flight.py b/game/ato/flight.py index 089ab5ec..3c7d71ce 100644 --- a/game/ato/flight.py +++ b/game/ato/flight.py @@ -7,6 +7,7 @@ from typing import Any, List, Optional, TYPE_CHECKING from dcs import Point from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet +from pydcs_extensions.hercules.hercules import Hercules from .flightroster import FlightRoster from .flightstate import FlightState, Navigating, Uninitialized from .flightstate.killed import Killed @@ -18,9 +19,9 @@ from ..sidc import ( Status, SymbolSet, ) +from game.dcs.aircrafttype import AircraftType if TYPE_CHECKING: - from game.dcs.aircrafttype import AircraftType from game.sim.gameupdateevents import GameUpdateEvents from game.sim.simulationresults import SimulationResults from game.squadrons import Squadron, Pilot @@ -161,6 +162,10 @@ class Flight(SidcDescribable): def is_helo(self) -> bool: return self.unit_type.dcs_unit_type.helicopter + @property + def is_hercules(self) -> bool: + return self.unit_type == AircraftType.named("C-130J-30 Super Hercules") + @property def from_cp(self) -> ControlPoint: return self.departure @@ -197,6 +202,8 @@ class Flight(SidcDescribable): return Su_33.fuel_max * 0.8 elif unit_type in {C_101EB, C_101CC}: return unit_type.fuel_max * 0.5 + elif unit_type == Hercules: + return unit_type.fuel_max * 0.75 return None def __repr__(self) -> str: diff --git a/game/ato/flightplans/airassault.py b/game/ato/flightplans/airassault.py index 015d287d..aa381741 100644 --- a/game/ato/flightplans/airassault.py +++ b/game/ato/flightplans/airassault.py @@ -25,7 +25,7 @@ class AirAssaultLayout(StandardLayout): pickup: FlightWaypoint | None nav_to_ingress: list[FlightWaypoint] ingress: FlightWaypoint - drop_off: FlightWaypoint + drop_off: FlightWaypoint | None # This is an implementation detail used by CTLD. The aircraft will not go to this # waypoint. It is used by CTLD as the destination for unloaded troops. target: FlightWaypoint @@ -37,7 +37,8 @@ class AirAssaultLayout(StandardLayout): yield self.pickup yield from self.nav_to_ingress yield self.ingress - yield self.drop_off + if self.drop_off is not None: + yield self.drop_off yield self.target yield from self.nav_to_home yield self.arrival @@ -53,7 +54,9 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay): @property def tot_waypoint(self) -> FlightWaypoint: - return self.layout.drop_off + if self.flight.is_helo and self.layout.drop_off is not None: + return self.layout.drop_off + return self.layout.target def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: if waypoint == self.tot_waypoint: @@ -80,8 +83,10 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay): class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): def layout(self) -> AirAssaultLayout: - if not self.flight.is_helo: - raise PlanningError("Air assault is only usable by helicopters") + if not self.flight.is_helo and not self.flight.is_hercules: + raise PlanningError( + "Air assault is only usable by helicopters and Anubis' C-130 mod" + ) assert self.package.waypoints is not None altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude @@ -89,7 +94,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): builder = WaypointBuilder(self.flight, self.coalition) - if self.flight.departure.cptype in [ + if self.flight.is_hercules or self.flight.departure.cptype in [ ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP, ControlPointType.OFF_MAP, @@ -114,12 +119,15 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): pickup_position = pickup.position assault_area = builder.assault_area(self.package.target) heading = self.package.target.position.heading_between_point(pickup_position) + if self.flight.is_hercules: + assault_area.only_for_player = False # TODO we can not gurantee a safe LZ for DropOff. See comment above. drop_off_zone = MissionTarget( "Dropoff zone", self.package.target.position.point_from_heading(heading, 1200), ) + dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None return AirAssaultLayout( departure=builder.takeoff(self.flight.departure), @@ -135,7 +143,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]): self.package.waypoints.ingress, self.package.target, ), - drop_off=builder.dropoff_zone(drop_off_zone), + drop_off=dz, target=assault_area, nav_to_home=builder.nav_path( drop_off_zone.position, diff --git a/game/ato/flightplans/waypointbuilder.py b/game/ato/flightplans/waypointbuilder.py index 6d3c6989..81f3a63c 100644 --- a/game/ato/flightplans/waypointbuilder.py +++ b/game/ato/flightplans/waypointbuilder.py @@ -23,7 +23,7 @@ from game.theater import ( TheaterGroundObject, TheaterUnit, ) -from game.utils import Distance, meters, nautical_miles +from game.utils import Distance, meters, nautical_miles, feet if TYPE_CHECKING: from game.coalition import Coalition @@ -225,15 +225,17 @@ class WaypointBuilder: position: Point, objective: MissionTarget, ) -> FlightWaypoint: + alt = self.doctrine.ingress_altitude alt_type: AltitudeReference = "BARO" - if self.is_helo: + if self.is_helo or self.flight.is_hercules: alt_type = "RADIO" + alt = meters(60) if self.is_helo else feet(1000) return FlightWaypoint( "INGRESS", ingress_type, position, - meters(60) if self.is_helo else self.doctrine.ingress_altitude, + alt, alt_type, description=f"INGRESS on {objective.name}", pretty_name=f"INGRESS on {objective.name}", diff --git a/game/missiongenerator/aircraft/aircraftbehavior.py b/game/missiongenerator/aircraft/aircraftbehavior.py index 9773987f..6faf7107 100644 --- a/game/missiongenerator/aircraft/aircraftbehavior.py +++ b/game/missiongenerator/aircraft/aircraftbehavior.py @@ -29,6 +29,7 @@ from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType from game.ato.flightplans.aewc import AewcFlightPlan from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan +from game.dcs.aircrafttype import AircraftType class AircraftBehavior: @@ -298,11 +299,15 @@ class AircraftBehavior: def configure_transport(self, group: FlyingGroup[Any], flight: Flight) -> None: group.task = Transport.name + roe = OptROE.Values.WeaponHold + if flight.is_hercules: + group.task = GroundAttack.name + roe = OptROE.Values.OpenFire self.configure_behavior( flight, group, react_on_threat=OptReactOnThreat.Values.EvadeFire, - roe=OptROE.Values.WeaponHold, + roe=roe, restrict_jettison=True, ) diff --git a/game/missiongenerator/aircraft/waypoints/airassaultingress.py b/game/missiongenerator/aircraft/waypoints/airassaultingress.py new file mode 100644 index 00000000..1bb992bf --- /dev/null +++ b/game/missiongenerator/aircraft/waypoints/airassaultingress.py @@ -0,0 +1,28 @@ +from dcs.point import MovingPoint +from dcs.task import Expend, WeaponType, CarpetBombing, OptROE + +from game.ato.flightwaypointtype import FlightWaypointType +from game.utils import feet, knots +from pydcs_extensions.hercules.hercules import Hercules +from .pydcswaypointbuilder import PydcsWaypointBuilder + + +class AirAssaultIngressBuilder(PydcsWaypointBuilder): + def add_tasks(self, waypoint: MovingPoint) -> None: + air_drop = self.group.units[0].unit_type in [Hercules] + if air_drop: + waypoint.speed = knots(230).meters_per_second + waypoint.speed_locked = True + waypoint.ETA_locked = False + tgt = self.flight.flight_plan.package.target.position + for wpt in self.flight.flight_plan.waypoints: + if wpt.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC: + tgt = wpt.position + break + bombing = CarpetBombing( + tgt, + weapon_type=WeaponType.Bombs, + expend=Expend.All, + carpet_length=feet(9000).meters, + ) + waypoint.add_task(bombing) diff --git a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py index da59387a..a388decf 100644 --- a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py +++ b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py @@ -20,6 +20,7 @@ from game.missiongenerator.aircraft.waypoints.cargostop import CargoStopBuilder from game.missiongenerator.missiondata import MissionData from game.settings import Settings from game.utils import pairwise +from .airassaultingress import AirAssaultIngressBuilder from .baiingress import BaiIngressBuilder from .landingzone import LandingZoneBuilder from .casingress import CasIngressBuilder @@ -136,6 +137,7 @@ class WaypointGenerator: FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder, FlightWaypointType.REFUEL: RefuelPointBuilder, FlightWaypointType.CARGO_STOP: CargoStopBuilder, + FlightWaypointType.INGRESS_AIR_ASSAULT: AirAssaultIngressBuilder, } builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder) return builder( diff --git a/requirements.txt b/requirements.txt index 7481c87e..0dd5d4d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ platformdirs==2.5.4 pluggy==1.0.0 pre-commit==2.20.0 pydantic==1.10.2 --e git+https://github.com/dcs-retribution/pydcs@f0bb4da4d458d714dedd1f3bba30deae5f25a80d#egg=pydcs +-e git+https://github.com/dcs-retribution/pydcs@fb1fefcb32fbab82ac36b8519b87bae32326784e#egg=pydcs pyinstaller==5.6.2 pyinstaller-hooks-contrib==2022.13 pyparsing==3.0.9 diff --git a/resources/customized_payloads/Hercules.lua b/resources/customized_payloads/Hercules.lua index 9f2012f6..1b33e6fd 100644 --- a/resources/customized_payloads/Hercules.lua +++ b/resources/customized_payloads/Hercules.lua @@ -121,6 +121,52 @@ local unitPayloads = { [1] = 31, }, }, + [9] = { + ["displayName"] = "Retribution CAS", + ["name"] = "Retribution CAS", + ["pylons"] = { + [1] = { + ["CLSID"] = "Herc_JATO", + ["num"] = 1, + }, + [2] = { + ["CLSID"] = "{Herc_105mm_Howitzer}", + ["num"] = 8, + }, + [3] = { + ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", + ["num"] = 6, + }, + [4] = { + ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", + ["num"] = 7, + }, + [5] = { + ["CLSID"] = "Herc_BattleStation_TGP", + ["num"] = 9, + }, + }, + ["tasks"] = { + [1] = 35, + [2] = 31, + }, + }, + [10] = { + ["name"] = "Retribution Air Assault", + ["pylons"] = { + [1] = { + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "Herc_JATO", + ["num"] = 1, + }, + }, + ["tasks"] = { + [1] = 32, + }, + }, }, ["tasks"] = { }, diff --git a/resources/plugins/herculescargo/Hercules_Cargo.lua b/resources/plugins/herculescargo/Hercules_Cargo.lua index 1a534cf2..a1504899 100644 --- a/resources/plugins/herculescargo/Hercules_Cargo.lua +++ b/resources/plugins/herculescargo/Hercules_Cargo.lua @@ -490,11 +490,11 @@ function Hercules_Cargo.Cargo_Initialize(initiator, Cargo_Contents, Cargo_Type_n Herc_Cargo[Herc_j].all_cargo_survive_to_the_ground = true else ------------------------------------------------------------------------------ - if Hercules_Cargo.Calculate_Object_Height_AGL(Cargo_Drop_initiator) < 100.0 then--aircraft more than 10m but less than 100m above ground + if Hercules_Cargo.Calculate_Object_Height_AGL(Cargo_Drop_initiator) < 152.4 then--aircraft more than 30ft but less than 500ft above ground Herc_Cargo[Herc_j].all_cargo_gets_destroyed = true else ------------------------------------------------------------------------------ - Herc_Cargo[Herc_j].destroy_cargo_dropped_without_parachute = true--aircraft more than 100m above ground + Herc_Cargo[Herc_j].destroy_cargo_dropped_without_parachute = true--aircraft more than 152.4m (500ft)above ground end end end @@ -568,7 +568,7 @@ function Hercules_Cargo.Hercules_Cargo_Drop_Events:onEvent(Cargo_Drop_Event) local Cargo_Container_Enclosed = Hercules_Cargo.types[GT_DisplayName]['container'] Hercules_Cargo.Cargo_Initialize(Cargo_Drop_Event.initiator, Cargo_Drop_Event.weapon, GT_Name, Cargo_Container_Enclosed) end -end + end end world.addEventHandler(Hercules_Cargo.Hercules_Cargo_Drop_Events)