RotorOps/Generator/RotorOpsMission.py
spencershepard f382a2e3cc
Develop (#40)
For users:
-Added KA-50 III and AV8BNA Harrier to slot selection
-Changed message in mission generated success dialog
-Zone protect SAMs now part of 'Advanced Defenses' feature
-Late activated friendly/enemy CAP units are placed in mission as a template for Deployed CAP fighters (ie will not be active unless using Advanced Defenses or 'DEPLOY_FIGHTERS' name for radar ground unit)
-improve idle troop behavior at bases/FARPs

For Mission creators:
-Updated pydcs library supports new units such as technicals
-Updated pydcs library supports Falklands map
-allow troop pickup from HELO_CARRIER
-enemy units with radar can be designated to deploy intercept fighters on detection (see RotorOps.fighter options in RotorOps.lua for details) with options for min detection altitude and distance (min detection altitude allows helis to fly 'under the radar')
-Insert RotorOpsServer.lua script and trigger actions if option set in scenario config: rotorops_server: true
-scenario template triggers should now be 'untouched' after mission generation, allowing previously unsupported triggers and actions to be used, along with color coding
-block adding player helicopters if slots locked in scenario config
-Added RotorOps.draw_conflict_zones setting to give users the ability to disable or enable displaying of zones on the map.
-allow disabling spinboxes in scenario config
-mission ends 10 mins after mission success/fail
-copy helicopter start type from templates

Internal:
-github actions workflow to automatically deploy to update server
-Startup version check will ignore micro version
-bypassing triggers and merging before save (to preserve unsupported triggers in pydcs). Our goal is to leave the trigrules and trig from the source mission untouched
-if using random weather, set ice halo to auto and crystals to none
-dont put planes at airports without ILS (to avoid putting planes at helicopter airports ie. Syria)
-improved guardPosition task
-refactored 'coalition' variables to 'coal' to help prevent introducing errors in RotorOps.lua
2023-01-03 12:05:18 -08:00

1205 lines
54 KiB
Python

from tokenize import String
import dcs
import dcs.cloud_presets
import os
import random
import RotorOpsGroups
import RotorOpsUnits
import RotorOpsUtils
import RotorOpsConflict
import aircraftMods
from RotorOpsImport import ImportObjects
import time
from MissionGenerator import logger
from MissionGenerator import directories
jtf_red = "Combined Joint Task Forces Red"
jtf_blue = "Combined Joint Task Forces Blue"
class RotorOpsMission:
def __init__(self):
self.m = dcs.mission.Mission()
self.conflict_zones = {}
self.staging_zones = {}
self.spawn_zones = {}
self.all_zones = {}
self.scripts = {}
self.res_map = {}
self.config = None # not used
self.imports = None
self.red_zones = {}
self.blue_zones = {}
self.primary_e_airport = None
class RotorOpsZone:
def __init__(self, name: str, flag: int, position: dcs.point, size: int):
self.name = name
self.flag = flag
self.position = position
self.size = size
self.player_helo_spawns = []
self.base_position = position
def getMission(self):
return self.m
def setConfig(self, config):
self.config = config
def addZone(self, zone_dict, zone: RotorOpsZone):
zone_dict[zone.name] = zone
def addResources(self, sound_directory, script_directory):
# add all of our required sounds
os.chdir(sound_directory)
path = os.getcwd()
dir_list = os.listdir(path)
# print("Files and directories in '", path, "' :")
# print(dir_list)
for filename in dir_list:
if filename.endswith(".ogg"):
# print(filename)
key = self.m.map_resource.add_resource_file(filename)
self.res_map[filename] = key
# add all of our lua scripts
os.chdir(script_directory)
path = os.getcwd()
dir_list = os.listdir(path)
# print("Files and directories in '", path, "' :")
# print(dir_list)
for filename in dir_list:
if filename.endswith(".lua"):
logger.info("Adding script to mission: " + filename)
self.scripts[filename] = self.m.map_resource.add_resource_file(filename)
def getUnitsFromMiz(self, file, side='both'):
forces = {}
vehicles = []
attack_helos = []
transport_helos = []
attack_planes = []
fighter_planes = []
helicopters = []
source_mission = dcs.mission.Mission()
try:
source_mission.load_file(file)
if side == 'both':
sides = ['red', 'blue']
else:
sides = [side]
for side in sides:
for country_name in source_mission.coalition.get(side).countries:
country_obj = source_mission.coalition.get(side).countries[country_name]
for vehicle_group in country_obj.vehicle_group:
vehicles.append(vehicle_group)
for helicopter_group in country_obj.helicopter_group:
helicopters.append(helicopter_group)
if helicopter_group.task == 'CAS':
attack_helos.append(helicopter_group)
elif helicopter_group.task == 'Transport':
transport_helos.append(helicopter_group)
for plane_group in country_obj.plane_group:
if plane_group.task == 'CAS':
attack_planes.append(plane_group)
elif plane_group.task == 'CAP':
fighter_planes.append(plane_group)
forces["vehicles"] = vehicles
forces["attack_helos"] = attack_helos
forces["transport_helos"] = transport_helos
forces["attack_planes"] = attack_planes
forces["fighter_planes"] = fighter_planes
forces["helicopters"] = helicopters
return forces
except:
logger.error("Failed to load units from " + file)
def generateMission(self, window, options):
os.chdir(directories.scenarios)
logger.info("Looking for mission files in " + os.getcwd())
window.statusBar().showMessage("Loading scenario mission", 10000)
# self.m.load_file(options["scenario_file"])
# Bypass trig, triggrules, and triggers. Then load triggers
# manually. We want to get our zones from the template mission, but leave the existing trigger actions and
# conditions the same, since pydcs cannot yet handle some of them well.
self.m.load_file(options["scenario_file"], True)
self.m.triggers.load_from_dict(self.m.bypassed_triggers)
# Create some 'empty' triggerrules so that we can maintain indexing when we merge dictionaries on save
for rule in self.m.bypassed_trigrules:
trig = dcs.triggers.TriggerOnce(comment="Empty " + str(rule))
trig.rules.append(dcs.condition.TimeAfter(1))
trig.actions.append(dcs.action.DoScript(dcs.action.String("Filler " + str(rule))))
self.m.triggerrules.triggers.append(trig)
# Add countries if they're missing
if not self.m.country(jtf_red):
self.m.coalition.get("red").add_country(dcs.countries.CombinedJointTaskForcesRed())
if not self.m.country(jtf_blue):
self.m.coalition.get("blue").add_country(dcs.countries.CombinedJointTaskForcesBlue())
if not self.m.country(
dcs.countries.UnitedNationsPeacekeepers.name):
self.m.coalition.get("neutrals").add_country(dcs.countries.UnitedNationsPeacekeepers())
if not self.m.country("Russia"):
self.m.coalition.get("red").add_country(dcs.countries.Russia())
if not self.m.country("USA"):
self.m.coalition.get("blue").add_country(dcs.countries.USA())
self.addMods()
self.importObjects(options)
red_forces = self.getUnitsFromMiz(options["red_forces_path"], "both")
blue_forces = self.getUnitsFromMiz(options["blue_forces_path"], "both")
# add images to briefing
self.m.add_picture_blue(directories.assets + '/briefing1.png')
self.m.add_picture_blue(directories.assets + '/briefing2.png')
# get import objects for generic farps etc
self.imports = options["objects"]["imports"]
activated_farp = None
defensive_farp = None
logistics_farp = None
logistics_base = None
zone_protect = None
for i in self.imports:
if i.filename == ("FARP_ACTIVATED_ZONE.miz"):
activated_farp = i.path
if i.filename == ("FARP_DEFENSIVE_ZONE.miz"):
defensive_farp = i.path
if i.filename == ("FARP_LOGISTICS_ZONE.miz"):
logistics_farp = i.path
if i.filename == ("STAGING_LOGISTICS_BASE.miz"):
logistics_base = i.path
if i.filename == ("ZONE_ACTIVATED_DEFENSE.miz"):
zone_protect = i.path
# it's possible to have import templates with the same filename, but we will let the latest override others
# todo: verify we have the required templates
# add zones to target mission
zone_names = ["ALPHA", "BRAVO", "CHARLIE", "DELTA"]
zone_flag = 101
for zone_name in zone_names:
for zone in self.m.triggers.zones():
if zone.name == zone_name:
self.addZone(self.conflict_zones,
self.RotorOpsZone(zone_name, zone_flag, zone.position, zone.radius))
zone_flag = zone_flag + 1
for zone in self.m.triggers.zones():
self.addZone(self.all_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
if zone_name == "STAGING":
self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
continue
if zone.name.rfind("STAGING") >= 0: # find additional staging zones
self.addZone(self.staging_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
elif zone.name.rfind("SPAWN") >= 0:
self.addZone(self.spawn_zones, self.RotorOpsZone(zone.name, None, zone.position, zone.radius))
self.blue_zones = self.staging_zones
self.red_zones = self.conflict_zones
if options["defending"]:
self.blue_zones = self.conflict_zones
self.red_zones = self.staging_zones
# swap airport sides
self.swapSides(options)
# Populate Red zones with ground units
window.statusBar().showMessage("Populating units into mission...", 10000)
start_type = dcs.mission.StartType.Cold
if options["player_hotstart"]:
start_type = dcs.mission.StartType.Warm
for zone_name in self.red_zones:
if red_forces["vehicles"]:
self.addGroundGroups(self.red_zones[zone_name], self.m.country(jtf_red), red_forces["vehicles"],
options["red_quantity"])
if options["zone_farps"] != "farp_never" and not options["defending"]:
helicopters = False
if options["farp_spawns"]:
helicopters = True
# Add red zone FARPS
vehicle_group = self.addZoneBase(options, zone_name, jtf_blue,
file=activated_farp,
config_name="zone_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name,
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " FARP Static",
copy_statics=False,
statics_names="",
vehicles_single_group=True,
trigger_name=zone_name + "_FARP",
trigger_radius=110
)
vehicle_group.late_activation = True
if options["advanced_defenses"]:
sam_group = self.addZoneBase(options, zone_name, jtf_red,
file=zone_protect,
config_name="zone_protect_file",
copy_vehicles=True,
vehicles_name=zone_name + " Defense Static",
vehicles_single_group=False
)
# Populate Blue zones with ground units
for i, zone_name in enumerate(self.blue_zones):
if blue_forces["vehicles"]:
self.addGroundGroups(self.blue_zones[zone_name], self.m.country(jtf_blue), blue_forces["vehicles"],
options["blue_quantity"])
# Add blue zone FARPS (not late activated) for defensive mode
if options["zone_farps"] != "farp_never" and options["defending"]:
helicopters = False
if options["farp_spawns"]:
helicopters = True
if options["crates"] and i == len(self.blue_zones) - 1:
# add a logistics zone to the last conflict zone
# addLogisticsZone(zone_name, jtf_blue, logistics_farp, "logistics_farp_file", helicopters)
self.addZoneBase(options, zone_name, jtf_blue,
file=logistics_farp,
config_name="logistics_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " Logistics FARP",
copy_statics=True,
statics_names=zone_name + " Logistics FARP",
vehicles_single_group=False,
trigger_name=zone_name + "_FARP",
trigger_radius=110
)
else:
# addDefensiveFARP(zone_name, jtf_blue, defensive_farp)
self.addZoneBase(options, zone_name, jtf_blue,
file=defensive_farp,
config_name="defensive_farp_file",
copy_helicopters=helicopters,
helicopters_name="ZONE " + zone_name + " EMPTY",
heli_start_type=None,
copy_vehicles=True,
vehicles_name=zone_name + " Defensive FARP",
copy_statics=True,
statics_names=zone_name + " Defensive FARP",
vehicles_single_group=False,
trigger_name=zone_name + "_FARP",
trigger_radius=110
)
# add main logistics base
if options["crates"] and zone_name == "STAGING":
# addLogisticsZone(zone_name, jtf_blue, logistics_base, "staging_logistics_file", helicopters=True)
self.addZoneBase(options, zone_name, jtf_blue,
file=logistics_base,
config_name="staging_logistics_file",
copy_helicopters=True,
helicopters_name="ZONE " + zone_name + " LOGISTICS",
heli_start_type=start_type,
copy_vehicles=True,
vehicles_name=zone_name + " Logistics Base",
copy_statics=True,
statics_names=zone_name + " Logistics Base",
vehicles_single_group=False,
trigger_name="STAGING_BASE",
trigger_radius=170
)
# Add player slots
window.statusBar().showMessage("Adding flights to mission...", 10000)
if options["slots"] == "Locked to Scenario" or options["slots"] == "None":
pass
else:
self.addPlayerHelos(options)
# Add AI Flights
self.addFlights(options, red_forces, blue_forces)
# Set the Editor Map View
self.m.map.position = self.conflict_zones["ALPHA"].position
self.m.map.zoom = 100000
# add files and triggers necessary for RotorOps.lua script
window.statusBar().showMessage("Adding resources to mission...", 10000)
self.addResources(directories.sound, directories.scripts)
RotorOpsConflict.triggerSetup(self, options)
# finalize the mission briefing
briefing = self.m.description_text() + '## RotorOps Credits ##\n\n' + options["credits"]
briefing = briefing + "\nFor more info on RotorOps, visit: DCS-HELICOPTERS.COM"
self.m.set_description_text(briefing)
# set the weather and time
if options["random_weather"]:
# self.m.random_weather = True
max = len(dcs.cloud_presets.CLOUD_PRESETS) - 1
preset_name = list(dcs.cloud_presets.CLOUD_PRESETS)[random.randint(0, max)]
cloud_preset = dcs.weather.CloudPreset.by_name(preset_name)
self.m.weather.clouds_base = random.randrange(cloud_preset.min_base, cloud_preset.max_base)
self.m.weather.clouds_preset = cloud_preset
wind_dir = random.randrange(0, 359) + 180
wind_speed = random.randrange(5, 10)
self.m.weather.wind_at_ground.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_ground.speed = wind_speed + random.randrange(-4, -1)
self.m.weather.wind_at_2000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_2000.speed = wind_speed + random.randrange(-2, 2)
self.m.weather.wind_at_8000.direction = (wind_dir + random.randrange(-90, 90) - 180) % 360
self.m.weather.wind_at_8000.speed = wind_speed + random.randrange(-1, 10)
self.m.weather.halo.preset = dcs.weather.Halo.Preset.Auto
self.m.weather.halo.crystals = None
logger.info("Cloud preset = " + cloud_preset.ui_name + ", ground windspeed = " + str(
self.m.weather.wind_at_ground.speed))
if options["time"] != "Default Time":
self.m.random_daytime(options["time"].lower())
print("Time set to " + options["time"])
# Save the mission file
window.statusBar().showMessage("Saving mission...", 10000)
if window.user_output_dir:
output_dir = window.user_output_dir # if user has set output dir
else:
output_dir = directories.output # default dir
os.chdir(output_dir)
output_filename = options["scenario_name"] + " " + time.strftime('%a%H%M%S') + '.miz'
# dcs.mission.save will use the bypassed trig, trigrules, and triggers. Our goal is to leave the trigrules and
# trig from the source mission untouched. See comments in self.m.load_file above
#merge dictionaries
self.m.bypassed_trig = self.m.triggerrules.trig() | self.m.bypassed_trig
self.m.bypassed_trigrules = self.m.triggerrules.trigrules() | self.m.bypassed_trigrules
self.m.bypassed_triggers = self.m.triggers.dict()
success = self.m.save(output_filename)
return {"success": success, "filename": output_filename, "directory": output_dir} # let the UI know the result
# Use the ImportObjects class to place farps and bases
def addZoneBase(self, options, _zone_name, country, file, config_name=None, copy_helicopters=False,
helicopters_name="", heli_start_type=None,
copy_vehicles=False, vehicles_name="", copy_statics=False, statics_names="",
vehicles_single_group=False, trigger_name=None, trigger_radius=110, farp=True):
# look for a marker object to position the base at a position other than zone center
flag = self.m.find_group(_zone_name)
if flag:
position = flag.units[0].position
heading = flag.units[0].heading
self.all_zones[_zone_name].base_position = position
else:
position = self.all_zones[_zone_name].position
heading = 0
if farp:
farp = self.m.farp(self.m.country(country), _zone_name + " FARP",
position, hidden=True, dead=False, farp_type=dcs.unit.InvisibleFARP)
# Add a trigger zone
if trigger_name:
self.m.triggers.add_triggerzone(position, trigger_radius, False, trigger_name)
# Use alternate template file if it has been defined in scenario config
if config_name and options[config_name]:
for i in self.imports:
if i.filename.removesuffix('.miz') == options[config_name]:
file = i.path
# if multiple files found, we want the latest file to override the first
# Import statics and vehicles
i = ImportObjects(file)
i.anchorByGroupName("ANCHOR")
if copy_statics:
i.copyStatics(self.m, country, statics_names,
position, heading)
vehicle_group = None
if copy_vehicles:
if vehicles_single_group:
vehicle_group = i.copyVehiclesAsGroup(self.m, country, vehicles_name, position,
heading)
else:
i.copyVehicles(self.m, country, vehicles_name,
position, heading)
# Add client helicopters and farp objects
if copy_helicopters:
helicopter_groups = i.copyHelicopters(self.m, jtf_blue, helicopters_name, position,
heading, heli_start_type)
for group in helicopter_groups:
self.all_zones[_zone_name].player_helo_spawns.append(group)
return vehicle_group # for setting properties such as late activation
def addGroundGroups(self, zone, _country, groups, quantity):
for a in range(0, quantity):
group = random.choice(groups)
unit_types = []
for unit in group.units:
if dcs.vehicles.vehicle_map[unit.type]:
unit_types.append(dcs.vehicles.vehicle_map[unit.type])
country = self.m.country(_country.name)
self.m.vehicle_group_platoon(
country,
zone.name + '-GND ' + str(a + 1),
unit_types,
zone.position.random_point_within(zone.size / 1.3, 100),
heading=random.randint(0, 359),
formation=dcs.unitgroup.VehicleGroup.Formation.Scattered,
)
def getCoalitionAirports(self, side: str):
coalition_airports = []
primary_airport = None
shortest_dist = 1000000
for airport_name in self.m.terrain.airports:
airportobj = self.m.terrain.airports[airport_name]
if airportobj.coalition == str.upper(side):
coalition_airports.append(airportobj)
start = self.staging_zones[list(self.staging_zones)[0]]
dist_from_start = dcs.mapping._distance(airportobj.position.x, airportobj.position.y, start.position.x,
start.position.y)
if dist_from_start < shortest_dist:
primary_airport = airportobj
shortest_dist = dist_from_start
return coalition_airports, primary_airport
def getParking(self, airport, aircraft, alt_airports=None, group_size=1):
if len(airport.free_parking_slots(aircraft)) >= group_size:
if not (aircraft.id in dcs.planes.plane_map and (
len(airport.runways) == 0 or not hasattr(airport.runways[0], "ils"))):
return airport
if alt_airports:
for airport in alt_airports:
if len(airport.free_parking_slots(aircraft)) >= group_size:
if not (aircraft.id in dcs.planes.plane_map and len(airport.runways) == 0):
return airport
logger.warn("No parking available for " + aircraft.id)
return None
# Find parking spots on FARPs and carriers
def getUnitParking(self, aircraft):
return
def swapSides(self, options):
# Swap airports
blue_airports, primary_blue = self.getCoalitionAirports("blue")
red_airports, primary_red = self.getCoalitionAirports("red")
for airport in blue_airports:
self.m.terrain.airports[airport.name].set_red()
for airport in red_airports:
self.m.terrain.airports[airport.name].set_blue()
combinedJointTaskForcesBlue = self.m.country(jtf_blue)
combinedJointTaskForcesRed = self.m.country(jtf_red)
# Swap ships
blue_ships = combinedJointTaskForcesBlue.ship_group.copy()
red_ships = combinedJointTaskForcesRed.ship_group.copy()
for group in blue_ships:
group.points[0].tasks.append(dcs.task.OptROE(dcs.task.OptROE.Values.ReturnFire))
combinedJointTaskForcesRed.add_ship_group(group)
combinedJointTaskForcesBlue.ship_group.remove(group)
for group in red_ships:
combinedJointTaskForcesBlue.add_ship_group(group)
combinedJointTaskForcesRed.ship_group.remove(group)
# Swap statics
blue_statics = combinedJointTaskForcesBlue.static_group.copy()
red_statics = combinedJointTaskForcesRed.static_group.copy()
for group in blue_statics:
combinedJointTaskForcesBlue.static_group.remove(group)
combinedJointTaskForcesRed.add_static_group(group)
for group in red_statics:
combinedJointTaskForcesRed.static_group.remove(group)
combinedJointTaskForcesBlue.add_static_group(group)
# Swap vehicles
blue_vehicles = combinedJointTaskForcesBlue.vehicle_group.copy()
red_vehicles = combinedJointTaskForcesRed.vehicle_group.copy()
for group in blue_vehicles:
combinedJointTaskForcesBlue.vehicle_group.remove(group)
combinedJointTaskForcesRed.add_vehicle_group(group)
for group in red_vehicles:
combinedJointTaskForcesRed.vehicle_group.remove(group)
combinedJointTaskForcesBlue.add_vehicle_group(group)
# Swap planes
blue_planes = combinedJointTaskForcesBlue.plane_group.copy()
red_planes = combinedJointTaskForcesRed.plane_group.copy()
for group in blue_planes:
combinedJointTaskForcesBlue.plane_group.remove(group)
combinedJointTaskForcesRed.add_plane_group(group)
for group in red_planes:
combinedJointTaskForcesRed.plane_group.remove(group)
combinedJointTaskForcesBlue.add_plane_group(group)
# Swap helicopters
blue_helos = combinedJointTaskForcesBlue.helicopter_group.copy()
red_helos = combinedJointTaskForcesRed.helicopter_group.copy()
for group in blue_helos:
combinedJointTaskForcesBlue.helicopter_group.remove(group)
combinedJointTaskForcesRed.add_helicopter_group(group)
for group in red_helos:
combinedJointTaskForcesRed.helicopter_group.remove(group)
combinedJointTaskForcesBlue.add_helicopter_group(group)
def addPlayerHelos(self, options):
client_helos = RotorOpsUnits.client_helos
unslotted_count = 0
slotted_count = 0
for helicopter in dcs.helicopters.helicopter_map:
if helicopter == options["slots"]:
client_helos = [dcs.helicopters.helicopter_map[
helicopter]] # if our ui slot option matches a specific helicopter type name
# get loadouts from miz file and put into a simple dict
default_loadouts = {}
default_unit_groups = self.getUnitsFromMiz(directories.home_dir + "\\config\\blue_player_loadouts.miz", "blue")
for helicopter_group in default_unit_groups["helicopters"]:
default_loadouts[helicopter_group.units[0].unit_type.id] = {}
default_loadouts[helicopter_group.units[0].unit_type.id]["pylons"] = helicopter_group.units[0].pylons
default_loadouts[helicopter_group.units[0].unit_type.id]["livery_id"] = helicopter_group.units[0].livery_id
default_loadouts[helicopter_group.units[0].unit_type.id]["fuel"] = helicopter_group.units[0].fuel
# find friendly carriers and farps
carrier = self.m.country(jtf_blue).find_ship_group(name="HELO_CARRIER")
if not carrier:
carrier = self.m.country(jtf_blue).find_ship_group(name="HELO_CARRIER_1")
farp = self.m.country(jtf_blue).find_static_group("HELO_FARP")
if not farp:
farp = self.m.country(jtf_blue).find_static_group("HELO_FARP_1")
heading = 0
if farp:
farp_heading = farp.units[0].heading
heading = farp_heading
friendly_airports, primary_f_airport = self.getCoalitionAirports("blue")
group_size = 1
player_helicopters = []
if options["slots"] == "Multiple Slots":
player_helicopters = options["player_slots"]
else:
player_helicopters.append(options["slots"]) # single helicopter type
if len(client_helos) == 1:
group_size = 2 # add a wingman if singleplayer
# Hot/Cold start options
start_type = dcs.mission.StartType.Cold
start_type_string = ""
start_type_point_type = "TakeOffGround"
start_type_action = dcs.point.PointAction.FromGroundArea
if options["player_hotstart"]:
start_type = dcs.mission.StartType.Warm
start_type_string = "HOT "
start_type_point_type = "TakeOffGroundHot"
start_type_action = dcs.point.PointAction.FromGroundAreaHot
farp_helicopter_count = 1
for helicopter_id in player_helicopters:
fg = None
helotype = None
if helicopter_id in dcs.helicopters.helicopter_map:
helotype = dcs.helicopters.helicopter_map[helicopter_id]
elif helicopter_id in dcs.planes.plane_map:
helotype = dcs.planes.plane_map[helicopter_id]
else:
continue
if carrier:
fg = self.m.flight_group_from_unit(self.m.country(jtf_blue),
"CARRIER " + start_type_string + helotype.id, helotype,
carrier,
dcs.task.CAS, group_size=group_size, start_type=start_type)
elif farp and farp_helicopter_count <= 4:
#old ugly FARPs, or single player groups with wingman require fg from unit
if farp.units[0].type != 'Invisible FARP':
print("making flight group from unit")
fg = self.m.flight_group_from_unit(self.m.country(jtf_blue),
"FARP " + start_type_string + helotype.id, helotype, farp,
dcs.task.CAS, group_size=group_size, start_type=start_type)
# invisible farps need manual unit placement for multiple units
elif farp.units[0].type == 'Invisible FARP':
print("making standard flight group")
pos = farp.units[0].position.point_from_heading(heading, 20)
farp_helicopter_count = farp_helicopter_count + 1
fg = self.m.flight_group(self.m.country(jtf_blue), "FARP " + start_type_string + helotype.id,
helotype, airport=None, position=pos, maintask=dcs.task.CAS, group_size=group_size, start_type=start_type)
fg.units[0].heading = farp_heading
if group_size > 1:
# move wingman if present
fg.units[1].position = farp.units[0].position.point_from_heading(180, 20)
fg.units[1].heading = farp_heading
# change heading for next helicopter placement
heading += 90
# hot or cold start
fg.points[0].action = start_type_action
fg.points[0].type = start_type_point_type
else:
parking = self.getParking(primary_f_airport, helotype, friendly_airports,
group_size=group_size)
if parking:
fg = self.m.flight_group_from_airport(self.m.country(jtf_blue),
primary_f_airport.name + " " + start_type_string + helotype.id,
helotype,
parking, group_size=group_size, start_type=start_type)
# if we were able to find a slot and create a flight group
if fg:
slotted_count = slotted_count + 1
fg.units[0].set_client()
# fg.load_task_default_loadout(dcs.task.CAS)
if helotype.id in default_loadouts:
fg.units[0].pylons = default_loadouts[helotype.id]["pylons"]
fg.units[0].livery_id = default_loadouts[helotype.id]["livery_id"]
fg.units[0].fuel = default_loadouts[helotype.id]["fuel"]
# setup wingman for single player
if len(fg.units) == 2:
fg.units[1].skill = dcs.unit.Skill.High
fg.units[1].pylons = fg.units[0].pylons
fg.units[1].livery_id = fg.units[0].livery_id
fg.units[1].fuel = fg.units[0].fuel
else:
logger.warn("No parking available for " + helotype.id)
unslotted_count = unslotted_count + 1
if unslotted_count > 0:
raise Exception("Player slots error: Unable to find parking for " + str(
unslotted_count) + " players. Maximum parking slots found was " + str(slotted_count))
class TrainingScenario():
@staticmethod
def random_orbit(rect: dcs.mapping.Rectangle):
x1 = random.randrange(int(rect.bottom), int(rect.top))
sy = rect.left
y1 = random.randrange(int(sy), int(rect.right))
heading = 90 if y1 < (sy + (rect.right - sy) / 2) else 270
heading = random.randrange(heading - 20, heading + 20)
race_dist = random.randrange(80 * 1000, 120 * 1000)
return dcs.mapping.Point(x1, y1), heading, race_dist
@staticmethod
def perpRacetrack(enemy_heading, friendly_pt, terrain):
heading = enemy_heading + random.randrange(70, 110)
race_dist = random.randrange(40 * 1000, 80 * 1000)
center_pt = dcs.mapping.point_from_heading(friendly_pt.x, friendly_pt.y,
enemy_heading - random.randrange(140, 220), 20000)
pt1 = dcs.mapping.point_from_heading(center_pt[0], center_pt[1], enemy_heading - 90,
random.randrange(20 * 1000, 40 * 1000))
return dcs.mapping.Point(pt1[0], pt1[1], terrain), heading, race_dist
def addFlights(self, options, red_forces, blue_forces):
combinedJointTaskForcesBlue = self.m.country(dcs.countries.CombinedJointTaskForcesBlue.name)
combinedJointTaskForcesRed = self.m.country(dcs.countries.CombinedJointTaskForcesRed.name)
friendly_airports, primary_f_airport = self.getCoalitionAirports("blue")
enemy_airports, primary_e_airport = self.getCoalitionAirports("red")
# find enemy carriers and farps
carrier = self.m.country(jtf_red).find_ship_group(name="HELO_CARRIER")
if not carrier:
carrier = self.m.country(jtf_red).find_ship_group(name="HELO_CARRIER_1")
farp = self.m.country(jtf_red).find_static_group("HELO_FARP")
if not farp:
farp = self.m.country(jtf_red).find_static_group("HELO_FARP_1")
e_airport_heading = dcs.mapping.heading_between_points(
friendly_airports[0].position.x, friendly_airports[0].position.y, enemy_airports[0].position.x,
primary_e_airport.position.y
)
e_airport_distance = dcs.mapping._distance(
primary_f_airport.position.x, primary_f_airport.position.y, primary_f_airport.position.x,
primary_f_airport.position.y
)
self.primary_e_airport = primary_e_airport
self.m.triggers.add_triggerzone(primary_e_airport.position, 1500, hidden=True, name="RED_AIRBASE")
if options["red_cap"]:
scenario_red_cap_spawn_zone = None
for zone in self.m.triggers.zones():
if zone.name == "RED_CAP_SPAWN":
scenario_red_cap_spawn_zone = True
if not scenario_red_cap_spawn_zone:
e_cap_spawn_point = primary_e_airport.position.point_from_heading(e_airport_heading, 100000)
self.m.triggers.add_triggerzone(e_cap_spawn_point, 30000, hidden=True, name="RED_CAP_SPAWN")
if options["blue_cap"]:
scenario_blue_cap_spawn_zone = None
for zone in self.m.triggers.zones():
if zone.name == "BLUE_CAP_SPAWN":
scenario_blue_cap_spawn_zone = True
if not scenario_blue_cap_spawn_zone:
f_cap_spawn_point = primary_f_airport.position.point_from_heading(e_airport_heading + 180, 100000)
self.m.triggers.add_triggerzone(f_cap_spawn_point, 30000, hidden=True, name="BLUE_CAP_SPAWN")
if options["f_awacs"]:
awacs_name = "AWACS"
awacs_freq = 266
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
awacs = self.m.awacs_flight(
combinedJointTaskForcesBlue,
awacs_name,
plane_type=dcs.planes.E_3A,
airport=self.getParking(primary_f_airport, dcs.planes.E_3A, friendly_airports),
position=pos,
race_distance=race_dist, heading=heading,
altitude=random.randrange(4000, 5500, 100), frequency=awacs_freq)
# AWACS Escort flight
source_plane = None
if blue_forces["fighter_planes"]:
source_group = random.choice(blue_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
else:
plane_type = dcs.countries.CombinedJointTaskForcesBlue.Plane.F_15C
awacs_escort = self.m.escort_flight(
combinedJointTaskForcesBlue, "AWACS Escort",
plane_type,
airport=self.getParking(primary_f_airport, plane_type, friendly_airports, group_size=2),
group_to_escort=awacs,
group_size=2)
awacs_escort.points[0].tasks.append(dcs.task.OptROE(dcs.task.OptROE.Values.WeaponFree))
if source_plane:
for unit in awacs_escort.units:
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
# add text to mission briefing with radio freq
briefing = self.m.description_text() + "\n\n" + awacs_name + " " + str(awacs_freq) + ".00 " + "\n"
self.m.set_description_text(briefing)
if options["f_tankers"]:
t1_name = "Tanker KC_130 Basket"
t1_freq = 253
t1_tac = "61Y"
t2_name = "Tanker KC_135 Boom"
t2_freq = 256
t2_tac = "101Y"
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
refuel_net = self.m.refuel_flight(
combinedJointTaskForcesBlue,
t1_name,
dcs.planes.KC130,
airport=self.getParking(primary_f_airport, dcs.planes.KC130, friendly_airports),
position=pos,
race_distance=race_dist,
heading=heading,
altitude=random.randrange(4000, 5500, 100),
start_type=dcs.mission.StartType.Warm,
speed=750,
frequency=t1_freq,
tacanchannel=t1_tac)
# pos, heading, race_dist = self.TrainingScenario.random_orbit(orbit_rect)
pos, heading, race_dist = self.TrainingScenario.perpRacetrack(e_airport_heading, primary_f_airport.position,
self.m.terrain)
refuel_rod = self.m.refuel_flight(
combinedJointTaskForcesBlue,
t2_name,
dcs.planes.KC_135,
airport=self.getParking(primary_f_airport, dcs.planes.KC_135, friendly_airports),
position=pos,
race_distance=race_dist, heading=heading,
altitude=random.randrange(4000, 5500, 100),
start_type=dcs.mission.StartType.Warm,
frequency=t2_freq,
tacanchannel=t2_tac)
# add text to mission briefing
briefing = self.m.description_text() + "\n\n" + t1_name + " " + str(
t1_freq) + ".00 " + t1_tac + "\n" + t2_name + " " + str(t2_freq) + ".00 " + t2_tac + "\n\n"
self.m.set_description_text(briefing)
def zone_attack(fg, airport):
fg.set_skill(dcs.unit.Skill.High)
fg.late_activation = True
if options["defending"]:
for zone_name in self.conflict_zones:
fg.add_waypoint(self.conflict_zones[zone_name].position, 1000)
else:
for zone_name in reversed(self.conflict_zones):
fg.add_waypoint(self.conflict_zones[zone_name].position, 1000)
if hasattr(airport, 'runways'):
fg.add_runway_waypoint(airport)
if airport:
fg.land_at(airport)
fg.points[0].tasks.append(dcs.task.OptReactOnThreat(dcs.task.OptReactOnThreat.Values.EvadeFire))
fg.points[0].tasks.append(dcs.task.OptROE(dcs.task.OptROE.Values.OpenFire))
if options["e_attack_helos"]:
source_helo = None
if red_forces["attack_helos"]:
source_group = random.choice(red_forces["attack_helos"])
source_helo = source_group.units[0]
helo_type = source_helo.unit_type
group_size = len(source_group.units)
if group_size > 2:
group_size = 2
else:
group_size = 2
helo_type = random.choice(RotorOpsUnits.e_attack_helos)
airport = self.getParking(primary_e_airport, helo_type, enemy_airports, group_size)
if carrier:
afg = self.m.flight_group_from_unit(
combinedJointTaskForcesRed,
"Enemy Attack Helicopters",
helo_type,
carrier,
maintask=dcs.task.CAS,
start_type=dcs.mission.StartType.Cold,
group_size=group_size)
zone_attack(afg, carrier)
elif farp:
afg = self.m.flight_group_from_unit(
combinedJointTaskForcesRed,
"Enemy Attack Helicopters",
helo_type,
farp,
maintask=dcs.task.CAS,
start_type=dcs.mission.StartType.Cold,
group_size=1) # more than one spawn on top of each other, setting group size to one for now
zone_attack(afg, farp)
elif airport:
afg = self.m.flight_group_from_airport(
combinedJointTaskForcesRed,
"Enemy Attack Helicopters",
helo_type,
airport=airport,
maintask=dcs.task.CAS,
start_type=dcs.mission.StartType.Cold,
group_size=group_size)
zone_attack(afg, airport)
else:
return
if source_helo and afg:
for unit in afg.units:
unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id
if options["e_attack_planes"]:
source_plane = None
if red_forces["attack_planes"]:
source_group = random.choice(red_forces["attack_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
group_size = len(source_group.units)
if group_size > 2:
group_size = 2
else:
group_size = 2
plane_type = random.choice(RotorOpsUnits.e_attack_planes)
airport = self.getParking(primary_e_airport, plane_type, enemy_airports, group_size)
if airport:
afg = self.m.flight_group_from_airport(
combinedJointTaskForcesRed, "Enemy Attack Planes", plane_type,
airport=airport,
maintask=dcs.task.CAS,
start_type=dcs.mission.StartType.Cold,
group_size=group_size)
zone_attack(afg, airport)
if source_plane:
for unit in afg.units:
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
if options["e_transport_helos"]:
source_helo = None
if red_forces["transport_helos"]:
source_group = random.choice(red_forces["transport_helos"])
source_helo = source_group.units[0]
helo_type = source_helo.unit_type
group_size = len(source_group.units)
if group_size > 2:
group_size = 2
else:
group_size = 1
helo_type = random.choice(RotorOpsUnits.e_transport_helos)
airport = self.getParking(primary_e_airport, helo_type, enemy_airports, group_size)
if airport:
afg = self.m.flight_group_from_airport(
combinedJointTaskForcesRed, "Enemy Transport Helicopters", helo_type,
airport=airport,
maintask=dcs.task.Transport,
start_type=dcs.mission.StartType.Cold,
group_size=group_size)
afg.late_activation = True
afg.units[0].skill = dcs.unit.Skill.Excellent
if source_helo:
for unit in afg.units:
unit.pylons = source_helo.pylons
unit.livery_id = source_helo.livery_id
if False:
for i in range(1,4):
randzone = random.choice(list(self.red_zones))
pt2 = self.red_zones[randzone].position
source_plane = None
if red_forces["fighter_planes"]:
source_group = random.choice(red_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
group_size = random.randrange(1, 2)
else:
group_size = random.randrange(1, 2)
plane_type = random.choice(RotorOpsUnits.e_attack_helos)
airport = self.getParking(primary_e_airport, plane_type, enemy_airports, group_size)
enemy_cap = self.m.patrol_flight(airport=airport,
name="Enemy CAP " + str(i),
country=combinedJointTaskForcesRed,
patrol_type=plane_type,
pos1=primary_e_airport.position,
pos2=pt2,
altitude=3000,
group_size=group_size,
max_engage_distance=40 * 1000
)
# enemy_cap.points[0].tasks[0] = dcs.task.EngageTargets(max_engage_distance, [dcs.task.Targets.All.Air.Planes])
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
if source_plane:
for unit in enemy_cap.units:
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
if options["red_cap"]:
if red_forces["fighter_planes"]:
for fighter_plane_group in red_forces["fighter_planes"]:
source_group = random.choice(red_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
enemy_cap = self.m.flight_group(
country=combinedJointTaskForcesRed,
name="RED CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=e_cap_spawn_point,
altitude=5000,
speed=300
)
enemy_cap.late_activation = True
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
else:
plane_type = random.choice(RotorOpsUnits.e_fighter_planes)
enemy_cap = self.m.flight_group(
country=combinedJointTaskForcesRed,
name="RED CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=e_cap_spawn_point,
altitude=5000,
speed=300
)
enemy_cap.late_activation = True
for unit in enemy_cap.units:
unit.skill = dcs.unit.Skill.Random
if options["blue_cap"]:
if blue_forces["fighter_planes"]:
for fighter_plane_group in blue_forces["fighter_planes"]:
source_group = random.choice(blue_forces["fighter_planes"])
source_plane = source_group.units[0]
plane_type = source_plane.unit_type
friendly_cap = self.m.flight_group(
country=combinedJointTaskForcesBlue,
name="BLUE CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=f_cap_spawn_point,
altitude=5000,
speed=300
)
friendly_cap.late_activation = True
for unit in friendly_cap.units:
unit.skill = dcs.unit.Skill.Random
unit.pylons = source_plane.pylons
unit.livery_id = source_plane.livery_id
else:
plane_type = random.choice(RotorOpsUnits.f_fighter_planes)
friendly_cap = self.m.flight_group(
country=combinedJointTaskForcesBlue,
name="BLUE CAP",
aircraft_type=plane_type,
airport=None,
maintask=dcs.task.CAP,
group_size=1,
position=f_cap_spawn_point,
altitude=5000,
speed=300
)
friendly_cap.late_activation = True
for unit in friendly_cap.units:
unit.skill = dcs.unit.Skill.Random
def importObjects(self, data):
imports = data["objects"]["imports"]
for side in "red", "blue", "neutrals":
coalition = self.m.coalition.get(side)
for country_name in coalition.countries:
for group in self.m.country(country_name).static_group:
prefix = "IMPORT-"
if group.name.find(prefix) == 0:
if group.units[0].name.find('IMPORT-') == 0:
logger.error(
group.units[
0].name + " IMPORT group's unit name cannot start with 'IMPORT'. Check the scenario template.")
raise Exception("Scenario file error: " + group.units[
0].name + " IMPORT group's unit name cannot start with 'IMPORT'")
# trim the groupname to our filename convention
filename = group.name.removeprefix(prefix)
i = filename.find('-')
if i > 8:
filename = filename[0:i]
print(filename)
for imp in imports:
if imp.filename == (filename + ".miz"):
i = ImportObjects(imp.path)
i.anchorByGroupName("ANCHOR")
new_statics, new_vehicles, new_helicopters = i.copyAll(self.m, country_name,
group.units[0].name,
group.units[0].position,
group.units[0].heading)
break
def addMods(self):
dcs.helicopters.helicopter_map["UH-60L"] = aircraftMods.UH_60L
self.m.country(jtf_blue).helicopters.append(aircraftMods.UH_60L)