mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge pull request #1183 from dcs-liberation/helipads
[WIP] Add possibility to add helipads to FOB control points
This commit is contained in:
commit
82f5287282
@ -7,7 +7,8 @@ Saves from 4.x are not compatible with 5.0.
|
|||||||
* **[Campaign]** Weather! Theaters now experience weather that is more realistic for the region and its current season. For example, Persian Gulf will have very hot, sunny summers and Marianas will experience lots of rain during fall. These changes affect pressure, temperature, clouds and precipitation. Additionally, temperature will drop during the night, by an amount that is somewhat realistic for the region.
|
* **[Campaign]** Weather! Theaters now experience weather that is more realistic for the region and its current season. For example, Persian Gulf will have very hot, sunny summers and Marianas will experience lots of rain during fall. These changes affect pressure, temperature, clouds and precipitation. Additionally, temperature will drop during the night, by an amount that is somewhat realistic for the region.
|
||||||
* **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated.
|
* **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated.
|
||||||
* **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts.
|
* **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts.
|
||||||
* **[Campaign]** Squadrons now have a home base and will not operate out of other bases. See https://github.com/dcs-liberation/dcs_liberation/discussions/1550 for details.
|
* **[Campaign]** FOBs control point can have FARP/helipad slot and host helicopters. To enable this feature on a FOB, add "Invisible FARP" statics objects near the FOB location in the campaign definition file.
|
||||||
|
* **[Campaign]** Squadrons now have a home base and will not operate out of other bases. See https://github.com/dcs-liberation/dcs_liberation/issues/1145 for status.
|
||||||
* **[Campaign]** Aircraft now belong to squadrons rather than bases to support squadron location transfers.
|
* **[Campaign]** Aircraft now belong to squadrons rather than bases to support squadron location transfers.
|
||||||
* **[Campaign]** Skipped turns are no longer counted as defeats on front lines.
|
* **[Campaign]** Skipped turns are no longer counted as defeats on front lines.
|
||||||
* **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions.
|
* **[Campaign AI]** Overhauled campaign AI target prioritization. This currently only affects the ordering of DEAD missions.
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class MizCampaignLoader:
|
|||||||
SHIPPING_LANE_UNIT_TYPE = HandyWind.id
|
SHIPPING_LANE_UNIT_TYPE = HandyWind.id
|
||||||
|
|
||||||
FOB_UNIT_TYPE = Unarmed.SKP_11.id
|
FOB_UNIT_TYPE = Unarmed.SKP_11.id
|
||||||
FARP_HELIPAD = "SINGLE_HELIPAD"
|
FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD"]
|
||||||
|
|
||||||
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
|
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
|
||||||
SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id
|
SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id
|
||||||
@ -212,7 +212,7 @@ class MizCampaignLoader:
|
|||||||
@property
|
@property
|
||||||
def helipads(self) -> Iterator[StaticGroup]:
|
def helipads(self) -> Iterator[StaticGroup]:
|
||||||
for group in self.blue.static_group:
|
for group in self.blue.static_group:
|
||||||
if group.units[0].type == self.FARP_HELIPAD:
|
if group.units[0].type in self.FARP_HELIPADS_TYPE:
|
||||||
yield group
|
yield group
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -184,6 +184,10 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
def flyable(self) -> bool:
|
def flyable(self) -> bool:
|
||||||
return self.dcs_unit_type.flyable
|
return self.dcs_unit_type.flyable
|
||||||
|
|
||||||
|
@property
|
||||||
|
def helicopter(self) -> bool:
|
||||||
|
return self.dcs_unit_type.helicopter
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def max_speed(self) -> Speed:
|
def max_speed(self) -> Speed:
|
||||||
return kph(self.dcs_unit_type.max_speed)
|
return kph(self.dcs_unit_type.max_speed)
|
||||||
|
|||||||
13
game/game.py
13
game/game.py
@ -8,6 +8,8 @@ from datetime import date, datetime, timedelta
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, List, Type, Union, cast, TYPE_CHECKING
|
from typing import Any, List, Type, Union, cast, TYPE_CHECKING
|
||||||
|
|
||||||
|
from dcs.countries import Switzerland, UnitedNationsPeacekeepers, USAFAggressors
|
||||||
|
from dcs.country import Country
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.task import CAP, CAS, PinpointStrike
|
from dcs.task import CAP, CAS, PinpointStrike
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
@ -195,6 +197,17 @@ class Game:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def neutral_country(self) -> Type[Country]:
|
||||||
|
"""Return the best fitting country that can be used as neutral faction in the generated mission"""
|
||||||
|
countries_in_use = [self.red.country_name, self.blue.country_name]
|
||||||
|
if UnitedNationsPeacekeepers not in countries_in_use:
|
||||||
|
return UnitedNationsPeacekeepers
|
||||||
|
elif Switzerland.name not in countries_in_use:
|
||||||
|
return Switzerland
|
||||||
|
else:
|
||||||
|
return USAFAggressors
|
||||||
|
|
||||||
def _generate_events(self) -> None:
|
def _generate_events(self) -> None:
|
||||||
for front_line in self.theater.conflicts():
|
for front_line in self.theater.conflicts():
|
||||||
self._generate_player_event(
|
self._generate_player_event(
|
||||||
|
|||||||
@ -39,6 +39,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
|||||||
from gen.visualgen import VisualGenerator
|
from gen.visualgen import VisualGenerator
|
||||||
from .. import db
|
from .. import db
|
||||||
from ..theater import Airfield, FrontLine
|
from ..theater import Airfield, FrontLine
|
||||||
|
from ..theater.bullseye import Bullseye
|
||||||
from ..unitmap import UnitMap
|
from ..unitmap import UnitMap
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -105,6 +106,9 @@ class Operation:
|
|||||||
cls.current_mission.coalition["red"] = Coalition(
|
cls.current_mission.coalition["red"] = Coalition(
|
||||||
"red", bullseye=cls.game.red.bullseye.to_pydcs()
|
"red", bullseye=cls.game.red.bullseye.to_pydcs()
|
||||||
)
|
)
|
||||||
|
cls.current_mission.coalition["neutrals"] = Coalition(
|
||||||
|
"neutrals", bullseye=Bullseye(Point(0, 0)).to_pydcs()
|
||||||
|
)
|
||||||
|
|
||||||
p_country = cls.game.blue.country_name
|
p_country = cls.game.blue.country_name
|
||||||
e_country = cls.game.red.country_name
|
e_country = cls.game.red.country_name
|
||||||
@ -115,6 +119,16 @@ class Operation:
|
|||||||
country_dict[db.country_id_from_name(e_country)]()
|
country_dict[db.country_id_from_name(e_country)]()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
belligerents = [
|
||||||
|
db.country_id_from_name(p_country),
|
||||||
|
db.country_id_from_name(e_country),
|
||||||
|
]
|
||||||
|
for country in country_dict.keys():
|
||||||
|
if country not in belligerents:
|
||||||
|
cls.current_mission.coalition["neutrals"].add_country(
|
||||||
|
country_dict[country]()
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
||||||
trigger = TriggerStart(comment=comment)
|
trigger = TriggerStart(comment=comment)
|
||||||
@ -365,6 +379,7 @@ class Operation:
|
|||||||
cls.laser_code_registry,
|
cls.laser_code_registry,
|
||||||
cls.unit_map,
|
cls.unit_map,
|
||||||
air_support=cls.airsupportgen.air_support,
|
air_support=cls.airsupportgen.air_support,
|
||||||
|
helipads=cls.groundobjectgen.helipads,
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.airgen.clear_parking_slots()
|
cls.airgen.clear_parking_slots()
|
||||||
|
|||||||
@ -32,6 +32,7 @@ from dcs.ships import (
|
|||||||
)
|
)
|
||||||
from dcs.terrain.terrain import Airport, ParkingSlot
|
from dcs.terrain.terrain import Airport, ParkingSlot
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
|
from dcs.unittype import FlyingType
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from game.point_with_heading import PointWithHeading
|
from game.point_with_heading import PointWithHeading
|
||||||
@ -410,6 +411,13 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_helipads(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns true if cp has helipads
|
||||||
|
"""
|
||||||
|
return len(self.helipads) > 0
|
||||||
|
|
||||||
def can_recruit_ground_units(self, game: Game) -> bool:
|
def can_recruit_ground_units(self, game: Game) -> bool:
|
||||||
"""Returns True if this control point is capable of recruiting ground units."""
|
"""Returns True if this control point is capable of recruiting ground units."""
|
||||||
if not self.can_deploy_ground_units:
|
if not self.can_deploy_ground_units:
|
||||||
@ -828,6 +836,22 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
"""Return the number of ammo depots, including dead ones"""
|
"""Return the number of ammo depots, including dead ones"""
|
||||||
return len(list(self.all_ammo_depots))
|
return len(list(self.all_ammo_depots))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_fuel_depots_count(self) -> int:
|
||||||
|
"""Return the number of available fuel depots"""
|
||||||
|
return len(
|
||||||
|
[
|
||||||
|
obj
|
||||||
|
for obj in self.connected_objectives
|
||||||
|
if obj.category == "fuel" and not obj.is_dead
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_fuel_depots_count(self) -> int:
|
||||||
|
"""Return the number of fuel depots, including dead ones"""
|
||||||
|
return len([obj for obj in self.connected_objectives if obj.category == "fuel"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
|
def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
|
||||||
return []
|
return []
|
||||||
@ -886,6 +910,11 @@ class Airfield(ControlPoint):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def total_aircraft_parking(self) -> int:
|
def total_aircraft_parking(self) -> int:
|
||||||
|
"""
|
||||||
|
Return total aircraft parking slots available
|
||||||
|
Note : additional helipads shouldn't contribute to this score as it could allow airfield
|
||||||
|
to buy more planes than what they are able to host
|
||||||
|
"""
|
||||||
return len(self.airport.parking_slots)
|
return len(self.airport.parking_slots)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1165,7 +1194,7 @@ class Fob(ControlPoint):
|
|||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def runway_is_operational(self) -> bool:
|
def runway_is_operational(self) -> bool:
|
||||||
return False
|
return self.has_helipads
|
||||||
|
|
||||||
def active_runway(
|
def active_runway(
|
||||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
@ -1189,10 +1218,10 @@ class Fob(ControlPoint):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def total_aircraft_parking(self) -> int:
|
def total_aircraft_parking(self) -> int:
|
||||||
return 0
|
return len(self.helipads)
|
||||||
|
|
||||||
def can_operate(self, aircraft: AircraftType) -> bool:
|
def can_operate(self, aircraft: AircraftType) -> bool:
|
||||||
return False
|
return aircraft.helicopter
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def heading(self) -> Heading:
|
def heading(self) -> Heading:
|
||||||
|
|||||||
@ -111,6 +111,9 @@ VERSION = _build_version_string()
|
|||||||
#: * DCS 2.7.4.9632 changed scenery target IDs. Any mission using map buildings as
|
#: * DCS 2.7.4.9632 changed scenery target IDs. Any mission using map buildings as
|
||||||
#: strike targets must check and potentially recreate all those objectives.
|
#: strike targets must check and potentially recreate all those objectives.
|
||||||
#:
|
#:
|
||||||
|
#: Version 8.1
|
||||||
|
#: * You can now add "Invisible FARP" static to FOB to add helicopter slots
|
||||||
|
#:
|
||||||
#: Version 9.0
|
#: Version 9.0
|
||||||
#: * Campaign files now define the initial squadron layouts. See TODO.
|
#: * Campaign files now define the initial squadron layouts. See TODO.
|
||||||
#: * CV and LHA control points now get their names from the group name in the campaign
|
#: * CV and LHA control points now get their names from the group name in the campaign
|
||||||
|
|||||||
@ -229,6 +229,7 @@ class AircraftConflictGenerator:
|
|||||||
laser_code_registry: LaserCodeRegistry,
|
laser_code_registry: LaserCodeRegistry,
|
||||||
unit_map: UnitMap,
|
unit_map: UnitMap,
|
||||||
air_support: AirSupport,
|
air_support: AirSupport,
|
||||||
|
helipads: dict[ControlPoint, list[StaticGroup]],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.m = mission
|
self.m = mission
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -239,6 +240,7 @@ class AircraftConflictGenerator:
|
|||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
self.flights: List[FlightData] = []
|
self.flights: List[FlightData] = []
|
||||||
self.air_support = air_support
|
self.air_support = air_support
|
||||||
|
self.helipads = helipads
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def use_client(self) -> bool:
|
def use_client(self) -> bool:
|
||||||
@ -534,6 +536,54 @@ class AircraftConflictGenerator:
|
|||||||
group_size=count,
|
group_size=count,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _generate_at_cp_helipad(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
side: Country,
|
||||||
|
unit_type: Type[FlyingType],
|
||||||
|
count: int,
|
||||||
|
start_type: str,
|
||||||
|
cp: ControlPoint,
|
||||||
|
) -> FlyingGroup[Any]:
|
||||||
|
assert count > 0
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
"airgen at cp's helipads : {} for {} at {}".format(
|
||||||
|
unit_type, side.id, cp.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
helipad = self.helipads[cp].pop()
|
||||||
|
except IndexError as ex:
|
||||||
|
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||||
|
|
||||||
|
group = self._generate_at_group(
|
||||||
|
name=name,
|
||||||
|
side=side,
|
||||||
|
unit_type=unit_type,
|
||||||
|
count=count,
|
||||||
|
start_type=start_type,
|
||||||
|
at=helipad,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note : A bit dirty, need better support in pydcs
|
||||||
|
group.points[0].action = PointAction.FromGroundArea
|
||||||
|
group.points[0].type = "TakeOffGround"
|
||||||
|
group.units[0].heading = helipad.units[0].heading
|
||||||
|
if start_type != "Cold":
|
||||||
|
group.points[0].action = PointAction.FromGroundAreaHot
|
||||||
|
group.points[0].type = "TakeOffGroundHot"
|
||||||
|
|
||||||
|
for i in range(count - 1):
|
||||||
|
try:
|
||||||
|
helipad = self.helipads[cp].pop()
|
||||||
|
group.units[1 + i].position = Point(helipad.x, helipad.y)
|
||||||
|
group.units[1 + i].heading = helipad.units[0].heading
|
||||||
|
except IndexError as ex:
|
||||||
|
raise RuntimeError(f"Not enough helipads available at {cp}") from ex
|
||||||
|
return group
|
||||||
|
|
||||||
def _add_radio_waypoint(
|
def _add_radio_waypoint(
|
||||||
self,
|
self,
|
||||||
group: FlyingGroup[Any],
|
group: FlyingGroup[Any],
|
||||||
@ -692,11 +742,13 @@ class AircraftConflictGenerator:
|
|||||||
self, cp: ControlPoint, country: Country, flight: Flight
|
self, cp: ControlPoint, country: Country, flight: Flight
|
||||||
) -> FlyingGroup[Any]:
|
) -> FlyingGroup[Any]:
|
||||||
name = namegen.next_aircraft_name(country, cp.id, flight)
|
name = namegen.next_aircraft_name(country, cp.id, flight)
|
||||||
|
group: FlyingGroup[Any]
|
||||||
try:
|
try:
|
||||||
if flight.start_type == "In Flight":
|
if flight.start_type == "In Flight":
|
||||||
group = self._generate_inflight(
|
group = self._generate_inflight(
|
||||||
name=name, side=country, flight=flight, origin=cp
|
name=name, side=country, flight=flight, origin=cp
|
||||||
)
|
)
|
||||||
|
return group
|
||||||
elif isinstance(cp, NavalControlPoint):
|
elif isinstance(cp, NavalControlPoint):
|
||||||
group_name = cp.get_carrier_group_name()
|
group_name = cp.get_carrier_group_name()
|
||||||
carrier_group = self.m.find_group(group_name)
|
carrier_group = self.m.find_group(group_name)
|
||||||
@ -705,7 +757,7 @@ class AircraftConflictGenerator:
|
|||||||
f"Carrier group {carrier_group} is a "
|
f"Carrier group {carrier_group} is a "
|
||||||
"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
"{carrier_group.__class__.__name__}, expected a ShipGroup"
|
||||||
)
|
)
|
||||||
group = self._generate_at_group(
|
return self._generate_at_group(
|
||||||
name=name,
|
name=name,
|
||||||
side=country,
|
side=country,
|
||||||
unit_type=flight.unit_type.dcs_unit_type,
|
unit_type=flight.unit_type.dcs_unit_type,
|
||||||
@ -714,11 +766,22 @@ class AircraftConflictGenerator:
|
|||||||
at=carrier_group,
|
at=carrier_group,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# If the flight is an helicopter flight, then prioritize dedicated helipads
|
||||||
|
if flight.unit_type.helicopter:
|
||||||
|
return self._generate_at_cp_helipad(
|
||||||
|
name=name,
|
||||||
|
side=country,
|
||||||
|
unit_type=flight.unit_type.dcs_unit_type,
|
||||||
|
count=flight.count,
|
||||||
|
start_type=flight.start_type,
|
||||||
|
cp=cp,
|
||||||
|
)
|
||||||
|
|
||||||
if not isinstance(cp, Airfield):
|
if not isinstance(cp, Airfield):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Attempted to spawn at airfield for non-airfield {cp}"
|
f"Attempted to spawn at airfield for non-airfield {cp}"
|
||||||
)
|
)
|
||||||
group = self._generate_at_airport(
|
return self._generate_at_airport(
|
||||||
name=name,
|
name=name,
|
||||||
side=country,
|
side=country,
|
||||||
unit_type=flight.unit_type.dcs_unit_type,
|
unit_type=flight.unit_type.dcs_unit_type,
|
||||||
@ -737,8 +800,7 @@ class AircraftConflictGenerator:
|
|||||||
name=name, side=country, flight=flight, origin=cp
|
name=name, side=country, flight=flight, origin=cp
|
||||||
)
|
)
|
||||||
group.points[0].alt = 1500
|
group.points[0].alt = 1500
|
||||||
|
return group
|
||||||
return group
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_reduced_fuel(
|
def set_reduced_fuel(
|
||||||
|
|||||||
@ -220,6 +220,8 @@ class FlightWaypoint:
|
|||||||
PointAction.FromParkingArea: FlightWaypointType.TAKEOFF,
|
PointAction.FromParkingArea: FlightWaypointType.TAKEOFF,
|
||||||
PointAction.FromParkingAreaHot: FlightWaypointType.TAKEOFF,
|
PointAction.FromParkingAreaHot: FlightWaypointType.TAKEOFF,
|
||||||
PointAction.FromRunway: FlightWaypointType.TAKEOFF,
|
PointAction.FromRunway: FlightWaypointType.TAKEOFF,
|
||||||
|
PointAction.FromGroundArea: FlightWaypointType.TAKEOFF,
|
||||||
|
PointAction.FromGroundAreaHot: FlightWaypointType.TAKEOFF,
|
||||||
}[point.action]
|
}[point.action]
|
||||||
if waypoint.waypoint_type == FlightWaypointType.NAV:
|
if waypoint.waypoint_type == FlightWaypointType.NAV:
|
||||||
waypoint.name = "NAV"
|
waypoint.name = "NAV"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from collections import defaultdict
|
||||||
from typing import (
|
from typing import (
|
||||||
Dict,
|
Dict,
|
||||||
Iterator,
|
Iterator,
|
||||||
@ -35,7 +36,7 @@ from dcs.task import (
|
|||||||
FireAtPoint,
|
FireAtPoint,
|
||||||
)
|
)
|
||||||
from dcs.triggers import TriggerStart, TriggerZone
|
from dcs.triggers import TriggerStart, TriggerZone
|
||||||
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
|
from dcs.unit import Ship, Unit, Vehicle, InvisibleFARP
|
||||||
from dcs.unitgroup import ShipGroup, StaticGroup, VehicleGroup
|
from dcs.unitgroup import ShipGroup, StaticGroup, VehicleGroup
|
||||||
from dcs.unittype import StaticType, ShipType, VehicleType
|
from dcs.unittype import StaticType, ShipType, VehicleType
|
||||||
from dcs.vehicles import vehicle_map
|
from dcs.vehicles import vehicle_map
|
||||||
@ -589,22 +590,45 @@ class HelipadGenerator:
|
|||||||
self.game = game
|
self.game = game
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.tacan_registry = tacan_registry
|
self.tacan_registry = tacan_registry
|
||||||
|
self.helipads: list[StaticGroup] = []
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
|
|
||||||
|
# Note : Helipad are generated as neutral object in order not to interfer with capture triggers
|
||||||
|
neutral_country = self.m.country(self.game.neutral_country.name)
|
||||||
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
|
country = self.m.country(self.game.coalition_for(self.cp.captured).country_name)
|
||||||
for i, helipad in enumerate(self.cp.helipads):
|
for i, helipad in enumerate(self.cp.helipads):
|
||||||
name = self.cp.name + "_helipad_" + str(i)
|
name = self.cp.name + "_helipad_" + str(i)
|
||||||
logging.info("Generating helipad : " + name)
|
logging.info("Generating helipad static : " + name)
|
||||||
pad = SingleHeliPad(name=(name + "_unit"))
|
pad = InvisibleFARP(name=name)
|
||||||
pad.position = Point(helipad.x, helipad.y)
|
pad.position = Point(helipad.x, helipad.y)
|
||||||
pad.heading = helipad.heading.degrees
|
pad.heading = helipad.heading.degrees
|
||||||
# pad.heliport_frequency = self.radio_registry.alloc_uhf() TODO : alloc radio & callsign
|
|
||||||
sg = unitgroup.StaticGroup(self.m.next_group_id(), name)
|
sg = unitgroup.StaticGroup(self.m.next_group_id(), name)
|
||||||
sg.add_unit(pad)
|
sg.add_unit(pad)
|
||||||
sp = StaticPoint()
|
sp = StaticPoint()
|
||||||
sp.position = pad.position
|
sp.position = pad.position
|
||||||
sg.add_point(sp)
|
sg.add_point(sp)
|
||||||
country.add_static_group(sg)
|
neutral_country.add_static_group(sg)
|
||||||
|
|
||||||
|
self.helipads.append(sg)
|
||||||
|
|
||||||
|
# Generate a FARP Ammo and Fuel stack for each pad
|
||||||
|
self.m.static_group(
|
||||||
|
country=country,
|
||||||
|
name=(name + "_fuel"),
|
||||||
|
_type=Fortification.FARP_Fuel_Depot,
|
||||||
|
position=pad.position.point_from_heading(helipad.heading.degrees, 35),
|
||||||
|
heading=pad.heading,
|
||||||
|
)
|
||||||
|
self.m.static_group(
|
||||||
|
country=country,
|
||||||
|
name=(name + "_ammo"),
|
||||||
|
_type=Fortification.FARP_Ammo_Dump_Coating,
|
||||||
|
position=pad.position.point_from_heading(
|
||||||
|
helipad.heading.degrees, 35
|
||||||
|
).point_from_heading(helipad.heading.degrees + 90, 10),
|
||||||
|
heading=pad.heading,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GroundObjectsGenerator:
|
class GroundObjectsGenerator:
|
||||||
@ -631,13 +655,18 @@ class GroundObjectsGenerator:
|
|||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
self.icls_alloc = iter(range(1, 21))
|
self.icls_alloc = iter(range(1, 21))
|
||||||
self.runways: Dict[str, RunwayData] = {}
|
self.runways: Dict[str, RunwayData] = {}
|
||||||
|
self.helipads: dict[ControlPoint, list[StaticGroup]] = defaultdict(list)
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
country = self.m.country(self.game.coalition_for(cp.captured).country_name)
|
country = self.m.country(self.game.coalition_for(cp.captured).country_name)
|
||||||
HelipadGenerator(
|
|
||||||
|
# Generate helipads
|
||||||
|
helipad_gen = HelipadGenerator(
|
||||||
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
self.m, cp, self.game, self.radio_registry, self.tacan_registry
|
||||||
).generate()
|
)
|
||||||
|
helipad_gen.generate()
|
||||||
|
self.helipads[cp] = helipad_gen.helipads
|
||||||
|
|
||||||
for ground_object in cp.ground_objects:
|
for ground_object in cp.ground_objects:
|
||||||
generator: GenericGroundObjectGenerator[Any]
|
generator: GenericGroundObjectGenerator[Any]
|
||||||
|
|||||||
@ -40,12 +40,14 @@ class QAircraftRecruitmentMenu(UnitTransactionFrame[Squadron]):
|
|||||||
row = 0
|
row = 0
|
||||||
|
|
||||||
unit_types: Set[AircraftType] = set()
|
unit_types: Set[AircraftType] = set()
|
||||||
|
|
||||||
for squadron in cp.squadrons:
|
for squadron in cp.squadrons:
|
||||||
unit_types.add(squadron.aircraft)
|
unit_types.add(squadron.aircraft)
|
||||||
|
|
||||||
sorted_squadrons = sorted(cp.squadrons, key=lambda s: (s.aircraft.name, s.name))
|
sorted_squadrons = sorted(cp.squadrons, key=lambda s: (s.aircraft.name, s.name))
|
||||||
for row, squadron in enumerate(sorted_squadrons):
|
for row, squadron in enumerate(sorted_squadrons):
|
||||||
self.add_purchase_row(squadron, task_box_layout, row)
|
self.add_purchase_row(squadron, task_box_layout, row)
|
||||||
|
|
||||||
stretch = QVBoxLayout()
|
stretch = QVBoxLayout()
|
||||||
stretch.addStretch()
|
stretch.addStretch()
|
||||||
task_box_layout.addLayout(stretch, row, 0)
|
task_box_layout.addLayout(stretch, row, 0)
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Syria - Battle for Golan Heights",
|
|
||||||
"theater": "Syria",
|
|
||||||
"authors": "Khopa",
|
|
||||||
"recommended_player_faction": "Israel 2000",
|
|
||||||
"recommended_enemy_faction": "Syria 2011",
|
|
||||||
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance friendly.</p>",
|
|
||||||
"miz": "golan_heights_lite.miz",
|
|
||||||
"performance": 1,
|
|
||||||
"version": "8.0"
|
|
||||||
}
|
|
||||||
Binary file not shown.
104
resources/campaigns/golan_heights_lite.yaml
Normal file
104
resources/campaigns/golan_heights_lite.yaml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
name: Syria - Battle for Golan Heights
|
||||||
|
theater: Syria
|
||||||
|
authors: Khopa
|
||||||
|
recommended_player_faction: Israel 2000
|
||||||
|
recommended_enemy_faction: Syria 2011
|
||||||
|
description: <p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance and helicopter friendly.</p>
|
||||||
|
miz: golan_heights_lite.miz
|
||||||
|
performance: 1
|
||||||
|
version: "9.0"
|
||||||
|
squadrons:
|
||||||
|
# Ramat-David
|
||||||
|
30:
|
||||||
|
- primary: AEW&C
|
||||||
|
aircraft:
|
||||||
|
- E-3A
|
||||||
|
- primary: Refueling
|
||||||
|
aircraft:
|
||||||
|
- KC-135 Stratotanker
|
||||||
|
- primary: Transport
|
||||||
|
aircraft:
|
||||||
|
- C-130
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- F-15C Eagle
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- F-4E Phantom II
|
||||||
|
- primary: Strike
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- F-15E Strike Eagle
|
||||||
|
- primary: SEAD
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- F-16CM Fighting Falcon (Block 50)
|
||||||
|
# Golan South
|
||||||
|
Golan South:
|
||||||
|
- primary: CAS
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- AH-1W SuperCobra
|
||||||
|
- primary: CAS
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- AH-64D Apache Longbow
|
||||||
|
- primary: Transport
|
||||||
|
aircraft:
|
||||||
|
- UH-1H Iroquois
|
||||||
|
# Golan North
|
||||||
|
Golan North:
|
||||||
|
- primary: CAS
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Mi-24P Hind-F
|
||||||
|
- primary: CAS
|
||||||
|
aircraft:
|
||||||
|
- SA 342M Gazelle
|
||||||
|
- primary: Transport
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Mi-8MTV2 Hip
|
||||||
|
# Marj Ruhayyil
|
||||||
|
23:
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- MiG-21bis Fishbed-N
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- MiG-23MLD Flogger-K
|
||||||
|
- primary: SEAD
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Su-17M4 Fitter-K
|
||||||
|
- primary: Strike
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Su-17M4 Fitter-K
|
||||||
|
# Damascus
|
||||||
|
7:
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- MiG-29S Fulcrum-C
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- MiG-21bis Fishbed-N
|
||||||
|
- primary: BARCAP
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- MiG-25PD Foxbat-E
|
||||||
|
- primary: SEAD
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Su-24M Fencer-D
|
||||||
|
- primary: Strike
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- Su-17M4 Fitter-K
|
||||||
Loading…
x
Reference in New Issue
Block a user