mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
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:
parent
55b173ec10
commit
64b1410de8
@ -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(
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -52,6 +52,8 @@ class CarrierInfo(UnitInfo):
|
||||
"""Carrier information."""
|
||||
|
||||
tacan: TacanChannel
|
||||
icls_channel: int | None
|
||||
link4_freq: RadioFrequency | None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user