Implemented support for player controllable carriers in Pretense campaigns. This functionality can be enabled or disabled in settings, because the controllable carriers in Pretense do not build and deploy AI missions autonomously, so the old functionality is retained.

Added new options in settings:
- Carriers steam into wind
- Navmesh to use for Pretense carrier zones
- Remove ground spawn statics, including invisible FARPs, at airbases.
- Percentage of randomly selected aircraft types (only for generated squadrons)
intended to allow the user to increase aircraft variety.

Will now store the ICLS channel and Link4 frequency in missiondata.py CarrierInfo.

Implemented artillery groups as Pretense garrisons. Artillery groups are spawned by the Artillery Bunker. Will now also ensure that the logistics units spawned as part of Pretense garrisons are actually capable of ammo resupply.

Fixed the Pretense generator generating a bit too many missions per squadron. Ground spawns: Also hot start aircraft which require ground crew support (ground air or chock removal) which might not be available at roadbases. Also, pretensetgogenerator.py will now correctly handle air defence units in ground_unit_of_class(). Added Roland groups in the Pretense generator.
This commit is contained in:
MetalStormGhost 2024-04-06 15:46:11 +03:00
parent 55b173ec10
commit 64b1410de8
13 changed files with 851 additions and 133 deletions

View File

@ -73,7 +73,7 @@ class DefaultSquadronAssigner:
# If we can't find any squadron matching the requirement, we should
# create one.
return self.air_wing.squadron_def_generator.generate_for_task(
config.primary, control_point
config.primary, control_point, self.game.settings.squadron_random_chance
)
def find_preferred_squadron(

View File

@ -21,7 +21,7 @@ class SquadronDefGenerator:
self.used_nicknames: set[str] = set()
def generate_for_task(
self, task: FlightType, control_point: ControlPoint
self, task: FlightType, control_point: ControlPoint, squadron_random_chance: int
) -> Optional[SquadronDef]:
aircraft_choice: Optional[AircraftType] = None
for aircraft in AircraftType.priority_list_for_task(task):
@ -30,9 +30,9 @@ class SquadronDefGenerator:
if not control_point.can_operate(aircraft):
continue
aircraft_choice = aircraft
# 50/50 chance to keep looking for an aircraft that isn't as far up the
# squadron_random_chance percent chance to keep looking for an aircraft that isn't as far up the
# priority list to maintain some unit variety.
if random.choice([True, False]):
if squadron_random_chance >= random.randint(1, 100):
break
if aircraft_choice is None:

View File

@ -157,6 +157,7 @@ class Game:
2: {},
}
self.pretense_air_groups: dict[str, Flight] = {}
self.pretense_carrier_zones: List[str] = []
self.on_load(game_still_initializing=True)

View File

@ -15,6 +15,7 @@ from dcs.planes import (
C_101CC,
Su_33,
MiG_15bis,
M_2000C,
)
from dcs.point import PointAction
from dcs.ships import KUZNECOW
@ -35,7 +36,7 @@ from game.missiongenerator.missiondata import MissionData
from game.naming import namegen
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
from game.utils import feet, meters
from pydcs_extensions import A_4E_C
from pydcs_extensions import A_4E_C, VSN_F4B, VSN_F4C
WARM_START_HELI_ALT = meters(500)
WARM_START_ALTITUDE = meters(3000)
@ -400,6 +401,18 @@ class FlightGroupSpawner:
group.points[0].type = "TakeOffGround"
group.units[0].heading = ground_spawn[0].units[0].heading
if (
cp.coalition.game.settings.ground_start_airbase_statics_farps_remove
and isinstance(cp, Airfield)
):
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
group.points[0].link_unit = None
group.points[0].helipad_id = None
# Hot start aircraft which require ground power to start, when ground power
# trucks have been disabled for performance reasons
ground_power_available = (
@ -410,10 +423,31 @@ class FlightGroupSpawner:
and self.flight.coalition.game.settings.ground_start_ground_power_trucks_roadbase
)
if self.start_type is not StartType.COLD or (
not ground_power_available
and self.flight.unit_type.dcs_unit_type
in [A_4E_C, F_5E_3, F_86F_Sabre, MiG_15bis, F_14A_135_GR, F_14B, C_101CC]
# Also hot start aircraft which require ground crew support (ground air or chock removal)
# which might not be available at roadbases
if (
self.start_type is not StartType.COLD
or (
not ground_power_available
and self.flight.unit_type.dcs_unit_type
in [
A_4E_C,
F_86F_Sabre,
MiG_15bis,
F_14A_135_GR,
F_14B,
C_101CC,
]
)
or (
self.flight.unit_type.dcs_unit_type
in [
F_5E_3,
M_2000C,
VSN_F4B,
VSN_F4C,
]
)
):
group.points[0].action = PointAction.FromGroundAreaHot
group.points[0].type = "TakeOffGroundHot"
@ -435,6 +469,17 @@ class FlightGroupSpawner:
ground_spawn[0].x, ground_spawn[0].y, terrain=terrain
)
group.units[1 + i].heading = ground_spawn[0].units[0].heading
if (
cp.coalition.game.settings.ground_start_airbase_statics_farps_remove
and isinstance(cp, Airfield)
):
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
except IndexError as ex:
raise NoParkingSlotError(
f"Not enough STOL slots available at {cp}"

View File

@ -52,6 +52,8 @@ class CarrierInfo(UnitInfo):
"""Carrier information."""
tacan: TacanChannel
icls_channel: int | None
link4_freq: RadioFrequency | None
@dataclass

View File

@ -60,6 +60,7 @@ from game.theater import (
TheaterGroundObject,
TheaterUnit,
NavalControlPoint,
Airfield,
)
from game.theater.theatergroundobject import (
CarrierGroundObject,
@ -626,6 +627,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
callsign=tacan_callsign,
freq=atc,
tacan=tacan,
icls_channel=icls,
link4_freq=link4,
blue=self.control_point.captured,
)
)
@ -940,7 +943,11 @@ class GroundSpawnRoadbaseGenerator:
country.id
)
if self.game.position_culled(ground_spawn[0]):
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
self.cp, Airfield
):
cull_farp_statics = True
elif self.game.position_culled(ground_spawn[0]):
cull_farp_statics = True
if self.cp.coalition.player:
for package in self.cp.coalition.ato.packages:
@ -1072,7 +1079,11 @@ class GroundSpawnGenerator:
country.id
)
if self.game.position_culled(vtol_pad[0]):
if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance(
self.cp, Airfield
):
cull_farp_statics = True
elif self.game.position_culled(vtol_pad[0]):
cull_farp_statics = True
if self.cp.coalition.player:
for package in self.cp.coalition.ato.packages:

View File

@ -191,13 +191,13 @@ class PretenseAircraftGenerator:
"""
squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task(
flight_type, cp
flight_type, cp, self.game.settings.squadron_random_chance
)
for retries in range(num_retries):
if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter:
squadron_def = (
coalition.air_wing.squadron_def_generator.generate_for_task(
flight_type, cp
flight_type, cp, self.game.settings.squadron_random_chance
)
)
@ -302,7 +302,10 @@ class PretenseAircraftGenerator:
# First check what are the capabilities of the squadrons on this CP
for squadron in cp.squadrons:
for task in sead_tasks:
if task in squadron.auto_assignable_mission_types:
if (
task in squadron.auto_assignable_mission_types
or FlightType.DEAD in squadron.auto_assignable_mission_types
):
sead_capable_cp = True
for task in strike_tasks:
if task in squadron.auto_assignable_mission_types:
@ -360,6 +363,8 @@ class PretenseAircraftGenerator:
continue
if cp.coalition != squadron.coalition:
continue
if num_of_sead >= self.game.settings.pretense_sead_flights_per_cp:
break
mission_types = squadron.auto_assignable_mission_types
if (
@ -400,6 +405,11 @@ class PretenseAircraftGenerator:
continue
if cp.coalition != squadron.coalition:
continue
if (
num_of_strike
>= self.game.settings.pretense_strike_flights_per_cp
):
break
mission_types = squadron.auto_assignable_mission_types
for task in strike_tasks:
@ -422,6 +432,8 @@ class PretenseAircraftGenerator:
continue
if cp.coalition != squadron.coalition:
continue
if num_of_cap >= self.game.settings.pretense_barcap_flights_per_cp:
break
mission_types = squadron.auto_assignable_mission_types
for task in patrol_tasks:
@ -444,6 +456,8 @@ class PretenseAircraftGenerator:
continue
if cp.coalition != squadron.coalition:
continue
if num_of_cas >= self.game.settings.pretense_cas_flights_per_cp:
break
mission_types = squadron.auto_assignable_mission_types
if (
@ -467,6 +481,8 @@ class PretenseAircraftGenerator:
continue
if cp.coalition != squadron.coalition:
continue
if num_of_bai >= self.game.settings.pretense_bai_flights_per_cp:
break
mission_types = squadron.auto_assignable_mission_types
if FlightType.BAI in mission_types:

View File

@ -7,13 +7,15 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, List, Type
from dcs import Mission
from dcs.action import DoScript, DoScriptFile
from dcs.ships import Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal
from dcs.translation import String
from dcs.triggers import TriggerStart
from dcs.vehicles import AirDefence
from dcs.unittype import VehicleType, ShipType
from dcs.vehicles import AirDefence, Unarmed
from game.ato import FlightType
from game.coalition import Coalition
@ -283,6 +285,7 @@ class PretenseLuaGenerator(LuaGenerator):
"nasamsb",
"nasamsc",
"rapier",
"roland",
"irondome",
"davidsling",
]:
@ -381,6 +384,8 @@ class PretenseLuaGenerator(LuaGenerator):
== AirDefence.Rapier_fsa_launcher
):
sam_presets["rapier"].enabled = True
if ground_unit.unit_type.dcs_unit_type == AirDefence.Roland_ADS:
sam_presets["roland"].enabled = True
if ground_unit.unit_type.dcs_unit_type == IRON_DOME_LN:
sam_presets["irondome"].enabled = True
if ground_unit.unit_type.dcs_unit_type == DAVID_SLING_LN:
@ -526,22 +531,9 @@ class PretenseLuaGenerator(LuaGenerator):
cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()])
cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red"
if cp_side == PRETENSE_BLUE_SIDE:
if random.randint(0, 1):
supply_ship = "shipSupplyTilde"
else:
supply_ship = "shipLandingShipLstMk2"
tanker_ship = "shipTankerSeawisegiant"
command_ship = "shipLandingShipSamuelChase"
ship_group = "blueShipGroup"
else:
if random.randint(0, 1):
supply_ship = "shipBulkerYakushev"
else:
supply_ship = "shipCargoIvanov"
tanker_ship = "shipTankerElnya"
command_ship = "shipLandingShipRopucha"
ship_group = "redShipGroup"
supply_ship = "oilPump"
tanker_ship = "chemTank"
command_ship = "comCenter"
lua_string_zones += (
" presets.upgrades.supply." + supply_ship + ":extend({\n"
@ -581,7 +573,7 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
lua_string_zones += (
" presets.upgrades.attack." + command_ship + ":extend({\n"
" presets.upgrades.airdef." + command_ship + ":extend({\n"
)
lua_string_zones += (
f" name = '{cp_name_trimmed}-mission-command-"
@ -592,11 +584,9 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_zones += (
" presets.defenses."
+ cp_side_str
+ "."
+ ship_group
+ ":extend({ name='"
+ ".shorad:extend({ name='"
+ cp_name_trimmed
+ "-sam-"
+ "-shorad-"
+ cp_side_str
+ "' }),\n"
)
@ -719,6 +709,8 @@ class PretenseLuaGenerator(LuaGenerator):
return lua_string_zones
def generate_pretense_zone_land(self, cp_name: str) -> str:
is_artillery_zone = random.choice([True, False])
lua_string_zones = ""
cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()])
@ -727,11 +719,12 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_zones += " presets.upgrades.basic.tent:extend({\n"
lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n"
lua_string_zones += " products = {\n"
lua_string_zones += (
" presets.special.red.infantry:extend({ name='"
+ cp_name_trimmed
+ "-defense-red'})\n"
)
if not is_artillery_zone:
lua_string_zones += (
" presets.special.red.infantry:extend({ name='"
+ cp_name_trimmed
+ "-defense-red'})\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
lua_string_zones += " presets.upgrades.basic.comPost:extend({\n"
@ -742,13 +735,25 @@ class PretenseLuaGenerator(LuaGenerator):
+ cp_name_trimmed
+ "-defense-red'}),\n"
)
lua_string_zones += (
" presets.defenses.red.infantry:extend({ name='"
+ cp_name_trimmed
+ "-garrison-red' })\n"
)
if not is_artillery_zone:
lua_string_zones += (
" presets.defenses.red.infantry:extend({ name='"
+ cp_name_trimmed
+ "-garrison-red' })\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
if is_artillery_zone:
lua_string_zones += " presets.upgrades.basic.artyBunker:extend({\n"
lua_string_zones += f" name='{cp_name_trimmed}-arty-red',\n"
lua_string_zones += " products = {\n"
lua_string_zones += (
" presets.defenses.red.artillery:extend({ name='"
+ cp_name_trimmed
+ "-artillery-red'})\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
lua_string_zones += self.generate_pretense_land_upgrade_supply(
cp_name, PRETENSE_RED_SIDE
@ -760,11 +765,12 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_zones += " presets.upgrades.basic.tent:extend({\n"
lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n"
lua_string_zones += " products = {\n"
lua_string_zones += (
" presets.special.blue.infantry:extend({ name='"
+ cp_name_trimmed
+ "-defense-blue'})\n"
)
if not is_artillery_zone:
lua_string_zones += (
" presets.special.blue.infantry:extend({ name='"
+ cp_name_trimmed
+ "-defense-blue'})\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
lua_string_zones += " presets.upgrades.basic.comPost:extend({\n"
@ -775,13 +781,25 @@ class PretenseLuaGenerator(LuaGenerator):
+ cp_name_trimmed
+ "-defense-blue'}),\n"
)
lua_string_zones += (
" presets.defenses.blue.infantry:extend({ name='"
+ cp_name_trimmed
+ "-garrison-blue' })\n"
)
if not is_artillery_zone:
lua_string_zones += (
" presets.defenses.blue.infantry:extend({ name='"
+ cp_name_trimmed
+ "-garrison-blue' })\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
if is_artillery_zone:
lua_string_zones += " presets.upgrades.basic.artyBunker:extend({\n"
lua_string_zones += f" name='{cp_name_trimmed}-arty-blue',\n"
lua_string_zones += " products = {\n"
lua_string_zones += (
" presets.defenses.blue.artillery:extend({ name='"
+ cp_name_trimmed
+ "-artillery-blue'})\n"
)
lua_string_zones += " }\n"
lua_string_zones += " }),\n"
lua_string_zones += self.generate_pretense_land_upgrade_supply(
cp_name, PRETENSE_BLUE_SIDE
@ -816,14 +834,204 @@ class PretenseLuaGenerator(LuaGenerator):
return lua_string_zones
def generate_pretense_carrier_zones(self) -> str:
lua_string_carrier_zones = "cmap1 = CarrierMap:new({"
for zone_name in self.game.pretense_carrier_zones:
lua_string_carrier_zones += f'"{zone_name}",'
lua_string_carrier_zones += "})\n"
return lua_string_carrier_zones
def generate_pretense_carriers(
self,
cp_name: str,
cp_side: int,
cp_carrier_group_type: Type[ShipType] | None,
cp_carrier_group_name: str | None,
) -> str:
lua_string_carrier = "\n"
cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()])
link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal]
is_link4carrier = False
carrier_unit_name = ""
icls_channel = 10
link4_freq = 339000000
tacan_channel = 44
tacan_callsign = ""
radio = 137500000
if cp_carrier_group_type is not None:
if cp_carrier_group_type in link4carriers:
is_link4carrier = True
else:
return lua_string_carrier
if cp_carrier_group_name is None:
return lua_string_carrier
for carrier in self.mission_data.carriers:
if cp_carrier_group_name == carrier.group_name:
carrier_unit_name = carrier.unit_name
tacan_channel = carrier.tacan.number
tacan_callsign = carrier.callsign
radio = carrier.freq.hertz
if carrier.link4_freq is not None:
link4_freq = carrier.link4_freq.hertz
if carrier.icls_channel is not None:
icls_channel = carrier.icls_channel
break
lua_string_carrier += (
f'{cp_name_trimmed} = CarrierCommand:new("'
+ carrier_unit_name
+ '", 3000, cmap1:getNavMap(), '
+ "{\n"
)
if is_link4carrier:
lua_string_carrier += " icls = " + str(icls_channel) + ",\n"
lua_string_carrier += " acls = true,\n"
lua_string_carrier += " link4 = " + str(link4_freq) + ",\n"
lua_string_carrier += (
" tacan = {channel = "
+ str(tacan_channel)
+ ', callsign="'
+ tacan_callsign
+ '"},\n'
)
lua_string_carrier += " radio = " + str(radio) + "\n"
lua_string_carrier += "}, 30000)\n"
for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]:
if mission_type == FlightType.SEAD:
mission_name = "supportTypes.strike"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, '
+ "{altitude = 25000, expend=AI.Task.WeaponExpend.ALL})\n"
)
elif mission_type == FlightType.CAS:
mission_name = "supportTypes.strike"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, '
+ "{altitude = 15000, expend=AI.Task.WeaponExpend.ONE})\n"
)
elif mission_type == FlightType.BAI:
mission_name = "supportTypes.strike"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, '
+ "{altitude = 10000, expend=AI.Task.WeaponExpend.ONE})\n"
)
elif mission_type == FlightType.STRIKE:
mission_name = "supportTypes.strike"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 2000, CarrierCommand.{mission_name}, '
+ "{altitude = 20000})\n"
)
elif mission_type == FlightType.BARCAP:
mission_name = "supportTypes.cap"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, '
+ "{altitude = 25000, range=25})\n"
)
elif mission_type == FlightType.REFUELING:
mission_name = "supportTypes.tanker"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
tanker_freq = 257.0
tanker_tacan = 37.0
tanker_variant = "Drogue"
for tanker in self.mission_data.tankers:
if tanker.group_name == air_group:
tanker_freq = tanker.freq.hertz / 1000000
tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0
if tanker.variant == "KC-135 Stratotanker":
tanker_variant = "Boom"
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 3000, CarrierCommand.{mission_name}, '
+ "{altitude = 19000, freq="
+ str(tanker_freq)
+ ", tacan="
+ str(tanker_tacan)
+ "})\n"
)
elif mission_type == FlightType.AEWC:
mission_name = "supportTypes.awacs"
for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][
mission_type
]:
awacs_freq = 257.5
for awacs in self.mission_data.awacs:
if awacs.group_name == air_group:
awacs_freq = awacs.freq.hertz / 1000000
lua_string_carrier += (
f'{cp_name_trimmed}:addSupportFlight("{air_group}", 5000, CarrierCommand.{mission_name}, '
+ "{altitude = 30000, freq="
+ str(awacs_freq)
+ "})\n"
)
# lua_string_carrier += f'{cp_name_trimmed}:addExtraSupport("BGM-109B", 10000, CarrierCommand.supportTypes.mslstrike, ' + '{salvo = 10, wpType = \'weapons.missiles.BGM_109B\'})\n'
return lua_string_carrier
def get_ground_unit(
self, coalition: Coalition, side: int, desired_unit_classes: list[UnitClass]
) -> str:
ammo_trucks: List[Type[VehicleType]] = [
Unarmed.S_75_ZIL,
Unarmed.GAZ_3308,
Unarmed.GAZ_66,
Unarmed.KAMAZ_Truck,
Unarmed.KrAZ6322,
Unarmed.Ural_375,
Unarmed.Ural_375_PBU,
Unarmed.Ural_4320_31,
Unarmed.Ural_4320T,
Unarmed.ZIL_135,
Unarmed.Blitz_36_6700A,
Unarmed.M_818,
Unarmed.Bedford_MWD,
]
for unit_class in desired_unit_classes:
if coalition.faction.has_access_to_unit_class(unit_class):
dcs_unit_type = PretenseGroundObjectGenerator.ground_unit_of_class(
coalition=coalition, unit_class=unit_class
)
if (
dcs_unit_type is not None
and unit_class == UnitClass.LOGISTICS
and dcs_unit_type.dcs_unit_type.__class__ not in ammo_trucks
):
# ground_unit_of_class returned a logistics unit not capable of ammo resupply
# Retry up to 10 times
for truck_retry in range(10):
dcs_unit_type = (
PretenseGroundObjectGenerator.ground_unit_of_class(
coalition=coalition, unit_class=unit_class
)
)
if (
dcs_unit_type is not None
and dcs_unit_type.dcs_unit_type in ammo_trucks
):
break
else:
dcs_unit_type = None
if dcs_unit_type is not None:
return dcs_unit_type.dcs_id
@ -849,6 +1057,11 @@ class PretenseLuaGenerator(LuaGenerator):
return "LAV-25"
else:
return "BTR-80"
elif desired_unit_classes[0] == UnitClass.ARTILLERY:
if side == PRETENSE_BLUE_SIDE:
return "M-109"
else:
return "SAU Gvozdika"
elif desired_unit_classes[0] == UnitClass.RECON:
if side == PRETENSE_BLUE_SIDE:
return "M1043 HMMWV Armament"
@ -865,10 +1078,16 @@ class PretenseLuaGenerator(LuaGenerator):
else:
return "KS-19"
elif desired_unit_classes[0] == UnitClass.MANPAD:
if side == PRETENSE_BLUE_SIDE:
return "Soldier stinger"
if coalition.game.date.year >= 1990:
if side == PRETENSE_BLUE_SIDE:
return "Soldier stinger"
else:
return "SA-18 Igla manpad"
else:
return "SA-18 Igla manpad"
if side == PRETENSE_BLUE_SIDE:
return "Soldier M4"
else:
return "Infantry AK"
elif desired_unit_classes[0] == UnitClass.LOGISTICS:
if side == PRETENSE_BLUE_SIDE:
return "M 818"
@ -910,6 +1129,26 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n"
lua_string_ground_groups += "}\n"
lua_string_ground_groups += (
'TemplateDB.templates["artillery-' + side_str + '"] = {\n'
)
lua_string_ground_groups += " units = {\n"
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n'
lua_string_ground_groups += " },\n"
lua_string_ground_groups += f' skill = "{skill_str}",\n'
lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n"
lua_string_ground_groups += "}\n"
lua_string_ground_groups += (
'TemplateDB.templates["defense-' + side_str + '"] = {\n'
)
@ -1173,6 +1412,30 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n"
lua_string_ground_groups += "}\n"
lua_string_ground_groups += (
'TemplateDB.templates["roland-' + side_str + '"] = {\n'
)
lua_string_ground_groups += " units = {\n"
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n'
lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n'
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += ' "Roland ADS",\n'
lua_string_ground_groups += ' "Roland Radar",\n'
lua_string_ground_groups += ' "Roland Radar",\n'
lua_string_ground_groups += ' "Roland Radar"\n'
lua_string_ground_groups += " },\n"
lua_string_ground_groups += " maxDist = 300,\n"
lua_string_ground_groups += f' skill = "{skill_str}",\n'
lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n"
lua_string_ground_groups += "}\n"
return lua_string_ground_groups
@staticmethod
@ -1219,7 +1482,6 @@ class PretenseLuaGenerator(LuaGenerator):
+ str(self.game.settings.pretense_maxdistfromfront_distance * 1000)
+ "\n"
)
trigger = TriggerStart(comment="Pretense config")
trigger.add_action(DoScript(String(lua_string_config)))
self.mission.triggerrules.triggers.append(trigger)
@ -1247,16 +1509,30 @@ class PretenseLuaGenerator(LuaGenerator):
)
lua_string_zones = ""
lua_string_carriers = self.generate_pretense_carrier_zones()
for cp in self.game.theater.controlpoints:
if isinstance(cp, OffMapSpawn):
continue
cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()])
cp_name = "".join(
[i for i in cp.name if i.isalnum() or i.isspace() or i == "-"]
)
cp_side = 2 if cp.captured else 1
if isinstance(cp, OffMapSpawn):
continue
elif (
cp.is_fleet
and cp.captured
and self.game.settings.pretense_controllable_carrier
):
# Friendly carrier, generate carrier parameters
cp_carrier_group_type = cp.get_carrier_group_type()
cp_carrier_group_name = cp.get_carrier_group_name()
lua_string_carriers += self.generate_pretense_carriers(
cp_name, cp_side, cp_carrier_group_type, cp_carrier_group_name
)
continue
for side in range(1, 3):
if cp_name_trimmed not in self.game.pretense_air[cp_side]:
self.game.pretense_air[side][cp_name_trimmed] = {}
@ -1306,7 +1582,10 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_zones += (
f"zones.{cp_name_trimmed}.keepActive = " + is_keep_active + "\n"
)
lua_string_zones += self.generate_pretense_zone_land(cp.name)
if cp.is_fleet:
lua_string_zones += self.generate_pretense_zone_sea(cp_name)
else:
lua_string_zones += self.generate_pretense_zone_land(cp_name)
lua_string_connman = " cm = ConnectionManager:new()\n"
@ -1326,7 +1605,10 @@ class PretenseLuaGenerator(LuaGenerator):
)
if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0:
# Also connect carrier and LHA control points to adjacent friendly points
if cp.is_fleet:
if cp.is_fleet and (
not self.game.settings.pretense_controllable_carrier
or not cp.captured
):
num_of_carrier_connections = 0
for (
other_cp
@ -1347,7 +1629,19 @@ class PretenseLuaGenerator(LuaGenerator):
for extra_connection in range(
self.game.settings.pretense_extra_zone_connections
):
if len(closest_cps) > extra_connection:
if (
cp.is_fleet
and cp.captured
and self.game.settings.pretense_controllable_carrier
):
break
elif (
closest_cps[extra_connection].is_fleet
and closest_cps[extra_connection].captured
and self.game.settings.pretense_controllable_carrier
):
break
elif len(closest_cps) > extra_connection:
lua_string_connman += self.generate_pretense_zone_connection(
connected_points,
cp.name,
@ -1387,9 +1681,9 @@ class PretenseLuaGenerator(LuaGenerator):
lua_string_jtac = ""
for jtac in self.mission_data.jtacs:
lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()"
lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()\n"
lua_string_jtac += (
"CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})"
"CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})\n"
)
init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r")
@ -1411,6 +1705,7 @@ class PretenseLuaGenerator(LuaGenerator):
+ lua_string_connman
+ init_body_2
+ lua_string_jtac
+ lua_string_carriers
+ init_body_3
+ lua_string_supply
+ init_footer

View File

@ -38,6 +38,7 @@ from ..ato.airtaaskingorder import AirTaskingOrder
from ..callsigns import callsign_for_support_unit
from ..dcs.aircrafttype import AircraftType
from ..missiongenerator import MissionGenerator
from ..theater import Airfield
if TYPE_CHECKING:
from game import Game
@ -104,6 +105,26 @@ class PretenseMissionGenerator(MissionGenerator):
self.generate_ground_conflicts()
self.generate_air_units(tgo_generator)
for cp in self.game.theater.controlpoints:
if (
self.game.settings.ground_start_airbase_statics_farps_remove
and isinstance(cp, Airfield)
):
while len(tgo_generator.ground_spawns[cp]) > 0:
ground_spawn = tgo_generator.ground_spawns[cp].pop()
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
while len(tgo_generator.ground_spawns_roadbase[cp]) > 0:
ground_spawn = tgo_generator.ground_spawns_roadbase[cp].pop()
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
self.mission.triggerrules.triggers.clear()
PretenseTriggerGenerator(self.mission, self.game).generate()
ForcedOptionsGenerator(self.mission, self.game).generate()

View File

@ -8,12 +8,14 @@ create the pydcs groups and statics for those areas and add them to the mission.
from __future__ import annotations
import random
import logging
from collections import defaultdict
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type, Iterator
from dcs import Mission, Point
from dcs.countries import *
from dcs.country import Country
from dcs.ships import Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal, LHA_Tarawa
from dcs.unitgroup import StaticGroup, VehicleGroup
from dcs.unittype import VehicleType
@ -23,7 +25,7 @@ from game.dcs.groundunittype import GroundUnitType
from game.missiongenerator.groundforcepainter import (
GroundForcePainter,
)
from game.missiongenerator.missiondata import MissionData
from game.missiongenerator.missiondata import MissionData, CarrierInfo
from game.missiongenerator.tgogenerator import (
TgoGenerator,
HelipadGenerator,
@ -33,10 +35,11 @@ from game.missiongenerator.tgogenerator import (
CarrierGenerator,
LhaGenerator,
MissileSiteGenerator,
GenericCarrierGenerator,
)
from game.point_with_heading import PointWithHeading
from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanRegistry
from game.radio.tacan import TacanRegistry, TacanBand, TacanUsage
from game.runways import RunwayData
from game.theater import (
ControlPoint,
@ -51,9 +54,11 @@ from game.theater.theatergroundobject import (
MissileSiteGroundObject,
BuildingGroundObject,
VehicleGroupGroundObject,
GenericCarrierGroundObject,
)
from game.theater.theatergroup import TheaterGroup
from game.unitmap import UnitMap
from game.utils import Heading
from pydcs_extensions import (
Char_M551_Sheridan,
BV410_RBS70,
@ -147,6 +152,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
faction_units = (
set(coalition.faction.frontline_units)
| set(coalition.faction.artillery_units)
| set(coalition.faction.air_defense_units)
| set(coalition.faction.logistics_units)
)
of_class = list({u for u in faction_units if u.unit_class is unit_class})
@ -608,6 +614,172 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator):
return vehicle_group
class PretenseGenericCarrierGenerator(GenericCarrierGenerator):
"""Base type for carrier group generation.
Used by both CV(N) groups and LHA groups.
"""
def __init__(
self,
ground_object: GenericCarrierGroundObject,
control_point: NavalControlPoint,
country: Country,
game: Game,
mission: Mission,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
icls_alloc: Iterator[int],
runways: Dict[str, RunwayData],
unit_map: UnitMap,
mission_data: MissionData,
) -> None:
super().__init__(
ground_object,
control_point,
country,
game,
mission,
radio_registry,
tacan_registry,
icls_alloc,
runways,
unit_map,
mission_data,
)
self.ground_object = ground_object
self.control_point = control_point
self.radio_registry = radio_registry
self.tacan_registry = tacan_registry
self.icls_alloc = icls_alloc
self.runways = runways
self.mission_data = mission_data
def generate(self) -> None:
if self.control_point.frequency is not None:
atc = self.control_point.frequency
if atc not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(atc)
else:
atc = self.radio_registry.alloc_uhf()
for g_id, group in enumerate(self.ground_object.groups):
if not group.units:
logging.warning(f"Found empty carrier group in {self.control_point}")
continue
ship_units = []
for unit in group.units:
if unit.alive:
# All alive Ships
print(
f"Added {unit.unit_name} to ship_units of group {group.group_name}"
)
ship_units.append(unit)
if not ship_units:
# Empty array (no alive units), skip this group
continue
ship_group = self.create_ship_group(group.group_name, ship_units, atc)
if self.game.settings.pretense_carrier_steams_into_wind:
# Always steam into the wind, even if the carrier is being moved.
# There are multiple unsimulated hours between turns, so we can
# count those as the time the carrier uses to move and the mission
# time as the recovery window.
brc = self.steam_into_wind(ship_group)
else:
brc = Heading(0)
# Set Carrier Specific Options
if g_id == 0 and self.control_point.runway_is_operational():
# Get Correct unit type for the carrier.
# This will upgrade to super carrier if option is enabled
carrier_type = self.carrier_type
if carrier_type is None:
raise RuntimeError(
f"Error generating carrier group for {self.control_point.name}"
)
ship_group.units[0].type = carrier_type.id
if self.control_point.tacan is None:
tacan = self.tacan_registry.alloc_for_band(
TacanBand.X, TacanUsage.TransmitReceive
)
else:
tacan = self.control_point.tacan
if self.control_point.tcn_name is None:
tacan_callsign = self.tacan_callsign()
else:
tacan_callsign = self.control_point.tcn_name
link4 = None
link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal]
if carrier_type in link4carriers:
if self.control_point.link4 is None:
link4 = self.radio_registry.alloc_uhf()
else:
link4 = self.control_point.link4
icls = None
icls_name = self.control_point.icls_name
if carrier_type in link4carriers or carrier_type == LHA_Tarawa:
if self.control_point.icls_channel is None:
icls = next(self.icls_alloc)
else:
icls = self.control_point.icls_channel
self.activate_beacons(
ship_group, tacan, tacan_callsign, icls, icls_name, link4
)
self.add_runway_data(
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
)
self.mission_data.carriers.append(
CarrierInfo(
group_name=ship_group.name,
unit_name=ship_group.units[0].name,
callsign=tacan_callsign,
freq=atc,
tacan=tacan,
icls_channel=icls,
link4_freq=link4,
blue=self.control_point.captured,
)
)
class PretenseCarrierGenerator(PretenseGenericCarrierGenerator):
def tacan_callsign(self) -> str:
# TODO: Assign these properly.
return random.choice(
[
"STE",
"CVN",
"CVH",
"CCV",
"ACC",
"ARC",
"GER",
"ABR",
"LIN",
"TRU",
]
)
class PretenseLhaGenerator(PretenseGenericCarrierGenerator):
def tacan_callsign(self) -> str:
# TODO: Assign these properly.
return random.choice(
[
"LHD",
"LHA",
"LHB",
"LHC",
"LHD",
"LDS",
]
)
class PretenseTgoGenerator(TgoGenerator):
"""Creates DCS groups and statics for the theater during mission generation.
@ -693,7 +865,7 @@ class PretenseTgoGenerator(TgoGenerator):
if isinstance(ground_object, CarrierGroundObject) and isinstance(
cp, NavalControlPoint
):
generator = CarrierGenerator(
generator = PretenseCarrierGenerator(
ground_object,
cp,
country,
@ -709,7 +881,7 @@ class PretenseTgoGenerator(TgoGenerator):
elif isinstance(ground_object, LhaGroundObject) and isinstance(
cp, NavalControlPoint
):
generator = LhaGenerator(
generator = PretenseLhaGenerator(
ground_object,
cp,
country,

View File

@ -1,6 +1,8 @@
from __future__ import annotations
import logging
import math
import random
from typing import TYPE_CHECKING, List
from dcs import Point
@ -29,7 +31,10 @@ from dcs.terrain.syria.airports import Damascus, Khalkhalah
from dcs.translation import String
from dcs.triggers import Event, TriggerCondition, TriggerOnce
from dcs.unit import Skill
from numpy import cross, einsum, arctan2
from shapely import MultiPolygon, Point as ShapelyPoint
from game.naming import ALPHA_MILITARY
from game.theater import Airfield
from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn
@ -56,10 +61,14 @@ TRIGGER_RADIUS_PRETENSE_TGO = 500
TRIGGER_RADIUS_PRETENSE_SUPPLY = 500
TRIGGER_RADIUS_PRETENSE_HELI = 1000
TRIGGER_RADIUS_PRETENSE_HELI_BUFFER = 500
TRIGGER_RADIUS_PRETENSE_CARRIER = 50000
TRIGGER_RADIUS_PRETENSE_CARRIER = 20000
TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL = 3000
TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER = 25000
TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500
TRIGGER_RUNWAY_WIDTH_PRETENSE = 400
SIMPLIFY_RUNS_PRETENSE_CARRIER = 10000
class Silence(Option):
Key = 7
@ -221,12 +230,105 @@ class PretenseTriggerGenerator:
self.mission.triggerrules.triggers.append(recapture_trigger)
def _generate_pretense_zone_triggers(self) -> None:
"""Creates a pair of triggers for each control point of `cls.capture_zone_types`.
One for the initial capture of a control point, and one if it is recaptured.
Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua`
"""Creates triggger zones for the Pretense campaign. These include:
- Carrier zones for friendly forces, generated from the navmesh / sea zone intersection
- Carrier zones for opposing forces
- Airfield and FARP zones
- Airfield and FARP spawn points / helicopter spawn points / ground object positions
"""
# First generate carrier zones for friendly forces
use_blue_navmesh = (
self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh"
)
sea_zones_landmap = self.game.coalition_for(
player=False
).nav_mesh.theater.landmap
if (
self.game.settings.pretense_controllable_carrier
and sea_zones_landmap is not None
):
navmesh_number = 0
for navmesh_poly in self.game.coalition_for(
player=use_blue_navmesh
).nav_mesh.polys:
navmesh_number += 1
if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly):
# Get the intersection between the navmesh zone and the sea zone
navmesh_sea_intersection = sea_zones_landmap.sea_zones.intersection(
navmesh_poly.poly
)
navmesh_zone_verticies = navmesh_sea_intersection
# Simplify it to get a quadrangle
for simplify_run in range(SIMPLIFY_RUNS_PRETENSE_CARRIER):
navmesh_zone_verticies = navmesh_sea_intersection.simplify(
float(simplify_run * 10), preserve_topology=False
)
if isinstance(navmesh_zone_verticies, MultiPolygon):
break
if len(navmesh_zone_verticies.exterior.coords) <= 4:
break
if isinstance(navmesh_zone_verticies, MultiPolygon):
continue
trigger_zone_verticies = []
terrain = self.game.theater.terrain
alpha = random.choice(ALPHA_MILITARY)
# Generate the quadrangle zone and four points inside it for carrier navigation
if len(navmesh_zone_verticies.exterior.coords) == 4:
zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15}
corner_point_num = 0
for point_coord in navmesh_zone_verticies.exterior.coords:
corner_point = Point(
x=point_coord[0], y=point_coord[1], terrain=terrain
)
nav_point = corner_point.point_from_heading(
corner_point.heading_between_point(
navmesh_sea_intersection.centroid
),
TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER,
)
corner_point_num += 1
zone_name = f"{alpha}-{navmesh_number}-{corner_point_num}"
if sea_zones_landmap.sea_zones.contains(
ShapelyPoint(nav_point.x, nav_point.y)
):
self.mission.triggers.add_triggerzone(
nav_point,
radius=TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL,
hidden=False,
name=zone_name,
color=zone_color,
)
trigger_zone_verticies.append(corner_point)
zone_name = f"{alpha}-{navmesh_number}"
trigger_zone = self.mission.triggers.add_triggerzone_quad(
navmesh_sea_intersection.centroid,
trigger_zone_verticies,
hidden=False,
name=zone_name,
color=zone_color,
)
try:
if len(self.game.pretense_carrier_zones) == 0:
self.game.pretense_carrier_zones = []
except AttributeError:
self.game.pretense_carrier_zones = []
self.game.pretense_carrier_zones.append(zone_name)
for cp in self.game.theater.controlpoints:
if cp.is_fleet:
if (
cp.is_fleet
and self.game.settings.pretense_controllable_carrier
and cp.captured
):
# Friendly carrier zones are generated above
continue
elif cp.is_fleet:
trigger_radius = float(TRIGGER_RADIUS_PRETENSE_CARRIER)
elif isinstance(cp, Fob) and cp.has_helipads:
trigger_radius = TRIGGER_RADIUS_PRETENSE_HELI
@ -247,6 +349,8 @@ class PretenseTriggerGenerator:
or isinstance(cp.dcs_airport, Khalkhalah)
or isinstance(cp.dcs_airport, Krasnodar_Pashkovsky)
):
# Increase the size of Pretense zones at Damascus, Khalkhalah and Krasnodar-Pashkovsky
# (which are quite spread out) so the zone would encompass the entire airfield.
trigger_radius = int(TRIGGER_RADIUS_CAPTURE * 1.8)
else:
trigger_radius = TRIGGER_RADIUS_CAPTURE

View File

@ -158,6 +158,7 @@ class Settings:
MISSION_RESTRICTIONS_SECTION,
default=True,
)
easy_communication: Optional[bool] = choices_option(
"Easy Communication",
page=DIFFICULTY_PAGE,
@ -176,6 +177,20 @@ class Settings:
# Campaign management
# General
squadron_random_chance: int = bounded_int_option(
"Percentage of randomly selected aircraft types (only for generated squadrons)",
page=CAMPAIGN_MANAGEMENT_PAGE,
section=GENERAL_SECTION,
default=50,
min=0,
max=100,
detail=(
"<p>Aircraft type selection is governed by the campaign and the squadron definitions available to "
"Retribution. Squadrons are generated by Retribution if the faction does not have access to the campaign "
"designer's squadron/aircraft definitions. Use the above to increase/decrease aircraft variety by making "
"some selections random instead of picking aircraft types from a priority list.</p>"
),
)
restrict_weapons_by_date: bool = boolean_option(
"Restrict weapons by date (WIP)",
page=CAMPAIGN_MANAGEMENT_PAGE,
@ -831,6 +846,17 @@ class Settings:
"Needed to cold-start some aircraft types. Might have a performance impact."
),
)
ground_start_airbase_statics_farps_remove: bool = boolean_option(
"Remove ground spawn statics, including invisible FARPs, at airbases",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=True,
detail=(
"Ammo and fuel statics and invisible FARPs should be unnecessary when creating "
"additional spawns for players at airbases. This setting will disable them and "
"potentially grant a marginal performance benefit."
),
)
ai_unlimited_fuel: bool = boolean_option(
"AI flights have unlimited fuel",
MISSION_GENERATOR_PAGE,
@ -994,6 +1020,43 @@ class Settings:
"parts of the economy. Use this to adjust performance."
),
)
pretense_controllable_carrier: bool = boolean_option(
"Controllable carrier",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=True,
detail=(
"This can be used to enable or disable the native carrier support in Pretense. The Pretense carrier "
"can be controlled through the communication menu (if the Pretense character has enough rank/CMD points) "
"and the player can call in AI aerial and cruise missile missions using it."
"The controllable carriers in Pretense do not build and deploy AI missions autonomously, so if you prefer "
"to have both sides deploy carrier aviation autonomously, you might want to disable this option. "
"When this option is disabled, moving the carrier can only be done with the Retribution interface."
),
)
pretense_carrier_steams_into_wind: bool = boolean_option(
"Carriers steam into wind",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=True,
detail=(
"This setting controls whether carriers and their escorts will steam into wind. Disable to "
"to ensure that the carriers stay within the carrier zone in Pretense, but note that "
"doing so might limit carrier operations, takeoff weights and landings."
),
)
pretense_carrier_zones_navmesh: str = choices_option(
"Navmesh to use for Pretense carrier zones",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
choices=["Blue navmesh", "Red navmesh"],
default="Blue navmesh",
detail=(
"Use the Retribution map interface options to compare the blue navmesh and the red navmesh."
"You can select which navmesh to use when generating the zones in which the controllable carrier(s) "
"move and operate."
),
)
pretense_extra_zone_connections: int = bounded_int_option(
"Extra friendly zone connections",
page=PRETENSE_PAGE,
@ -1018,7 +1081,7 @@ class Settings:
"Number of AI SEAD flights per control point / zone",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)
@ -1026,7 +1089,7 @@ class Settings:
"Number of AI CAS flights per control point / zone",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)
@ -1034,7 +1097,7 @@ class Settings:
"Number of AI BAI flights per control point / zone",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)
@ -1042,7 +1105,7 @@ class Settings:
"Number of AI Strike flights per control point / zone",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)
@ -1050,7 +1113,7 @@ class Settings:
"Number of AI BARCAP flights per control point / zone",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)
@ -1066,7 +1129,7 @@ class Settings:
"Number of player flights per aircraft type at each base",
page=PRETENSE_PAGE,
section=GENERAL_SECTION,
default=2,
default=1,
min=1,
max=10,
)

View File

@ -21,6 +21,12 @@ presets = {
cost = 1500,
type = 'upgrade',
template = "outpost"
}),
artyBunker = Preset:new({
display = 'Artillery Bunker',
cost = 2000,
type = 'upgrade',
template = "ammo-depot"
})
},
attack = {
@ -36,30 +42,12 @@ presets = {
type = 'upgrade',
template = "ammo-depot"
}),
shipTankerSeawisegiant = Preset:new({
display = 'Tanker Seawise Giant',
cost = 1500,
type = 'upgrade',
template = "ship-tanker-seawisegiant"
}),
shipLandingShipSamuelChase = Preset:new({
display = 'LST USS Samuel Chase',
cost = 1500,
type = 'upgrade',
template = "ship-landingship-samuelchase"
}),
shipLandingShipRopucha = Preset:new({
display = 'LS Ropucha',
cost = 1500,
type = 'upgrade',
template = "ship-landingship-ropucha"
}),
shipTankerElnya = Preset:new({
display = 'Tanker Elnya',
cost = 1500,
type = 'upgrade',
template = "ship-tanker-elnya"
})
chemTank = Preset:new({
display='Chemical Tank',
cost = 2000,
type ='upgrade',
template = "chem-tank"
}),
},
supply = {
fuelCache = Preset:new({
@ -178,30 +166,6 @@ presets = {
income = 50,
template = "tv-tower"
}),
shipSupplyTilde = Preset:new({
display = 'Ship_Tilde_Supply',
cost = 1500,
type = 'upgrade',
template = "ship-supply-tilde"
}),
shipLandingShipLstMk2 = Preset:new({
display = 'LST Mk.II',
cost = 1500,
type = 'upgrade',
template = "ship-landingship-lstmk2"
}),
shipBulkerYakushev = Preset:new({
display = 'Bulker Yakushev',
cost = 1500,
type = 'upgrade',
template = "ship-bulker-yakushev"
}),
shipCargoIvanov = Preset:new({
display = 'Cargo Ivanov',
cost = 1500,
type = 'upgrade',
template = "ship-cargo-ivanov"
})
},
airdef = {
bunker = Preset:new({
@ -226,6 +190,12 @@ presets = {
type='defense',
template='infantry-red',
}),
artillery = Preset:new({
display = 'Artillery',
cost=2500,
type='defense',
template='artillery-red',
}),
shorad = Preset:new({
display = 'SHORAD',
cost=2500,
@ -298,6 +268,12 @@ presets = {
type='defense',
template='rapier-red',
}),
roland = Preset:new({
display = 'SAM',
cost=3000,
type='defense',
template='roland-red',
}),
irondome = Preset:new({
display = 'SAM',
cost=20000,
@ -324,6 +300,12 @@ presets = {
type='defense',
template='infantry-blue',
}),
artillery = Preset:new({
display = 'Artillery',
cost=2500,
type='defense',
template='artillery-blue',
}),
shorad = Preset:new({
display = 'SHORAD',
cost=2500,
@ -396,6 +378,12 @@ presets = {
type='defense',
template='rapier-blue',
}),
roland = Preset:new({
display = 'SAM',
cost=3000,
type='defense',
template='roland-blue',
}),
irondome = Preset:new({
display = 'SAM',
cost=20000,